Adding tour progress
This commit is contained in:
Родитель
7c5e7ae1e0
Коммит
982868ab15
|
@ -1,3 +1,9 @@
|
|||
## v0.0.41 (12/12/2020)
|
||||
|
||||
- The `CodeTour` view now indicates the tours/steps that you've already taken
|
||||
- The `CodeTour` view now displays an icon next to the active tour step
|
||||
- The `CodeTour: Hide Markers` and `CodeTour: Show Markers` commands are now hidden from the command palette
|
||||
|
||||
## v0.0.40 (12/11/2020)
|
||||
|
||||
- Tours with titles that start with `#1 -` or `1 -` are now automatically considered the primary tour, if there isn't already a tour that's explicitly marked as being the primary.
|
||||
|
|
60
package.json
60
package.json
|
@ -3,7 +3,7 @@
|
|||
"displayName": "CodeTour",
|
||||
"description": "VS Code extension that allows you to record and playback guided tours of codebases, directly within the editor",
|
||||
"publisher": "vsls-contrib",
|
||||
"version": "0.0.40",
|
||||
"version": "0.0.41",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vsls-contrib/codetour"
|
||||
|
@ -84,6 +84,15 @@
|
|||
"command": "codetour.changeTourTitle",
|
||||
"title": "Change Title"
|
||||
},
|
||||
{
|
||||
"command": "codetour.resetProgress",
|
||||
"title": "Reset Progress",
|
||||
"category": "CodeTour"
|
||||
},
|
||||
{
|
||||
"command": "codetour.resetTourProgress",
|
||||
"title": "Reset Progress"
|
||||
},
|
||||
{
|
||||
"command": "codetour.deleteTourStep",
|
||||
"title": "Delete Step"
|
||||
|
@ -210,10 +219,6 @@
|
|||
"command": "codetour.endTour",
|
||||
"when": "codetour:inTour"
|
||||
},
|
||||
{
|
||||
"command": "codetour.hideMarkers",
|
||||
"when": "codetour:hasTours && codetour:showingMarkers"
|
||||
},
|
||||
{
|
||||
"command": "codetour.previewTour",
|
||||
"when": "codetour:inTour && codetour:recording"
|
||||
|
@ -223,12 +228,12 @@
|
|||
"when": "workspaceFolderCount != 0"
|
||||
},
|
||||
{
|
||||
"command": "codetour.resumeTour",
|
||||
"when": "codetour:inTour"
|
||||
"command": "codetour.resetProgress",
|
||||
"when": "codetour:hasProgress"
|
||||
},
|
||||
{
|
||||
"command": "codetour.showMarkers",
|
||||
"when": "codetour:hasTours && !codetour:showingMarkers"
|
||||
"command": "codetour.resumeTour",
|
||||
"when": "codetour:inTour"
|
||||
},
|
||||
{
|
||||
"command": "codetour.startTour",
|
||||
|
@ -282,6 +287,10 @@
|
|||
"command": "codetour.exportTour",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codetour.hideMarkers",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codetour.makeTourPrimary",
|
||||
"when": "false"
|
||||
|
@ -298,10 +307,18 @@
|
|||
"command": "codetour.previousTourStep",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codetour.resetTourProgress",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codetour.saveTourStep",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codetour.showMarkers",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codetour.unmakeTourPrimary",
|
||||
"when": "false"
|
||||
|
@ -428,13 +445,8 @@
|
|||
"group": "basic@1"
|
||||
},
|
||||
{
|
||||
"command": "codetour.makeTourPrimary",
|
||||
"when": "viewItem =~ /^codetour.tour(.recording)?(.active)?$/",
|
||||
"group": "basic@2"
|
||||
},
|
||||
{
|
||||
"command": "codetour.unmakeTourPrimary",
|
||||
"when": "viewItem =~ /^codetour.tour.primary(.recording)?(.active)?$/",
|
||||
"command": "codetour.resetTourProgress",
|
||||
"when": "false",
|
||||
"group": "basic@2"
|
||||
},
|
||||
{
|
||||
|
@ -452,20 +464,30 @@
|
|||
"when": "viewItem =~ /^codetour.tour(.primary)?(.recording)?(.active)?$/ && gitOpenRepositoryCount != 0",
|
||||
"group": "change@3"
|
||||
},
|
||||
{
|
||||
"command": "codetour.makeTourPrimary",
|
||||
"when": "viewItem =~ /^codetour.tour(.recording)?(.active)?$/",
|
||||
"group": "edit@1"
|
||||
},
|
||||
{
|
||||
"command": "codetour.unmakeTourPrimary",
|
||||
"when": "viewItem =~ /^codetour.tour.primary(.recording)?(.active)?$/",
|
||||
"group": "edit@2"
|
||||
},
|
||||
{
|
||||
"command": "codetour.editTour",
|
||||
"when": "viewItem =~ /^codetour.tour(.primary)?(.active)?$/",
|
||||
"group": "edit@1"
|
||||
"group": "edit@2"
|
||||
},
|
||||
{
|
||||
"command": "codetour.previewTour",
|
||||
"when": "viewItem =~ /^codetour.tour(.primary)?.recording/",
|
||||
"group": "edit@1"
|
||||
"group": "edit@2"
|
||||
},
|
||||
{
|
||||
"command": "codetour.deleteTour",
|
||||
"when": "viewItem =~ /^codetour.tour(.primary)?(.recording)?(.active)?$/",
|
||||
"group": "edit@2"
|
||||
"group": "edit@3"
|
||||
},
|
||||
{
|
||||
"command": "codetour.exportTour",
|
||||
|
|
|
@ -11,10 +11,12 @@ import { registerCompletionProvider } from "./recorder/completionProvider";
|
|||
import { store } from "./store";
|
||||
import { promptForTour } from "./store/actions";
|
||||
import { discoverTours } from "./store/provider";
|
||||
import { initializeStorage } from "./store/storage";
|
||||
import { registerTreeProvider } from "./tree";
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
registerCommands();
|
||||
initializeStorage(context);
|
||||
|
||||
// If the user has a workspace open, then attempt to discover
|
||||
// the tours contained within it and optionally prompt the user.
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
selectTour,
|
||||
startCodeTour
|
||||
} from "../store/actions";
|
||||
import { progress } from "../store/storage";
|
||||
import { CodeTourNode } from "../tree/nodes";
|
||||
import { readUriContents } from "../utils";
|
||||
|
||||
|
@ -48,6 +49,22 @@ export function registerPlayerCommands() {
|
|||
}
|
||||
);
|
||||
|
||||
vscode.commands.registerCommand(
|
||||
`${EXTENSION_NAME}.finishTour`,
|
||||
async (title?: string) => {
|
||||
await progress.update();
|
||||
|
||||
if (title) {
|
||||
vscode.commands.executeCommand(
|
||||
`${EXTENSION_NAME}.startTourByTitle`,
|
||||
title
|
||||
);
|
||||
} else {
|
||||
vscode.commands.executeCommand(`${EXTENSION_NAME}.endTour`);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Purpose: Command link
|
||||
vscode.commands.registerCommand(
|
||||
`${EXTENSION_NAME}.navigateToStep`,
|
||||
|
@ -254,4 +271,8 @@ export function registerPlayerCommands() {
|
|||
vscode.commands.registerCommand(`${EXTENSION_NAME}.showMarkers`, () =>
|
||||
setShowMarkers(true)
|
||||
);
|
||||
|
||||
vscode.commands.registerCommand(`${EXTENSION_NAME}.resetProgress`, () =>
|
||||
progress.reset()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -251,9 +251,9 @@ async function renderCurrentStep() {
|
|||
const argsContent = encodeURIComponent(
|
||||
JSON.stringify([nextTour.title])
|
||||
);
|
||||
content += `${prefix}[Next Tour (${tourTitle})](command:codetour.startTourByTitle?${argsContent} "Start next tour")`;
|
||||
content += `${prefix}[Next Tour (${tourTitle})](command:codetour.finishTour?${argsContent} "Start next tour")`;
|
||||
} else {
|
||||
content += `${prefix}[Finish Tour](command:codetour.endTour "Finish the tour")`;
|
||||
content += `${prefix}[Finish Tour](command:codetour.finishTour "Finish the tour")`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
getWorkspaceUri,
|
||||
readUriContents
|
||||
} from "../utils";
|
||||
import { progress } from "./storage";
|
||||
|
||||
const CAN_EDIT_TOUR_KEY = `${EXTENSION_NAME}:canEditTour`;
|
||||
const IN_TOUR_KEY = `${EXTENSION_NAME}:inTour`;
|
||||
|
@ -111,7 +112,9 @@ export function moveCurrentCodeTourBackward() {
|
|||
_onDidStartTour.fire([store.activeTour!.tour, store.activeTour!.step]);
|
||||
}
|
||||
|
||||
export function moveCurrentCodeTourForward() {
|
||||
export async function moveCurrentCodeTourForward() {
|
||||
await progress.update();
|
||||
|
||||
store.activeTour!.step++;
|
||||
|
||||
_onDidStartTour.fire([store.activeTour!.tour, store.activeTour!.step]);
|
||||
|
|
|
@ -60,12 +60,15 @@ export interface ActiveTour {
|
|||
tours?: CodeTour[];
|
||||
}
|
||||
|
||||
type CodeTourProgress = [string, number[]];
|
||||
|
||||
export interface Store {
|
||||
tours: CodeTour[];
|
||||
activeTour: ActiveTour | null;
|
||||
hasTours: boolean;
|
||||
isRecording: boolean;
|
||||
showMarkers: boolean;
|
||||
progress: CodeTourProgress[];
|
||||
}
|
||||
|
||||
export const store: Store = observable({
|
||||
|
@ -75,5 +78,6 @@ export const store: Store = observable({
|
|||
get hasTours() {
|
||||
return this.tours.length > 0;
|
||||
},
|
||||
showMarkers: false
|
||||
showMarkers: false,
|
||||
progress: []
|
||||
});
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
import { observable } from "mobx";
|
||||
import { commands, ExtensionContext, Uri, workspace } from "vscode";
|
||||
import { CodeTour, store } from ".";
|
||||
|
||||
const CODETOUR_PROGRESS_KEY = "codetour:progress";
|
||||
|
||||
export var progress: {
|
||||
update(): void;
|
||||
isComplete(tour: CodeTour, stepNumber?: number): boolean;
|
||||
reset(): void;
|
||||
};
|
||||
|
||||
function getProgress(tour: CodeTour) {
|
||||
const progress = store.progress.find(([id]) => tour.id === id);
|
||||
return progress?.[1] || observable([]);
|
||||
}
|
||||
|
||||
export function initializeStorage(context: ExtensionContext) {
|
||||
store.progress = context.globalState.get(CODETOUR_PROGRESS_KEY) || [];
|
||||
|
||||
progress = {
|
||||
async update() {
|
||||
const progress = store.progress.find(
|
||||
([id]) => store.activeTour?.tour.id === id
|
||||
);
|
||||
|
||||
if (progress && !progress![1].includes(store.activeTour!.step)) {
|
||||
progress![1].push(store.activeTour!.step);
|
||||
} else {
|
||||
store.progress.push([
|
||||
store.activeTour!.tour.id,
|
||||
[store.activeTour!.step]
|
||||
]);
|
||||
}
|
||||
|
||||
commands.executeCommand("setContext", "codetour:hasProgress", true);
|
||||
return context.globalState.update(CODETOUR_PROGRESS_KEY, store.progress);
|
||||
},
|
||||
isComplete(tour: CodeTour, stepNumber?: number): boolean {
|
||||
const tourProgress = getProgress(tour);
|
||||
if (stepNumber !== undefined) {
|
||||
return tourProgress.includes(stepNumber);
|
||||
} else {
|
||||
return tourProgress.length >= tour.steps.length;
|
||||
}
|
||||
},
|
||||
async reset(tour?: CodeTour) {
|
||||
commands.executeCommand("setContext", "codetour:hasProgress", false);
|
||||
|
||||
store.progress = tour
|
||||
? store.progress.filter(tourProgress => tourProgress[0] !== tour.id)
|
||||
: [];
|
||||
return context.globalState.update(CODETOUR_PROGRESS_KEY, store.progress);
|
||||
}
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
context.globalState.setKeysForSync([CODETOUR_PROGRESS_KEY]);
|
||||
|
||||
const workspaceHasProgress = store.progress.some(([id]) =>
|
||||
workspace.getWorkspaceFolder(Uri.parse(id))
|
||||
);
|
||||
|
||||
if (workspaceHasProgress) {
|
||||
commands.executeCommand("setContext", "codetour:hasProgress", true);
|
||||
}
|
||||
}
|
|
@ -9,7 +9,6 @@ import {
|
|||
} from "vscode";
|
||||
import { EXTENSION_NAME } from "../constants";
|
||||
import { store } from "../store";
|
||||
import { getTourTitle } from "../utils";
|
||||
import { CodeTourNode, CodeTourStepNode } from "./nodes";
|
||||
|
||||
class CodeTourTreeProvider implements TreeDataProvider<TreeItem>, Disposable {
|
||||
|
@ -25,6 +24,10 @@ class CodeTourTreeProvider implements TreeDataProvider<TreeItem>, Disposable {
|
|||
store.tours,
|
||||
store.hasTours,
|
||||
store.isRecording,
|
||||
store.progress.map(([id, completedSteps]) => [
|
||||
id,
|
||||
completedSteps.map(step => step)
|
||||
]),
|
||||
store.activeTour
|
||||
? [
|
||||
store.activeTour.tour.title,
|
||||
|
@ -144,11 +147,6 @@ export function registerTreeProvider(extensionPath: string) {
|
|||
return;
|
||||
}
|
||||
|
||||
const title = getTourTitle(store.activeTour.tour);
|
||||
treeView.message = `Current step: #${store.activeTour.step + 1} of ${
|
||||
store.activeTour.tour.steps.length
|
||||
} (${title})`;
|
||||
|
||||
revealCurrentStepNode();
|
||||
} else {
|
||||
// TODO: Once VS Code supports it, we want
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
import { ThemeIcon, TreeItem, TreeItemCollapsibleState, Uri } from "vscode";
|
||||
import {
|
||||
ThemeColor,
|
||||
ThemeIcon,
|
||||
TreeItem,
|
||||
TreeItemCollapsibleState,
|
||||
Uri
|
||||
} from "vscode";
|
||||
import { CONTENT_URI, EXTENSION_NAME, FS_SCHEME } from "../constants";
|
||||
import { CodeTour, store } from "../store";
|
||||
import { progress } from "../store/storage";
|
||||
import { getFileUri, getStepLabel, getWorkspaceUri } from "../utils";
|
||||
|
||||
function isRecording(tour: CodeTour) {
|
||||
|
@ -11,6 +18,12 @@ function isRecording(tour: CodeTour) {
|
|||
);
|
||||
}
|
||||
|
||||
const completeIcon = new ThemeIcon(
|
||||
"check",
|
||||
// @ts-ignore
|
||||
new ThemeColor("terminal.ansiGreen")
|
||||
);
|
||||
|
||||
export class CodeTourNode extends TreeItem {
|
||||
constructor(public tour: CodeTour, extensionPath: string) {
|
||||
super(
|
||||
|
@ -45,6 +58,8 @@ export class CodeTourNode extends TreeItem {
|
|||
? new ThemeIcon("record")
|
||||
: isActive
|
||||
? new ThemeIcon("play-circle")
|
||||
: progress.isComplete(tour)
|
||||
? completeIcon
|
||||
: new ThemeIcon("location");
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +99,17 @@ export class CodeTourStepNode extends TreeItem {
|
|||
|
||||
this.resourceUri = resourceUri;
|
||||
|
||||
if (step.directory) {
|
||||
const isActive =
|
||||
store.activeTour &&
|
||||
tour.id === store.activeTour?.tour.id &&
|
||||
store.activeTour.step === stepNumber;
|
||||
|
||||
if (isActive) {
|
||||
this.iconPath = new ThemeIcon("play-circle");
|
||||
} else if (progress.isComplete(tour, stepNumber)) {
|
||||
// @ts-ignore
|
||||
this.iconPath = completeIcon;
|
||||
} else if (step.directory) {
|
||||
this.iconPath = ThemeIcon.Folder;
|
||||
} else {
|
||||
this.iconPath = ThemeIcon.File;
|
||||
|
|
Загрузка…
Ссылка в новой задаче