diff --git a/src/components/icons.tsx b/src/components/icons.tsx index 5b488f6..38489da 100644 --- a/src/components/icons.tsx +++ b/src/components/icons.tsx @@ -33,6 +33,9 @@ import { FileOutput, X, } from 'lucide-react'; +import dynamic from 'next/dynamic'; +import dynamicIconImport from 'lucide-react/dynamicIconImports'; +import React from 'react'; export type Icon = keyof typeof Icons; @@ -138,3 +141,26 @@ export function DynamicIcon(props: DynamicIconProps) { const Icon = Icons[icon]; return ; } + +interface LooseIconProps extends LucideProps { + icon: keyof typeof dynamicIconImport | undefined | null; +} + +export const allIconNames = Object.keys(dynamicIconImport); + +export function isValidIconName( + icon: string | null | undefined, +): icon is keyof typeof dynamicIconImport { + if (!icon) return false; + return allIconNames.includes(icon); +} + +function LooseIconRaw({ icon, ...props }: LooseIconProps) { + if (!icon) return null; + + const LucideIcon = dynamic(dynamicIconImport[icon]); + + return ; +} + +export const LooseIcon = React.memo(LooseIconRaw); diff --git a/src/components/nodes/floating-nodes.tsx b/src/components/nodes/floating-nodes.tsx index bdca803..18d4463 100644 --- a/src/components/nodes/floating-nodes.tsx +++ b/src/components/nodes/floating-nodes.tsx @@ -2,16 +2,22 @@ import { tableSchema } from '@/lib/table'; import React, { memo } from 'react'; import { Handle, NodeProps, Position } from 'reactflow'; import { Separator } from '../ui/separator'; +import { LooseIcon, isValidIconName } from '../icons'; export const FloatingNode = memo((props: NodeProps) => { const table = tableSchema.safeParse(props.data); if (!table.success) return null; + const icon = isValidIconName(table.data.icon) ? table.data.icon : null; + return (
-

{table.data.name}

+ +

{table.data.name}

+ +
    {table.data.columns.map(column => ( diff --git a/src/lib/lang-miro/grammar/miro.grammar b/src/lib/lang-miro/grammar/miro.grammar index 5866840..a0e2606 100644 --- a/src/lib/lang-miro/grammar/miro.grammar +++ b/src/lib/lang-miro/grammar/miro.grammar @@ -1,18 +1,30 @@ @top Database { Table } -Table { TableName "{" commaSep? "}" } +Table { + TableName Icon? + "{" commaSep? "}" +} + +Icon { "i-" IconName } -Column { ColumnName ColumnType list? Relationship? } +Column { + ColumnName + ColumnType + list? + Relationship? +} Relationship { "->" TableName "." ColumnName } @tokens { - TableName { Identifier } - ColumnName { Identifier } + TableName { Name } + ColumnName { Name } ColumnType { Identifier } Constraint { Identifier } space { $[\r\t\n ] } - Identifier { $[A-Za-z_]+ } + Identifier { $[\[A-Za-z_?\]]+ } + Name { $[A-Za-z_]+ } + IconName { $[\[A-Za-z_?\-\]]+ } "{" "}" } diff --git a/src/lib/lang-miro/grammar/miro.terms.ts b/src/lib/lang-miro/grammar/miro.terms.ts index b0713d6..6e962d9 100644 --- a/src/lib/lang-miro/grammar/miro.terms.ts +++ b/src/lib/lang-miro/grammar/miro.terms.ts @@ -3,8 +3,10 @@ export const Database = 1, Table = 3, TableName = 4, - Column = 6, - ColumnName = 7, - ColumnType = 8, - Constraint = 9, - Relationship = 10 + Icon = 5, + IconName = 6, + Column = 8, + ColumnName = 9, + ColumnType = 10, + Constraint = 11, + Relationship = 12 diff --git a/src/lib/lang-miro/grammar/miro.ts b/src/lib/lang-miro/grammar/miro.ts index 11771f8..a9efe01 100644 --- a/src/lib/lang-miro/grammar/miro.ts +++ b/src/lib/lang-miro/grammar/miro.ts @@ -3,16 +3,16 @@ import {LRParser} from "@lezer/lr" import {MiroStyleTags} from "../highlight" export const parser = LRParser.deserialize({ version: 14, - states: "$[OVQPOOO[QPO'#C_QOQPOOOaQQO,58yOiQSO'#CbOnQPO'#ClOOQO1G.e1G.eOvQPO1G.eO{QWO,58|O!ZQQO'#ChO!fQPO,59WOOQO7+$P7+$PO!nQWO'#CmO!|QPO'#CfOOQO1G.h1G.hO#RQPO1G.hOOQO,59S,59SOOQO-E6f-E6fOOQO'#Cg'#CgO#^QWO,59XO#lQPO,59QOOQO7+$S7+$SOOQO-E6e-E6eO#qQQO1G.lOOQO7+$W7+$W", - stateData: "#v~O_OS~OSPO~OTRO~OQUOVSO~OWWO~OdXOQ`X~OQZO~OX[Ob]OQUadUa~OVSOQ[Xd[X~OdXOQ`a~OXbOQaXbaXdaX~OSdO~Ob]OQUidUi~OXbOQaabaadaa~OcgO~OVhO~O", - goto: "!UbPPPcPPfPPPlrxPPP!O!RRQOQTRR`XQ^WRe_Qc[RfcQYTRaYRVRR_W", - nodeNames: "⚠ Database } Table TableName { Column ColumnName ColumnType Constraint Relationship", - maxTerm: 20, + states: "%QOVQPOOO[QQO'#C_QOQPOOOdQSO'#CaOiQWO,58yOqQPO,58yOOQO,58{,58{OvQ`O'#CdO{QPO'#CoOOQO1G.e1G.eO!TQPO1G.eO!YQWO1G.eO!bQpO,59OO!pQWO'#CjO!{QPO,59ZOOQO7+$P7+$PO#TQPO7+$PO#YQpO'#CpO#hQPO'#ChOOQO1G.j1G.jO#mQPO1G.jOOQO,59U,59UOOQO-E6h-E6hOOQO< ({ + label: `i-${icon}`, + type: 'icon', + })), +); + const miroTypeCompletion = ( context: CompletionContext, ): CompletionResult | null => { @@ -103,7 +111,7 @@ export function miro() { MiroHighlighting, MiroLinter, autocompletion({ - override: [miroTypeCompletion, completeAnyWord], + override: [miroTypeCompletion, completeAnyWord, iconCompletion], }), ]); } diff --git a/src/lib/lang-miro/parser.ts b/src/lib/lang-miro/parser.ts index d229629..450e565 100644 --- a/src/lib/lang-miro/parser.ts +++ b/src/lib/lang-miro/parser.ts @@ -11,7 +11,7 @@ export class MiroLang { this.tree = parser.parse(this.miroLang); } - private nodeToString(node: SyntaxNode | undefined): string { + private nodeToString(node: SyntaxNode | undefined | null): string { if (!node) return ''; return this.miroLang.slice(node.from, node.to); } @@ -25,6 +25,10 @@ export class MiroLang { .forEach(tableNode => { const tableName = this.nodeToString(tableNode.firstChild!); + const icon = this.nodeToString( + tableNode.firstChild?.nextSibling, + ).substring(2); + const columns = tableNode.getChildren('Column').map(columnNode => { const columnName = this.nodeToString(columnNode.firstChild!); const columnType = this.nodeToString( @@ -61,6 +65,7 @@ export class MiroLang { tables.push({ name: tableName, + icon, columns, }); }); diff --git a/src/lib/table.ts b/src/lib/table.ts index 04c79cd..9d6b96a 100644 --- a/src/lib/table.ts +++ b/src/lib/table.ts @@ -22,6 +22,7 @@ export const columnSchema = z.object({ export const tableSchema = z.object({ name: z.string(), + icon: z.string().optional(), columns: z.array(columnSchema), });