From 6fd25c6c2e57bbe0f6d8b1e14bd4f6c1ca0c552e Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Fri, 10 Jun 2022 10:17:52 +0200 Subject: [PATCH 1/2] feat: organize tours by sub-directories --- src/player/tree/index.ts | 24 +++++++-- src/player/tree/nodes.ts | 15 +++++- src/store/index.ts | 12 +++++ src/store/provider.ts | 103 ++++++++++++++++++++++++--------------- 4 files changed, 110 insertions(+), 44 deletions(-) diff --git a/src/player/tree/index.ts b/src/player/tree/index.ts index bd14340..b8288a4 100644 --- a/src/player/tree/index.ts +++ b/src/player/tree/index.ts @@ -11,10 +11,10 @@ import { TreeItem, window } from "vscode"; -import { EXTENSION_NAME } from "../../constants"; import { generatePreviewContent } from ".."; +import { EXTENSION_NAME } from "../../constants"; import { store } from "../../store"; -import { CodeTourNode, CodeTourStepNode } from "./nodes"; +import { CodeTourFolderNode, CodeTourNode, CodeTourStepNode } from "./nodes"; class CodeTourTreeProvider implements TreeDataProvider, Disposable { private _disposables: Disposable[] = []; @@ -58,9 +58,13 @@ class CodeTourTreeProvider implements TreeDataProvider, Disposable { if (!store.hasTours && !store.activeTour) { return undefined; } else { - const tours = store.tours.map( - tour => new CodeTourNode(tour, this.extensionPath) - ); + const tours = store.root.tours.map(tour => { + if ('tours' in tour) { + return new CodeTourFolderNode(tour, this.extensionPath); + } else { + return new CodeTourNode(tour, this.extensionPath); + } + }); if ( store.activeTour && @@ -73,6 +77,16 @@ class CodeTourTreeProvider implements TreeDataProvider, Disposable { return tours; } + } else if (element instanceof CodeTourFolderNode) { + const tours = element.tour.tours.map(tour => { + if ('tours' in tour) { + return new CodeTourFolderNode(tour, this.extensionPath); + } else { + return new CodeTourNode(tour, this.extensionPath); + } + }); + + return tours; } else if (element instanceof CodeTourNode) { if (element.tour.steps.length === 0) { let item; diff --git a/src/player/tree/nodes.ts b/src/player/tree/nodes.ts index 84bd619..e1af704 100644 --- a/src/player/tree/nodes.ts +++ b/src/player/tree/nodes.ts @@ -9,7 +9,7 @@ import { Uri } from "vscode"; import { CONTENT_URI, EXTENSION_NAME, FS_SCHEME } from "../../constants"; -import { CodeTour, store } from "../../store"; +import { CodeTour, CodeTourFolder, store } from "../../store"; import { progress } from "../../store/storage"; import { getFileUri, getStepLabel, getWorkspaceUri } from "../../utils"; @@ -67,6 +67,19 @@ export class CodeTourNode extends TreeItem { } } +export class CodeTourFolderNode extends TreeItem { + constructor(public tour: CodeTourFolder, extensionPath: string) { + super( + tour.title, + TreeItemCollapsibleState.Collapsed + ); + + this.description = `${tour.tours.length} tours`; + this.contextValue = "codetour.folder"; + this.iconPath = new ThemeIcon("folder"); + } +} + export class CodeTourStepNode extends TreeItem { constructor(public tour: CodeTour, public stepNumber: number) { super(getStepLabel(tour, stepNumber)); diff --git a/src/store/index.ts b/src/store/index.ts index 7f9bc8d..74ea6f1 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -47,6 +47,13 @@ export interface CodeTour { when?: string; } +export interface CodeTourFolder { + title: string; + tours: CodeTourNode[]; +} + +export type CodeTourNode = CodeTour | CodeTourFolder; + export interface ActiveTour { tour: CodeTour; step: number; @@ -72,6 +79,7 @@ type CodeTourProgress = [string, number[]]; export type CodeTourStepTuple = [CodeTour, CodeTourStep, number, number?]; export interface Store { + root: CodeTourFolder; tours: CodeTour[]; activeTour: ActiveTour | null; activeEditorSteps?: CodeTourStepTuple[]; @@ -83,6 +91,10 @@ export interface Store { } export const store: Store = observable({ + root: { + title: "", + tours: [] + }, tours: [], activeTour: null, isRecording: false, diff --git a/src/store/provider.ts b/src/store/provider.ts index cc60ca5..4c3df7b 100644 --- a/src/store/provider.ts +++ b/src/store/provider.ts @@ -5,7 +5,7 @@ import * as jexl from "jexl"; import { comparer, runInAction, set } from "mobx"; import * as os from "os"; import * as vscode from "vscode"; -import { CodeTour, store } from "."; +import { CodeTour, CodeTourFolder, store } from "."; import { EXTENSION_NAME, VSCODE_DIRECTORY } from "../constants"; import { readUriContents, updateMarkerTitles } from "../utils"; import { endCurrentCodeTour } from "./actions"; @@ -41,24 +41,28 @@ if (customDirectory) { } export async function discoverTours(): Promise { - const tours = await Promise.all( - vscode.workspace.workspaceFolders!.map(async workspaceFolder => { - const mainTours = await discoverMainTours(workspaceFolder.uri); - const tours = await discoverSubTours(workspaceFolder.uri); - - if (mainTours) { - tours.push(...mainTours); - } - - return tours; - }) - ); + const root: CodeTourFolder = { + title: "", + tours: [] + }; + + for (const workspaceFolder of vscode.workspace.workspaceFolders!) { + await discoverSubTours(workspaceFolder.uri, root); + + const mainTours = await discoverMainTours(workspaceFolder.uri); + + if (mainTours) { + root.tours.push(...mainTours); + } + } runInAction(() => { - store.tours = tours - .flat() - .sort((a, b) => a.title.localeCompare(b.title)) - .filter(tour => !tour.when || jexl.evalSync(tour.when, TOUR_CONTEXT)); + const tours: CodeTour[] = []; + + sortAndFilterFolder(root, tours); + + store.root = root; + store.tours = tours; if (store.activeTour) { const tour = store.tours.find( @@ -85,9 +89,21 @@ export async function discoverTours(): Promise { updateMarkerTitles(); } -async function discoverMainTours( - workspaceUri: vscode.Uri -): Promise { +function sortAndFilterFolder(folder: CodeTourFolder, tours: CodeTour[]): void { + folder.tours + .sort((a, b) => a.title.localeCompare(b.title)) + .filter(tour => 'tours' in tour || !tour.when || jexl.evalSync(tour.when, TOUR_CONTEXT)); + + for (const tour of folder.tours) { + if ('tours' in tour) { + sortAndFilterFolder(tour, tours) + } else { + tours.push(tour); + } + } +} + +async function discoverMainTours(workspaceUri: vscode.Uri): Promise { const tours = await Promise.all( MAIN_TOUR_FILES.map(async tourFile => { try { @@ -104,47 +120,58 @@ async function discoverMainTours( return tours.filter(tour => tour); } -async function readTourDirectory(uri: vscode.Uri): Promise { +async function readTourDirectory(uri: vscode.Uri, folder: CodeTourFolder): Promise { try { const tourFiles = await vscode.workspace.fs.readDirectory(uri); - const tours = await Promise.all( + + await Promise.all( tourFiles.map(async ([file, type]) => { const fileUri = vscode.Uri.joinPath(uri, file); if (type === vscode.FileType.File) { - return readTourFile(fileUri); + return readTourFile(fileUri, folder); } else { - return readTourDirectory(fileUri); + const newFolder = findOrCreateFolder(file, folder); + + return readTourDirectory(fileUri, newFolder); } }) ); - - // @ts-ignore - return tours.flat().filter(tour => tour); - } catch { - return []; - } + } catch {} } -async function readTourFile( - tourUri: vscode.Uri -): Promise { +async function readTourFile(tourUri: vscode.Uri, folder: CodeTourFolder): Promise { try { const tourContent = await readUriContents(tourUri); const tour = JSON.parse(tourContent); tour.id = decodeURIComponent(tourUri.toString()); - return tour; + folder.tours.push(tour); } catch {} } -async function discoverSubTours(workspaceUri: vscode.Uri): Promise { - const tours = await Promise.all( +async function discoverSubTours(workspaceUri: vscode.Uri, folder: CodeTourFolder): Promise { + await Promise.all( SUB_TOUR_DIRECTORIES.map(directory => { const uri = vscode.Uri.joinPath(workspaceUri, directory); - return readTourDirectory(uri); + return readTourDirectory(uri, folder); }) ); +} + +function findOrCreateFolder(title: string, folder: CodeTourFolder): CodeTourFolder { + for(const tour of folder.tours) { + if ('tours' in tour && tour.title === title) { + return tour; + } + } + + const newFolder = { + title, + tours: [] + }; - return tours.flat(); + folder.tours.push(newFolder); + + return newFolder; } vscode.workspace.onDidChangeWorkspaceFolders(discoverTours); From 72f6ca2dc4f5ffa26100c9e9755a7749d6b6f4b5 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Fri, 10 Jun 2022 18:30:21 +0200 Subject: [PATCH 2/2] fix: detect when a directory is renamed --- src/store/provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/store/provider.ts b/src/store/provider.ts index 4c3df7b..39441c7 100644 --- a/src/store/provider.ts +++ b/src/store/provider.ts @@ -177,7 +177,7 @@ function findOrCreateFolder(title: string, folder: CodeTourFolder): CodeTourFold vscode.workspace.onDidChangeWorkspaceFolders(discoverTours); const watcher = vscode.workspace.createFileSystemWatcher( - `**/{${SUB_TOUR_DIRECTORIES.join(",")}}/**/*.tour` + `**/{${SUB_TOUR_DIRECTORIES.join(",")}}/**` ); watcher.onDidChange(discoverTours);