@valbuild/ui 0.26.0 → 0.28.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.
- package/dist/valbuild-ui.cjs.js +41 -15
- package/dist/valbuild-ui.esm.js +41 -15
- package/package.json +7 -3
- package/server/.tmp/assets/index-18cfa26c.css +1 -0
- package/server/.tmp/assets/index-513f7a9c.js +197 -0
- package/{index.html → server/.tmp/index.html} +3 -1
- package/server/dist/style.css +0 -3
- package/server/dist/valbuild-ui-main.cjs.js +60 -34
- package/server/dist/valbuild-ui-main.esm.js +60 -34
- package/server/dist/valbuild-ui-server.cjs.js +1 -1
- package/server/dist/valbuild-ui-server.esm.js +1 -1
- package/.babelrc.json +0 -10
- package/.storybook/main.js +0 -25
- package/.storybook/preview-head.html +0 -6
- package/.storybook/preview.js +0 -33
- package/.storybook/theme.css +0 -34
- package/CHANGELOG.md +0 -0
- package/components.json +0 -16
- package/fix-server-hack.js +0 -54
- package/fullscreen.vite.config.ts +0 -9
- package/jest.config.js +0 -4
- package/postcss.config.js +0 -6
- package/rollup.config.js +0 -23
- package/server.vite.config.ts +0 -31
- package/src/App.tsx +0 -73
- package/src/assets/icons/Bold.tsx +0 -23
- package/src/assets/icons/Chevron.tsx +0 -28
- package/src/assets/icons/FontColor.tsx +0 -30
- package/src/assets/icons/ImageIcon.tsx +0 -29
- package/src/assets/icons/Italic.tsx +0 -24
- package/src/assets/icons/Logo.tsx +0 -103
- package/src/assets/icons/Section.tsx +0 -41
- package/src/assets/icons/Strikethrough.tsx +0 -22
- package/src/assets/icons/TextIcon.tsx +0 -20
- package/src/assets/icons/Underline.tsx +0 -22
- package/src/assets/icons/Undo.tsx +0 -20
- package/src/components/Button.tsx +0 -68
- package/src/components/Checkbox.tsx +0 -51
- package/src/components/DraggableList.stories.tsx +0 -20
- package/src/components/DraggableList.tsx +0 -95
- package/src/components/Dropdown.tsx +0 -101
- package/src/components/EditButton.tsx +0 -10
- package/src/components/ErrorText.tsx +0 -3
- package/src/components/ExpandLogo.tsx +0 -72
- package/src/components/Grid.stories.tsx +0 -43
- package/src/components/Grid.tsx +0 -139
- package/src/components/RichTextEditor/ContentEditable.tsx +0 -117
- package/src/components/RichTextEditor/Nodes/ImageNode.tsx +0 -100
- package/src/components/RichTextEditor/Plugins/AutoFocus.tsx +0 -12
- package/src/components/RichTextEditor/Plugins/ImagePlugin.tsx +0 -45
- package/src/components/RichTextEditor/Plugins/LinkEditorPlugin.tsx +0 -58
- package/src/components/RichTextEditor/Plugins/Toolbar.tsx +0 -412
- package/src/components/RichTextEditor/RichTextEditor.tsx +0 -105
- package/src/components/UploadModal.tsx +0 -109
- package/src/components/User.tsx +0 -17
- package/src/components/ValFormField.tsx +0 -574
- package/src/components/ValFullscreen.tsx +0 -1278
- package/src/components/ValMenu.tsx +0 -92
- package/src/components/ValOverlay.tsx +0 -488
- package/src/components/ValOverlayContext.tsx +0 -80
- package/src/components/ValWindow.stories.tsx +0 -146
- package/src/components/ValWindow.tsx +0 -220
- package/src/components/dashboard/DashboardButton.tsx +0 -25
- package/src/components/dashboard/DashboardDropdown.tsx +0 -59
- package/src/components/dashboard/Dropdown.stories.tsx +0 -11
- package/src/components/dashboard/Dropdown.tsx +0 -70
- package/src/components/dashboard/FormGroup.stories.tsx +0 -37
- package/src/components/dashboard/FormGroup.tsx +0 -42
- package/src/components/dashboard/Grid2.stories.tsx +0 -56
- package/src/components/dashboard/Grid2.tsx +0 -72
- package/src/components/dashboard/Tree.stories.tsx +0 -91
- package/src/components/dashboard/Tree.tsx +0 -72
- package/src/components/dashboard/ValDashboardEditor.tsx +0 -269
- package/src/components/dashboard/ValDashboardGrid.tsx +0 -142
- package/src/components/dashboard/ValTreeNavigator.tsx +0 -253
- package/src/components/forms/Form.tsx +0 -126
- package/src/components/forms/FormContainer.tsx +0 -24
- package/src/components/forms/ImageForm.tsx +0 -195
- package/src/components/forms/TextArea.tsx +0 -24
- package/src/components/ui/accordion.tsx +0 -58
- package/src/components/ui/alert-dialog.tsx +0 -139
- package/src/components/ui/avatar.tsx +0 -48
- package/src/components/ui/button.tsx +0 -56
- package/src/components/ui/calendar.tsx +0 -62
- package/src/components/ui/card.tsx +0 -86
- package/src/components/ui/checkbox.tsx +0 -28
- package/src/components/ui/command.tsx +0 -153
- package/src/components/ui/dialog.tsx +0 -120
- package/src/components/ui/dropdown-menu.tsx +0 -198
- package/src/components/ui/form.tsx +0 -177
- package/src/components/ui/input.tsx +0 -24
- package/src/components/ui/label.tsx +0 -24
- package/src/components/ui/popover.tsx +0 -29
- package/src/components/ui/progress.tsx +0 -26
- package/src/components/ui/radio-group.tsx +0 -42
- package/src/components/ui/scroll-area.tsx +0 -51
- package/src/components/ui/select.tsx +0 -119
- package/src/components/ui/switch.tsx +0 -27
- package/src/components/ui/tabs.tsx +0 -53
- package/src/components/ui/toggle.tsx +0 -43
- package/src/components/ui/tooltip.tsx +0 -28
- package/src/components/usePatch.ts +0 -86
- package/src/components/useTheme.ts +0 -45
- package/src/dto/SerializedSchema.ts +0 -69
- package/src/dto/Session.ts +0 -12
- package/src/dto/SessionMode.ts +0 -5
- package/src/dto/Tree.ts +0 -18
- package/src/exports.ts +0 -6
- package/src/index.css +0 -115
- package/src/index.tsx +0 -14
- package/src/lib/IValStore.ts +0 -6
- package/src/lib/utils.ts +0 -6
- package/src/main.jsx +0 -10
- package/src/richtext/conversion/conversion.test.ts +0 -146
- package/src/richtext/conversion/lexicalToRichTextSource.test.ts +0 -89
- package/src/richtext/conversion/lexicalToRichTextSource.ts +0 -285
- package/src/richtext/conversion/parseRichTextSource.test.ts +0 -469
- package/src/richtext/conversion/parseRichTextSource.ts +0 -233
- package/src/richtext/conversion/richTextSourceToLexical.test.ts +0 -381
- package/src/richtext/conversion/richTextSourceToLexical.ts +0 -293
- package/src/richtext/shadowRootPolyFill.js +0 -115
- package/src/server.ts +0 -70
- package/src/stories/Button.stories.tsx +0 -20
- package/src/stories/Checkbox.stories.tsx +0 -14
- package/src/stories/Dropdown.stories.tsx +0 -23
- package/src/stories/Introduction.mdx +0 -221
- package/src/stories/RichTextEditor.stories.tsx +0 -24
- package/src/stories/assets/code-brackets.svg +0 -1
- package/src/stories/assets/colors.svg +0 -1
- package/src/stories/assets/comments.svg +0 -1
- package/src/stories/assets/direction.svg +0 -1
- package/src/stories/assets/flow.svg +0 -1
- package/src/stories/assets/plugin.svg +0 -1
- package/src/stories/assets/repo.svg +0 -1
- package/src/stories/assets/stackalt.svg +0 -1
- package/src/utils/Remote.ts +0 -15
- package/src/utils/imageMimeType.ts +0 -23
- package/src/utils/readImage.ts +0 -54
- package/src/utils/resolvePath.ts +0 -32
- package/src/vite-env.d.ts +0 -1
- package/src/vite-index.tsx +0 -7
- package/src/vite-server.ts +0 -42
- package/tailwind.config.js +0 -83
- package/tsconfig.json +0 -19
- package/vite.config.ts +0 -43
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { useValOverlayContext } from "./ValOverlayContext";
|
|
2
|
-
import { ValApi } from "@valbuild/core";
|
|
3
|
-
import classNames from "classnames";
|
|
4
|
-
import {
|
|
5
|
-
Maximize,
|
|
6
|
-
Minimize,
|
|
7
|
-
Moon,
|
|
8
|
-
Pause,
|
|
9
|
-
Play,
|
|
10
|
-
Power,
|
|
11
|
-
Sun,
|
|
12
|
-
} from "lucide-react";
|
|
13
|
-
import React from "react";
|
|
14
|
-
|
|
15
|
-
const className = "p-1 border rounded-full shadow border-accent";
|
|
16
|
-
const PREV_URL_KEY = "valbuild:urlBeforeNavigation";
|
|
17
|
-
|
|
18
|
-
export function ValMenu({ api }: { api: ValApi }) {
|
|
19
|
-
const { theme, setTheme, editMode, setEditMode } = useValOverlayContext();
|
|
20
|
-
return (
|
|
21
|
-
<div className="flex flex-row items-center justify-center w-full h-full px-1 py-2 border-2 rounded-full gap-x-3 text-primary bg-background border-fill">
|
|
22
|
-
<MenuButton
|
|
23
|
-
active={editMode === "hover"}
|
|
24
|
-
onClick={() => {
|
|
25
|
-
setEditMode((prev) => (prev === "hover" ? "off" : "hover"));
|
|
26
|
-
}}
|
|
27
|
-
>
|
|
28
|
-
<div className="h-[24px] w-[24px] flex justify-center items-center">
|
|
29
|
-
{editMode === "hover" ? <Pause size={18} /> : <Play size={18} />}
|
|
30
|
-
</div>
|
|
31
|
-
</MenuButton>
|
|
32
|
-
<MenuButton
|
|
33
|
-
onClick={() => {
|
|
34
|
-
setTheme(theme === "dark" ? "light" : "dark");
|
|
35
|
-
}}
|
|
36
|
-
>
|
|
37
|
-
<div className="h-[24px] w-[24px] flex justify-center items-center">
|
|
38
|
-
{theme === "dark" && <Sun size={15} />}
|
|
39
|
-
{theme === "light" && <Moon size={15} />}
|
|
40
|
-
</div>
|
|
41
|
-
</MenuButton>
|
|
42
|
-
<MenuButton
|
|
43
|
-
active={editMode === "full"}
|
|
44
|
-
onClick={() => {
|
|
45
|
-
// Save the current url so we can go back to it when returning from fullscreen mode
|
|
46
|
-
if (editMode !== "full") {
|
|
47
|
-
localStorage.setItem(PREV_URL_KEY, window.location.href);
|
|
48
|
-
window.location.href = api.getEditUrl();
|
|
49
|
-
} else if (editMode === "full") {
|
|
50
|
-
const prevUrl = localStorage.getItem(PREV_URL_KEY);
|
|
51
|
-
window.location.href = prevUrl || "/";
|
|
52
|
-
}
|
|
53
|
-
}}
|
|
54
|
-
>
|
|
55
|
-
<div className="h-[24px] w-[24px] flex justify-center items-center">
|
|
56
|
-
{editMode === "full" ? (
|
|
57
|
-
<Minimize size={15} />
|
|
58
|
-
) : (
|
|
59
|
-
<Maximize size={15} />
|
|
60
|
-
)}
|
|
61
|
-
</div>
|
|
62
|
-
</MenuButton>
|
|
63
|
-
|
|
64
|
-
<a className={className} href={api.getDisableUrl()}>
|
|
65
|
-
<div className="h-[24px] w-[24px] flex justify-center items-center">
|
|
66
|
-
<Power size={18} />
|
|
67
|
-
</div>
|
|
68
|
-
</a>
|
|
69
|
-
</div>
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function MenuButton({
|
|
74
|
-
active,
|
|
75
|
-
onClick,
|
|
76
|
-
children,
|
|
77
|
-
}: {
|
|
78
|
-
active?: boolean;
|
|
79
|
-
children: React.ReactNode;
|
|
80
|
-
onClick: () => void;
|
|
81
|
-
}) {
|
|
82
|
-
return (
|
|
83
|
-
<button
|
|
84
|
-
className={classNames(className, {
|
|
85
|
-
"bg-accent drop-shadow-[0px_0px_12px_rgba(56,205,152,0.60)]": active,
|
|
86
|
-
})}
|
|
87
|
-
onClick={onClick}
|
|
88
|
-
>
|
|
89
|
-
{children}
|
|
90
|
-
</button>
|
|
91
|
-
);
|
|
92
|
-
}
|
|
@@ -1,488 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
Dispatch,
|
|
5
|
-
SetStateAction,
|
|
6
|
-
useCallback,
|
|
7
|
-
useEffect,
|
|
8
|
-
useState,
|
|
9
|
-
} from "react";
|
|
10
|
-
import { Session } from "../dto/Session";
|
|
11
|
-
import { ValMenu } from "./ValMenu";
|
|
12
|
-
import { EditMode, ValOverlayContext, WindowSize } from "./ValOverlayContext";
|
|
13
|
-
import { Remote } from "../utils/Remote";
|
|
14
|
-
import { ValWindow } from "./ValWindow";
|
|
15
|
-
import { result } from "@valbuild/core/fp";
|
|
16
|
-
import { Internal, SerializedSchema, SourcePath } from "@valbuild/core";
|
|
17
|
-
import { Modules, resolvePath } from "../utils/resolvePath";
|
|
18
|
-
import { ValApi } from "@valbuild/core";
|
|
19
|
-
import { ValFormField } from "./ValFormField";
|
|
20
|
-
import { usePatch } from "./usePatch";
|
|
21
|
-
import { Button } from "./ui/button";
|
|
22
|
-
import { useTheme } from "./useTheme";
|
|
23
|
-
import { IValStore } from "../lib/IValStore";
|
|
24
|
-
|
|
25
|
-
export type ValOverlayProps = {
|
|
26
|
-
defaultTheme?: "dark" | "light";
|
|
27
|
-
api: ValApi;
|
|
28
|
-
store: IValStore;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export function ValOverlay({ defaultTheme, api, store }: ValOverlayProps) {
|
|
32
|
-
const [theme, setTheme] = useTheme(defaultTheme);
|
|
33
|
-
const session = useSession(api);
|
|
34
|
-
|
|
35
|
-
const [editMode, setEditMode] = useInitEditMode();
|
|
36
|
-
const [hoverTarget, setHoverTarget] = useHoverTarget(editMode);
|
|
37
|
-
const [windowTarget, setWindowTarget] = useState<WindowTarget | null>(null);
|
|
38
|
-
const [highlight, setHighlight] = useState(false);
|
|
39
|
-
const { selectedSchema, selectedSource, error, loading } = useValModules(
|
|
40
|
-
api,
|
|
41
|
-
windowTarget?.path
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
const { initPatchCallback, onSubmitPatch } = usePatch(
|
|
45
|
-
windowTarget?.path ? [windowTarget.path] : [],
|
|
46
|
-
api,
|
|
47
|
-
store
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
const [windowSize, setWindowSize] = useState<WindowSize>();
|
|
51
|
-
useEffect(() => {
|
|
52
|
-
store.updateAll();
|
|
53
|
-
}, []);
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<ValOverlayContext.Provider
|
|
57
|
-
value={{
|
|
58
|
-
api,
|
|
59
|
-
theme,
|
|
60
|
-
session,
|
|
61
|
-
editMode,
|
|
62
|
-
setEditMode,
|
|
63
|
-
highlight,
|
|
64
|
-
setHighlight,
|
|
65
|
-
setTheme,
|
|
66
|
-
windowSize,
|
|
67
|
-
setWindowSize,
|
|
68
|
-
}}
|
|
69
|
-
>
|
|
70
|
-
<div data-mode={theme} className="antialiased">
|
|
71
|
-
<div className="fixed -translate-x-1/2 z-overlay left-1/2 bottom-4">
|
|
72
|
-
<ValMenu api={api} />
|
|
73
|
-
</div>
|
|
74
|
-
{editMode === "hover" && hoverTarget && (
|
|
75
|
-
<ValHover
|
|
76
|
-
hoverTarget={hoverTarget}
|
|
77
|
-
setHoverTarget={setHoverTarget}
|
|
78
|
-
setEditMode={setEditMode}
|
|
79
|
-
setWindowTarget={setWindowTarget}
|
|
80
|
-
/>
|
|
81
|
-
)}
|
|
82
|
-
{editMode === "window" && windowTarget && (
|
|
83
|
-
<ValWindow
|
|
84
|
-
onClose={() => {
|
|
85
|
-
setWindowTarget(null);
|
|
86
|
-
setEditMode("hover");
|
|
87
|
-
}}
|
|
88
|
-
>
|
|
89
|
-
<div className="px-4 py-2 text-sm border-b border-highlight">
|
|
90
|
-
<WindowHeader
|
|
91
|
-
path={windowTarget.path}
|
|
92
|
-
type={selectedSchema?.type}
|
|
93
|
-
/>
|
|
94
|
-
</div>
|
|
95
|
-
{loading && <div className="text-primary">Loading...</div>}
|
|
96
|
-
{error && <div className="text-red">{error}</div>}
|
|
97
|
-
{selectedSchema !== undefined && selectedSource !== undefined && (
|
|
98
|
-
<ValFormField
|
|
99
|
-
path={windowTarget.path}
|
|
100
|
-
disabled={loading}
|
|
101
|
-
source={selectedSource}
|
|
102
|
-
schema={selectedSchema}
|
|
103
|
-
registerPatchCallback={initPatchCallback(windowTarget.path)}
|
|
104
|
-
/>
|
|
105
|
-
)}
|
|
106
|
-
<div className="flex items-end justify-end py-2">
|
|
107
|
-
<SubmitButton disabled={false} onClick={onSubmitPatch} />
|
|
108
|
-
</div>
|
|
109
|
-
</ValWindow>
|
|
110
|
-
)}
|
|
111
|
-
</div>
|
|
112
|
-
</ValOverlayContext.Provider>
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function SubmitButton({
|
|
117
|
-
disabled,
|
|
118
|
-
onClick,
|
|
119
|
-
}: {
|
|
120
|
-
disabled?: boolean;
|
|
121
|
-
onClick?: () => void;
|
|
122
|
-
}) {
|
|
123
|
-
return (
|
|
124
|
-
<Button
|
|
125
|
-
className="px-4 py-2 border border-highlight disabled:border-border"
|
|
126
|
-
disabled={disabled}
|
|
127
|
-
onClick={onClick}
|
|
128
|
-
>
|
|
129
|
-
Submit
|
|
130
|
-
</Button>
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function useValModules(api: ValApi, path: string | undefined) {
|
|
135
|
-
const [modules, setModules] = useState<Remote<Modules>>();
|
|
136
|
-
const moduleId =
|
|
137
|
-
path && Internal.splitModuleIdAndModulePath(path as SourcePath)[0];
|
|
138
|
-
|
|
139
|
-
useEffect(() => {
|
|
140
|
-
if (path) {
|
|
141
|
-
setModules({ status: "loading" });
|
|
142
|
-
api
|
|
143
|
-
.getModules({
|
|
144
|
-
patch: true,
|
|
145
|
-
includeSchema: true,
|
|
146
|
-
includeSource: true,
|
|
147
|
-
treePath: moduleId,
|
|
148
|
-
})
|
|
149
|
-
.then((res) => {
|
|
150
|
-
if (result.isOk(res)) {
|
|
151
|
-
setModules({ status: "success", data: res.value.modules });
|
|
152
|
-
} else {
|
|
153
|
-
console.error({ status: "error", error: res.error });
|
|
154
|
-
setModules({ status: "error", error: res.error.message });
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
}, [path]);
|
|
159
|
-
if (!path || modules?.status === "not-asked") {
|
|
160
|
-
return {
|
|
161
|
-
error: null,
|
|
162
|
-
selectedSource: undefined,
|
|
163
|
-
selectedSchema: undefined,
|
|
164
|
-
loading: false,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
if (modules?.status === "loading") {
|
|
168
|
-
return {
|
|
169
|
-
error: null,
|
|
170
|
-
selectedSource: undefined,
|
|
171
|
-
selectedSchema: undefined,
|
|
172
|
-
loading: true,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
if (modules?.status === "error") {
|
|
176
|
-
return {
|
|
177
|
-
error: modules.error,
|
|
178
|
-
selectedSource: undefined,
|
|
179
|
-
selectedSchema: undefined,
|
|
180
|
-
loading: false,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
if (!modules?.data) {
|
|
184
|
-
return {
|
|
185
|
-
error: "No modules",
|
|
186
|
-
selectedSource: undefined,
|
|
187
|
-
selectedSchema: undefined,
|
|
188
|
-
loading: false,
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const resolvedModulePath = resolvePath(path as SourcePath, modules.data);
|
|
193
|
-
|
|
194
|
-
const {
|
|
195
|
-
error,
|
|
196
|
-
source: selectedSource,
|
|
197
|
-
schema: selectedSchema,
|
|
198
|
-
} = resolvedModulePath && result.isOk(resolvedModulePath)
|
|
199
|
-
? {
|
|
200
|
-
...resolvedModulePath.value,
|
|
201
|
-
error: null,
|
|
202
|
-
}
|
|
203
|
-
: {
|
|
204
|
-
error:
|
|
205
|
-
resolvedModulePath && result.isErr(resolvedModulePath)
|
|
206
|
-
? resolvedModulePath.error.message
|
|
207
|
-
: null,
|
|
208
|
-
source: undefined,
|
|
209
|
-
schema: undefined,
|
|
210
|
-
};
|
|
211
|
-
return {
|
|
212
|
-
error,
|
|
213
|
-
selectedSource,
|
|
214
|
-
selectedSchema,
|
|
215
|
-
loading: false,
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
type WindowTarget = {
|
|
220
|
-
element?: HTMLElement | undefined;
|
|
221
|
-
mouse: { x: number; y: number };
|
|
222
|
-
path: SourcePath;
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
type HoverTarget = {
|
|
226
|
-
element?: HTMLElement | undefined;
|
|
227
|
-
path: SourcePath;
|
|
228
|
-
};
|
|
229
|
-
function ValHover({
|
|
230
|
-
hoverTarget,
|
|
231
|
-
setEditMode,
|
|
232
|
-
setWindowTarget,
|
|
233
|
-
setHoverTarget,
|
|
234
|
-
}: {
|
|
235
|
-
hoverTarget: HoverTarget;
|
|
236
|
-
setEditMode: Dispatch<EditMode>;
|
|
237
|
-
setHoverTarget: Dispatch<HoverTarget | null>;
|
|
238
|
-
setWindowTarget: Dispatch<WindowTarget | null>;
|
|
239
|
-
}) {
|
|
240
|
-
const rect = hoverTarget.element?.getBoundingClientRect();
|
|
241
|
-
return (
|
|
242
|
-
<div
|
|
243
|
-
id="val-hover"
|
|
244
|
-
className="fixed border-2 cursor-pointer z-overlay-hover border-highlight drop-shadow-[0px_0px_12px_rgba(56,205,152,0.60)]"
|
|
245
|
-
style={{
|
|
246
|
-
top: rect?.top,
|
|
247
|
-
left: rect?.left,
|
|
248
|
-
width: rect?.width,
|
|
249
|
-
height: rect?.height,
|
|
250
|
-
}}
|
|
251
|
-
onClick={(ev) => {
|
|
252
|
-
setWindowTarget({
|
|
253
|
-
...hoverTarget,
|
|
254
|
-
mouse: { x: ev.pageX, y: ev.pageY },
|
|
255
|
-
});
|
|
256
|
-
setEditMode("window");
|
|
257
|
-
setHoverTarget(null);
|
|
258
|
-
}}
|
|
259
|
-
>
|
|
260
|
-
<div className="flex items-center justify-end w-full text-xs">
|
|
261
|
-
<div
|
|
262
|
-
className="flex items-center justify-center px-3 py-1 text-primary bg-highlight"
|
|
263
|
-
style={{
|
|
264
|
-
maxHeight: rect?.height && rect.height - 4,
|
|
265
|
-
fontSize:
|
|
266
|
-
rect?.height && rect.height <= 16 ? rect.height - 4 : undefined,
|
|
267
|
-
}}
|
|
268
|
-
>
|
|
269
|
-
Edit
|
|
270
|
-
</div>
|
|
271
|
-
</div>
|
|
272
|
-
</div>
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function useHoverTarget(editMode: EditMode) {
|
|
277
|
-
const [targetElement, setTargetElement] = useState<HTMLElement>();
|
|
278
|
-
const [targetPath, setTargetPath] = useState<SourcePath>();
|
|
279
|
-
const [targetRect, setTargetRect] = useState<DOMRect>();
|
|
280
|
-
useEffect(() => {
|
|
281
|
-
if (editMode === "hover") {
|
|
282
|
-
let curr: HTMLElement | null = null;
|
|
283
|
-
const mouseOverListener = (e: MouseEvent) => {
|
|
284
|
-
const target = e.target as HTMLElement | null;
|
|
285
|
-
curr = target;
|
|
286
|
-
// TODO: use .contains?
|
|
287
|
-
do {
|
|
288
|
-
if (curr?.dataset.valPath) {
|
|
289
|
-
setTargetElement(curr);
|
|
290
|
-
setTargetPath(curr.dataset.valPath as SourcePath);
|
|
291
|
-
setTargetRect(curr.getBoundingClientRect());
|
|
292
|
-
break;
|
|
293
|
-
}
|
|
294
|
-
} while ((curr = curr?.parentElement || null));
|
|
295
|
-
};
|
|
296
|
-
|
|
297
|
-
document.addEventListener("mouseover", mouseOverListener);
|
|
298
|
-
|
|
299
|
-
return () => {
|
|
300
|
-
setTargetElement(undefined);
|
|
301
|
-
setTargetPath(undefined);
|
|
302
|
-
document.removeEventListener("mouseover", mouseOverListener);
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
}, [editMode]);
|
|
306
|
-
useEffect(() => {
|
|
307
|
-
const scrollListener = () => {
|
|
308
|
-
if (targetElement) {
|
|
309
|
-
setTargetRect(targetElement.getBoundingClientRect());
|
|
310
|
-
}
|
|
311
|
-
};
|
|
312
|
-
document.addEventListener("scroll", scrollListener, { passive: true });
|
|
313
|
-
return () => {
|
|
314
|
-
document.removeEventListener("scroll", scrollListener);
|
|
315
|
-
};
|
|
316
|
-
}, [targetElement]);
|
|
317
|
-
|
|
318
|
-
return [
|
|
319
|
-
{
|
|
320
|
-
path: targetPath,
|
|
321
|
-
element: targetElement,
|
|
322
|
-
rect: targetRect,
|
|
323
|
-
} as HoverTarget,
|
|
324
|
-
(target: HoverTarget | null) => {
|
|
325
|
-
setTargetElement(target?.element);
|
|
326
|
-
setTargetPath(target?.path);
|
|
327
|
-
setTargetRect(target?.element?.getBoundingClientRect());
|
|
328
|
-
},
|
|
329
|
-
] as const;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// TODO: do something fun on highlight?
|
|
333
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
334
|
-
function useHighlight(
|
|
335
|
-
highlight: boolean,
|
|
336
|
-
setTarget: Dispatch<HoverTarget | null>
|
|
337
|
-
) {
|
|
338
|
-
useEffect(() => {
|
|
339
|
-
if (highlight) {
|
|
340
|
-
const elements =
|
|
341
|
-
document.querySelectorAll<HTMLElement>("[data-val-path]");
|
|
342
|
-
let index = 0;
|
|
343
|
-
let timeout: NodeJS.Timeout | null = null;
|
|
344
|
-
|
|
345
|
-
const highlight = () => {
|
|
346
|
-
const element = elements[index];
|
|
347
|
-
const path = element.dataset.valPath as SourcePath;
|
|
348
|
-
if (path) {
|
|
349
|
-
setTarget({
|
|
350
|
-
path,
|
|
351
|
-
element,
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
index++;
|
|
355
|
-
if (index >= elements.length) {
|
|
356
|
-
index = 0;
|
|
357
|
-
}
|
|
358
|
-
timeout = setTimeout(highlight, 1000);
|
|
359
|
-
};
|
|
360
|
-
highlight();
|
|
361
|
-
return () => {
|
|
362
|
-
if (timeout) {
|
|
363
|
-
clearTimeout(timeout);
|
|
364
|
-
}
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
}, [highlight]);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
const LOCAL_STORAGE_EDIT_MODE_KEY = "val-edit-mode";
|
|
371
|
-
|
|
372
|
-
function useInitEditMode() {
|
|
373
|
-
const [editMode, setEditModeRaw] = useState<EditMode>("off");
|
|
374
|
-
useEffect(() => {
|
|
375
|
-
try {
|
|
376
|
-
const storedEditMode = localStorage.getItem(LOCAL_STORAGE_EDIT_MODE_KEY);
|
|
377
|
-
if (
|
|
378
|
-
storedEditMode === "off" ||
|
|
379
|
-
storedEditMode === "hover" ||
|
|
380
|
-
storedEditMode === "window" ||
|
|
381
|
-
storedEditMode === "full"
|
|
382
|
-
) {
|
|
383
|
-
setEditModeRaw(storedEditMode === "window" ? "hover" : storedEditMode);
|
|
384
|
-
} else {
|
|
385
|
-
localStorage.removeItem(LOCAL_STORAGE_EDIT_MODE_KEY);
|
|
386
|
-
setEditModeRaw("off");
|
|
387
|
-
}
|
|
388
|
-
} catch (err) {
|
|
389
|
-
setEditModeRaw("off");
|
|
390
|
-
}
|
|
391
|
-
}, []);
|
|
392
|
-
|
|
393
|
-
const setEditMode: Dispatch<SetStateAction<EditMode>> = useCallback((v) => {
|
|
394
|
-
if (typeof v === "function") {
|
|
395
|
-
setEditModeRaw((prev) => {
|
|
396
|
-
const next = v(prev);
|
|
397
|
-
localStorage.setItem(LOCAL_STORAGE_EDIT_MODE_KEY, next);
|
|
398
|
-
return next;
|
|
399
|
-
});
|
|
400
|
-
} else {
|
|
401
|
-
localStorage.setItem(LOCAL_STORAGE_EDIT_MODE_KEY, v);
|
|
402
|
-
setEditModeRaw(v);
|
|
403
|
-
}
|
|
404
|
-
}, []);
|
|
405
|
-
return [editMode, setEditMode] as const;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
function useSession(api: ValApi) {
|
|
409
|
-
const [session, setSession] = useState<Remote<Session>>({
|
|
410
|
-
status: "not-asked",
|
|
411
|
-
});
|
|
412
|
-
const [sessionResetId, setSessionResetId] = useState(0);
|
|
413
|
-
useEffect(() => {
|
|
414
|
-
setSession({ status: "loading" });
|
|
415
|
-
api.getSession().then(async (res) => {
|
|
416
|
-
try {
|
|
417
|
-
if (result.isOk(res)) {
|
|
418
|
-
const session = res.value;
|
|
419
|
-
setSession({ status: "success", data: Session.parse(session) });
|
|
420
|
-
} else {
|
|
421
|
-
if (sessionResetId < 3) {
|
|
422
|
-
setTimeout(() => {
|
|
423
|
-
setSessionResetId(sessionResetId + 1);
|
|
424
|
-
}, 200 * sessionResetId);
|
|
425
|
-
} else {
|
|
426
|
-
setSession({ status: "error", error: "Could not fetch session" });
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
} catch (e) {
|
|
430
|
-
setSession({
|
|
431
|
-
status: "error",
|
|
432
|
-
error: "Got an error while trying to get session",
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
});
|
|
436
|
-
}, [sessionResetId]);
|
|
437
|
-
return session;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
function WindowHeader({
|
|
441
|
-
path,
|
|
442
|
-
type,
|
|
443
|
-
}: {
|
|
444
|
-
path: SourcePath;
|
|
445
|
-
type?: SerializedSchema["type"];
|
|
446
|
-
}) {
|
|
447
|
-
const segments = path.split("/").slice(1);
|
|
448
|
-
return (
|
|
449
|
-
<span className="flex items-center justify-between">
|
|
450
|
-
<span>
|
|
451
|
-
<span className="pr-1 text-xs opacity-50">/</span>
|
|
452
|
-
{segments.map((segment, i) => {
|
|
453
|
-
if (i === segments.length - 1) {
|
|
454
|
-
return (
|
|
455
|
-
<span key={i} className="text-primary">
|
|
456
|
-
{segment.split(".").map((s, i) => {
|
|
457
|
-
let name = s;
|
|
458
|
-
if (i === 0) {
|
|
459
|
-
return (
|
|
460
|
-
<span key={i + "."}>
|
|
461
|
-
<span>{name}</span>
|
|
462
|
-
</span>
|
|
463
|
-
);
|
|
464
|
-
} else {
|
|
465
|
-
name = JSON.parse(s);
|
|
466
|
-
}
|
|
467
|
-
return (
|
|
468
|
-
<span key={i + "."}>
|
|
469
|
-
<span className="px-1 text-xs text-highlight">/</span>
|
|
470
|
-
<span>{name}</span>
|
|
471
|
-
</span>
|
|
472
|
-
);
|
|
473
|
-
})}
|
|
474
|
-
</span>
|
|
475
|
-
);
|
|
476
|
-
}
|
|
477
|
-
return (
|
|
478
|
-
<span key={i}>
|
|
479
|
-
<span>{segment}</span>
|
|
480
|
-
<span className="px-1 text-xs opacity-50">/</span>
|
|
481
|
-
</span>
|
|
482
|
-
);
|
|
483
|
-
})}
|
|
484
|
-
</span>
|
|
485
|
-
{type && <span className="ml-4">({type})</span>}
|
|
486
|
-
</span>
|
|
487
|
-
);
|
|
488
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import React, { Dispatch, SetStateAction } from "react";
|
|
2
|
-
import type { Remote } from "../utils/Remote";
|
|
3
|
-
import type { Session } from "../dto/Session";
|
|
4
|
-
import { ValApi } from "@valbuild/core";
|
|
5
|
-
|
|
6
|
-
export type Theme = "dark" | "light";
|
|
7
|
-
export type EditMode = "off" | "hover" | "window" | "full";
|
|
8
|
-
export type WindowSize = {
|
|
9
|
-
width: number;
|
|
10
|
-
height: number;
|
|
11
|
-
innerHeight: number;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export const ValOverlayContext = React.createContext<{
|
|
15
|
-
api: ValApi;
|
|
16
|
-
session: Remote<Session>;
|
|
17
|
-
editMode: EditMode;
|
|
18
|
-
highlight: boolean;
|
|
19
|
-
setHighlight: Dispatch<SetStateAction<boolean>>;
|
|
20
|
-
setEditMode: Dispatch<SetStateAction<EditMode>>;
|
|
21
|
-
theme: Theme;
|
|
22
|
-
setTheme: (theme: Theme) => void;
|
|
23
|
-
setWindowSize: (size: WindowSize) => void;
|
|
24
|
-
windowSize?: WindowSize;
|
|
25
|
-
}>({
|
|
26
|
-
get api(): never {
|
|
27
|
-
throw Error(
|
|
28
|
-
"ValOverlayContext not found. Ensure components are wrapped by ValOverlayProvider!"
|
|
29
|
-
);
|
|
30
|
-
},
|
|
31
|
-
get session(): never {
|
|
32
|
-
throw Error(
|
|
33
|
-
"ValOverlayContext not found. Ensure components are wrapped by ValOverlayProvider!"
|
|
34
|
-
);
|
|
35
|
-
},
|
|
36
|
-
get theme(): never {
|
|
37
|
-
throw Error(
|
|
38
|
-
"ValOverlayContext not found. Ensure components are wrapped by ValOverlayProvider!"
|
|
39
|
-
);
|
|
40
|
-
},
|
|
41
|
-
get setTheme(): never {
|
|
42
|
-
throw Error(
|
|
43
|
-
"ValOverlayContext not found. Ensure components are wrapped by ValOverlayProvider!"
|
|
44
|
-
);
|
|
45
|
-
},
|
|
46
|
-
get editMode(): never {
|
|
47
|
-
throw Error(
|
|
48
|
-
"ValOverlayContext not found. Ensure components are wrapped by ValOverlayProvider!"
|
|
49
|
-
);
|
|
50
|
-
},
|
|
51
|
-
get setEditMode(): never {
|
|
52
|
-
throw Error(
|
|
53
|
-
"ValOverlayContext not found. Ensure components are wrapped by ValOverlayProvider!"
|
|
54
|
-
);
|
|
55
|
-
},
|
|
56
|
-
get highlight(): never {
|
|
57
|
-
throw Error(
|
|
58
|
-
"ValOverlayContext not found. Ensure components are wrapped by ValOverlayProvider!"
|
|
59
|
-
);
|
|
60
|
-
},
|
|
61
|
-
get setHighlight(): never {
|
|
62
|
-
throw Error(
|
|
63
|
-
"ValOverlayContext not found. Ensure components are wrapped by ValOverlayProvider!"
|
|
64
|
-
);
|
|
65
|
-
},
|
|
66
|
-
get setWindowSize(): never {
|
|
67
|
-
throw Error(
|
|
68
|
-
"ValOverlayContext not found. Ensure components are wrapped by ValOverlayProvider!"
|
|
69
|
-
);
|
|
70
|
-
},
|
|
71
|
-
get windowSize(): never {
|
|
72
|
-
throw Error(
|
|
73
|
-
"ValOverlayContext not found. Ensure components are wrapped by ValOverlayProvider!"
|
|
74
|
-
);
|
|
75
|
-
},
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
export function useValOverlayContext() {
|
|
79
|
-
return React.useContext(ValOverlayContext);
|
|
80
|
-
}
|