diff --git a/resources/icons/gutter-comment-unresolved.svg b/resources/icons/gutter-comment-unresolved.svg
new file mode 100644
index 0000000..36c1219
--- /dev/null
+++ b/resources/icons/gutter-comment-unresolved.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/icons/gutter-edit.svg b/resources/icons/gutter-edit.svg
new file mode 100644
index 0000000..552fbf9
--- /dev/null
+++ b/resources/icons/gutter-edit.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/icons/gutter-pass.svg b/resources/icons/gutter-pass.svg
new file mode 100644
index 0000000..6a6c314
--- /dev/null
+++ b/resources/icons/gutter-pass.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/api/extendedBase.ts b/src/api/extendedBase.ts
index 1da9b38..6d395fa 100644
--- a/src/api/extendedBase.ts
+++ b/src/api/extendedBase.ts
@@ -12,6 +12,14 @@ export interface UrlLinkedFileProvider {
url: string,
}
+export interface CommentThreadSchema {
+ messages: CommentThreadMessageSchema[],
+ resolved?: boolean,
+ resolved_at?: string, //ISO format date string
+ resolved_by_user_id?: string,
+ resolved_by_user?: UserInfoSchema,
+}
+
export interface CommentThreadMessageSchema {
id: string,
content: string,
@@ -22,32 +30,28 @@ export interface CommentThreadMessageSchema {
user: UserInfoSchema,
}
+export interface DocumentReviewSchema {
+ id: string,
+ metadata: {user_id:string, ts:string}, //"ts" is ISO format date string
+}
+
+export interface DocumentReviewChangeSchema extends DocumentReviewSchema {
+ op: {p:number, i?:string, d?:string},
+}
+
+export interface DocumentReviewCommentSchema {
+ op: {p:number, c:string, t:string}, // "c" for quoted text, "t" for thread_id
+ thread?: CommentThreadSchema, // to be filled in via API call
+}
+
export interface DocumentRangesSchema {
- changes: {
- id: string,
- op: {p:number, i?:string, d?:string},
- metadata: {user_id:string, ts:string}, //"ts" is ISO format date string
- }[],
- comments: {
- id: string,
- // "c" for quoted text, "t" for threadId
- op: {p:number, c:string, t:string},
- metadata: {user_id:string, ts:string}, //"ts" is ISO format date string
- }[],
+ changes: DocumentReviewChangeSchema[],
+ comments: DocumentReviewCommentSchema[],
}
export interface ExtendedResponseSchema extends ResponseSchema {
- threads: {[threadId:string]: {
- messages: CommentThreadMessageSchema[],
- resolved?: boolean,
- resolved_at?: string, //ISO format date string
- resolved_by_user_id?: string,
- resolved_by_user?: UserInfoSchema,
- }},
- ranges: {
- id: string,//document id
- ranges: DocumentRangesSchema,
- }[],
+ threads: {[threadId:string]: CommentThreadSchema},
+ ranges: {[docId:string]: DocumentRangesSchema},
}
export class ExtendedBaseAPI extends BaseAPI {
@@ -78,7 +82,8 @@ export class ExtendedBaseAPI extends BaseAPI {
async getAllDocumentRanges(identity: Identity, project_id: string) {
this.setIdentity(identity);
return await this.request('GET', `project/${project_id}/ranges`, undefined, (res) => {
- const ranges = JSON.parse(res!);
+ const rangeList = JSON.parse(res!) as {id:string, ranges:DocumentRangesSchema}[];
+ const ranges = Object.assign({}, ...rangeList.map((r) => ({[r.id]: r.ranges})));
return {ranges};
}) as ExtendedResponseSchema;
}
diff --git a/src/collaboration/clientManager.ts b/src/collaboration/clientManager.ts
index c108ca9..fd8561c 100644
--- a/src/collaboration/clientManager.ts
+++ b/src/collaboration/clientManager.ts
@@ -5,6 +5,7 @@ import { SocketIOAPI, UpdateUserSchema } from '../api/socketio';
import { VirtualFileSystem } from '../core/remoteFileSystemProvider';
import { ChatViewProvider } from './chatViewProvider';
import { LocalReplicaSCMProvider } from '../scm/localReplicaSCM';
+import { ReviewPanelProvider } from './reviewPanelProvider';
interface ExtendedUpdateUserSchema extends UpdateUserSchema {
selection?: {
@@ -59,6 +60,7 @@ export class ClientManager {
private readonly onlineUsers: {[K:string]:ExtendedUpdateUserSchema} = {};
private connectedFlag: boolean = true;
private readonly chatViewer: ChatViewProvider;
+ private readonly reviewPanel: ReviewPanelProvider;
constructor(
private readonly vfs: VirtualFileSystem,
@@ -101,6 +103,7 @@ export class ClientManager {
});
this.chatViewer = new ChatViewProvider(this.vfs, this.publicId, this.context.extensionUri, this.socket);
+ this.reviewPanel = new ReviewPanelProvider(this.vfs, this.context, this.socket);
this.status = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 0);
this.updateStatus();
}
@@ -376,6 +379,8 @@ export class ClientManager {
}),
// register chat view provider
...this.chatViewer.triggers,
+ // register review panel provider
+ ...this.reviewPanel.triggers,
// update this client's position
vscode.window.onDidChangeTextEditorSelection(async e => {
if (e.kind===undefined) { return; }
diff --git a/src/collaboration/reviewPanelProvider.ts b/src/collaboration/reviewPanelProvider.ts
new file mode 100644
index 0000000..537fd3a
--- /dev/null
+++ b/src/collaboration/reviewPanelProvider.ts
@@ -0,0 +1,62 @@
+import * as vscode from 'vscode';
+import { VirtualFileSystem } from '../core/remoteFileSystemProvider';
+import { SocketIOAPI } from '../api/socketio';
+import { DocumentRangesSchema } from '../api/extendedBase';
+
+const reviewOpenCommentDecorationOption: vscode.DecorationRenderOptions = {
+ // backgroundColor: 'rgba(243, 177, 17, 0.3)',
+ backgroundColor: new vscode.ThemeColor('editor.hoverHighlightBackground'),
+ gutterIconPath: 'resources/gutter-comment-unresolved.svg',
+};
+
+const reviewResolvedCommentDecorationOption: vscode.DecorationRenderOptions = {
+ gutterIconPath: 'resources/gutter-pass.svg',
+};
+
+const reviewInsertChangeDecorationOption: vscode.DecorationRenderOptions = {
+ // color: 'rgba(44, 142, 48, 0.3)',
+ backgroundColor: new vscode.ThemeColor('diffEditor.insertedTextBackground'),
+ gutterIconPath: 'resources/gutter-edit.svg',
+};
+
+const reviewDeleteChangeDecorationOption: vscode.DecorationRenderOptions = {
+ // color: 'rgba(197, 6, 11, 1.0)',
+ fontStyle: 'text-decoration: line-through;',
+ backgroundColor: new vscode.ThemeColor('diffEditor.removedTextBackground'),
+ gutterIconPath: 'resources/gutter-edit.svg',
+};
+
+function fixGutterIconAbsolutePaths(context: vscode.ExtensionContext) {
+ for (const option of [
+ reviewOpenCommentDecorationOption,
+ reviewResolvedCommentDecorationOption,
+ reviewInsertChangeDecorationOption,
+ reviewDeleteChangeDecorationOption
+ ]) {
+ if (option.gutterIconPath) {
+ option.gutterIconPath = context.asAbsolutePath(option.gutterIconPath as string);
+ }
+ }
+}
+
+export class ReviewPanelProvider {
+ private reviewRecords?: {[docId:string]: DocumentRangesSchema};
+
+ constructor(
+ private readonly vfs: VirtualFileSystem,
+ private readonly context: vscode.ExtensionContext,
+ private readonly socket: SocketIOAPI,
+ ) {
+ // init review records
+ }
+
+ private updateReviewDecorations() {
+
+ }
+
+ get triggers() {
+ return [
+
+ ];
+ }
+}
diff --git a/src/core/remoteFileSystemProvider.ts b/src/core/remoteFileSystemProvider.ts
index 2567a97..875a7d2 100644
--- a/src/core/remoteFileSystemProvider.ts
+++ b/src/core/remoteFileSystemProvider.ts
@@ -1178,6 +1178,24 @@ export class VirtualFileSystem extends vscode.Disposable {
return false;
}
}
+
+ async getAllDocumentReviews() {
+ const identity = await GlobalStateManager.authenticate(this.context, this.serverName);
+ const rangesRes = await (this.api as ExtendedBaseAPI).getAllDocumentRanges(identity, this.projectId);
+ if (rangesRes.type==='success') {
+ const threadsRes = await (this.api as ExtendedBaseAPI).getAllCommentThreads(identity, this.projectId);
+ if (threadsRes.type==='success') {
+ for (const range of Object.values(rangesRes.ranges)) {
+ for (const comment of range.comments) {
+ comment.thread = threadsRes.threads[comment.op.t];
+ }
+ }
+ return rangesRes.ranges;
+ }
+ }
+
+ return undefined;
+ }
}
export class RemoteFileSystemProvider implements vscode.FileSystemProvider {