@valbuild/ui 0.26.0 → 0.27.0

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 (138) hide show
  1. package/package.json +7 -3
  2. package/server/.tmp/assets/index-082e6676.css +1 -0
  3. package/server/.tmp/assets/index-3108ab2a.js +197 -0
  4. package/{index.html → server/.tmp/index.html} +3 -1
  5. package/.babelrc.json +0 -10
  6. package/.storybook/main.js +0 -25
  7. package/.storybook/preview-head.html +0 -6
  8. package/.storybook/preview.js +0 -33
  9. package/.storybook/theme.css +0 -34
  10. package/CHANGELOG.md +0 -0
  11. package/components.json +0 -16
  12. package/fix-server-hack.js +0 -54
  13. package/fullscreen.vite.config.ts +0 -9
  14. package/jest.config.js +0 -4
  15. package/postcss.config.js +0 -6
  16. package/rollup.config.js +0 -23
  17. package/server.vite.config.ts +0 -31
  18. package/src/App.tsx +0 -73
  19. package/src/assets/icons/Bold.tsx +0 -23
  20. package/src/assets/icons/Chevron.tsx +0 -28
  21. package/src/assets/icons/FontColor.tsx +0 -30
  22. package/src/assets/icons/ImageIcon.tsx +0 -29
  23. package/src/assets/icons/Italic.tsx +0 -24
  24. package/src/assets/icons/Logo.tsx +0 -103
  25. package/src/assets/icons/Section.tsx +0 -41
  26. package/src/assets/icons/Strikethrough.tsx +0 -22
  27. package/src/assets/icons/TextIcon.tsx +0 -20
  28. package/src/assets/icons/Underline.tsx +0 -22
  29. package/src/assets/icons/Undo.tsx +0 -20
  30. package/src/components/Button.tsx +0 -68
  31. package/src/components/Checkbox.tsx +0 -51
  32. package/src/components/DraggableList.stories.tsx +0 -20
  33. package/src/components/DraggableList.tsx +0 -95
  34. package/src/components/Dropdown.tsx +0 -101
  35. package/src/components/EditButton.tsx +0 -10
  36. package/src/components/ErrorText.tsx +0 -3
  37. package/src/components/ExpandLogo.tsx +0 -72
  38. package/src/components/Grid.stories.tsx +0 -43
  39. package/src/components/Grid.tsx +0 -139
  40. package/src/components/RichTextEditor/ContentEditable.tsx +0 -117
  41. package/src/components/RichTextEditor/Nodes/ImageNode.tsx +0 -100
  42. package/src/components/RichTextEditor/Plugins/AutoFocus.tsx +0 -12
  43. package/src/components/RichTextEditor/Plugins/ImagePlugin.tsx +0 -45
  44. package/src/components/RichTextEditor/Plugins/LinkEditorPlugin.tsx +0 -58
  45. package/src/components/RichTextEditor/Plugins/Toolbar.tsx +0 -412
  46. package/src/components/RichTextEditor/RichTextEditor.tsx +0 -105
  47. package/src/components/UploadModal.tsx +0 -109
  48. package/src/components/User.tsx +0 -17
  49. package/src/components/ValFormField.tsx +0 -574
  50. package/src/components/ValFullscreen.tsx +0 -1278
  51. package/src/components/ValMenu.tsx +0 -92
  52. package/src/components/ValOverlay.tsx +0 -488
  53. package/src/components/ValOverlayContext.tsx +0 -80
  54. package/src/components/ValWindow.stories.tsx +0 -146
  55. package/src/components/ValWindow.tsx +0 -220
  56. package/src/components/dashboard/DashboardButton.tsx +0 -25
  57. package/src/components/dashboard/DashboardDropdown.tsx +0 -59
  58. package/src/components/dashboard/Dropdown.stories.tsx +0 -11
  59. package/src/components/dashboard/Dropdown.tsx +0 -70
  60. package/src/components/dashboard/FormGroup.stories.tsx +0 -37
  61. package/src/components/dashboard/FormGroup.tsx +0 -42
  62. package/src/components/dashboard/Grid2.stories.tsx +0 -56
  63. package/src/components/dashboard/Grid2.tsx +0 -72
  64. package/src/components/dashboard/Tree.stories.tsx +0 -91
  65. package/src/components/dashboard/Tree.tsx +0 -72
  66. package/src/components/dashboard/ValDashboardEditor.tsx +0 -269
  67. package/src/components/dashboard/ValDashboardGrid.tsx +0 -142
  68. package/src/components/dashboard/ValTreeNavigator.tsx +0 -253
  69. package/src/components/forms/Form.tsx +0 -126
  70. package/src/components/forms/FormContainer.tsx +0 -24
  71. package/src/components/forms/ImageForm.tsx +0 -195
  72. package/src/components/forms/TextArea.tsx +0 -24
  73. package/src/components/ui/accordion.tsx +0 -58
  74. package/src/components/ui/alert-dialog.tsx +0 -139
  75. package/src/components/ui/avatar.tsx +0 -48
  76. package/src/components/ui/button.tsx +0 -56
  77. package/src/components/ui/calendar.tsx +0 -62
  78. package/src/components/ui/card.tsx +0 -86
  79. package/src/components/ui/checkbox.tsx +0 -28
  80. package/src/components/ui/command.tsx +0 -153
  81. package/src/components/ui/dialog.tsx +0 -120
  82. package/src/components/ui/dropdown-menu.tsx +0 -198
  83. package/src/components/ui/form.tsx +0 -177
  84. package/src/components/ui/input.tsx +0 -24
  85. package/src/components/ui/label.tsx +0 -24
  86. package/src/components/ui/popover.tsx +0 -29
  87. package/src/components/ui/progress.tsx +0 -26
  88. package/src/components/ui/radio-group.tsx +0 -42
  89. package/src/components/ui/scroll-area.tsx +0 -51
  90. package/src/components/ui/select.tsx +0 -119
  91. package/src/components/ui/switch.tsx +0 -27
  92. package/src/components/ui/tabs.tsx +0 -53
  93. package/src/components/ui/toggle.tsx +0 -43
  94. package/src/components/ui/tooltip.tsx +0 -28
  95. package/src/components/usePatch.ts +0 -86
  96. package/src/components/useTheme.ts +0 -45
  97. package/src/dto/SerializedSchema.ts +0 -69
  98. package/src/dto/Session.ts +0 -12
  99. package/src/dto/SessionMode.ts +0 -5
  100. package/src/dto/Tree.ts +0 -18
  101. package/src/exports.ts +0 -6
  102. package/src/index.css +0 -115
  103. package/src/index.tsx +0 -14
  104. package/src/lib/IValStore.ts +0 -6
  105. package/src/lib/utils.ts +0 -6
  106. package/src/main.jsx +0 -10
  107. package/src/richtext/conversion/conversion.test.ts +0 -146
  108. package/src/richtext/conversion/lexicalToRichTextSource.test.ts +0 -89
  109. package/src/richtext/conversion/lexicalToRichTextSource.ts +0 -285
  110. package/src/richtext/conversion/parseRichTextSource.test.ts +0 -469
  111. package/src/richtext/conversion/parseRichTextSource.ts +0 -233
  112. package/src/richtext/conversion/richTextSourceToLexical.test.ts +0 -381
  113. package/src/richtext/conversion/richTextSourceToLexical.ts +0 -293
  114. package/src/richtext/shadowRootPolyFill.js +0 -115
  115. package/src/server.ts +0 -70
  116. package/src/stories/Button.stories.tsx +0 -20
  117. package/src/stories/Checkbox.stories.tsx +0 -14
  118. package/src/stories/Dropdown.stories.tsx +0 -23
  119. package/src/stories/Introduction.mdx +0 -221
  120. package/src/stories/RichTextEditor.stories.tsx +0 -24
  121. package/src/stories/assets/code-brackets.svg +0 -1
  122. package/src/stories/assets/colors.svg +0 -1
  123. package/src/stories/assets/comments.svg +0 -1
  124. package/src/stories/assets/direction.svg +0 -1
  125. package/src/stories/assets/flow.svg +0 -1
  126. package/src/stories/assets/plugin.svg +0 -1
  127. package/src/stories/assets/repo.svg +0 -1
  128. package/src/stories/assets/stackalt.svg +0 -1
  129. package/src/utils/Remote.ts +0 -15
  130. package/src/utils/imageMimeType.ts +0 -23
  131. package/src/utils/readImage.ts +0 -54
  132. package/src/utils/resolvePath.ts +0 -32
  133. package/src/vite-env.d.ts +0 -1
  134. package/src/vite-index.tsx +0 -7
  135. package/src/vite-server.ts +0 -42
  136. package/tailwind.config.js +0 -83
  137. package/tsconfig.json +0 -19
  138. package/vite.config.ts +0 -43
