@elixpo/lixsketch 5.4.7 → 5.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/dist/react/AIRenderer-M4LMJ66J.js +1105 -0
  2. package/dist/react/AIRenderer-M4LMJ66J.js.map +7 -0
  3. package/dist/react/Arrow-BJDAXPMH.js +8 -0
  4. package/dist/react/Arrow-BJDAXPMH.js.map +7 -0
  5. package/dist/react/Circle-KPG7TKIX.js +8 -0
  6. package/dist/react/Circle-KPG7TKIX.js.map +7 -0
  7. package/dist/react/CodeShape-K4DIZAIV.js +8 -0
  8. package/dist/react/CodeShape-K4DIZAIV.js.map +7 -0
  9. package/dist/react/CopyPaste-4CKJRHJ3.js +569 -0
  10. package/dist/react/CopyPaste-4CKJRHJ3.js.map +7 -0
  11. package/dist/react/EraserTrail-PA6IQLQN.js +20 -0
  12. package/dist/react/EraserTrail-PA6IQLQN.js.map +7 -0
  13. package/dist/react/EventDispatcher-SRFH6LQZ.js +463 -0
  14. package/dist/react/EventDispatcher-SRFH6LQZ.js.map +7 -0
  15. package/dist/react/Frame-6UR5XPAX.js +10 -0
  16. package/dist/react/Frame-6UR5XPAX.js.map +7 -0
  17. package/dist/react/FreehandStroke-CK24NUT3.js +8 -0
  18. package/dist/react/FreehandStroke-CK24NUT3.js.map +7 -0
  19. package/dist/react/GraphEngine-QE5B2PMR.js +340 -0
  20. package/dist/react/GraphEngine-QE5B2PMR.js.map +7 -0
  21. package/dist/react/GraphMathParser-2WMTY4IE.js +10 -0
  22. package/dist/react/GraphMathParser-2WMTY4IE.js.map +7 -0
  23. package/dist/react/IconShape-KMIA6T2H.js +10 -0
  24. package/dist/react/IconShape-KMIA6T2H.js.map +7 -0
  25. package/dist/react/ImageShape-CHHGBXUQ.js +10 -0
  26. package/dist/react/ImageShape-CHHGBXUQ.js.map +7 -0
  27. package/dist/react/LayerOrder-XCHF4MSS.js +66 -0
  28. package/dist/react/LayerOrder-XCHF4MSS.js.map +7 -0
  29. package/dist/react/Line-2KOQEEGA.js +10 -0
  30. package/dist/react/Line-2KOQEEGA.js.map +7 -0
  31. package/dist/react/LixScriptParser-JYWJX375.js +1033 -0
  32. package/dist/react/LixScriptParser-JYWJX375.js.map +7 -0
  33. package/dist/react/MermaidFlowchartRenderer-4QBAZ5MB.js +412 -0
  34. package/dist/react/MermaidFlowchartRenderer-4QBAZ5MB.js.map +7 -0
  35. package/dist/react/MermaidSequenceParser-UKG2NSZY.js +8 -0
  36. package/dist/react/MermaidSequenceParser-UKG2NSZY.js.map +7 -0
  37. package/dist/react/MermaidSequenceRenderer-HPLHY5II.js +387 -0
  38. package/dist/react/MermaidSequenceRenderer-HPLHY5II.js.map +7 -0
  39. package/dist/react/Rectangle-6FE7RN6W.js +8 -0
  40. package/dist/react/Rectangle-6FE7RN6W.js.map +7 -0
  41. package/dist/react/ResizeCode-WAMDTKSW.js +2 -0
  42. package/dist/react/ResizeCode-WAMDTKSW.js.map +7 -0
  43. package/dist/react/ResizeShapes-LKEQTITP.js +250 -0
  44. package/dist/react/ResizeShapes-LKEQTITP.js.map +7 -0
  45. package/dist/react/SceneSerializer-4U4B24JM.js +739 -0
  46. package/dist/react/SceneSerializer-4U4B24JM.js.map +7 -0
  47. package/dist/react/Selection-VVUHY5YJ.js +34 -0
  48. package/dist/react/Selection-VVUHY5YJ.js.map +7 -0
  49. package/dist/react/SketchEngine-YA4RG3KX.js +413 -0
  50. package/dist/react/SketchEngine-YA4RG3KX.js.map +7 -0
  51. package/dist/react/TextShape-54ZJ66E3.js +8 -0
  52. package/dist/react/TextShape-54ZJ66E3.js.map +7 -0
  53. package/dist/react/UndoRedo-M2JZUBKN.js +30 -0
  54. package/dist/react/UndoRedo-M2JZUBKN.js.map +7 -0
  55. package/dist/react/ZoomPan-U3TXC5AJ.js +186 -0
  56. package/dist/react/ZoomPan-U3TXC5AJ.js.map +7 -0
  57. package/dist/react/arrowTool-CWEMIRXK.js +17 -0
  58. package/dist/react/arrowTool-CWEMIRXK.js.map +7 -0
  59. package/dist/react/boxicons-5JNJQPUP.woff +0 -0
  60. package/dist/react/boxicons-6NVWQSCQ.eot +0 -0
  61. package/dist/react/boxicons-AQ5N6WP7.woff2 +0 -0
  62. package/dist/react/boxicons-GDR4JU3X.ttf +0 -0
  63. package/dist/react/boxicons-NNFHLMOR.svg +1660 -0
  64. package/dist/react/chunk-2OQXI7QB.js +564 -0
  65. package/dist/react/chunk-2OQXI7QB.js.map +7 -0
  66. package/dist/react/chunk-5OUBTHHO.js +187 -0
  67. package/dist/react/chunk-5OUBTHHO.js.map +7 -0
  68. package/dist/react/chunk-6PFXZKL2.js +628 -0
  69. package/dist/react/chunk-6PFXZKL2.js.map +7 -0
  70. package/dist/react/chunk-6SFNWGGZ.js +453 -0
  71. package/dist/react/chunk-6SFNWGGZ.js.map +7 -0
  72. package/dist/react/chunk-7TZSIR6V.js +210 -0
  73. package/dist/react/chunk-7TZSIR6V.js.map +7 -0
  74. package/dist/react/chunk-AJAZC37E.js +662 -0
  75. package/dist/react/chunk-AJAZC37E.js.map +7 -0
  76. package/dist/react/chunk-BU4D2WYQ.js +1213 -0
  77. package/dist/react/chunk-BU4D2WYQ.js.map +7 -0
  78. package/dist/react/chunk-BZE4CEBG.js +747 -0
  79. package/dist/react/chunk-BZE4CEBG.js.map +7 -0
  80. package/dist/react/chunk-CB2RANCB.js +212 -0
  81. package/dist/react/chunk-CB2RANCB.js.map +7 -0
  82. package/dist/react/chunk-DNAG7ML6.js +128 -0
  83. package/dist/react/chunk-DNAG7ML6.js.map +7 -0
  84. package/dist/react/chunk-HZWLPXFN.js +198 -0
  85. package/dist/react/chunk-HZWLPXFN.js.map +7 -0
  86. package/dist/react/chunk-I6HCCIXB.js +572 -0
  87. package/dist/react/chunk-I6HCCIXB.js.map +7 -0
  88. package/dist/react/chunk-IZ5OGWGW.js +235 -0
  89. package/dist/react/chunk-IZ5OGWGW.js.map +7 -0
  90. package/dist/react/chunk-JJCJNLNN.js +1490 -0
  91. package/dist/react/chunk-JJCJNLNN.js.map +7 -0
  92. package/dist/react/chunk-JQ3IRFDP.js +222 -0
  93. package/dist/react/chunk-JQ3IRFDP.js.map +7 -0
  94. package/dist/react/chunk-L5OARZJ7.js +1134 -0
  95. package/dist/react/chunk-L5OARZJ7.js.map +7 -0
  96. package/dist/react/chunk-LHRTXNVE.js +1600 -0
  97. package/dist/react/chunk-LHRTXNVE.js.map +7 -0
  98. package/dist/react/chunk-Q2GA4MBZ.js +1653 -0
  99. package/dist/react/chunk-Q2GA4MBZ.js.map +7 -0
  100. package/dist/react/chunk-QNSE5BAY.js +519 -0
  101. package/dist/react/chunk-QNSE5BAY.js.map +7 -0
  102. package/dist/react/chunk-QXFBG7OM.js +191 -0
  103. package/dist/react/chunk-QXFBG7OM.js.map +7 -0
  104. package/dist/react/chunk-T6ZVV5ZO.js +714 -0
  105. package/dist/react/chunk-T6ZVV5ZO.js.map +7 -0
  106. package/dist/react/chunk-URB6MY46.js +974 -0
  107. package/dist/react/chunk-URB6MY46.js.map +7 -0
  108. package/dist/react/chunk-VN62KLOC.js +931 -0
  109. package/dist/react/chunk-VN62KLOC.js.map +7 -0
  110. package/dist/react/chunk-VQVEPJSA.js +158 -0
  111. package/dist/react/chunk-VQVEPJSA.js.map +7 -0
  112. package/dist/react/chunk-XISEAGBV.js +1632 -0
  113. package/dist/react/chunk-XISEAGBV.js.map +7 -0
  114. package/dist/react/chunk-YKJUBFHE.js +575 -0
  115. package/dist/react/chunk-YKJUBFHE.js.map +7 -0
  116. package/dist/react/circleTool-NUATSEI2.js +14 -0
  117. package/dist/react/circleTool-NUATSEI2.js.map +7 -0
  118. package/dist/react/codeTool-MHCB2FNB.js +38 -0
  119. package/dist/react/codeTool-MHCB2FNB.js.map +7 -0
  120. package/dist/react/eraserTool-2GS2LQCS.js +103 -0
  121. package/dist/react/eraserTool-2GS2LQCS.js.map +7 -0
  122. package/dist/react/frameTool-PWVKKXSP.js +14 -0
  123. package/dist/react/frameTool-PWVKKXSP.js.map +7 -0
  124. package/dist/react/freehandTool-SLYGASVP.js +14 -0
  125. package/dist/react/freehandTool-SLYGASVP.js.map +7 -0
  126. package/dist/react/iconTool-GPZ7O7NH.js +18 -0
  127. package/dist/react/iconTool-GPZ7O7NH.js.map +7 -0
  128. package/dist/react/imageTool-D5R4XGEU.js +15 -0
  129. package/dist/react/imageTool-D5R4XGEU.js.map +7 -0
  130. package/dist/react/index.js +5349 -0
  131. package/dist/react/index.js.map +7 -0
  132. package/dist/react/laserTool-XVMV63AV.js +239 -0
  133. package/dist/react/laserTool-XVMV63AV.js.map +7 -0
  134. package/dist/react/lineTool-WNI5EMZ5.js +14 -0
  135. package/dist/react/lineTool-WNI5EMZ5.js.map +7 -0
  136. package/dist/react/lixCode-OJMVUXXI.ttf +0 -0
  137. package/dist/react/lixDefault-JEAPAUJL.ttf +0 -0
  138. package/dist/react/lixFancy-BPXYM5CQ.ttf +0 -0
  139. package/dist/react/lixFont-CPKEUDVM.woff2 +0 -0
  140. package/dist/react/rectangleTool-U7DZ6ZTA.js +14 -0
  141. package/dist/react/rectangleTool-U7DZ6ZTA.js.map +7 -0
  142. package/dist/react/styles.css +6627 -0
  143. package/dist/react/textTool-M7S6F3VC.js +21 -0
  144. package/dist/react/textTool-M7S6F3VC.js.map +7 -0
  145. package/package.json +39 -5
  146. package/src/EngineShortcuts.js +83 -27
  147. package/src/core/AIRenderer.js +30 -18
  148. package/src/core/GraphEngine.js +64 -41
  149. package/src/core/MermaidFlowchartRenderer.js +153 -88
  150. package/src/core/MermaidSequenceRenderer.js +168 -93
  151. package/src/core/SceneSerializer.js +86 -2
  152. package/src/core/Selection.js +119 -19
  153. package/src/core/UndoRedo.js +82 -15
  154. package/src/core/ZoomPan.js +15 -5
  155. package/src/index.js +14 -1
  156. package/src/react/LixSketchCanvas.jsx +347 -0
  157. package/src/react/components/AppMenu.jsx +482 -0
  158. package/src/react/components/Footer.jsx +85 -0
  159. package/src/react/components/Toolbar.jsx +140 -0
  160. package/src/react/components/canvas/CanvasLoadingOverlay.jsx +76 -0
  161. package/src/react/components/canvas/ContextMenu.jsx +466 -0
  162. package/src/react/components/canvas/FindBar.jsx +177 -0
  163. package/src/react/components/canvas/ImageSourcePicker.jsx +133 -0
  164. package/src/react/components/canvas/MultiSelectActions.jsx +120 -0
  165. package/src/react/components/canvas/SVGCanvas.jsx +145 -0
  166. package/src/react/components/canvas/SaveStatusIndicator.jsx +29 -0
  167. package/src/react/components/modals/CanvasPropertiesModal.jsx +358 -0
  168. package/src/react/components/modals/CommandPalette.jsx +307 -0
  169. package/src/react/components/modals/ExportImageModal.jsx +287 -0
  170. package/src/react/components/modals/GraphModal.jsx +174 -0
  171. package/src/react/components/modals/HelpModal.jsx +208 -0
  172. package/src/react/components/modals/LixScriptModal.jsx +130 -0
  173. package/src/react/components/modals/ShortcutsModal.jsx +125 -0
  174. package/src/react/components/sidebars/ArrowSidebar.jsx +173 -0
  175. package/src/react/components/sidebars/CircleSidebar.jsx +134 -0
  176. package/src/react/components/sidebars/FrameSidebar.jsx +263 -0
  177. package/src/react/components/sidebars/IconSidebar.jsx +279 -0
  178. package/src/react/components/sidebars/ImageSidebar.jsx +69 -0
  179. package/src/react/components/sidebars/LineSidebar.jsx +109 -0
  180. package/src/react/components/sidebars/PaintbrushSidebar.jsx +119 -0
  181. package/src/react/components/sidebars/RectangleSidebar.jsx +165 -0
  182. package/src/react/components/sidebars/ShapeSidebar.jsx +130 -0
  183. package/src/react/components/sidebars/TextSidebar.jsx +174 -0
  184. package/src/react/hooks/inertStores.js +58 -0
  185. package/src/react/hooks/useSketchEngine.js +76 -0
  186. package/src/react/hooks/useTranslation.js +92 -0
  187. package/src/react/index.js +41 -0
  188. package/src/react/store/useSketchStore.js +234 -0
  189. package/src/react/store/useUIStore.js +196 -0
  190. package/src/react/styles/arrowSidebar.css +263 -0
  191. package/src/react/styles/canvasStroke.css +47 -0
  192. package/src/react/styles/canvasTools.css +769 -0
  193. package/src/react/styles/circleSidebar.css +206 -0
  194. package/src/react/styles/fonts/fonts.css +29 -0
  195. package/src/react/styles/fonts/lixCode.ttf +0 -0
  196. package/src/react/styles/fonts/lixDefault.ttf +0 -0
  197. package/src/react/styles/fonts/lixDocs.ttf +0 -0
  198. package/src/react/styles/fonts/lixFancy.ttf +0 -0
  199. package/src/react/styles/fonts/lixFont.woff2 +0 -0
  200. package/src/react/styles/lineSidebar.css +207 -0
  201. package/src/react/styles/paintbrushSidebar.css +247 -0
  202. package/src/react/styles/squareSidebar.css +206 -0
  203. package/src/react/styles/writeText.css +149 -0
  204. package/src/react/styles.css +175 -0
  205. package/src/react/toolsEnum.js +20 -0
  206. package/src/react/utils/toast.js +18 -0
  207. package/src/shapes/Frame.js +7 -1
  208. package/src/shapes/Line.js +11 -5
  209. package/src/tools/arrowTool.js +30 -4
  210. package/src/tools/circleTool.js +33 -9
  211. package/src/tools/codeTool.js +5 -3
  212. package/src/tools/frameTool.js +47 -7
  213. package/src/tools/freehandTool.js +12 -4
  214. package/src/tools/iconTool.js +7 -1
  215. package/src/tools/imageTool.js +7 -1
  216. package/src/tools/lineTool.js +27 -3
  217. package/src/tools/rectangleTool.js +45 -8
  218. package/src/tools/textTool.js +23 -6
