@valbuild/react 0.12.0 → 0.13.3
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.
- package/dist/ValProvider-52f2fa13.esm.js +731 -0
- package/dist/ValProvider-7364ec46.cjs.dev.js +746 -0
- package/dist/ValProvider-a45a47b9.worker.esm.js +726 -0
- package/dist/ValProvider-c7a8476b.browser.esm.js +731 -0
- package/dist/ValProvider-e1d5ffbe.cjs.js +7 -0
- package/dist/ValProvider-e1d5ffbe.cjs.prod.js +746 -0
- package/dist/ValUI-0fbdafd4.cjs.prod.js +398 -0
- package/dist/ValUI-371e9bf4.cjs.dev.js +398 -0
- package/dist/ValUI-51404232.browser.esm.js +396 -0
- package/dist/ValUI-9a3eb570.esm.js +396 -0
- package/dist/declarations/src/AuthStatus.d.ts +6 -0
- package/dist/declarations/src/ShadowRoot.d.ts +4 -0
- package/dist/declarations/src/ValProvider.d.ts +1 -1
- package/dist/declarations/src/ValProviderWrapper.d.ts +3 -0
- package/dist/declarations/src/ValUI.d.ts +8 -0
- package/dist/declarations/src/index.d.ts +1 -1
- package/dist/{slicedToArray-0ead6329.cjs.dev.js → slicedToArray-0eb0bcdb.cjs.prod.js} +3 -17
- package/dist/{slicedToArray-bf9b195a.worker.esm.js → slicedToArray-1a246338.browser.esm.js} +3 -16
- package/dist/{slicedToArray-236143cd.browser.esm.js → slicedToArray-9e7d1407.worker.esm.js} +3 -16
- package/dist/{slicedToArray-57b117df.cjs.prod.js → slicedToArray-b7cf26e0.cjs.dev.js} +3 -17
- package/dist/{slicedToArray-390fde8c.esm.js → slicedToArray-d846e1d2.esm.js} +3 -16
- package/dist/unsupportedIterableToArray-51bb61c2.esm.js +16 -0
- package/dist/unsupportedIterableToArray-738344ef.worker.esm.js +16 -0
- package/dist/unsupportedIterableToArray-9e97e24a.cjs.dev.js +18 -0
- package/dist/unsupportedIterableToArray-afbea1dd.cjs.prod.js +18 -0
- package/dist/unsupportedIterableToArray-d3087ed5.browser.esm.js +16 -0
- package/dist/valbuild-react.browser.esm.js +9 -1083
- package/dist/valbuild-react.cjs.dev.js +24 -1074
- package/dist/valbuild-react.cjs.prod.js +24 -1074
- package/dist/valbuild-react.esm.js +13 -1079
- package/dist/valbuild-react.worker.esm.js +13 -1079
- package/jest.config.js +5 -0
- package/jsx-dev-runtime/dist/valbuild-react-jsx-dev-runtime.browser.esm.js +2 -1
- package/jsx-dev-runtime/dist/valbuild-react-jsx-dev-runtime.cjs.dev.js +2 -1
- package/jsx-dev-runtime/dist/valbuild-react-jsx-dev-runtime.cjs.prod.js +2 -1
- package/jsx-dev-runtime/dist/valbuild-react-jsx-dev-runtime.esm.js +2 -1
- package/jsx-dev-runtime/dist/valbuild-react-jsx-dev-runtime.worker.esm.js +2 -1
- package/jsx-runtime/dist/valbuild-react-jsx-runtime.browser.esm.js +2 -1
- package/jsx-runtime/dist/valbuild-react-jsx-runtime.cjs.dev.js +2 -1
- package/jsx-runtime/dist/valbuild-react-jsx-runtime.cjs.prod.js +2 -1
- package/jsx-runtime/dist/valbuild-react-jsx-runtime.esm.js +2 -1
- package/jsx-runtime/dist/valbuild-react-jsx-runtime.worker.esm.js +2 -1
- package/package.json +9 -6
- package/src/AuthStatus.tsx +13 -0
- package/src/ShadowRoot.tsx +32 -0
- package/src/ValApi.ts +65 -0
- package/src/ValProvider.tsx +51 -0
- package/src/ValProviderWrapper.tsx +15 -0
- package/src/ValRichText.tsx +141 -0
- package/src/ValStore.ts +62 -0
- package/src/ValUI.tsx +353 -0
- package/src/assets.ts +124 -0
- package/src/hooks/useVal.test.tsx +57 -0
- package/src/hooks/useVal.ts +35 -0
- package/src/index.ts +6 -0
- package/src/jsx-dev-runtime.js +47 -0
- package/src/jsx-namespace.d.ts +46 -0
- package/src/jsx-runtime.d.ts +1 -0
- package/src/jsx-runtime.dev.d.ts +1 -0
- package/src/jsx-runtime.js +52 -0
package/src/ValUI.tsx
ADDED
@@ -0,0 +1,353 @@
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
2
|
+
import { useEffect, useState } from "react";
|
3
|
+
import { ValApi } from "./ValApi";
|
4
|
+
import { ValStore } from "./ValStore";
|
5
|
+
import { Inputs, Style, ValOverlay } from "@valbuild/ui";
|
6
|
+
import {
|
7
|
+
FileSource,
|
8
|
+
FILE_REF_PROP,
|
9
|
+
Internal,
|
10
|
+
RichText,
|
11
|
+
SourcePath,
|
12
|
+
VAL_EXTENSION,
|
13
|
+
} from "@valbuild/core";
|
14
|
+
import { PatchJSON } from "@valbuild/core/patch";
|
15
|
+
import { ImageMetadata } from "@valbuild/core/src/schema/image";
|
16
|
+
import { AuthStatus } from "./AuthStatus";
|
17
|
+
import { ShadowRoot } from "./ShadowRoot";
|
18
|
+
|
19
|
+
export type ValUIProps = {
|
20
|
+
valStore: ValStore;
|
21
|
+
valApi: ValApi;
|
22
|
+
};
|
23
|
+
|
24
|
+
export default function ValUI({ valApi, valStore }: ValUIProps) {
|
25
|
+
const [selectedSources, setSelectedSources] = useState<string[]>([]);
|
26
|
+
const [editMode, setEditMode] = useState(false);
|
27
|
+
const [editFormPosition, setEditFormPosition] = useState<{
|
28
|
+
left: number;
|
29
|
+
top: number;
|
30
|
+
} | null>(null);
|
31
|
+
|
32
|
+
const [authentication, setAuthentication] = useState<AuthStatus>({
|
33
|
+
status: "not-asked",
|
34
|
+
});
|
35
|
+
|
36
|
+
useEffect(() => {
|
37
|
+
if (editMode) {
|
38
|
+
valStore.updateAll();
|
39
|
+
}
|
40
|
+
}, [editMode]);
|
41
|
+
useEffect(() => {
|
42
|
+
let openValFormListener: ((e: MouseEvent) => void) | undefined = undefined;
|
43
|
+
let styleElement: HTMLStyleElement | undefined = undefined;
|
44
|
+
const editButtonClickOptions = {
|
45
|
+
capture: true,
|
46
|
+
passive: true,
|
47
|
+
};
|
48
|
+
if (editMode) {
|
49
|
+
// highlight val element by appending a new style
|
50
|
+
styleElement = document.createElement("style");
|
51
|
+
styleElement.id = "val-edit-highlight";
|
52
|
+
styleElement.innerHTML = `
|
53
|
+
.val-edit-mode >* [data-val-path] {
|
54
|
+
outline: #ffff00 solid 1px;
|
55
|
+
cursor: pointer;
|
56
|
+
}
|
57
|
+
`;
|
58
|
+
document.body.appendChild(styleElement);
|
59
|
+
|
60
|
+
// capture event clicks on data-val-path elements
|
61
|
+
openValFormListener = (e: MouseEvent) => {
|
62
|
+
if (e.target instanceof Element) {
|
63
|
+
let parent = e.target;
|
64
|
+
while (parent && parent !== document.body) {
|
65
|
+
if (parent.getAttribute("data-val-path")) {
|
66
|
+
break;
|
67
|
+
}
|
68
|
+
if (parent.parentElement) {
|
69
|
+
parent = parent.parentElement;
|
70
|
+
} else {
|
71
|
+
break;
|
72
|
+
}
|
73
|
+
}
|
74
|
+
const valSources = parent?.getAttribute("data-val-path");
|
75
|
+
if (valSources) {
|
76
|
+
e.stopPropagation();
|
77
|
+
setSelectedSources(
|
78
|
+
valSources.split(
|
79
|
+
","
|
80
|
+
) /* TODO: just split on commas will not work if path contains , */
|
81
|
+
);
|
82
|
+
setEditFormPosition({
|
83
|
+
left: e.pageX,
|
84
|
+
top: e.pageY,
|
85
|
+
});
|
86
|
+
// } else if (!isValElement(e.target)) {
|
87
|
+
// console.log("click outside", e.target);
|
88
|
+
// setEditFormPosition(null);
|
89
|
+
// setSelectedSources([]);
|
90
|
+
}
|
91
|
+
}
|
92
|
+
};
|
93
|
+
document.addEventListener(
|
94
|
+
"click",
|
95
|
+
openValFormListener,
|
96
|
+
editButtonClickOptions
|
97
|
+
);
|
98
|
+
}
|
99
|
+
return () => {
|
100
|
+
if (openValFormListener) {
|
101
|
+
document.removeEventListener(
|
102
|
+
"click",
|
103
|
+
openValFormListener,
|
104
|
+
editButtonClickOptions
|
105
|
+
);
|
106
|
+
}
|
107
|
+
styleElement?.remove();
|
108
|
+
};
|
109
|
+
}, [editMode]);
|
110
|
+
useEffect(() => {
|
111
|
+
if (editMode) {
|
112
|
+
document.body.classList.add("val-edit-mode");
|
113
|
+
} else {
|
114
|
+
document.body.classList.remove("val-edit-mode");
|
115
|
+
}
|
116
|
+
|
117
|
+
if (editMode) {
|
118
|
+
if (authentication.status !== "authenticated") {
|
119
|
+
valApi
|
120
|
+
.getSession()
|
121
|
+
.then(async (res) => {
|
122
|
+
if (res.status === 401) {
|
123
|
+
setAuthentication({
|
124
|
+
status: "unauthenticated",
|
125
|
+
});
|
126
|
+
} else if (res.ok) {
|
127
|
+
const data = await res.json();
|
128
|
+
if (data.mode === "local") {
|
129
|
+
setAuthentication({ status: "local" });
|
130
|
+
} else if (data.mode === "proxy") {
|
131
|
+
setAuthentication({
|
132
|
+
status: "authenticated",
|
133
|
+
});
|
134
|
+
} else {
|
135
|
+
setAuthentication({
|
136
|
+
status: "error",
|
137
|
+
message: "Unknown authentication mode",
|
138
|
+
});
|
139
|
+
}
|
140
|
+
} else {
|
141
|
+
let message = "Unknown error";
|
142
|
+
try {
|
143
|
+
message = await res.text();
|
144
|
+
} catch {
|
145
|
+
// ignore
|
146
|
+
}
|
147
|
+
setAuthentication({
|
148
|
+
status: "error",
|
149
|
+
message,
|
150
|
+
});
|
151
|
+
}
|
152
|
+
})
|
153
|
+
.catch((err) => {
|
154
|
+
console.error("Failed to fetch session", err);
|
155
|
+
setAuthentication({
|
156
|
+
status: "error",
|
157
|
+
message: "Unknown authentication mode",
|
158
|
+
});
|
159
|
+
});
|
160
|
+
}
|
161
|
+
} else {
|
162
|
+
if (authentication.status === "error") {
|
163
|
+
setAuthentication({
|
164
|
+
status: "not-asked",
|
165
|
+
});
|
166
|
+
}
|
167
|
+
}
|
168
|
+
}, [editMode, authentication.status]);
|
169
|
+
|
170
|
+
const [showEditButton, setShowEditButton] = useState(false);
|
171
|
+
useEffect(() => {
|
172
|
+
setShowEditButton(true);
|
173
|
+
}, []);
|
174
|
+
|
175
|
+
const [inputs, setInputs] = useState<Inputs>({});
|
176
|
+
|
177
|
+
useEffect(() => {
|
178
|
+
setInputs({});
|
179
|
+
for (const path of selectedSources) {
|
180
|
+
valApi.getModule(path).then((serializedModule) => {
|
181
|
+
let input: Inputs[string] | undefined;
|
182
|
+
if (
|
183
|
+
serializedModule.schema.type === "string" &&
|
184
|
+
typeof serializedModule.source === "string"
|
185
|
+
) {
|
186
|
+
input = {
|
187
|
+
status: "completed",
|
188
|
+
type: "text",
|
189
|
+
data: serializedModule.source,
|
190
|
+
};
|
191
|
+
} else if (
|
192
|
+
serializedModule.schema.type === "richtext" &&
|
193
|
+
typeof serializedModule.source === "object"
|
194
|
+
) {
|
195
|
+
input = {
|
196
|
+
status: "completed",
|
197
|
+
type: "richtext",
|
198
|
+
data: serializedModule.source as RichText, // TODO: validate
|
199
|
+
};
|
200
|
+
} else if (
|
201
|
+
serializedModule.schema.type === "image" &&
|
202
|
+
serializedModule.source &&
|
203
|
+
typeof serializedModule.source === "object" &&
|
204
|
+
FILE_REF_PROP in serializedModule.source &&
|
205
|
+
typeof serializedModule.source[FILE_REF_PROP] === "string" &&
|
206
|
+
VAL_EXTENSION in serializedModule.source &&
|
207
|
+
typeof serializedModule.source[VAL_EXTENSION] === "string"
|
208
|
+
) {
|
209
|
+
input = {
|
210
|
+
status: "completed",
|
211
|
+
type: "image",
|
212
|
+
data: Internal.convertImageSource(
|
213
|
+
serializedModule.source as FileSource<ImageMetadata>
|
214
|
+
),
|
215
|
+
};
|
216
|
+
}
|
217
|
+
console.log("input path", path);
|
218
|
+
console.log("serialized path", serializedModule.path);
|
219
|
+
if (!input) {
|
220
|
+
throw new Error(
|
221
|
+
`Unsupported module type: ${serializedModule.schema.type}`
|
222
|
+
);
|
223
|
+
}
|
224
|
+
setInputs((inputs) => {
|
225
|
+
return {
|
226
|
+
...inputs,
|
227
|
+
[serializedModule.path]: input,
|
228
|
+
} as Inputs;
|
229
|
+
});
|
230
|
+
});
|
231
|
+
}
|
232
|
+
}, [selectedSources.join(",")]);
|
233
|
+
if (!showEditButton) {
|
234
|
+
return null;
|
235
|
+
}
|
236
|
+
return (
|
237
|
+
<ShadowRoot>
|
238
|
+
{/* TODO: */}
|
239
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
240
|
+
<link
|
241
|
+
rel="preconnect"
|
242
|
+
href="https://fonts.gstatic.com"
|
243
|
+
crossOrigin="anonymous"
|
244
|
+
/>
|
245
|
+
<link
|
246
|
+
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;1,400&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,400;1,700&display=swap"
|
247
|
+
rel="stylesheet"
|
248
|
+
/>
|
249
|
+
<Style />
|
250
|
+
<div data-mode="dark">
|
251
|
+
<ValOverlay
|
252
|
+
editMode={editMode}
|
253
|
+
setEditMode={setEditMode}
|
254
|
+
closeValWindow={() => {
|
255
|
+
setEditFormPosition(null);
|
256
|
+
setSelectedSources([]);
|
257
|
+
setInputs({});
|
258
|
+
}}
|
259
|
+
valWindow={
|
260
|
+
(editFormPosition && {
|
261
|
+
position: editFormPosition,
|
262
|
+
inputs,
|
263
|
+
onSubmit: (inputs) => {
|
264
|
+
Promise.all(
|
265
|
+
Object.entries(inputs).map(([path, input]) => {
|
266
|
+
if (input.status === "completed") {
|
267
|
+
const [moduleId, modulePath] =
|
268
|
+
Internal.splitModuleIdAndModulePath(path as SourcePath);
|
269
|
+
if (input.type === "text") {
|
270
|
+
const patch: PatchJSON = [
|
271
|
+
{
|
272
|
+
value: input.data,
|
273
|
+
op: "replace",
|
274
|
+
path: `/${modulePath
|
275
|
+
.split(".")
|
276
|
+
.map((p) => JSON.parse(p))
|
277
|
+
.join("/")}`,
|
278
|
+
},
|
279
|
+
];
|
280
|
+
return valApi.patchModuleContent(moduleId, patch);
|
281
|
+
} else if (input.type === "image") {
|
282
|
+
const pathParts = modulePath
|
283
|
+
.split(".")
|
284
|
+
.map((p) => JSON.parse(p));
|
285
|
+
|
286
|
+
if (!input?.data || !("src" in input.data)) {
|
287
|
+
// TODO: We probably need to have an Output type that is different from the Input: we have a union of both cases in Input right now, and we believe we do not want that
|
288
|
+
console.warn(
|
289
|
+
"No .src on input provided - this might mean no changes was made"
|
290
|
+
);
|
291
|
+
return;
|
292
|
+
}
|
293
|
+
const patch: PatchJSON = [
|
294
|
+
{
|
295
|
+
value: input.data.src,
|
296
|
+
op: "replace",
|
297
|
+
path: `/${pathParts.slice(0, -1).join("/")}/$${
|
298
|
+
pathParts[pathParts.length - 1]
|
299
|
+
}`,
|
300
|
+
},
|
301
|
+
];
|
302
|
+
if (input.data.metadata) {
|
303
|
+
if (input.data.addMetadata) {
|
304
|
+
patch.push({
|
305
|
+
value: input.data.metadata,
|
306
|
+
op: "add",
|
307
|
+
path: `/${pathParts.join("/")}/metadata`,
|
308
|
+
});
|
309
|
+
} else {
|
310
|
+
patch.push({
|
311
|
+
value: input.data.metadata,
|
312
|
+
op: "replace",
|
313
|
+
path: `/${pathParts.join("/")}/metadata`,
|
314
|
+
});
|
315
|
+
}
|
316
|
+
}
|
317
|
+
console.log("patch", patch);
|
318
|
+
return valApi.patchModuleContent(moduleId, patch);
|
319
|
+
} else if (input.type === "richtext") {
|
320
|
+
const patch: PatchJSON = [
|
321
|
+
{
|
322
|
+
value: input.data,
|
323
|
+
op: "replace",
|
324
|
+
path: `/${modulePath
|
325
|
+
.split(".")
|
326
|
+
.map((p) => JSON.parse(p))
|
327
|
+
.join("/")}`,
|
328
|
+
},
|
329
|
+
];
|
330
|
+
return valApi.patchModuleContent(moduleId, patch);
|
331
|
+
}
|
332
|
+
throw new Error(
|
333
|
+
`Unsupported input type: ${(input as any).type}`
|
334
|
+
);
|
335
|
+
} else {
|
336
|
+
console.error("Submitted incomplete input, ignoring...");
|
337
|
+
return Promise.resolve();
|
338
|
+
}
|
339
|
+
})
|
340
|
+
).then(() => {
|
341
|
+
setEditFormPosition(null);
|
342
|
+
setSelectedSources([]);
|
343
|
+
setInputs({});
|
344
|
+
});
|
345
|
+
},
|
346
|
+
}) ??
|
347
|
+
undefined
|
348
|
+
}
|
349
|
+
/>
|
350
|
+
</div>
|
351
|
+
</ShadowRoot>
|
352
|
+
);
|
353
|
+
}
|
package/src/assets.ts
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
import * as base64 from "base64-arraybuffer";
|
2
|
+
|
3
|
+
function dataUrl(mimeType: string, data: string): string {
|
4
|
+
return `data:${mimeType};base64,${base64.encode(
|
5
|
+
new TextEncoder().encode(data)
|
6
|
+
)}`;
|
7
|
+
}
|
8
|
+
|
9
|
+
// TODO: stroke should be currentColor
|
10
|
+
export const editIcon = (size: number, stroke: string) =>
|
11
|
+
dataUrl(
|
12
|
+
"image/svg+xml",
|
13
|
+
`
|
14
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${stroke}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>
|
15
|
+
`
|
16
|
+
);
|
17
|
+
|
18
|
+
export const logo = dataUrl(
|
19
|
+
"image/svg+xml",
|
20
|
+
`<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
21
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
22
|
+
|
23
|
+
<svg
|
24
|
+
width="101.83195mm"
|
25
|
+
height="103.55328mm"
|
26
|
+
viewBox="0 0 101.83195 103.55328"
|
27
|
+
version="1.1"
|
28
|
+
id="svg974"
|
29
|
+
inkscape:export-filename="logo.svg"
|
30
|
+
inkscape:export-xdpi="96"
|
31
|
+
inkscape:export-ydpi="96"
|
32
|
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
33
|
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
34
|
+
xmlns="http://www.w3.org/2000/svg"
|
35
|
+
xmlns:svg="http://www.w3.org/2000/svg">
|
36
|
+
<sodipodi:namedview
|
37
|
+
id="namedview976"
|
38
|
+
pagecolor="#ffffff"
|
39
|
+
bordercolor="#000000"
|
40
|
+
borderopacity="0.25"
|
41
|
+
inkscape:showpageshadow="2"
|
42
|
+
inkscape:pageopacity="0.0"
|
43
|
+
inkscape:pagecheckerboard="0"
|
44
|
+
inkscape:deskcolor="#d1d1d1"
|
45
|
+
inkscape:document-units="mm"
|
46
|
+
showgrid="false"
|
47
|
+
inkscape:zoom="1.4040232"
|
48
|
+
inkscape:cx="39.173141"
|
49
|
+
inkscape:cy="503.90904"
|
50
|
+
inkscape:window-width="3832"
|
51
|
+
inkscape:window-height="2087"
|
52
|
+
inkscape:window-x="0"
|
53
|
+
inkscape:window-y="69"
|
54
|
+
inkscape:window-maximized="1"
|
55
|
+
inkscape:current-layer="layer1" />
|
56
|
+
<defs
|
57
|
+
id="defs971" />
|
58
|
+
<g
|
59
|
+
inkscape:label="Layer 1"
|
60
|
+
inkscape:groupmode="layer"
|
61
|
+
id="layer1"
|
62
|
+
transform="translate(-46.162121,-16.863144)">
|
63
|
+
<ellipse
|
64
|
+
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:4.41854;stroke-dasharray:none;stroke-opacity:1"
|
65
|
+
id="path2242"
|
66
|
+
ry="49.567371"
|
67
|
+
rx="48.706703"
|
68
|
+
cy="68.639786"
|
69
|
+
cx="97.078094" />
|
70
|
+
<path
|
71
|
+
style="fill:#000000;fill-opacity:1;stroke:#f2f2f2;stroke-width:9.9912;stroke-dasharray:none;stroke-opacity:1"
|
72
|
+
d="m 65.105895,44.462411 18.85692,45.934668 13.064363,-39.90188 15.829132,39.947835 23.07956,-0.1822"
|
73
|
+
id="path4245"
|
74
|
+
sodipodi:nodetypes="ccccc" />
|
75
|
+
<path
|
76
|
+
style="fill:#000000;fill-opacity:1;stroke:#f2f2f2;stroke-width:4.85097;stroke-dasharray:none;stroke-opacity:1"
|
77
|
+
d="M 108.18755,79.963752 C 101.58768,84.940963 94.021144,82.50121 86.406627,79.693345"
|
78
|
+
id="path4249"
|
79
|
+
sodipodi:nodetypes="cc" />
|
80
|
+
</g>
|
81
|
+
</svg>`
|
82
|
+
);
|
83
|
+
|
84
|
+
export const valcmsLogo = dataUrl(
|
85
|
+
"image/svg+xml",
|
86
|
+
`<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
87
|
+
<svg
|
88
|
+
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
89
|
+
xmlns:cc="http://creativecommons.org/ns#"
|
90
|
+
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
91
|
+
xmlns:svg="http://www.w3.org/2000/svg"
|
92
|
+
xmlns="http://www.w3.org/2000/svg"
|
93
|
+
width="20"
|
94
|
+
height="20"
|
95
|
+
viewBox="0 0 52.916665 52.916668"
|
96
|
+
version="1.1"
|
97
|
+
id="svg8">
|
98
|
+
<defs
|
99
|
+
id="defs2" />
|
100
|
+
<metadata
|
101
|
+
id="metadata5">
|
102
|
+
<rdf:RDF>
|
103
|
+
<cc:Work
|
104
|
+
rdf:about="">
|
105
|
+
<dc:format>image/svg+xml</dc:format>
|
106
|
+
<dc:type
|
107
|
+
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
108
|
+
<dc:title></dc:title>
|
109
|
+
</cc:Work>
|
110
|
+
</rdf:RDF>
|
111
|
+
</metadata>
|
112
|
+
<g
|
113
|
+
id="layer1">
|
114
|
+
<path
|
115
|
+
style="fill:none;stroke:white;stroke-width:5.00377;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
116
|
+
d="M 3.9337727,9.883415 14.718441,43.556958 25.597715,9.9678121 35.530965,43.388176 h 15.798599 v 0 h 0.09461"
|
117
|
+
id="path10" />
|
118
|
+
<path
|
119
|
+
style="fill:none;stroke:white;stroke-width:5.00377;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
120
|
+
d="m 19.826972,27.859518 11.257682,0.0844 v 0 0"
|
121
|
+
id="path837" />
|
122
|
+
</g>
|
123
|
+
</svg>`
|
124
|
+
);
|
@@ -0,0 +1,57 @@
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
2
|
+
import { useVal } from "./useVal";
|
3
|
+
import { initVal, Val } from "@valbuild/core";
|
4
|
+
import { renderHook } from "@testing-library/react";
|
5
|
+
import { ValContext } from "../ValProvider";
|
6
|
+
import { ReactElement } from "react";
|
7
|
+
import { ValStore } from "../ValStore";
|
8
|
+
import { ValApi } from "../ValApi";
|
9
|
+
|
10
|
+
const valApi = new ValApi("mock");
|
11
|
+
const valStore = new ValStore(valApi);
|
12
|
+
|
13
|
+
const Providers = ({ children }: { children: ReactElement }) => (
|
14
|
+
<ValContext.Provider
|
15
|
+
value={{
|
16
|
+
valStore,
|
17
|
+
valApi,
|
18
|
+
}}
|
19
|
+
>
|
20
|
+
{children}
|
21
|
+
</ValContext.Provider>
|
22
|
+
);
|
23
|
+
|
24
|
+
// const { s, val } = initVal();
|
25
|
+
|
26
|
+
describe("useVal", () => {
|
27
|
+
test.skip("extracts ValString from string", () => {
|
28
|
+
// const mod = val.content("foo", s.string(), "bar");
|
29
|
+
// const { result } = renderHook(() => useVal(mod, "en_US"), {
|
30
|
+
// wrapper: Providers,
|
31
|
+
// });
|
32
|
+
// expect(result.current).toStrictEqual<Val<string>>({
|
33
|
+
// val: "bar",
|
34
|
+
// valSrc: "foo?en_US?",
|
35
|
+
// });
|
36
|
+
});
|
37
|
+
|
38
|
+
test.skip("extracts ValString from ValObject", () => {
|
39
|
+
// const mod = val.content("baz", s.object({ foo: s.string() }), {
|
40
|
+
// foo: "bar",
|
41
|
+
// });
|
42
|
+
// const { result } = renderHook(() => useVal(mod, "en_US"), {
|
43
|
+
// wrapper: Providers,
|
44
|
+
// });
|
45
|
+
// const vo: Val<{ foo: string }> = result.current;
|
46
|
+
// expect(vo.foo).toStrictEqual<Val<string>>({
|
47
|
+
// valSrc: `baz?en_US?."foo"`,
|
48
|
+
// val: "bar",
|
49
|
+
// });
|
50
|
+
// expect(val).toStrictEqual<ValObject<{ foo: string }>>({
|
51
|
+
// foo: {
|
52
|
+
// id: "baz.foo",
|
53
|
+
// val: "bar",
|
54
|
+
// },
|
55
|
+
// });
|
56
|
+
});
|
57
|
+
});
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import {
|
2
|
+
SelectorSource,
|
3
|
+
SelectorOf,
|
4
|
+
GenericSelector,
|
5
|
+
Val,
|
6
|
+
Internal,
|
7
|
+
} from "@valbuild/core";
|
8
|
+
import { JsonOfSource } from "@valbuild/core/src/val";
|
9
|
+
|
10
|
+
export function useVal<T extends SelectorSource>(
|
11
|
+
selector: T,
|
12
|
+
locale?: string
|
13
|
+
): SelectorOf<T> extends GenericSelector<infer S>
|
14
|
+
? Val<JsonOfSource<S>>
|
15
|
+
: never {
|
16
|
+
// const mod = selectable.getModule();
|
17
|
+
// const valStore = useValStore();
|
18
|
+
// const remoteContent = useSyncExternalStore(
|
19
|
+
// valStore.subscribe(mod.id),
|
20
|
+
// valStore.getSnapshot(mod.id),
|
21
|
+
// valStore.getServerSnapshot(mod.id)
|
22
|
+
// );
|
23
|
+
// if (remoteContent) {
|
24
|
+
// return selectable.getVal(remoteContent.source as S, locale);
|
25
|
+
// }
|
26
|
+
// const content = mod.content;
|
27
|
+
// const validationError = content.validate();
|
28
|
+
// if (validationError) {
|
29
|
+
// throw new Error(
|
30
|
+
// `Invalid source value. Errors:\n${validationError.join("\n")}`
|
31
|
+
// );
|
32
|
+
// }
|
33
|
+
|
34
|
+
return Internal.getVal(selector, locale);
|
35
|
+
}
|
package/src/index.ts
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
/* export { useContent } from "./useContent";
|
2
|
+
export { useText } from "./useText";
|
3
|
+
export { WithVal } from "./WithVal"; */
|
4
|
+
export { ValProviderWrapper as ValProvider } from "./ValProviderWrapper";
|
5
|
+
export { useVal } from "./hooks/useVal";
|
6
|
+
export { ValRichText } from "./ValRichText";
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import { Internal } from "@valbuild/core";
|
2
|
+
import * as ReactJSXRuntimeDev from "react/jsx-dev-runtime";
|
3
|
+
export * from "react/jsx-dev-runtime";
|
4
|
+
|
5
|
+
const isIntrinsicElement = (type) => {
|
6
|
+
// TODO: think this is not correct, but good enough for now?
|
7
|
+
return typeof type === "string";
|
8
|
+
};
|
9
|
+
|
10
|
+
const devalProps = (type, props) => {
|
11
|
+
const valSources = [];
|
12
|
+
|
13
|
+
if (isIntrinsicElement(type)) {
|
14
|
+
for (const [key, value] of Object.entries(props)) {
|
15
|
+
if (typeof value === "object" && value !== null && "val" in value) {
|
16
|
+
const valPath = Internal.getValPath(value);
|
17
|
+
if (valPath) {
|
18
|
+
valSources.push(valPath);
|
19
|
+
if (typeof value.val === "string" || value.val === null) {
|
20
|
+
props[key] = value.val;
|
21
|
+
} else {
|
22
|
+
throw Error("TODO: unhandled value type");
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
if (valSources.length > 0) {
|
30
|
+
props["data-val-path"] = valSources.join(",");
|
31
|
+
}
|
32
|
+
};
|
33
|
+
|
34
|
+
export function jsxDEV(type, props, key, isStaticChildren, source, self) {
|
35
|
+
// console.log("jsxDEV", type, props, key, isStaticChildren, source, self);
|
36
|
+
|
37
|
+
devalProps(type, props);
|
38
|
+
|
39
|
+
return ReactJSXRuntimeDev.jsxDEV(
|
40
|
+
type,
|
41
|
+
props,
|
42
|
+
key,
|
43
|
+
isStaticChildren,
|
44
|
+
source,
|
45
|
+
self
|
46
|
+
);
|
47
|
+
}
|
@@ -0,0 +1,46 @@
|
|
1
|
+
import { Source, Val } from "@valbuild/core";
|
2
|
+
|
3
|
+
// unpack all here to avoid infinite self-referencing when defining our own JSX namespace
|
4
|
+
type ReactJSXElement = JSX.Element;
|
5
|
+
type ReactJSXElementClass = JSX.ElementClass;
|
6
|
+
type ReactJSXElementAttributesProperty = JSX.ElementAttributesProperty;
|
7
|
+
type ReactJSXElementChildrenAttribute = JSX.ElementChildrenAttribute;
|
8
|
+
type ReactJSXLibraryManagedAttributes<C, P> = JSX.LibraryManagedAttributes<
|
9
|
+
C,
|
10
|
+
P
|
11
|
+
>;
|
12
|
+
type ReactJSXIntrinsicAttributes = JSX.IntrinsicAttributes;
|
13
|
+
type ReactJSXIntrinsicClassAttributes<T> = JSX.IntrinsicClassAttributes<T>;
|
14
|
+
type ReactJSXIntrinsicElements = JSX.IntrinsicElements;
|
15
|
+
|
16
|
+
type MaybeVal<T> = T extends Source ? Val<T> | T : T;
|
17
|
+
type WithVal<T extends object> = {
|
18
|
+
[K in keyof T]: K extends "key" | "ref" | "className"
|
19
|
+
? T[K]
|
20
|
+
: K extends "style"
|
21
|
+
? WithVal<React.CSSProperties>
|
22
|
+
: T[K] extends object
|
23
|
+
? T[K]
|
24
|
+
: MaybeVal<T[K]>;
|
25
|
+
};
|
26
|
+
|
27
|
+
export namespace ValJSX {
|
28
|
+
export type Element = ReactJSXElement;
|
29
|
+
export type ElementClass = ReactJSXElementClass;
|
30
|
+
export type ElementAttributesProperty = ReactJSXElementAttributesProperty;
|
31
|
+
export type ElementChildrenAttribute = ReactJSXElementChildrenAttribute;
|
32
|
+
|
33
|
+
export type LibraryManagedAttributes<C, P> = ReactJSXLibraryManagedAttributes<
|
34
|
+
C,
|
35
|
+
P
|
36
|
+
>;
|
37
|
+
|
38
|
+
export type IntrinsicAttributes = ReactJSXIntrinsicAttributes;
|
39
|
+
export type IntrinsicClassAttributes<T> = ReactJSXIntrinsicClassAttributes<T>;
|
40
|
+
|
41
|
+
export type IntrinsicElements = {
|
42
|
+
[K in keyof ReactJSXIntrinsicElements]: WithVal<
|
43
|
+
ReactJSXIntrinsicElements[K]
|
44
|
+
>;
|
45
|
+
};
|
46
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export { ValJSX as JSX } from "./jsx-namespace";
|
@@ -0,0 +1 @@
|
|
1
|
+
export { ValJSX as JSX } from "./jsx-namespace";
|