From 91049bebd983f419fbfde8563f51a7f87324f11b Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Thu, 21 Apr 2022 17:11:07 +0200 Subject: [PATCH] #325 - Better welcome experience --- CHANGELOG.md | 1 + src/commands/Folders.ts | 41 +++++++++- src/dashboardWebView/DashboardMessage.ts | 7 +- .../components/Contents/Overview.tsx | 2 +- src/dashboardWebView/components/Dashboard.tsx | 7 +- .../components/Steps/Step.tsx | 2 +- .../components/Steps/StepsToGetStarted.tsx | 76 ++++++++++++++++--- .../components/WelcomeScreen.tsx | 11 ++- src/dashboardWebView/models/Settings.ts | 8 +- src/helpers/DashboardSettings.ts | 6 +- src/listeners/dashboard/SettingsListener.ts | 16 +++- 11 files changed, 153 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73b04297..7f4887f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - [#314](https://github.com/estruyf/vscode-front-matter/issues/314): New preview actions to open the page in the browser and refresh the preview - [#322](https://github.com/estruyf/vscode-front-matter/issues/322): Show parent folder name when file is an index page (`index.md` / `_index.md`) - [#323](https://github.com/estruyf/vscode-front-matter/issues/323): Added 11ty, jekyll, and docusaurus to the framework selection list +- [#325](https://github.com/estruyf/vscode-front-matter/issues/325): Better welcome experience that allows you to add content folders straight from the welcome view ### ⚡️ Optimizations diff --git a/src/commands/Folders.ts b/src/commands/Folders.ts index 22f2731a..ef26089a 100644 --- a/src/commands/Folders.ts +++ b/src/commands/Folders.ts @@ -1,7 +1,7 @@ import { Questions } from './../helpers/Questions'; import { SETTING_CONTENT_PAGE_FOLDERS, SETTING_CONTENT_STATIC_FOLDER, SETTING_CONTENT_SUPPORTED_FILETYPES, TelemetryEvent } from './../constants'; import { commands, Uri, workspace, window } from "vscode"; -import { basename, dirname, join, sep } from "path"; +import { basename, dirname, join, relative, sep } from "path"; import { ContentFolder, FileInfo, FolderInfo } from "../models"; import uniqBy = require("lodash.uniqby"); import { Template } from "./Template"; @@ -15,6 +15,7 @@ import { MediaHelpers } from '../helpers/MediaHelpers'; import { MediaListener, PagesListener, SettingsListener } from '../listeners/dashboard'; import { DEFAULT_FILE_TYPES } from '../constants/DefaultFileTypes'; import { Telemetry } from '../helpers/Telemetry'; +import { glob } from 'glob'; export const WORKSPACE_PLACEHOLDER = `[[workspace]]`; @@ -349,4 +350,42 @@ export class Folders { absPath = isWindows ? absPath.split('\\').join('/') : absPath; return absPath; } + + /** + * Find the content folders + */ + public static async getContentFolders() { + // Find folders that contain files + const wsFolder = Folders.getWorkspaceFolder(); + const supportedFiles = Settings.get(SETTING_CONTENT_SUPPORTED_FILETYPES) || DEFAULT_FILE_TYPES; + const patterns = supportedFiles.map(fileType => `${join(wsFolder?.fsPath || "", "**", `*${fileType.startsWith('.') ? '' : '.'}${fileType}`)}`); + let folders: string[] = []; + + for (const pattern of patterns) { + folders = [...folders, ...(await this.findFolders(pattern))]; + } + + // Filter out the workspace folder + if (wsFolder) { + folders = folders.filter(folder => folder !== wsFolder.fsPath); + } + + const uniqueFolders = [...new Set(folders)]; + return uniqueFolders.map(folder => relative(wsFolder?.path || "", folder)); + } + + /** + * Retrieve all content folders + * @param pattern + * @returns + */ + private static findFolders(pattern: string): Promise { + return new Promise(resolve => { + glob(pattern, { ignore: "**/node_modules/**" }, (err, files) => { + const allFolders = files.map(file => dirname(file)); + const uniqueFolders = [...new Set(allFolders)]; + resolve(uniqueFolders); + }); + }); + } } diff --git a/src/dashboardWebView/DashboardMessage.ts b/src/dashboardWebView/DashboardMessage.ts index 1272bd05..e9a77851 100644 --- a/src/dashboardWebView/DashboardMessage.ts +++ b/src/dashboardWebView/DashboardMessage.ts @@ -5,6 +5,11 @@ export enum DashboardMessage { getMode = 'getMode', showWarning = 'showWarning', + // Welcome view + initializeProject = 'initializeProject', + setFramework = 'setFramework', + addFolder = 'addFolder', + // Content dashboard getData = 'getData', createContent = 'createContent', @@ -39,8 +44,6 @@ export enum DashboardMessage { // Other getTheme = 'getTheme', updateSetting = 'updateSetting', - initializeProject = 'initializeProject', - setFramework = 'setFramework', setState = 'setState', runCustomScript = 'runCustomScript', sendTelemetry = 'sendTelemetry', diff --git a/src/dashboardWebView/components/Contents/Overview.tsx b/src/dashboardWebView/components/Contents/Overview.tsx index a48653d3..3f84dd30 100644 --- a/src/dashboardWebView/components/Contents/Overview.tsx +++ b/src/dashboardWebView/components/Contents/Overview.tsx @@ -25,7 +25,7 @@ export const Overview: React.FunctionComponent = ({pages, settin
{ - settings && settings?.folders?.length > 0 ? ( + settings && settings?.contentFolders?.length > 0 ? (

No Markdown to show

) : ( <> diff --git a/src/dashboardWebView/components/Dashboard.tsx b/src/dashboardWebView/components/Dashboard.tsx index be1ac68f..f9cec4e2 100644 --- a/src/dashboardWebView/components/Dashboard.tsx +++ b/src/dashboardWebView/components/Dashboard.tsx @@ -12,6 +12,7 @@ import { DataView } from './DataView'; import { Snippets } from './SnippetsView/Snippets'; import { FeatureFlag } from '../../components/features/FeatureFlag'; import { FEATURE_FLAG } from '../../constants'; +import { Messenger } from '@estruyf/vscode/dist/client'; export interface IDashboardProps { showWelcome: boolean; @@ -23,15 +24,17 @@ export const Dashboard: React.FunctionComponent = ({showWelcome const mode = useRecoilValue(ModeAtom); useDarkMode(); + const viewState: any = Messenger.getState() || {}; + if (!settings) { return ; } - if (showWelcome) { + if (showWelcome || viewState.isWelcomeConfiguring) { return ; } - if (!settings.initialized || settings.folders?.length === 0) { + if (!settings.initialized || settings.contentFolders?.length === 0) { return ; } diff --git a/src/dashboardWebView/components/Steps/Step.tsx b/src/dashboardWebView/components/Steps/Step.tsx index ce85fb4c..6b6e2c8d 100644 --- a/src/dashboardWebView/components/Steps/Step.tsx +++ b/src/dashboardWebView/components/Steps/Step.tsx @@ -47,7 +47,7 @@ export const Step: React.FunctionComponent = ({name, description, st {name} -
{description}
+
{description}
); diff --git a/src/dashboardWebView/components/Steps/StepsToGetStarted.tsx b/src/dashboardWebView/components/Steps/StepsToGetStarted.tsx index 983a0e9b..36f4b2f2 100644 --- a/src/dashboardWebView/components/Steps/StepsToGetStarted.tsx +++ b/src/dashboardWebView/components/Steps/StepsToGetStarted.tsx @@ -4,17 +4,33 @@ import { DashboardMessage } from '../../DashboardMessage'; import { Settings } from '../../models/Settings'; import { Status } from '../../models/Status'; import { Step } from './Step'; -import { useState } from 'react'; +import { useMemo, useState } from 'react'; import { Menu } from '@headlessui/react'; import { MenuItem } from '../Menu'; -import { Framework } from '../../../models'; -import {ChevronDownIcon} from '@heroicons/react/outline'; +import { ContentFolder, Framework } from '../../../models'; +import {CheckCircleIcon, ChevronDownIcon} from '@heroicons/react/outline'; +import {CheckCircleIcon as CheckCircleIconSolid} from '@heroicons/react/solid'; import { FrameworkDetectors } from '../../../constants/FrameworkDetectors'; +import { join } from 'path'; export interface IStepsToGetStartedProps { settings: Settings; } +const Folder = ({ wsFolder, folder, folders, addFolder }: { wsFolder: string, folder: string, folders: ContentFolder[], addFolder: (folder: string) => void}) => { + + const isAdded = useMemo(() => folders.find(f => f.path.toLowerCase() === join(wsFolder, folder).toLowerCase()), [folder, folders, wsFolder]); + + return ( +
+ + {folder} +
+ ) +} + export const StepsToGetStarted: React.FunctionComponent = ({settings}: React.PropsWithChildren) => { const [framework, setFramework] = useState(null); @@ -23,7 +39,21 @@ export const StepsToGetStarted: React.FunctionComponent const setFrameworkAndSendMessage = (framework: string) => { setFramework(framework); Messenger.send(DashboardMessage.setFramework, framework); - } + }; + + const addFolder = (folder: string) => { + Messenger.send(DashboardMessage.addFolder, folder); + }; + + const reload = () => { + const crntState: any = Messenger.getState() || {}; + Messenger.setState({ + ...crntState, + isWelcomeConfiguring: false + }); + + Messenger.send(DashboardMessage.reload); + }; const steps = [ { @@ -76,15 +106,41 @@ export const StepsToGetStarted: React.FunctionComponent onClick: undefined }, { - name: 'Register content folders (manual action)', - description: <>Register your content folder(s). You can perform this action by right-clicking on the folder in the explorer view, and selecting register folder. Once a folder is set, Front Matter can be used to list all contents and allow you to create content., - status: settings.folders && settings.folders.length > 0 ? Status.Completed : Status.NotStarted + name: 'Register content folder(s)', + description: ( + <> +

Add one of the folders we found in your project as a content folder. Once a folder is set, Front Matter can be used to list all contents and allow you to create content.

+ + { + settings?.dashboardState?.welcome?.contentFolders?.length > 0 && ( +
+
+ Folders containing content: +
+
+ {settings?.dashboardState?.welcome?.contentFolders?.map((folder) => ( + + ))} +
+
+ ) + } + +

IMPORTANT: You can perform this action by right-clicking on the folder in the explorer view, and selecting register folder.

+ + ), + status: settings.contentFolders && settings.contentFolders.length > 0 ? Status.Completed : Status.NotStarted }, { name: 'Show the dashboard', - description: <>Once both actions are completed, click on this action to load the dashboard., - status: (settings.initialized && settings.folders && settings.folders.length > 0) ? Status.Active : Status.NotStarted, - onClick: (settings.initialized && settings.folders && settings.folders.length > 0) ? () => { Messenger.send(DashboardMessage.reload); } : undefined + description: <>Once all actions are completed, the dashboard can be loaded., + status: (settings.initialized && settings.contentFolders && settings.contentFolders.length > 0) ? Status.Active : Status.NotStarted, + onClick: (settings.initialized && settings.contentFolders && settings.contentFolders.length > 0) ? reload : undefined } ]; diff --git a/src/dashboardWebView/components/WelcomeScreen.tsx b/src/dashboardWebView/components/WelcomeScreen.tsx index 985920f6..537f7f80 100644 --- a/src/dashboardWebView/components/WelcomeScreen.tsx +++ b/src/dashboardWebView/components/WelcomeScreen.tsx @@ -15,18 +15,23 @@ export interface IWelcomeScreenProps { export const WelcomeScreen: React.FunctionComponent = ({settings}: React.PropsWithChildren) => { React.useEffect(() => { - Messenger.send(DashboardMessage.sendTelemetry, { event: TelemetryEvent.webviewWelcomeScreen }); + const crntState: any = Messenger.getState() || {}; + Messenger.setState({ + ...crntState, + isWelcomeConfiguring: true + }); + return () => { Messenger.send(DashboardMessage.reload) }; }, []); return ( -
+
@@ -82,7 +87,7 @@ export const WelcomeScreen: React.FunctionComponent = ({set

- Once you completed both actions, the dashboard will show its full potential. You can also use the extension from the Front Matter side panel. There you will find the actions you can perform specifically for your pages. + You can also use the extension from the Front Matter side panel. There you will find the actions you can perform specifically for your pages.

diff --git a/src/dashboardWebView/models/Settings.ts b/src/dashboardWebView/models/Settings.ts index 205f0250..ec1eb326 100644 --- a/src/dashboardWebView/models/Settings.ts +++ b/src/dashboardWebView/models/Settings.ts @@ -10,8 +10,7 @@ export interface Settings { beta: boolean; initialized: boolean; wsFolder: string; - staticFolder: string; - folders: ContentFolder[]; + staticFolder: string; tags: string[]; categories: string[]; openOnStart: boolean | null; @@ -36,6 +35,7 @@ export interface Settings { export interface DashboardState { contents: ContentsViewState; media: MediaViewState; + welcome: WelcomeViewState; } export interface ContentsViewState { @@ -47,4 +47,8 @@ export interface ContentsViewState { export interface MediaViewState extends ContentsViewState { selectedFolder: string | null | undefined; mimeTypes: string[] | null | undefined; +} + +export interface WelcomeViewState { + contentFolders: string[]; } \ No newline at end of file diff --git a/src/helpers/DashboardSettings.ts b/src/helpers/DashboardSettings.ts index 1cc2c98b..5821b883 100644 --- a/src/helpers/DashboardSettings.ts +++ b/src/helpers/DashboardSettings.ts @@ -19,12 +19,13 @@ export class DashboardSettings { const ext = Extension.getInstance(); const wsFolder = Folders.getWorkspaceFolder(); const isInitialized = await Template.isInitialized(); + + const contentFolders = await Folders.getContentFolders(); return { beta: ext.isBetaVersion(), wsFolder: wsFolder ? wsFolder.fsPath : '', staticFolder: Settings.get(SETTING_CONTENT_STATIC_FOLDER), - folders: Folders.get(), initialized: isInitialized, tags: Settings.getTaxonomy(TaxonomyType.Tag), categories: Settings.getTaxonomy(TaxonomyType.Category), @@ -53,6 +54,9 @@ export class DashboardSettings { defaultSorting: Settings.get(SETTING_MEDIA_SORTING_DEFAULT), selectedFolder: await ext.getState(ExtensionState.SelectedFolder, "workspace"), mimeTypes: Settings.get(SETTING_MEDIA_SUPPORTED_MIMETYPES) + }, + welcome: { + contentFolders } }, dataFiles: await this.getDataFiles(), diff --git a/src/listeners/dashboard/SettingsListener.ts b/src/listeners/dashboard/SettingsListener.ts index a5989f77..4b70fe7e 100644 --- a/src/listeners/dashboard/SettingsListener.ts +++ b/src/listeners/dashboard/SettingsListener.ts @@ -1,4 +1,7 @@ -import { SETTING_CONTENT_STATIC_FOLDER, SETTING_FRAMEWORK_ID } from "../../constants"; +import { join } from "path"; +import { commands, Uri } from "vscode"; +import { Folders } from "../../commands/Folders"; +import { COMMAND_NAME, SETTING_CONTENT_STATIC_FOLDER, SETTING_FRAMEWORK_ID } from "../../constants"; import { DashboardCommand } from "../../dashboardWebView/DashboardCommand"; import { DashboardMessage } from "../../dashboardWebView/DashboardMessage"; import { DashboardSettings, Settings } from "../../helpers"; @@ -26,6 +29,9 @@ export class SettingsListener extends BaseListener { case DashboardMessage.setFramework: this.setFramework(msg?.data); break; + case DashboardMessage.addFolder: + this.addFolder(msg?.data); + break; } } @@ -68,4 +74,12 @@ export class SettingsListener extends BaseListener { SettingsListener.getSettings(); } + + private static addFolder(folder: string) { + if (folder) { + const wsFolder = Folders.getWorkspaceFolder(); + const folderUri = Uri.file(join(wsFolder?.fsPath || "", folder)); + commands.executeCommand(COMMAND_NAME.registerFolder, folderUri); + } + } } \ No newline at end of file