#325 - Better welcome experience

This commit is contained in:
Elio Struyf
2022-04-21 17:11:07 +02:00
parent 9f6c35b9ec
commit 91049bebd9
11 changed files with 153 additions and 24 deletions
+1
View File
@@ -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
+40 -1
View File
@@ -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<string[]>(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<string[]> {
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);
});
});
}
}
+5 -2
View File
@@ -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',
@@ -25,7 +25,7 @@ export const Overview: React.FunctionComponent<IOverviewProps> = ({pages, settin
<div className={`max-w-xl text-center`}>
<FrontMatterIcon className={`text-vulcan-300 dark:text-whisper-800 h-32 mx-auto opacity-90 mb-8`} />
{
settings && settings?.folders?.length > 0 ? (
settings && settings?.contentFolders?.length > 0 ? (
<p className={`text-xl font-medium`}>No Markdown to show</p>
) : (
<>
@@ -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<IDashboardProps> = ({showWelcome
const mode = useRecoilValue(ModeAtom);
useDarkMode();
const viewState: any = Messenger.getState() || {};
if (!settings) {
return <Spinner />;
}
if (showWelcome) {
if (showWelcome || viewState.isWelcomeConfiguring) {
return <WelcomeScreen settings={settings} />;
}
if (!settings.initialized || settings.folders?.length === 0) {
if (!settings.initialized || settings.contentFolders?.length === 0) {
return <WelcomeScreen settings={settings} />;
}
@@ -47,7 +47,7 @@ export const Step: React.FunctionComponent<IStepProps> = ({name, description, st
<span className="ml-4 min-w-0 flex flex-col">
<span className="text-xs font-semibold tracking-wide uppercase text-vulcan-500 dark:text-whisper-500">{name}</span>
<div className="text-sm text-vulcan-400 dark:text-whisper-600">{description}</div>
<div className="mt-1 text-sm text-vulcan-400 dark:text-whisper-600">{description}</div>
</span>
</>
);
@@ -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 (
<div className={`text-sm flex items-center ${isAdded ? "text-teal-800" : "text-vulcan-300 dark:text-whisper-800" }`}>
<button onClick={() => addFolder(folder)} className='mr-2 hover:text-teal-500' title={`Add as a content folder to Front Matter`}>
{ isAdded ? <CheckCircleIconSolid className={`h-4 w-4`} /> : <CheckCircleIcon className={`h-4 w-4`} /> }
</button>
<span>{folder}</span>
</div>
)
}
export const StepsToGetStarted: React.FunctionComponent<IStepsToGetStartedProps> = ({settings}: React.PropsWithChildren<IStepsToGetStartedProps>) => {
const [framework, setFramework] = useState<string | null>(null);
@@ -23,7 +39,21 @@ export const StepsToGetStarted: React.FunctionComponent<IStepsToGetStartedProps>
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<IStepsToGetStartedProps>
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 <b>register folder</b>. 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: (
<>
<p>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.</p>
{
settings?.dashboardState?.welcome?.contentFolders?.length > 0 && (
<div className="mt-4">
<div className="text-sm">
Folders containing content:
</div>
<div className="mt-1 space-y-1">
{settings?.dashboardState?.welcome?.contentFolders?.map((folder) => (
<Folder
key={folder}
folder={folder}
addFolder={addFolder}
wsFolder={settings.wsFolder}
folders={settings.contentFolders} />
))}
</div>
</div>
)
}
<p className='mt-4 text-vulcan-300 dark:text-gray-400'><b>IMPORTANT</b>: You can perform this action by <b>right-clicking on the folder in the explorer view</b>, and selecting <b>register folder</b>.</p>
</>
),
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
}
];
@@ -15,18 +15,23 @@ export interface IWelcomeScreenProps {
export const WelcomeScreen: React.FunctionComponent<IWelcomeScreenProps> = ({settings}: React.PropsWithChildren<IWelcomeScreenProps>) => {
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 (
<div className={`h-full overflow-auto py-24`}>
<div className={`h-full overflow-auto py-8`}>
<main>
<div className="mx-auto max-w-7xl">
<div className="grid grid-cols-12 gap-8">
@@ -82,7 +87,7 @@ export const WelcomeScreen: React.FunctionComponent<IWelcomeScreenProps> = ({set
<StepsToGetStarted settings={settings} />
<p className="mt-5 text-sm text-vulcan-300 dark:text-whisper-700">
Once you completed both actions, the dashboard will show its full potential. You can also use the extension from the <b>Front Matter</b> side panel. There you will find the actions you can perform specifically for your pages.
You can also use the extension from the <b>Front Matter</b> side panel. There you will find the actions you can perform specifically for your pages.
</p>
</div>
</div>
+6 -2
View File
@@ -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[];
}
+5 -1
View File
@@ -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<string>(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<string>(SETTING_MEDIA_SORTING_DEFAULT),
selectedFolder: await ext.getState<string | undefined>(ExtensionState.SelectedFolder, "workspace"),
mimeTypes: Settings.get<string[]>(SETTING_MEDIA_SUPPORTED_MIMETYPES)
},
welcome: {
contentFolders
}
},
dataFiles: await this.getDataFiles(),
+15 -1
View File
@@ -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);
}
}
}