From 42cc53cefc4879f938677167ab1ca97d1098bd13 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 19 Jan 2022 10:15:58 +0100 Subject: [PATCH] #226 - Allow to start the local server for the framework or SSG --- package.json | 5 ++ src/constants/FrameworkDetectors.ts | 20 ++++-- src/constants/settings.ts | 1 + src/explorerView/ExplorerView.ts | 69 ++++++++++++------- src/models/PanelSettings.ts | 6 ++ src/panelWebView/CommandToCode.ts | 2 + src/panelWebView/components/Actions.tsx | 3 + src/panelWebView/components/BaseView.tsx | 2 + .../components/GlobalSettings.tsx | 29 ++++++++ .../components/StartServerButton.tsx | 22 ++++++ src/panelWebView/hooks/useStartCommand.tsx | 28 ++++++++ 11 files changed, 158 insertions(+), 29 deletions(-) create mode 100644 src/panelWebView/components/StartServerButton.tsx create mode 100644 src/panelWebView/hooks/useStartCommand.tsx diff --git a/package.json b/package.json index c4e8cd95..c2397bc8 100644 --- a/package.json +++ b/package.json @@ -522,6 +522,11 @@ "default": "", "markdownDescription": "Specify the ID of your static site generator or framework you are using for your website. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.framework.id)" }, + "frontMatter.framework.startCommand": { + "type": ["string", "null"], + "default": null, + "markdownDescription": "Specify the command you want to use to start your static site generator or framework. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.framework.startcommand)" + }, "frontMatter.media.defaultSorting": { "type": "string", "default": "", diff --git a/src/constants/FrameworkDetectors.ts b/src/constants/FrameworkDetectors.ts index 5433233d..b15a7925 100644 --- a/src/constants/FrameworkDetectors.ts +++ b/src/constants/FrameworkDetectors.ts @@ -2,20 +2,32 @@ export const FrameworkDetectors = [ { "framework": {"name": "gatsby", "dist": "public", "static": "static", "build": "gatsby build"}, "requiredFiles": ["gatsby-config.js"], - "requiredDependencies": ["gatsby"] + "requiredDependencies": ["gatsby"], + "commands": { + "start": "npx gatsby develop" + } }, { "framework": {"name": "hugo", "dist": "public", "static": "static", "build": "hugo"}, - "requiredFiles": ["config.toml", "config.yaml", "config.yml"] + "requiredFiles": ["config.toml", "config.yaml", "config.yml"], + "commands": { + "start": "hugo server -D" + } }, { "framework": {"name": "next", "dist": ".next", "static": "public", "build": "next build"}, "requiredFiles": ["next.config.js"], - "requiredDependencies": ["next"] + "requiredDependencies": ["next"], + "commands": { + "start": "npx next dev" + } }, { "framework": {"name": "nuxt", "dist": "dist", "static": "static", "build": "nuxt"}, "requiredFiles": ["nuxt.config.js"], - "requiredDependencies": ["nuxt"] + "requiredDependencies": ["nuxt"], + "commands": { + "start": "npx nuxt" + } } ]; \ No newline at end of file diff --git a/src/constants/settings.ts b/src/constants/settings.ts index 8d69ee64..31e2b535 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -62,6 +62,7 @@ export const SETTINGS_DATA_FOLDERS = "data.folders"; export const SETTINGS_DATA_TYPES = "data.types"; export const SETTINGS_FRAMEWORK_ID = "framework.id"; +export const SETTINGS_FRAMEWORK_START = "framework.startCommand"; export const SETTING_SITE_BASEURL = "site.baseURL"; diff --git a/src/explorerView/ExplorerView.ts b/src/explorerView/ExplorerView.ts index ed79b248..6178369a 100644 --- a/src/explorerView/ExplorerView.ts +++ b/src/explorerView/ExplorerView.ts @@ -1,9 +1,9 @@ import { DashboardData } from '../models/DashboardData'; import { Template } from '../commands/Template'; -import { DefaultFields, SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_AUTO_UPDATE_DATE, SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_PREVIEW_HOST, SETTING_DATE_FORMAT, SETTING_COMMA_SEPARATED_FIELDS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_PANEL_FREEFORM, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS, SETTINGS_CONTENT_DRAFT_FIELD, SETTING_SEO_SLUG_LENGTH, SETTING_SITE_BASEURL, SETTING_TAXONOMY_CUSTOM, CONTEXT } from '../constants'; +import { DefaultFields, SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_AUTO_UPDATE_DATE, SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_PREVIEW_HOST, SETTING_DATE_FORMAT, SETTING_COMMA_SEPARATED_FIELDS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_PANEL_FREEFORM, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS, SETTINGS_CONTENT_DRAFT_FIELD, SETTING_SEO_SLUG_LENGTH, SETTING_SITE_BASEURL, SETTING_TAXONOMY_CUSTOM, CONTEXT, SETTINGS_FRAMEWORK_ID, SETTINGS_FRAMEWORK_START } from '../constants'; import * as os from 'os'; import { PanelSettings, CustomScript as ICustomScript } from '../models/PanelSettings'; -import { CancellationToken, Disposable, Uri, Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext, window, workspace, commands, env as vscodeEnv } from "vscode"; +import { CancellationToken, Disposable, Uri, Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext, window, workspace, commands, env as vscodeEnv, ThemeIcon } from "vscode"; import { ArticleHelper, Settings } from "../helpers"; import { Command } from "../panelWebView/Command"; import { CommandToCode } from '../panelWebView/CommandToCode'; @@ -161,13 +161,13 @@ export class ExplorerView implements WebviewViewProvider, Disposable { await commands.executeCommand(COMMAND_NAME.createTemplate); break; case CommandToCode.updateModifiedUpdating: - this.updateModifiedUpdating(msg.data || false); + this.updateSetting(SETTING_AUTO_UPDATE_DATE, msg.data || false); break; case CommandToCode.toggleWritingSettings: this.toggleWritingSettings(); break; case CommandToCode.updateFmHighlight: - this.updateFmHighlight((msg.data !== null && msg.data !== undefined) ? msg.data : false); + this.updateSetting(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, (msg.data !== null && msg.data !== undefined) ? msg.data : false); break; case CommandToCode.toggleCenterMode: await commands.executeCommand(`workbench.action.toggleCenteredLayout`); @@ -179,7 +179,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable { await commands.executeCommand(COMMAND_NAME.dashboard); break; case CommandToCode.updatePreviewUrl: - this.updatePreviewUrl(msg.data || ""); + this.updateSetting(SETTING_PREVIEW_HOST, msg.data || ""); break; case CommandToCode.openInEditor: openFileInEditor(msg.data); @@ -194,6 +194,12 @@ export class ExplorerView implements WebviewViewProvider, Disposable { } as DashboardData); this.getMediaSelection(); break; + case CommandToCode.frameworkCommand: + this.openTerminalWithCommand(msg.data.command); + break; + case CommandToCode.updateStartCommand: + await this.updateSetting(SETTINGS_FRAMEWORK_START, msg.data || ""); + break; } }); @@ -344,6 +350,29 @@ export class ExplorerView implements WebviewViewProvider, Disposable { this.pushMetadata(article.data); } + /** + * Open a terminal and run the passed command + * @param command + */ + private openTerminalWithCommand(command: string) { + if (command) { + let terminal = window.activeTerminal; + + if (!terminal || (terminal && terminal.state.isInteractedWith === true)) { + terminal = window.createTerminal({ + name: `Starting local server: ${command}`, + iconPath: new ThemeIcon('server-environment'), + message: `Starting local server: ${command}`, + }); + } + + if (terminal) { + terminal.sendText(command); + terminal.show(false); + } + } + } + /** * Run a custom script * @param msg @@ -405,7 +434,11 @@ export class ExplorerView implements WebviewViewProvider, Disposable { contentTypes: Settings.get(SETTING_TAXONOMY_CONTENT_TYPES) || [], dashboardViewData: Dashboard.viewData, draftField: Settings.get(SETTINGS_CONTENT_DRAFT_FIELD), - isBacker: await Extension.getInstance().getState(CONTEXT.backer, 'global') + isBacker: await Extension.getInstance().getState(CONTEXT.backer, 'global'), + framework: Settings.get(SETTINGS_FRAMEWORK_ID), + commands: { + start: Settings.get(SETTINGS_FRAMEWORK_START) + } } as PanelSettings }); } @@ -702,26 +735,12 @@ export class ExplorerView implements WebviewViewProvider, Disposable { } /** - * Update the preview URL + * Updates a setting and refreshes the retrieved settings + * @param setting + * @param value */ - private async updatePreviewUrl(previewUrl: string) { - await Settings.update(SETTING_PREVIEW_HOST, previewUrl); - this.getSettings(); - } - - /** - * Toggle the Front Matter highlighting - */ - private async updateFmHighlight(autoUpdate: boolean) { - await Settings.update(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, autoUpdate); - this.getSettings(); - } - - /** - * Toggle the modified auto-update setting - */ - private async updateModifiedUpdating(autoUpdate: boolean) { - await Settings.update(SETTING_AUTO_UPDATE_DATE, autoUpdate); + private async updateSetting(setting: string, value: any) { + await Settings.update(setting, value); this.getSettings(); } diff --git a/src/models/PanelSettings.ts b/src/models/PanelSettings.ts index c6981a1b..e64c2a9f 100644 --- a/src/models/PanelSettings.ts +++ b/src/models/PanelSettings.ts @@ -21,6 +21,12 @@ export interface PanelSettings { dashboardViewData: DashboardData | undefined; draftField: DraftField; isBacker: boolean | undefined; + framework: string | undefined; + commands: FrameworkCommands; +} + +export interface FrameworkCommands { + start: string | undefined; } export interface ContentType { diff --git a/src/panelWebView/CommandToCode.ts b/src/panelWebView/CommandToCode.ts index 339a9456..e55cf729 100644 --- a/src/panelWebView/CommandToCode.ts +++ b/src/panelWebView/CommandToCode.ts @@ -28,4 +28,6 @@ export enum CommandToCode { selectImage = "select-image", updateCustomTaxonomy = "updateCustomTaxonomy", addToCustomTaxonomy = "addToCustomTaxonomy", + frameworkCommand = "framework-command", + updateStartCommand = "update-start-command", } \ No newline at end of file diff --git a/src/panelWebView/components/Actions.tsx b/src/panelWebView/components/Actions.tsx index e4728a3e..9a51d496 100644 --- a/src/panelWebView/components/Actions.tsx +++ b/src/panelWebView/components/Actions.tsx @@ -4,6 +4,7 @@ import { Collapsible } from './Collapsible'; import { CustomScript } from './CustomScript'; import { Preview } from './Preview'; import { SlugAction } from './SlugAction'; +import { StartServerButton } from './StartServerButton'; export interface IActionsProps { metadata: any; @@ -24,6 +25,8 @@ const Actions: React.FunctionComponent = ({ metadata, settings }: { settings?.preview?.host && } + + { (settings && settings.scripts && settings.scripts.length > 0) && ( settings.scripts.map((value, idx) => ( diff --git a/src/panelWebView/components/BaseView.tsx b/src/panelWebView/components/BaseView.tsx index 4a1a5b29..ad8d6003 100644 --- a/src/panelWebView/components/BaseView.tsx +++ b/src/panelWebView/components/BaseView.tsx @@ -7,6 +7,7 @@ import { GlobalSettings } from './GlobalSettings'; import { OtherActions } from './OtherActions'; import { FolderAndFiles } from './FolderAndFiles'; import { SponsorMsg } from './SponsorMsg'; +import { StartServerButton } from './StartServerButton'; export interface IBaseViewProps { settings: PanelSettings | undefined; @@ -45,6 +46,7 @@ const BaseView: React.FunctionComponent = ({settings, folderAndF
+ diff --git a/src/panelWebView/components/GlobalSettings.tsx b/src/panelWebView/components/GlobalSettings.tsx index fe503b15..ddcfce9b 100644 --- a/src/panelWebView/components/GlobalSettings.tsx +++ b/src/panelWebView/components/GlobalSettings.tsx @@ -5,6 +5,7 @@ import { MessageHelper } from '../../helpers/MessageHelper'; import { useDebounce } from '../../hooks/useDebounce'; import { Collapsible } from './Collapsible'; import { VsCheckbox, VsLabel } from './VscodeComponents'; +import useStartCommand from '../hooks/useStartCommand'; export interface IGlobalSettingsProps { settings: PanelSettings | undefined; @@ -14,7 +15,11 @@ export interface IGlobalSettingsProps { const GlobalSettings: React.FunctionComponent = ({settings, isBase}: React.PropsWithChildren) => { const { modifiedDateUpdate, fmHighlighting } = settings || {}; const [ previewUrl, setPreviewUrl ] = React.useState(""); + const [ startCommandValue, setStartCommandValue ] = React.useState(null); const [ isDirty, setIsDirty ] = React.useState(false); + const { startCommand } = useStartCommand(settings); + + const debounceStartCommand = useDebounce(startCommandValue, 1000); const debouncePreviewUrl = useDebounce(previewUrl, 1000); const onDateCheck = () => { @@ -30,12 +35,21 @@ const GlobalSettings: React.FunctionComponent = ({settings setPreviewUrl(e.currentTarget.value); }; + const updateStartCommand = (e: React.ChangeEvent) => { + setIsDirty(true); + setStartCommandValue(e.currentTarget.value); + }; + React.useEffect(() => { if (settings?.preview.host) { setPreviewUrl(settings.preview.host); } }, [settings?.preview.host]); + React.useEffect(() => { + setStartCommandValue(startCommand); + }, [startCommand]); + React.useEffect(() => { if (isDirty) { setIsDirty(false); @@ -43,6 +57,13 @@ const GlobalSettings: React.FunctionComponent = ({settings } }, [debouncePreviewUrl]); + React.useEffect(() => { + if (isDirty) { + setIsDirty(false); + MessageHelper.sendMessage(CommandToCode.updateStartCommand, debounceStartCommand); + } + }, [debounceStartCommand]); + return ( <> @@ -62,6 +83,14 @@ const GlobalSettings: React.FunctionComponent = ({settings value={previewUrl} onChange={previewChange} />
+
+ Local server command + +
); diff --git a/src/panelWebView/components/StartServerButton.tsx b/src/panelWebView/components/StartServerButton.tsx new file mode 100644 index 00000000..6ab35f20 --- /dev/null +++ b/src/panelWebView/components/StartServerButton.tsx @@ -0,0 +1,22 @@ +import * as React from 'react'; +import { FrameworkDetectors } from '../../constants/FrameworkDetectors'; +import { MessageHelper } from '../../helpers/MessageHelper'; +import { PanelSettings } from '../../models'; +import { CommandToCode } from '../CommandToCode'; +import useStartCommand from '../hooks/useStartCommand'; + +export interface IStartServerButtonProps { + settings: PanelSettings | undefined; +} + +export const StartServerButton: React.FunctionComponent = ({settings}: React.PropsWithChildren) => { + const { startCommand } = useStartCommand(settings); + + const startLocalServer = (command: string) => { + MessageHelper.sendMessage(CommandToCode.frameworkCommand, { command }); + }; + + return ( + startCommand ? : null + ); +}; \ No newline at end of file diff --git a/src/panelWebView/hooks/useStartCommand.tsx b/src/panelWebView/hooks/useStartCommand.tsx new file mode 100644 index 00000000..e8045393 --- /dev/null +++ b/src/panelWebView/hooks/useStartCommand.tsx @@ -0,0 +1,28 @@ +import { useState, useEffect } from 'react'; +import { FrameworkDetectors } from '../../constants/FrameworkDetectors'; +import { PanelSettings } from '../../models'; + +export default function useStartCommand(settings?: PanelSettings) { + const [startCommand, setStartCommand] = useState(null); + + useEffect(() => { + if (settings?.commands?.start) { + setStartCommand(settings?.commands?.start); + return; + } + + let command: string = ''; + if (settings?.framework) { + const framework = FrameworkDetectors.find(f => f.framework.name === settings.framework); + if (framework?.commands?.start) { + command = framework.commands.start; + } + } + + setStartCommand(command); + }, [settings?.framework, settings?.commands?.start]); + + return { + startCommand + }; +} \ No newline at end of file