@@ -1,253 +0,0 @@
1
- import { Internal, SerializedModule, SourcePath } from "@valbuild/core";
2
- import { Json, JsonArray } from "@valbuild/core/src/Json";
3
- import { ValApi } from "@valbuild/core";
4
- import classNames from "classnames";
5
- import { Dispatch, FC, SetStateAction, useState } from "react";
6
- import Chevron from "../../assets/icons/Chevron";
7
- import { DraggableList, DraggableResult } from "../DraggableList";
8
-
9
- const ValTreeArrayModuleItem: FC<{
10
- submodule: Json;
11
- selectedModule: string;
12
- setSelectedModule: Dispatch<SetStateAction<string>>;
13
- idx: number;
14
- path: string;
15
- reOrder: (oldIdx: number, newIdx: number) => void;
16
- reorderMode: boolean;
17
- }> = ({
18
- submodule,
19
- selectedModule,
20
- setSelectedModule,
21
- idx,
22
- path,
23
- reOrder,
24
- reorderMode,
25
- }) => {
26
- const title = resolveTitle(submodule);
27
- return (
28
- <div className="w-fit" draggable id={idx.toString()}>
29
- <div className="flex gap-4">
30
- <button
31
- onClick={() => setSelectedModule(path + idx.toString())}
32
- className={classNames(
33
- "px-4 py-2 text-start hover:bg-light-gray/20 hover:rounded-lg",
34
- {
35
- "font-extrabold ":
36
- module.path + idx.toString() === selectedModule,
37
- }
38
- )}
39
- >
40
- {title}
41
- </button>
42
- {reorderMode && (
43
- <div className="flex gap-2">
44
- <button
45
- onClick={() => reOrder(idx, idx + 1)}
46
- className="disabled:text-dark-gray"
47
- >
48
- <Chevron className="rotate-90" />
49
- </button>
50
- <button
51
- onClick={() => reOrder(idx, idx - 1)}
52
- className="disabled:text-dark-gray"
53
- >
54
- <Chevron className="-rotate-90" />
55
- </button>
56
- </div>
57
- )}
58
- </div>
59
- </div>
60
- );
61
- };
62
-
63
- const DEFAULT_TITLE = "Untitled";
64
- function resolveTitle(submodule: Json): string {
65
- if (!submodule) {
66
- return DEFAULT_TITLE;
67
- }
68
- if (typeof submodule === "string") {
69
- return submodule;
70
- }
71
- if (typeof submodule === "object") {
72
- if ("title" in submodule && typeof submodule.title === "string") {
73
- return submodule.title;
74
- }
75
- if ("name" in submodule && typeof submodule.name === "string") {
76
- return submodule.name;
77
- }
78
- const firstStringField = Object.entries(submodule).find(([, value]) => {
79
- return typeof value === "string";
80
- })?.[1];
81
- if (typeof firstStringField === "string") {
82
- return firstStringField;
83
- }
84
- }
85
- return DEFAULT_TITLE;
86
- }
87
-
88
- const ValTreeNavigatorArrayModule: FC<{
89
- module: SerializedModule;
90
- selectedModule: string;
91
- setSelectedModule: Dispatch<SetStateAction<string>>;
92
- valApi: ValApi;
93
- }> = ({ module, selectedModule, setSelectedModule }) => {
94
- const [collapsed, setCollapsed] = useState(true);
95
- const [items, setItems] = useState<Json[]>(
96
- (module.source as JsonArray).map((submodule) => submodule as Json)
97
- );
98
- const [reOrderMode, setReOrderMode] = useState(false);
99
-
100
- const reOrder = async (oldIndex: number, newIndex: number) => {
101
- const sanitizedNewIndex =
102
- newIndex < 0 ? items.length - 1 : newIndex % items.length;
103
- const path = module.path + oldIndex.toString();
104
- const newPath = module.path + sanitizedNewIndex.toString();
105
- // const [moduleId, modulePath] = Internal.splitModuleIdAndModulePath(
106
- // path as SourcePath
107
- // );
108
-
109
- const [newModuleId, newModulePath] = Internal.splitModuleIdAndModulePath(
110
- newPath as SourcePath
111
- );
112
- // const patch: PatchJSON = [
113
- // {
114
- // op: "move",
115
- // from: `/${modulePath
116
- // .split(".")
117
- // .map((p) => {
118
- // return JSON.parse(p);
119
- // })
120
- // .join("/")}`,
121
- // path: `/${newModulePath
122
- // .split(".")
123
- // .map((p) => {
124
- // return JSON.parse(p);
125
- // })
126
- // .join("/")}`,
127
- // },
128
- // ];
129
- // await valApi.patchModuleContent(moduleId, patch);
130
- if (selectedModule === path) {
131
- setSelectedModule(`${newModuleId}.${newModulePath}`);
132
- }
133
- setItems((items) => {
134
- const newItems = [...items];
135
- const item = newItems.splice(oldIndex, 1)[0];
136
- newItems.splice(sanitizedNewIndex, 0, item);
137
- return newItems;
138
- });
139
- };
140
-
141
- const toggleReorderMode = () => {
142
- setReOrderMode(!reOrderMode);
143
- if (collapsed) setCollapsed(false);
144
- };
145
-
146
- return (
147
- <div className="relative flex flex-col gap-3">
148
- <div className="flex items-center justify-between ">
149
- <button
150
- className="flex items-center justify-between px-4 py-2 hover:bg-light-gray/20 hover:rounded-lg "
151
- onClick={() => {
152
- setSelectedModule(`${module.path.toString()}`);
153
- setCollapsed(!collapsed);
154
- }}
155
- >
156
- <div className="flex items-center gap-2">
157
- <Chevron className={classNames({ "rotate-90": !collapsed })} />
158
- <h1 className="text-xl">{module.path}</h1>
159
- </div>
160
- </button>
161
- <div className="flex gap-2">
162
- <button
163
- className="relative w-[20px] h-[20px] flex flex-col justify-between items-center"
164
- onClick={toggleReorderMode}
165
- >
166
- <Chevron className="top-0 -rotate-90" />
167
- <Chevron className="rotate-90 " />
168
- </button>
169
- <button className="text-2xl w-[20px] h-[20px] rounded-full flex justify-center items-center">
170
- +
171
- </button>
172
- </div>
173
- </div>
174
-
175
- {!collapsed && (
176
- <DraggableList
177
- onDragEnd={(res: DraggableResult) => reOrder(res.from, res.to)}
178
- >
179
- {items.map((submodule, idx) => (
180
- <ValTreeArrayModuleItem
181
- submodule={submodule}
182
- idx={idx}
183
- selectedModule={selectedModule}
184
- setSelectedModule={setSelectedModule}
185
- path={module.path}
186
- key={idx}
187
- reOrder={reOrder}
188
- reorderMode={reOrderMode}
189
- />
190
- ))}
191
- </DraggableList>
192
- )}
193
- </div>
194
- );
195
- };
196
-
197
- const ValTreeNavigatorModule: FC<{
198
- module: SerializedModule;
199
- setSelectedModule: Dispatch<SetStateAction<string>>;
200
- }> = ({ module, setSelectedModule }) => {
201
- return (
202
- <div className="relative flex flex-col gap-3 px-4 py-2 hover:bg-light-gray/20 hover:rounded-lg ">
203
- <button
204
- className="flex items-center justify-between"
205
- onClick={() => setSelectedModule(module.path)}
206
- >
207
- <div className="flex items-center gap-2">
208
- <Chevron className={classNames("opacity-0")} />
209
- <h1 className="text-xl">{module.path}</h1>
210
- </div>
211
- </button>
212
- </div>
213
- );
214
- };
215
-
216
- interface ValTreeNavigator {
217
- modules: SerializedModule[];
218
- selectedModule: string;
219
- setSelectedModule: Dispatch<SetStateAction<string>>;
220
- valApi: ValApi;
221
- }
222
- export const ValTreeNavigator: FC<ValTreeNavigator> = ({
223
- modules,
224
- selectedModule,
225
- setSelectedModule,
226
- valApi,
227
- }) => {
228
- console.log("hello?????????????", modules);
229
- return (
230
- <div
231
- className={classNames("flex flex-col gap-4 font-serif text-lg px-4 py-3")}
232
- >
233
- {modules.map((module, idx) => (
234
- <div key={idx}>
235
- {module.schema.type === "array" ? (
236
- <ValTreeNavigatorArrayModule
237
- module={module}
238
- key={idx}
239
- selectedModule={selectedModule}
240
- setSelectedModule={setSelectedModule}
241
- valApi={valApi}
242
- />
243
- ) : (
244
- <ValTreeNavigatorModule
245
- module={module}
246
- setSelectedModule={setSelectedModule}
247
- />
248
- )}
249
- </div>
250
- ))}
251
- </div>
252
- );
253
- };
@@ -1,126 +0,0 @@
1
- import { AnyRichTextOptions, RichTextSource } from "@valbuild/core";
2
- import { LexicalEditor } from "lexical";
3
- import { useEffect, useState } from "react";
4
- import { RichTextEditor } from "../RichTextEditor/RichTextEditor";
5
- import { FormContainer } from "./FormContainer";
6
- import { ImageForm, ImageData } from "./ImageForm";
7
- import { TextData, TextArea } from "./TextArea";
8
-
9
- export type Inputs = {
10
- [path: string]:
11
- | { status: "requested" }
12
- | {
13
- status: "completed";
14
- type: "text";
15
- data: TextData;
16
- }
17
- | { status: "completed"; type: "image"; data: ImageData }
18
- | {
19
- status: "completed";
20
- type: "richtext";
21
- data: RichTextSource<AnyRichTextOptions>;
22
- };
23
- };
24
-
25
- export type FormProps = {
26
- onSubmit: (nextInputs: Inputs) => void;
27
- inputs: Inputs;
28
- };
29
-
30
- export function Form({ onSubmit, inputs }: FormProps): React.ReactElement {
31
- const [currentInputs, setCurrentInputs] = useState<Inputs>();
32
- const [richTextEditor, setRichTextEditor] = useState<{
33
- [path: string]: LexicalEditor;
34
- }>();
35
-
36
- useEffect(() => {
37
- setCurrentInputs(inputs);
38
- }, [inputs]);
39
-
40
- return (
41
- <FormContainer
42
- onSubmit={() => {
43
- if (currentInputs) {
44
- onSubmit(
45
- Object.fromEntries(
46
- Object.entries(currentInputs).map(([path, input]) => {
47
- if (input.status === "completed" && input.type === "richtext") {
48
- if (!richTextEditor) {
49
- throw Error(
50
- "Cannot save rich text - editor not initialized"
51
- );
52
- }
53
- return [
54
- path,
55
- {
56
- status: "completed",
57
- type: "richtext",
58
- data: richTextEditor[path].getEditorState().toJSON()
59
- ?.root,
60
- },
61
- ];
62
- }
63
- return [path, input];
64
- })
65
- )
66
- );
67
- }
68
- }}
69
- >
70
- {currentInputs &&
71
- Object.entries(currentInputs).map(([path, input]) => (
72
- <div key={path}>
73
- {input.status === "requested" && (
74
- <div className="p-2 text-center text-primary">Loading...</div>
75
- )}
76
- {input.status === "completed" && input.type === "image" && (
77
- <ImageForm
78
- name={path}
79
- data={input.data}
80
- onChange={(data) => {
81
- if (data.value) {
82
- setCurrentInputs({
83
- ...currentInputs,
84
- [path]: {
85
- status: "completed",
86
- type: "image",
87
- data: data.value,
88
- },
89
- });
90
- }
91
- }}
92
- error={null}
93
- />
94
- )}
95
- {input.status === "completed" && input.type === "text" && (
96
- <TextArea
97
- name={path}
98
- text={input.data}
99
- onChange={(data) => {
100
- setCurrentInputs({
101
- ...currentInputs,
102
- [path]: {
103
- status: "completed",
104
- type: "text",
105
- data: data,
106
- },
107
- });
108
- }}
109
- />
110
- )}
111
- {input.status === "completed" && input.type === "richtext" && (
112
- <RichTextEditor
113
- richtext={input.data}
114
- onEditor={(editor) => {
115
- setRichTextEditor({
116
- ...richTextEditor,
117
- [path]: editor,
118
- });
119
- }}
120
- />
121
- )}
122
- </div>
123
- ))}
124
- </FormContainer>
125
- );
126
- }
@@ -1,24 +0,0 @@
1
- import { PrimaryButton } from "../Button";
2
-
3
- export function FormContainer({
4
- children,
5
- onSubmit,
6
- }: {
7
- children: React.ReactNode;
8
- onSubmit: () => void;
9
- }) {
10
- return (
11
- <form
12
- className="flex flex-col justify-between w-full px-4 py-2"
13
- onSubmit={(ev) => {
14
- ev.preventDefault();
15
- onSubmit();
16
- }}
17
- >
18
- {children}
19
- <div className="flex justify-end">
20
- <PrimaryButton>Save</PrimaryButton>
21
- </div>
22
- </form>
23
- );
24
- }
@@ -1,195 +0,0 @@
1
- import { useEffect, useState } from "react";
2
- import { UploadCloud } from "react-feather";
3
- import { ErrorText } from "../ErrorText";
4
-
5
- type Error = "invalid-file" | "file-too-large";
6
- export type ImageData =
7
- | {
8
- src: string;
9
- addMetadata: boolean;
10
- metadata?: { width: number; height: number; sha256: string };
11
- }
12
- | {
13
- url: string;
14
- metadata?: { width: number; height: number; sha256: string };
15
- }
16
- | null;
17
- export type ImageInputProps = {
18
- name: string;
19
- data: ImageData;
20
- maxSize?: number;
21
- error: Error | null;
22
- onChange: (
23
- result: { error: null; value: ImageData } | { error: Error; value: null }
24
- ) => void;
25
- };
26
-
27
- const textEncoder = new TextEncoder();
28
- // TODO: handle hashes some other way
29
- const getSHA256Hash = async (bits: Uint8Array) => {
30
- const hashBuffer = await window.crypto.subtle.digest("SHA-256", bits);
31
- const hashArray = Array.from(new Uint8Array(hashBuffer));
32
- const hash = hashArray
33
- .map((item) => item.toString(16).padStart(2, "0"))
34
- .join("");
35
- return hash;
36
- };
37
-
38
- export function ImageForm({
39
- name,
40
- data: data,
41
- maxSize,
42
- error,
43
- onChange,
44
- }: ImageInputProps) {
45
- const [currentData, setCurrentData] = useState<ImageData>(null);
46
- useEffect(() => {
47
- setCurrentData(data);
48
- }, [data]);
49
- const [currentError, setCurrentError] = useState<Error | null>(null);
50
- useEffect(() => {
51
- setCurrentError(error);
52
- }, [data]);
53
-
54
- // TODO: we should update the Input type - we should never have a url here?
55
- const src =
56
- (currentData &&
57
- (("src" in currentData && currentData.src) ||
58
- ("url" in currentData && currentData.url))) ||
59
- undefined;
60
-
61
- return (
62
- <div className="w-full py-2 max-w-[90vw] object-contain">
63
- <label htmlFor={name}>
64
- <div className="flex items-center justify-center w-full h-full min-h-[200px] cursor-pointer">
65
- {currentData !== null && (
66
- <img className="object-contain max-h-[300px]" src={src} />
67
- )}
68
- {!src && <UploadCloud size={24} className="text-primary" />}
69
- </div>
70
- <input
71
- hidden
72
- type="file"
73
- id={name}
74
- name={name}
75
- onChange={(e) => {
76
- const file = e.target.files?.[0];
77
- if (file) {
78
- if (maxSize && file.size > maxSize) {
79
- onChange({ error: "file-too-large", value: null });
80
- return;
81
- }
82
- const reader = new FileReader();
83
- reader.addEventListener("load", () => {
84
- const result = reader.result;
85
- if (typeof result === "string") {
86
- const image = new Image();
87
- image.onload = async () => {
88
- const nextSource = {
89
- src: result,
90
- metadata: {
91
- width: image.naturalWidth,
92
- height: image.naturalHeight,
93
- sha256: await getSHA256Hash(textEncoder.encode(result)),
94
- },
95
- addMetadata: !currentData?.metadata,
96
- };
97
- setCurrentData(nextSource);
98
- setCurrentError(null);
99
- onChange({
100
- error: null,
101
- value: nextSource,
102
- });
103
- };
104
- image.src = result;
105
- } else {
106
- onChange({ error: "invalid-file", value: null });
107
- }
108
- });
109
- reader.readAsDataURL(file);
110
- }
111
- }}
112
- />
113
- </label>
114
- {currentData &&
115
- (!currentData.metadata ||
116
- typeof currentData.metadata.height === "undefined" ||
117
- typeof currentData.metadata.width === "undefined" ||
118
- typeof currentData.metadata.sha256 === "undefined") && (
119
- <ErrorText>
120
- <div className="">
121
- <div>Validation failed: missing metadata</div>
122
- <button
123
- className="px-4 py-2 border border-dark-gray"
124
- onClick={async (ev) => {
125
- ev.preventDefault();
126
-
127
- const image = new Image();
128
- image.onload = async () => {
129
- const nextSource = {
130
- src: image.src,
131
- metadata: {
132
- width: image.naturalWidth,
133
- height: image.naturalHeight,
134
- sha256: await getSHA256Hash(
135
- textEncoder.encode(image.src)
136
- ),
137
- },
138
- addMetadata: !currentData?.metadata,
139
- };
140
- setCurrentData(nextSource);
141
- setCurrentError(null);
142
- onChange({
143
- error: null,
144
- value: nextSource,
145
- });
146
- };
147
-
148
- if ("url" in currentData) {
149
- const src = await fetch(currentData.url)
150
- .then((response) => response.blob())
151
- .then((blob) => {
152
- return new Promise<string>((resolve, reject) => {
153
- const reader = new FileReader();
154
- reader.onload = function () {
155
- if (typeof this.result !== "string") {
156
- return reject(
157
- Error(
158
- `Could not read file from url: ${currentData.url}`
159
- )
160
- );
161
- }
162
- resolve(this.result);
163
- }; // <--- `this.result` contains a base64 data URI
164
- reader.readAsDataURL(blob);
165
- });
166
- });
167
- image.src = src;
168
- } else {
169
- image.src = currentData.src;
170
- }
171
- }}
172
- >
173
- Fix
174
- </button>
175
- </div>
176
- </ErrorText>
177
- )}
178
- {currentData?.metadata && (
179
- <div className="ml-auto text-primary">
180
- Dimensions: {currentData.metadata.width}x{currentData.metadata.height}
181
- </div>
182
- )}
183
- {currentError && <ImageError error={currentError} />}
184
- </div>
185
- );
186
- }
187
-
188
- function ImageError({ error }: { error: Error }): React.ReactElement | null {
189
- if (error === "invalid-file") {
190
- return <ErrorText>Invalid file</ErrorText>;
191
- } else if (error === "file-too-large") {
192
- return <ErrorText>File is too large</ErrorText>;
193
- }
194
- return null;
195
- }
@@ -1,24 +0,0 @@
1
- export type TextData = string;
2
- export type TextInputProps = {
3
- name?: string;
4
- text: TextData;
5
- disabled?: boolean;
6
- onChange: (value: string) => void;
7
- };
8
-
9
- export function TextArea({ name, disabled, text, onChange }: TextInputProps) {
10
- return (
11
- <div
12
- className="w-full h-full py-2 overflow-y-scroll grow-wrap"
13
- data-replicated-value={text} /* see grow-wrap */
14
- >
15
- <textarea
16
- disabled={disabled}
17
- name={name}
18
- className="p-2 border outline-none resize-none bg-fill text-primary border-border focus-visible:border-highlight"
19
- defaultValue={text}
20
- onChange={(e) => onChange(e.target.value)}
21
- />
22
- </div>
23
- );
24
- }
@@ -1,58 +0,0 @@
1
- import * as React from "react";
2
- import * as AccordionPrimitive from "@radix-ui/react-accordion";
3
- import { ChevronDown } from "lucide-react";
4
-
5
- import { cn } from "../../lib/utils";
6
-
7
- const Accordion = AccordionPrimitive.Root;
8
-
9
- const AccordionItem = React.forwardRef<
10
- React.ElementRef<typeof AccordionPrimitive.Item>,
11
- React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
12
- >(({ className, ...props }, ref) => (
13
- <AccordionPrimitive.Item
14
- ref={ref}
15
- className={cn("border-b", className)}
16
- {...props}
17
- />
18
- ));
19
- AccordionItem.displayName = "AccordionItem";
20
-
21
- const AccordionTrigger = React.forwardRef<
22
- React.ElementRef<typeof AccordionPrimitive.Trigger>,
23
- React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
24
- >(({ className, children, ...props }, ref) => (
25
- <AccordionPrimitive.Header className="flex">
26
- <AccordionPrimitive.Trigger
27
- ref={ref}
28
- className={cn(
29
- "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
30
- className
31
- )}
32
- {...props}
33
- >
34
- {children}
35
- <ChevronDown className="w-4 h-4 transition-transform duration-200 shrink-0" />
36
- </AccordionPrimitive.Trigger>
37
- </AccordionPrimitive.Header>
38
- ));
39
- AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
40
-
41
- const AccordionContent = React.forwardRef<
42
- React.ElementRef<typeof AccordionPrimitive.Content>,
43
- React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
44
- >(({ className, children, ...props }, ref) => (
45
- <AccordionPrimitive.Content
46
- ref={ref}
47
- className={cn(
48
- "overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down",
49
- className
50
- )}
51
- {...props}
52
- >
53
- <div className="pt-0 pb-4">{children}</div>
54
- </AccordionPrimitive.Content>
55
- ));
56
- AccordionContent.displayName = AccordionPrimitive.Content.displayName;
57
-
58
- export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };