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),
});