diff --git a/src/plus/webviews/patchDetails/patchDetailsWebview.ts b/src/plus/webviews/patchDetails/patchDetailsWebview.ts index 1cbceef..ffe830c 100644 --- a/src/plus/webviews/patchDetails/patchDetailsWebview.ts +++ b/src/plus/webviews/patchDetails/patchDetailsWebview.ts @@ -34,6 +34,7 @@ import type { Change, CreatePatchParams, DidExplainParams, + DraftPatchCheckedParams, FileActionParams, Mode, Preferences, @@ -50,8 +51,10 @@ import { DidChangeCreateNotificationType, DidChangeDraftNotificationType, DidChangeNotificationType, + DidChangePatchRepositoryNotificationType, DidChangePreferencesNotificationType, DidExplainCommandType, + DraftPatchCheckedCommandType, ExplainCommandType, OpenFileCommandType, OpenFileComparePreviousCommandType, @@ -205,6 +208,9 @@ export class PatchDetailsWebviewProvider case UpdatePreferencesCommandType.method: onIpc(UpdatePreferencesCommandType, e, params => this.updatePreferences(params)); break; + case DraftPatchCheckedCommandType.method: + onIpc(DraftPatchCheckedCommandType, e, params => this.onPatchChecked(params)); + break; } } @@ -413,6 +419,43 @@ export class PatchDetailsWebviewProvider // TODO@eamodio Open the patch contents for the selected repo in an untitled editor } + private async onPatchChecked(params: DraftPatchCheckedParams) { + if (params.patch.repository.located || params.checked === false) return; + + const patch = (this._context.draft as Draft)?.changesets?.[0].patches?.find( + p => p.gkRepositoryId === params.patch.gkRepositoryId, + ); + if (patch?.repository == null || isRepository(patch.repository)) return; + + const repo = await this.container.repositoryIdentity.getRepository(patch.repository, { + openIfNeeded: true, + prompt: true, + }); + + if (repo == null) { + void window.showErrorMessage(`Unable to locate repository '${patch.repository.name}'`); + } else { + patch.repository = repo; + } + + void this.notifyPatchRepositoryUpdated(patch); + } + + private notifyPatchRepositoryUpdated(patch: DraftPatch) { + return this.host.notify(DidChangePatchRepositoryNotificationType, { + patch: serialize({ + ...patch, + contents: undefined, + commit: undefined, + repository: { + id: patch.gkRepositoryId, + name: patch.repository?.name ?? '', + located: patch.repository != null && isRepository(patch.repository), + }, + }), + }); + } + private updateCreateCheckedState(params: UpdateCreatePatchRepositoryCheckedStateParams) { const changeset = this._context.create?.changes.get(params.repoUri); if (changeset == null) return; @@ -669,6 +712,7 @@ export class PatchDetailsWebviewProvider repository: { id: p.gkRepositoryId, name: p.repository?.name ?? '', + located: p.repository != null && isRepository(p.repository), }, })), ), diff --git a/src/plus/webviews/patchDetails/protocol.ts b/src/plus/webviews/patchDetails/protocol.ts index b376cc9..5d586a8 100644 --- a/src/plus/webviews/patchDetails/protocol.ts +++ b/src/plus/webviews/patchDetails/protocol.ts @@ -13,8 +13,10 @@ export const messageHeadlineSplitterToken = '\x00\n\x00'; export type FileShowOptions = TextDocumentShowOptions; -type PatchDetails = Serialized< - Omit & { repository: { id: GkRepositoryId; name: string } } +export type PatchDetails = Serialized< + Omit & { + repository: { id: GkRepositoryId; name: string; located: boolean }; + } >; interface LocalDraftDetails { @@ -183,6 +185,12 @@ export interface OpenInCommitGraphParams { } export const OpenInCommitGraphCommandType = new IpcCommandType('patch/openInGraph'); +export interface DraftPatchCheckedParams { + patch: PatchDetails; + checked: boolean; +} +export const DraftPatchCheckedCommandType = new IpcCommandType('patch/checked'); + export interface SelectPatchRepoParams { repoPath: string; } @@ -254,3 +262,10 @@ export type DidExplainParams = } | { error: { message: string } }; export const DidExplainCommandType = new IpcNotificationType('patch/didExplain'); + +export interface DidChangePatchRepositoryParams { + patch: PatchDetails; +} +export const DidChangePatchRepositoryNotificationType = new IpcNotificationType( + 'patch/draft/didChangeRepository', +); diff --git a/src/webviews/apps/plus/patchDetails/components/gl-draft-details.ts b/src/webviews/apps/plus/patchDetails/components/gl-draft-details.ts index 384bacd..016f1f0 100644 --- a/src/webviews/apps/plus/patchDetails/components/gl-draft-details.ts +++ b/src/webviews/apps/plus/patchDetails/components/gl-draft-details.ts @@ -6,7 +6,12 @@ import { unsafeHTML } from 'lit/directives/unsafe-html.js'; import { when } from 'lit/directives/when.js'; import type { TextDocumentShowOptions } from 'vscode'; import type { DraftPatchFileChange } from '../../../../../gk/models/drafts'; -import type { DraftDetails, FileActionParams, State } from '../../../../../plus/webviews/patchDetails/protocol'; +import type { + DraftDetails, + FileActionParams, + PatchDetails, + State, +} from '../../../../../plus/webviews/patchDetails/protocol'; import { makeHierarchical } from '../../../../../system/array'; import { flatCount } from '../../../../../system/iterable'; import type { @@ -59,6 +64,11 @@ export interface ShowPatchInGraphDetail { // [key: string]: unknown; } +export interface PatchCheckedDetail { + patch: PatchDetails; + checked: boolean; +} + @customElement('gl-draft-details') export class GlDraftDetails extends GlTreeBase { @property({ type: Object }) @@ -99,6 +109,16 @@ export class GlDraftDetails extends GlTreeBase { this.selectedPatches = []; } else { this.selectedPatches = patches.map(p => p.id); + for (const patch of patches) { + const index = this.selectedPatches.indexOf(patch.id); + if (patch.repository.located) { + if (index === -1) { + this.selectedPatches.push(patch.id); + } + } else if (index > -1) { + this.selectedPatches.splice(index, 1); + } + } } // } else if (patches?.length === 1) { // this.selectedPatches = [patches[0].id]; @@ -244,6 +264,7 @@ export class GlDraftDetails extends GlTreeBase { `; } + // TODO: make a local state instead of a getter get treeModel(): TreeModel[] { if (this.state?.draft?.patches == null) return []; @@ -262,9 +283,12 @@ export class GlDraftDetails extends GlTreeBase { } // checkable only for multi-repo - const options = { checkable: patches.length > 1 }; + const isMultiRepo = patches.length > 1; const models = patches?.map(p => - this.draftPatchToTreeModel(p, isTree, this.state.preferences?.files?.compact, options), + this.draftPatchToTreeModel(p, isTree, this.state.preferences?.files?.compact, { + checkable: isMultiRepo, + checked: this.selectedPatches.includes(p.id), + }), ); return models; } @@ -530,13 +554,13 @@ export class GlDraftDetails extends GlTreeBase { this.selectedPatches.splice(selectedIndex, 1); } - // const [repoPath] = e.detail.context; - // const event = new CustomEvent('repo-checked', { - // detail: { - // path: repoPath, - // }, - // }); - // this.dispatchEvent(event); + const event = new CustomEvent('gl-patch-checked', { + detail: { + patch: patch, + checked: e.detail.checked, + }, + }); + this.dispatchEvent(event); } override onTreeItemSelected(e: CustomEvent) { @@ -606,7 +630,12 @@ export class GlDraftDetails extends GlTreeBase { compact = true, options?: Partial, ): TreeModel { - const model = this.repoToTreeModel(patch.repository.name, patch.gkRepositoryId, options); + const model = this.repoToTreeModel( + patch.repository.name, + patch.gkRepositoryId, + options, + patch.repository.located ? undefined : 'missing', + ); if (!patch.files?.length) return model; @@ -677,7 +706,7 @@ export class GlDraftDetails extends GlTreeBase { declare global { interface HTMLElementTagNameMap { - 'gl-patch-details': GlDraftDetails; + 'gl-draft-details': GlDraftDetails; } interface WindowEventMap { @@ -688,5 +717,6 @@ declare global { 'gl-patch-file-compare-previous': CustomEvent; 'gl-patch-file-compare-working': CustomEvent; 'gl-patch-file-open': CustomEvent; + 'gl-patch-checked': CustomEvent; } } diff --git a/src/webviews/apps/plus/patchDetails/components/gl-tree-base.ts b/src/webviews/apps/plus/patchDetails/components/gl-tree-base.ts index 65122a7..ca9a5ee 100644 --- a/src/webviews/apps/plus/patchDetails/components/gl-tree-base.ts +++ b/src/webviews/apps/plus/patchDetails/components/gl-tree-base.ts @@ -160,7 +160,12 @@ export class GlTreeBase extends GlElement): TreeModel { + protected repoToTreeModel( + name: string, + path: string, + options?: Partial, + description?: string, + ): TreeModel { return { branch: false, expanded: true, @@ -170,6 +175,7 @@ export class GlTreeBase extends GlElement> { 'gl-patch-file-open', e => this.onOpenFile(e.detail), ), + DOM.on('gl-draft-details', 'gl-patch-checked', e => + this.onPatchChecked(e.detail), + ), ]; return disposables; @@ -198,11 +203,33 @@ export class PatchDetailsApp extends App> { }); break; + case DidChangePatchRepositoryNotificationType.method: + onIpc(DidChangePatchRepositoryNotificationType, msg, params => { + // assertsSerialized(params.state); + + const draft = this.state.draft!; + const patches = draft.patches!; + const patchIndex = patches.findIndex(p => p.id === params.patch.id); + patches.splice(patchIndex, 1, params.patch); + + this.state = { + ...this.state, + draft: draft, + }; + this.setState(this.state); + this.attachState(true); + }); + break; + default: super.onMessageReceived?.(e); } } + private onPatchChecked(e: PatchCheckedDetail) { + this.sendCommand(DraftPatchCheckedCommandType, e); + } + private onCreateCheckRepo(e: CreatePatchCheckRepositoryEventDetail) { this.sendCommand(UpdateCreatePatchRepositoryCheckedStateCommandType, e); }