@@ -0,0 +1,174 @@
1
+ "use client"
2
+
3
+ import useSketchStore, { TOOLS } from '../../store/useSketchStore'
4
+ import ShapeSidebar, { ToolbarButton, Divider, LayerControls } from './ShapeSidebar'
5
+ import { useState, useCallback, useEffect } from 'react'
6
+ import { useTranslation } from '../../hooks/useTranslation'
7
+
8
+ const TEXT_COLORS = ['#1a1a20', '#ffffff', '#FF8383', '#3A994C', '#56A2E8', '#FFD700', '#FF69B4', '#A855F7']
9
+
10
+ const FONTS = [
11
+ { value: 'lixFont', label: 'Lix' },
12
+ { value: 'lixCode', label: 'Code' },
13
+ { value: 'lixDefault', label: 'Default' },
14
+ { value: 'lixFancy', label: 'Fancy' },
15
+ ]
16
+
17
+ const SIZE_MAP = { S: '20px', M: '30px', L: '48px', XL: '72px' }
18
+
19
+ const LANGUAGES = [
20
+ 'javascript', 'typescript', 'python', 'java', 'c', 'cpp', 'csharp',
21
+ 'go', 'rust', 'ruby', 'php', 'swift', 'kotlin', 'html', 'css',
22
+ 'sql', 'bash', 'json', 'yaml', 'markdown',
23
+ ]
24
+
25
+ export default function TextSidebar() {
26
+ const { t } = useTranslation()
27
+ const activeTool = useSketchStore((s) => s.activeTool)
28
+ const selectedShapeSidebar = useSketchStore((s) => s.selectedShapeSidebar)
29
+ const [textColor, setTextColor] = useState('#fff')
30
+ const [fontSize, setFontSize] = useState('M')
31
+ const [font, setFont] = useState('lixFont')
32
+ const [codeMode, setCodeMode] = useState(false)
33
+ const [language, setLanguage] = useState('javascript')
34
+
35
+ // Sync code mode state from engine when a shape is selected
36
+ useEffect(() => {
37
+ window.__onCodeModeChanged = (isCode) => {
38
+ setCodeMode(isCode)
39
+ window.isTextInCodeMode = isCode
40
+ }
41
+ return () => { window.__onCodeModeChanged = null }
42
+ }, [])
43
+
44
+ const updateColor = useCallback((c) => {
45
+ setTextColor(c)
46
+ if (window.updateSelectedTextStyle) window.updateSelectedTextStyle({ color: c })
47
+ }, [])
48
+
49
+ const updateFont = useCallback((f) => {
50
+ setFont(f)
51
+ if (window.updateSelectedTextStyle) window.updateSelectedTextStyle({ font: f })
52
+ }, [])
53
+
54
+ const updateSize = useCallback((s) => {
55
+ setFontSize(s)
56
+ if (window.updateSelectedTextStyle) window.updateSelectedTextStyle({ fontSize: SIZE_MAP[s] })
57
+ }, [])
58
+
59
+ const toggleCodeMode = useCallback(() => {
60
+ const next = !codeMode
61
+ setCodeMode(next)
62
+ window.isTextInCodeMode = next
63
+ if (next) {
64
+ // Convert selected text → code
65
+ if (window.__convertTextToCode) window.__convertTextToCode()
66
+ } else {
67
+ // Convert selected code → text
68
+ if (window.__convertCodeToText) window.__convertCodeToText()
69
+ }
70
+ }, [codeMode])
71
+
72
+ const updateLanguage = useCallback((lang) => {
73
+ setLanguage(lang)
74
+ if (window.__setCodeLanguage) window.__setCodeLanguage(lang)
75
+ }, [])
76
+
77
+ const visible = activeTool === TOOLS.TEXT || activeTool === TOOLS.CODE || selectedShapeSidebar === 'text'
78
+
79
+ return (
80
+ <>
81
+ <ShapeSidebar visible={visible}>
82
+ {/* Color */}
83
+ <ToolbarButton tooltip={t('sidebar.textColor')}
84
+ preview={<span className="w-4 h-4 rounded-md border border-white/20" style={{ backgroundColor: textColor }} />}
85
+ >
86
+ <p className="text-xs text-text-muted uppercase tracking-wider mb-2">{t('sidebar.color')}</p>
87
+ <div className="grid grid-cols-4 gap-1.5">
88
+ {TEXT_COLORS.map((c) => (
89
+ <button key={c} onClick={() => updateColor(c)}
90
+ className={`w-7 h-7 rounded-md border-[1.5px] transition-all duration-100 ${textColor === c ? 'border-[#5B57D1] scale-110' : 'border-white/[0.08] hover:border-white/20'}`}
91
+ style={{ backgroundColor: c }}
92
+ />
93
+ ))}
94
+ </div>
95
+ </ToolbarButton>
96
+
97
+ {!codeMode && (
98
+ <>
99
+ <Divider />
100
+
101
+ {/* Font */}
102
+ <ToolbarButton icon="bxs-font-family" tooltip={t('sidebar.font')}>
103
+ <p className="text-xs text-text-muted uppercase tracking-wider mb-2">{t('sidebar.font')}</p>
104
+ <div className="flex flex-col gap-0.5">
105
+ {FONTS.map((f) => (
106
+ <button key={f.value} onClick={() => updateFont(f.value)}
107
+ className={`flex items-center px-2.5 py-1.5 rounded-lg text-xs transition-all duration-100 ${font === f.value ? 'bg-[#5B57D1] text-white' : 'text-text-secondary hover:bg-white/[0.06]'}`}
108
+ style={{ fontFamily: f.value }}
109
+ >
110
+ {f.label}
111
+ </button>
112
+ ))}
113
+ </div>
114
+ </ToolbarButton>
115
+ </>
116
+ )}
117
+
118
+ <Divider />
119
+
120
+ {/* Size */}
121
+ <ToolbarButton icon="bxs-chevrons-up" tooltip={t('sidebar.size')}>
122
+ <p className="text-xs text-text-muted uppercase tracking-wider mb-2">{t('sidebar.size')}</p>
123
+ <div className="flex items-center gap-1">
124
+ {['S', 'M', 'L', 'XL'].map((s) => (
125
+ <button key={s} onClick={() => updateSize(s)}
126
+ className={`w-8 h-8 flex items-center justify-center rounded-lg text-xs transition-all duration-100 ${fontSize === s ? 'bg-[#5B57D1]/20 text-[#5B57D1]' : 'text-text-muted hover:bg-white/[0.06]'}`}
127
+ >{s}</button>
128
+ ))}
129
+ </div>
130
+ </ToolbarButton>
131
+
132
+ <Divider />
133
+
134
+ {/* Code mode */}
135
+ <ToolbarButton icon="bxs-terminal" tooltip={t('sidebar.codeMode')}>
136
+ <p className="text-xs text-text-muted uppercase tracking-wider mb-2">{t('sidebar.code')}</p>
137
+ <div className="flex flex-col gap-2">
138
+ <button onClick={toggleCodeMode}
139
+ className={`flex items-center gap-2 px-2.5 py-1.5 rounded-lg text-xs transition-all duration-100 ${codeMode ? 'bg-[#5B57D1] text-white' : 'text-text-secondary hover:bg-white/[0.06]'}`}
140
+ >
141
+ <div className={`w-6 h-3 rounded-full transition-all duration-150 relative ${codeMode ? 'bg-white/30' : 'bg-white/10'}`}>
142
+ <div className={`absolute top-0.5 w-2 h-2 rounded-full bg-white transition-all duration-150 ${codeMode ? 'left-3.5' : 'left-0.5'}`} />
143
+ </div>
144
+ {codeMode ? t('sidebar.on') : t('sidebar.off')}
145
+ </button>
146
+ {codeMode && (
147
+ <div className="flex flex-wrap gap-1 max-w-[180px]">
148
+ {LANGUAGES.map((lang) => (
149
+ <button key={lang} onClick={() => updateLanguage(lang)}
150
+ className={`px-1.5 py-0.5 rounded text-[9px] transition-all duration-100 ${language === lang ? 'bg-[#5B57D1]/20 text-[#5B57D1]' : 'text-text-dim hover:bg-white/[0.06] hover:text-text-secondary'}`}
151
+ >{lang}</button>
152
+ ))}
153
+ </div>
154
+ )}
155
+ </div>
156
+ </ToolbarButton>
157
+ <Divider />
158
+ <LayerControls />
159
+ </ShapeSidebar>
160
+
161
+ {/* Keyboard shortcut hint — positioned below the property bar */}
162
+ <div
163
+ className={`absolute bottom-7 left-1/2 -translate-x-1/2 z-[998] font-[lixFont] transition-all duration-200 ${
164
+ visible ? 'opacity-100 translate-y-0' : 'opacity-0 pointer-events-none translate-y-2'
165
+ }`}
166
+ >
167
+ <p className="text-[10px] text-white/30 leading-relaxed text-center whitespace-nowrap">
168
+ <span className="text-white/50">Ctrl+Enter</span> / <span className="text-white/50">Enter</span> — {t('sidebar.renderText')} &nbsp;&nbsp;
169
+ <span className="text-white/50">Shift+Enter</span> — {t('sidebar.newLine')}
170
+ </p>
171
+ </div>
172
+ </>
173
+ )
174
+ }
@@ -0,0 +1,58 @@
1
+ // Inert replacements for the standalone product's auth / cloud / collab
2
+ // state. These let us copy components that reference cloud-coupled hooks
3
+ // without hauling the whole product backend along. Each export mirrors
4
+ // the shape the consumer expects but with no-op implementations.
5
+
6
+ const noop = () => {};
7
+
8
+ const inertAuth = {
9
+ isAuthenticated: false,
10
+ user: null,
11
+ setUser: noop,
12
+ setAuthenticated: noop,
13
+ logout: noop,
14
+ };
15
+
16
+ const inertCollab = {
17
+ roomId: null,
18
+ connected: false,
19
+ setRoom: noop,
20
+ setConnected: noop,
21
+ };
22
+
23
+ const inertProfile = {
24
+ profile: null,
25
+ setProfile: noop,
26
+ };
27
+
28
+ function makeStore(state) {
29
+ // Match the zustand call signature used in selectors:
30
+ // const x = useStore((s) => s.foo)
31
+ // const x = useStore.getState().foo
32
+ function useStore(selector) {
33
+ return typeof selector === 'function' ? selector(state) : state;
34
+ }
35
+ useStore.getState = () => state;
36
+ useStore.setState = noop;
37
+ useStore.subscribe = () => () => {};
38
+ return useStore;
39
+ }
40
+
41
+ export const useAuthStore = makeStore(inertAuth);
42
+ export const useCollabStore = makeStore(inertCollab);
43
+ export const useProfileStore = makeStore(inertProfile);
44
+
45
+ export const WORKER_URL = '';
46
+ export function getSessionID() {
47
+ if (typeof window !== 'undefined' && window.__sessionID) return window.__sessionID;
48
+ return null;
49
+ }
50
+
51
+ export async function triggerDocCloudSync() { /* offline mode: no-op */ }
52
+ export function persistLayoutMode() { /* offline mode: no-op */ }
53
+
54
+ // Cloud-sync trigger used by the standalone product's AppMenu / keyboard
55
+ // shortcuts. The package's offline build should never hit a server, so we
56
+ // resolve immediately. The host's onSceneChange callback (passed to
57
+ // LixSketchCanvas) handles real persistence.
58
+ export async function triggerCloudSync() { return false; }
@@ -0,0 +1,76 @@
1
+ "use client"
2
+
3
+ import { useEffect, useRef } from 'react'
4
+ import useSketchStore from '../store/useSketchStore'
5
+ import { WORKER_URL } from './inertStores'
6
+
7
+ /**
8
+ * Hook that initializes the SketchEngine on the provided SVG element
9
+ * and bridges Zustand activeTool changes to the engine's window flags.
10
+ */
11
+ export default function useSketchEngine(svgRef, ready = true) {
12
+ const engineRef = useRef(null)
13
+
14
+ useEffect(() => {
15
+ if (!ready || !svgRef.current) return
16
+
17
+ let cancelled = false
18
+
19
+ async function initEngine() {
20
+ try {
21
+ // Expose Zustand store API on window BEFORE engine init
22
+ // so SketchEngine globals can call into React state
23
+ const storeState = useSketchStore.getState()
24
+ window.__sketchStoreApi = {
25
+ setSelectedShapeSidebar: (sidebar) => useSketchStore.getState().setSelectedShapeSidebar(sidebar),
26
+ clearSelectedShapeSidebar: () => useSketchStore.getState().clearSelectedShapeSidebar(),
27
+ setActiveTool: (tool, opts) => useSketchStore.getState().setActiveTool(tool, opts),
28
+ setZoom: (zoom) => useSketchStore.setState({ zoom }),
29
+ getState: () => useSketchStore.getState(),
30
+ }
31
+
32
+ // Import the engine class directly (not via ../../index.js) so the
33
+ // chunk doesn't pull SceneSerializer + the shape graph in before
34
+ // engine.init() has had a chance to wire window.svg / window.rough.
35
+ const { SketchEngine } = await import('../../SketchEngine.js')
36
+ if (cancelled) return
37
+
38
+ const engine = new SketchEngine(svgRef.current)
39
+ await engine.init()
40
+ engineRef.current = engine
41
+ window.__sketchEngine = engine
42
+ window.__WORKER_URL = WORKER_URL
43
+
44
+ // Sync current tool immediately after init
45
+ const currentTool = useSketchStore.getState().activeTool
46
+ engine.setActiveTool(currentTool)
47
+ } catch (err) {
48
+ console.error('[useSketchEngine] Failed to initialize:', err)
49
+ }
50
+ }
51
+
52
+ initEngine()
53
+
54
+ return () => {
55
+ cancelled = true
56
+ if (engineRef.current) {
57
+ engineRef.current.cleanup()
58
+ engineRef.current = null
59
+ }
60
+ }
61
+ }, [svgRef, ready])
62
+
63
+ // Subscribe to Zustand activeTool changes and bridge to engine
64
+ useEffect(() => {
65
+ const unsub = useSketchStore.subscribe(
66
+ (state, prevState) => {
67
+ if (state.activeTool !== prevState?.activeTool && engineRef.current) {
68
+ engineRef.current.setActiveTool(state.activeTool)
69
+ }
70
+ }
71
+ )
72
+ return unsub
73
+ }, [])
74
+
75
+ return engineRef
76
+ }
@@ -0,0 +1,92 @@
1
+ // Lightweight i18n stub for the offline package. Converts dotted keys
2
+ // into readable labels so consumers don't have to ship a full translation
3
+ // table just to get nice button names. A real `t()` from the host can be
4
+ // passed in via `options.t` if needed (LixSketchCanvas can wire it up
5
+ // later).
6
+ //
7
+ // t('sidebar.sectionHeader.stroke') → 'Stroke'
8
+ // t('sidebar.bringForward') → 'Bring forward'
9
+ // t('menu.canvasBackground') → 'Canvas background'
10
+ // t('shortcuts.deselect', 'Deselect') → 'Deselect' (fallback wins)
11
+
12
+ // Hand-written overrides for keys that don't reduce cleanly from the
13
+ // last segment alone (acronyms, hyphens, multi-word phrases).
14
+ const OVERRIDES = {
15
+ 'sidebar.sectionHeader.stroke': 'Stroke',
16
+ 'sidebar.sectionHeader.fill': 'Fill',
17
+ 'sidebar.sectionHeader.background': 'Background',
18
+ 'sidebar.sectionHeader.backgroundImage': 'Background image',
19
+ 'sidebar.sectionHeader.width': 'Width',
20
+ 'sidebar.sectionHeader.edge': 'Edge style',
21
+ 'sidebar.sectionHeader.head': 'Arrow head',
22
+ 'sidebar.sectionHeader.type': 'Type',
23
+ 'sidebar.sectionHeader.name': 'Name',
24
+ 'sidebar.sectionHeader.color': 'Color',
25
+ 'sidebar.sectionHeader.fit': 'Fit',
26
+ 'sidebar.sectionHeader.roughness': 'Roughness',
27
+ 'sidebar.sectionHeader.sloppiness': 'Sloppiness',
28
+ 'sidebar.sectionHeader.taper': 'Taper',
29
+ 'menu.canvasBackground': 'Canvas background',
30
+ 'menu.canvasProperties': 'Canvas properties',
31
+ 'menu.exportImage': 'Export as image',
32
+ 'menu.findText': 'Find text',
33
+ 'menu.quickSave': 'Quick save',
34
+ 'menu.resetCanvas': 'Reset canvas',
35
+ 'menu.saveShare': 'Save & share',
36
+ 'menu.showGrid': 'Show grid',
37
+ 'menu.signIn': 'Sign in',
38
+ 'menu.signOut': 'Sign out',
39
+ 'prefs.toolLock': 'Tool lock',
40
+ 'prefs.toggleGrid': 'Toggle grid',
41
+ 'prefs.snapObjects': 'Snap to objects',
42
+ 'prefs.snapMidpoints': 'Snap to midpoints',
43
+ 'prefs.zenMode': 'Zen mode',
44
+ 'prefs.viewMode': 'View mode',
45
+ 'prefs.canvasShapeProps': 'Canvas & shape properties',
46
+ 'prefs.arrowBinding': 'Arrow binding',
47
+ 'prefs.language': 'Language',
48
+ 'sidebar.bringForward': 'Bring forward',
49
+ 'sidebar.bringToFront': 'Bring to front',
50
+ 'sidebar.sendBackward': 'Send backward',
51
+ 'sidebar.sendToBack': 'Send to back',
52
+ 'sidebar.fillColor': 'Fill color',
53
+ 'sidebar.strokeColor': 'Stroke color',
54
+ 'sidebar.codeMode': 'Code mode',
55
+ 'sidebar.renderText': 'Render text',
56
+ 'sidebar.newLine': 'New line',
57
+ };
58
+
59
+ function camelToWords(s) {
60
+ return s.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/-/g, ' ');
61
+ }
62
+
63
+ function autoFormat(key) {
64
+ const last = key.split('.').pop() || key;
65
+ const words = camelToWords(last);
66
+ return words.charAt(0).toUpperCase() + words.slice(1);
67
+ }
68
+
69
+ function tImpl(key, fallbackOrOpts) {
70
+ if (typeof key !== 'string') return fallbackOrOpts ?? '';
71
+ // The product calls t() with various signatures: (key), (key, fallback),
72
+ // (key, { defaultValue }). Handle all of them.
73
+ let fallback = '';
74
+ if (typeof fallbackOrOpts === 'string') {
75
+ fallback = fallbackOrOpts;
76
+ } else if (fallbackOrOpts && typeof fallbackOrOpts === 'object') {
77
+ fallback = fallbackOrOpts.defaultValue || '';
78
+ }
79
+ if (fallback) return fallback;
80
+ if (OVERRIDES[key]) return OVERRIDES[key];
81
+ return autoFormat(key);
82
+ }
83
+
84
+ export function useTranslation() {
85
+ return {
86
+ t: tImpl,
87
+ locale: 'en',
88
+ setLocale: () => {},
89
+ };
90
+ }
91
+
92
+ export default useTranslation;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @elixpo/lixsketch/react — React component bindings for the LixSketch
3
+ * engine. Mountable in any React app (e.g. blogs.elixpo) without running a
4
+ * second server.
5
+ *
6
+ * Usage:
7
+ * import { LixSketchCanvas } from '@elixpo/lixsketch/react';
8
+ * import '@elixpo/lixsketch/react/styles';
9
+ *
10
+ * <LixSketchCanvas
11
+ * initialScene={scene}
12
+ * onSceneChange={(scene, metadata) => save(scene, metadata)}
13
+ * onUploadImage={async (dataUrl) => {
14
+ * const r = await fetch('/api/media/upload', { … });
15
+ * return { url: r.url, sizeBytes: r.sizeBytes };
16
+ * }}
17
+ * onExit={() => router.back()}
18
+ * />
19
+ *
20
+ * The component is offline-first: it does not touch any cloud sync, AI, or
21
+ * auth code paths. The host owns persistence and image upload via the two
22
+ * callbacks above.
23
+ */
24
+
25
+ export { default as LixSketchCanvas } from './LixSketchCanvas.jsx';
26
+
27
+ // Re-exports — we import each from its specific source file rather than
28
+ // going through `../index.js`, which would pull SceneSerializer.js (and
29
+ // the entire shape graph that references engine globals) into the entry
30
+ // chunk. The engine entry stays available for hosts that explicitly want
31
+ // raw scene IO (`import { saveScene } from '@elixpo/lixsketch'`).
32
+ export { TOOLS } from './toolsEnum.js';
33
+ export { compressImage } from '../utils/imageCompressor.js';
34
+ export {
35
+ ALLOWED_IMAGE_MIME_TYPES,
36
+ IMAGE_ACCEPT_ATTR,
37
+ ALLOWED_IMAGE_EXTENSIONS,
38
+ isAllowedImage,
39
+ isAllowedImageDataUrl,
40
+ } from '../utils/allowedImageTypes.js';
41
+ export { installEngineShortcuts, SHORTCUT_MAP } from '../EngineShortcuts.js';
@@ -0,0 +1,234 @@
1
+ import { create } from 'zustand'
2
+
3
+ // Tool enum replaces 15 boolean flags
4
+ export const TOOLS = {
5
+ SELECT: 'select',
6
+ PAN: 'pan',
7
+ RECTANGLE: 'rectangle',
8
+ CIRCLE: 'circle',
9
+ LINE: 'line',
10
+ ARROW: 'arrow',
11
+ FREEHAND: 'freehand',
12
+ TEXT: 'text',
13
+ CODE: 'code',
14
+ ERASER: 'eraser',
15
+ LASER: 'laser',
16
+ IMAGE: 'image',
17
+ FRAME: 'frame',
18
+ ICON: 'icon',
19
+ }
20
+
21
+ // Action types for undo/redo
22
+ export const ACTION = {
23
+ CREATE: 'create',
24
+ DELETE: 'delete',
25
+ MODIFY: 'modify',
26
+ PASTE: 'paste',
27
+ }
28
+
29
+ // Tool -> cursor mapping
30
+ const TOOL_CURSORS = {
31
+ [TOOLS.SELECT]: 'default',
32
+ [TOOLS.PAN]: 'grab',
33
+ [TOOLS.RECTANGLE]: 'crosshair',
34
+ [TOOLS.CIRCLE]: 'crosshair',
35
+ [TOOLS.LINE]: 'crosshair',
36
+ [TOOLS.ARROW]: 'crosshair',
37
+ [TOOLS.FREEHAND]: 'crosshair',
38
+ [TOOLS.TEXT]: 'text',
39
+ [TOOLS.CODE]: 'text',
40
+ [TOOLS.LASER]: 'crosshair',
41
+ [TOOLS.IMAGE]: 'crosshair',
42
+ [TOOLS.FRAME]: 'crosshair',
43
+ [TOOLS.ICON]: 'crosshair',
44
+ }
45
+
46
+ // Tool -> sidebar mapping
47
+ const TOOL_SIDEBARS = {
48
+ [TOOLS.FREEHAND]: 'paintbrush',
49
+ [TOOLS.RECTANGLE]: 'rectangle',
50
+ [TOOLS.CIRCLE]: 'circle',
51
+ [TOOLS.LINE]: 'line',
52
+ [TOOLS.ARROW]: 'arrow',
53
+ [TOOLS.TEXT]: 'text',
54
+ [TOOLS.CODE]: 'text',
55
+ [TOOLS.FRAME]: 'frame',
56
+ }
57
+
58
+ // Keyboard shortcut -> tool mapping
59
+ export const SHORTCUT_MAP = {
60
+ h: TOOLS.PAN,
61
+ v: TOOLS.SELECT,
62
+ 1: TOOLS.SELECT,
63
+ r: TOOLS.RECTANGLE,
64
+ 2: TOOLS.RECTANGLE,
65
+ o: TOOLS.CIRCLE,
66
+ 4: TOOLS.CIRCLE,
67
+ a: TOOLS.ARROW,
68
+ 5: TOOLS.ARROW,
69
+ l: TOOLS.LINE,
70
+ 6: TOOLS.LINE,
71
+ p: TOOLS.FREEHAND,
72
+ 7: TOOLS.FREEHAND,
73
+ t: TOOLS.TEXT,
74
+ 8: TOOLS.TEXT,
75
+ 9: TOOLS.IMAGE,
76
+ e: TOOLS.ERASER,
77
+ 0: TOOLS.ERASER,
78
+ i: TOOLS.ICON,
79
+ f: TOOLS.FRAME,
80
+ k: TOOLS.LASER,
81
+ }
82
+
83
+ const useSketchStore = create((set, get) => ({
84
+ // --- Active tool (replaces 15 boolean flags) ---
85
+ activeTool: TOOLS.SELECT,
86
+ activeSidebar: null, // which sidebar to show
87
+ selectedShapeSidebar: null, // sidebar shown when a shape is selected (overrides activeSidebar)
88
+
89
+ setActiveTool: (tool, { afterDraw } = {}) => {
90
+ // In view mode, only pan is allowed
91
+ if (get().viewMode && tool !== TOOLS.PAN) return
92
+ // Tool lock: don't switch to SELECT after drawing
93
+ if (afterDraw && tool === TOOLS.SELECT && get().toolLock) return
94
+ set({
95
+ activeTool: tool,
96
+ activeSidebar: TOOL_SIDEBARS[tool] || null,
97
+ selectedShapeSidebar: null,
98
+ })
99
+ },
100
+
101
+ // Called by engine when a shape is selected/deselected
102
+ setSelectedShapeSidebar: (sidebar) => set({ selectedShapeSidebar: sidebar }),
103
+ clearSelectedShapeSidebar: () => set({ selectedShapeSidebar: null }),
104
+
105
+ getCursor: () => {
106
+ const tool = get().activeTool
107
+ if (tool === TOOLS.ERASER) {
108
+ return `url("data:image/svg+xml;base64,${typeof btoa !== 'undefined' ? btoa('<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><circle cx="10" cy="10" r="7" fill="#222" stroke="white" stroke-width="2"/></svg>') : ''}") 10 10, auto`
109
+ }
110
+ if (tool === TOOLS.LASER) {
111
+ return `url("data:image/svg+xml;base64,${typeof btoa !== 'undefined' ? btoa('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#1971c2" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>') : ''}") 0 0, auto`
112
+ }
113
+ return TOOL_CURSORS[tool] || 'crosshair'
114
+ },
115
+
116
+ // --- Shapes ---
117
+ shapes: [],
118
+ currentShape: null,
119
+ lastMousePos: null,
120
+
121
+ addShape: (shape) =>
122
+ set((s) => ({ shapes: [...s.shapes, shape] })),
123
+
124
+ removeShape: (shape) =>
125
+ set((s) => ({ shapes: s.shapes.filter((sh) => sh !== shape) })),
126
+
127
+ setCurrentShape: (shape) => set({ currentShape: shape }),
128
+ setLastMousePos: (pos) => set({ lastMousePos: pos }),
129
+
130
+ clearShapes: () => set({ shapes: [], currentShape: null }),
131
+
132
+ // --- Undo/Redo ---
133
+ undoStack: [],
134
+ redoStack: [],
135
+
136
+ pushUndo: (action) =>
137
+ set((s) => ({
138
+ undoStack: [...s.undoStack, action],
139
+ redoStack: [], // clear redo on new action
140
+ })),
141
+
142
+ popUndo: () => {
143
+ const { undoStack } = get()
144
+ if (undoStack.length === 0) return null
145
+ const action = undoStack[undoStack.length - 1]
146
+ set((s) => ({
147
+ undoStack: s.undoStack.slice(0, -1),
148
+ redoStack: [...s.redoStack, action],
149
+ }))
150
+ return action
151
+ },
152
+
153
+ popRedo: () => {
154
+ const { redoStack } = get()
155
+ if (redoStack.length === 0) return null
156
+ const action = redoStack[redoStack.length - 1]
157
+ set((s) => ({
158
+ redoStack: s.redoStack.slice(0, -1),
159
+ undoStack: [...s.undoStack, action],
160
+ }))
161
+ return action
162
+ },
163
+
164
+ clearHistory: () => set({ undoStack: [], redoStack: [] }),
165
+
166
+ // --- Viewport / Zoom ---
167
+ zoom: 1,
168
+ viewBox: { x: 0, y: 0, width: 0, height: 0 },
169
+ translation: { x: 0, y: 0 },
170
+ isPanning: false,
171
+ panStart: null,
172
+
173
+ setZoom: (zoom) => set({ zoom: Math.max(0.4, Math.min(30, zoom)) }),
174
+ setViewBox: (vb) => set({ viewBox: vb }),
175
+ setTranslation: (t) => set({ translation: t }),
176
+ setIsPanning: (v) => set({ isPanning: v }),
177
+ setPanStart: (p) => set({ panStart: p }),
178
+
179
+ // --- Canvas background ---
180
+ canvasBackground: 'var(--lixsketch-bg, #ffffff)',
181
+ setCanvasBackground: (color) => set({ canvasBackground: color }),
182
+
183
+ // --- Grid ---
184
+ gridEnabled: false,
185
+ toggleGrid: () => set((s) => ({ gridEnabled: !s.gridEnabled })),
186
+
187
+ // --- Modes ---
188
+ viewMode: false,
189
+ zenMode: false,
190
+ toolLock: false,
191
+ snapToObjects: false,
192
+
193
+ toggleViewMode: () => {
194
+ const entering = !get().viewMode
195
+ if (entering) {
196
+ set({ viewMode: true, zenMode: false, activeTool: TOOLS.PAN, activeSidebar: null, selectedShapeSidebar: null })
197
+ } else {
198
+ set({ viewMode: false, activeTool: TOOLS.SELECT, activeSidebar: null })
199
+ }
200
+ },
201
+ toggleZenMode: () => set((s) => ({ zenMode: !s.zenMode, viewMode: false })),
202
+
203
+ // --- Canvas/Docs split layout ---
204
+ // 'canvas' = sketch only, 'split' = side-by-side, 'docs' = doc only
205
+ // Hydrated from localStorage on first store consumption (see
206
+ // hydrateLayoutMode below). Persisted on every change.
207
+ layoutMode: 'canvas',
208
+ setLayoutMode: (mode) => {
209
+ if (!['canvas', 'split', 'docs'].includes(mode)) return
210
+ set({ layoutMode: mode })
211
+ if (typeof window !== 'undefined') {
212
+ try { localStorage.setItem('lixsketch-layout-mode', mode) } catch {}
213
+ }
214
+ },
215
+ hydrateLayoutMode: () => {
216
+ if (typeof window === 'undefined') return
217
+ try {
218
+ const saved = localStorage.getItem('lixsketch-layout-mode')
219
+ if (saved && ['canvas', 'split', 'docs'].includes(saved)) {
220
+ set({ layoutMode: saved })
221
+ }
222
+ } catch {}
223
+ },
224
+ toggleToolLock: () => set((s) => ({ toolLock: !s.toolLock })),
225
+ toggleSnapToObjects: () => set((s) => ({ snapToObjects: !s.snapToObjects })),
226
+
227
+ // --- RoughJS refs (set once after mount) ---
228
+ roughCanvas: null,
229
+ roughGenerator: null,
230
+ setRoughRefs: (canvas, generator) =>
231
+ set({ roughCanvas: canvas, roughGenerator: generator }),
232
+ }))
233
+
234
+ export default useSketchStore