mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-08-05 16:33:04 +02:00
#325 - Better welcome experience
This commit is contained in:
@@ -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
@@ -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,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>
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user