From a6bdfc34210f377599d6fef3b14f47fa6a6b5567 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 2 Mar 2022 22:00:45 +0100 Subject: [PATCH] Snippet dialog --- .../Modals/{Metadata.tsx => FormDialog.tsx} | 4 +- .../components/SnippetsView/Item.tsx | 99 ++++++++++--------- .../components/SnippetsView/SnippetForm.tsx | 72 ++++++++++++++ .../components/SnippetsView/Snippets.tsx | 14 ++- src/helpers/SnippetParser.ts | 58 ++++++----- 5 files changed, 172 insertions(+), 75 deletions(-) rename src/dashboardWebView/components/Modals/{Metadata.tsx => FormDialog.tsx} (93%) create mode 100644 src/dashboardWebView/components/SnippetsView/SnippetForm.tsx diff --git a/src/dashboardWebView/components/Modals/Metadata.tsx b/src/dashboardWebView/components/Modals/FormDialog.tsx similarity index 93% rename from src/dashboardWebView/components/Modals/Metadata.tsx rename to src/dashboardWebView/components/Modals/FormDialog.tsx index df9d5109..729e05a0 100644 --- a/src/dashboardWebView/components/Modals/Metadata.tsx +++ b/src/dashboardWebView/components/Modals/FormDialog.tsx @@ -2,7 +2,7 @@ import { Dialog, Transition } from '@headlessui/react'; import * as React from 'react'; import { Fragment, useRef } from 'react'; -export interface IMetadataProps { +export interface IFormDialogProps { title: string; description: string; okBtnText: string; @@ -13,7 +13,7 @@ export interface IMetadataProps { trigger: () => void; } -export const Metadata: React.FunctionComponent = ({title, description, cancelBtnText, okBtnText, dismiss, isSaveDisabled, trigger, children}: React.PropsWithChildren) => { +export const FormDialog: React.FunctionComponent = ({title, description, cancelBtnText, okBtnText, dismiss, isSaveDisabled, trigger, children}: React.PropsWithChildren) => { const cancelButtonRef = useRef(null); diff --git a/src/dashboardWebView/components/SnippetsView/Item.tsx b/src/dashboardWebView/components/SnippetsView/Item.tsx index 5ae1906e..bc84673a 100644 --- a/src/dashboardWebView/components/SnippetsView/Item.tsx +++ b/src/dashboardWebView/components/SnippetsView/Item.tsx @@ -1,17 +1,22 @@ import { Messenger } from '@estruyf/vscode/dist/client'; +import { DotsHorizontalIcon, PlusIcon } from '@heroicons/react/outline'; import * as React from 'react'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { Choice, Scanner, SnippetParser, TokenType, Variable, VariableResolver } from '../../../helpers/SnippetParser'; import { DashboardMessage } from '../../DashboardMessage'; import { ViewDataSelector } from '../../state'; +import { FormDialog } from '../Modals/FormDialog'; +import { SnippetForm } from './SnippetForm'; export interface IItemProps { + title: string; snippet: any; } -export const Item: React.FunctionComponent = ({ snippet }: React.PropsWithChildren) => { +export const Item: React.FunctionComponent = ({ title, snippet }: React.PropsWithChildren) => { const viewData = useRecoilValue(ViewDataSelector); + const [ showInsertDialog, setShowInsertDialog ] = useState(false); // Todo: On add, show dialog to insert the placeholders and content @@ -21,49 +26,55 @@ export const Item: React.FunctionComponent = ({ snippet }: React.Pro snippet: snippet.body.join(`\n`) }); }; - - useEffect(() => { - const snippetParser = new SnippetParser(); - const body = snippet.body.join(`\n`); - - const parsed = snippetParser.parse(body); - const placeholders = parsed.placeholderInfo.all; - - for (const placeholder of placeholders) { - const tmString = placeholder.toTextmateString(); - - for (const child of placeholder.children as any[]) { - if (child instanceof Choice) { - console.log(child.options) - } else { - console.log(tmString, child.value); - } - } - } - - const resolver: VariableResolver = { - resolve: (variable: Variable): string | undefined => { - console.log(`variable`, variable); - return undefined; - } - }; - - parsed.resolveVariables(resolver); - }, []); return ( -
  • -
    {snippet.title}
    -

    {snippet.description}

    -
    - { - viewData?.data?.filePath ? ( - - ) : ( -
    Edit
    - ) - } -
    -
  • + <> +
  • +
    {title}
    + +
    +
    +
    + +
    + +
    + { + viewData?.data?.filePath ? ( + <> + + + ) : ( +
    Edit
    + ) + } +
    +
    +
    + +

    {snippet.description}

    +
  • + + { + showInsertDialog && ( + setShowInsertDialog(false)} + okBtnText='Insert' + cancelBtnText='Cancel'> + + + + + ) + } + ); }; \ No newline at end of file diff --git a/src/dashboardWebView/components/SnippetsView/SnippetForm.tsx b/src/dashboardWebView/components/SnippetsView/SnippetForm.tsx new file mode 100644 index 00000000..d57b76d9 --- /dev/null +++ b/src/dashboardWebView/components/SnippetsView/SnippetForm.tsx @@ -0,0 +1,72 @@ +import * as React from 'react'; +import { useEffect, useState } from 'react'; +import { Choice, SnippetParser, Variable, VariableResolver } from '../../../helpers/SnippetParser'; + +export interface ISnippetFormProps { + snippet: any; +} + +export const SnippetForm: React.FunctionComponent = ({ snippet }: React.PropsWithChildren) => { + const [ fields, setFields ] = useState([]); + + useEffect(() => { + const snippetParser = new SnippetParser(); + const body = snippet.body.join(`\n`); + + const parsed = snippetParser.parse(body); + const placeholders = parsed.placeholderInfo.all; + + const allFields: any[] = []; + + for (const placeholder of placeholders) { + const tmString = placeholder.toTextmateString(); + console.log(`tmString`, placeholder); + + if (placeholder.children.length === 0) { + allFields.push({ + type: 'text', + name: placeholder.index, + value: '', + tmString + }); + } else { + for (const child of placeholder.children as any[]) { + if (child instanceof Choice) { + allFields.push({ + type: 'select', + name: placeholder.index, + value: (child as any).value, + options: child.options, + tmString + }); + } else { + allFields.push({ + type: 'text', + name: placeholder.index, + value: (child as any).value, + tmString + }); + } + } + } + } + + setFields(allFields); + }, []); + + return ( +
    +
    {snippet.body.join(`\n`)}
    + +
      + { + fields.map((field: any, index: number) => ( +
    • +

      {field.name} - {field.value} - {(field.options || []).join(',')}

      +
    • + )) + } +
    +
    + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/SnippetsView/Snippets.tsx b/src/dashboardWebView/components/SnippetsView/Snippets.tsx index 1534a7c1..ea579897 100644 --- a/src/dashboardWebView/components/SnippetsView/Snippets.tsx +++ b/src/dashboardWebView/components/SnippetsView/Snippets.tsx @@ -1,7 +1,7 @@ import { CodeIcon } from '@heroicons/react/outline'; import * as React from 'react'; +import { useMemo } from 'react'; import { useRecoilValue } from 'recoil'; -import { AddIcon } from '../../../panelWebView/components/Icons/AddIcon'; import { SettingsSelector, ViewDataSelector } from '../../state'; import { PageLayout } from '../Layout/PageLayout'; import { Item } from './Item'; @@ -12,7 +12,8 @@ export const Snippets: React.FunctionComponent = (props: React.P const settings = useRecoilValue(SettingsSelector); const viewData = useRecoilValue(ViewDataSelector); - const snippets = settings?.snippets || []; + const snippetKeys = useMemo(() => Object.keys(settings?.snippets) || [], [settings?.snippets]); + const snippets = settings?.snippets || {}; return ( @@ -25,11 +26,14 @@ export const Snippets: React.FunctionComponent = (props: React.P } { - snippets && snippets.length > 0 ? ( + snippetKeys && snippetKeys.length > 0 ? (
      { - snippets.map((snippet: any, index: number) => ( - + snippetKeys.map((snippetKey: any, index: number) => ( + )) }
    diff --git a/src/helpers/SnippetParser.ts b/src/helpers/SnippetParser.ts index 81700d58..cf278388 100644 --- a/src/helpers/SnippetParser.ts +++ b/src/helpers/SnippetParser.ts @@ -221,23 +221,8 @@ export abstract class TransformableMarker extends Marker { } export class Placeholder extends TransformableMarker { - static compareByIndex(a: Placeholder, b: Placeholder): number { - if (a.index === b.index) { - return 0; - } else if (a.isFinalTabstop) { - return 1; - } else if (b.isFinalTabstop) { - return -1; - } else if (a.index < b.index) { - return -1; - } else if (a.index > b.index) { - return 1; - } else { - return 0; - } - } - constructor(public index: number) { + constructor(public index: number | string) { super(); } @@ -629,7 +614,7 @@ export class SnippetParser { // fill in values for placeholders. the first placeholder of an index // that has a value defines the value for all placeholders with that index - const placeholderDefaultValues = new Map(); + const placeholderDefaultValues = new Map(); const incompletePlaceholders: Placeholder[] = []; let placeholderCount = 0; snippet.walk(marker => { @@ -876,7 +861,7 @@ export class SnippetParser { return this._backTo(token); } - const variable = new Variable(name!); + const placeholder = new Placeholder(String(name!)); if (this._accept(TokenType.Colon)) { // ${foo:} @@ -884,24 +869,49 @@ export class SnippetParser { // ...} -> done if (this._accept(TokenType.CurlyClose)) { - parent.appendChild(variable); + parent.appendChild(placeholder); return true; } - if (this._parse(variable)) { + if (this._parse(placeholder)) { continue; } // fallback parent.appendChild(new Text('${' + name! + ':')); - variable.children.forEach(parent.appendChild, parent); + placeholder.children.forEach(parent.appendChild, parent); return true; } + } else if (this._accept(TokenType.Pipe)) { + const choice = new Choice(); + + while (true) { + if (this._parseChoiceElement(choice)) { + + if (this._accept(TokenType.Comma)) { + // opt, -> more + continue; + } + + if (this._accept(TokenType.Pipe)) { + placeholder.appendChild(choice); + if (this._accept(TokenType.CurlyClose)) { + // ..|} -> done + parent.appendChild(placeholder); + return true; + } + } + } + + this._backTo(token); + return false; + } + } else if (this._accept(TokenType.Forwardslash)) { // ${foo///} - if (this._parseTransform(variable)) { - parent.appendChild(variable); + if (this._parseTransform(placeholder)) { + parent.appendChild(placeholder); return true; } @@ -910,7 +920,7 @@ export class SnippetParser { } else if (this._accept(TokenType.CurlyClose)) { // ${foo} - parent.appendChild(variable); + parent.appendChild(placeholder); return true; } else {