tsondb 0.7.9 → 0.7.11
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/src/web/components/InstanceRouteSkeleton.js +36 -3
- package/dist/src/web/components/Layout.js +4 -2
- package/dist/src/web/components/Settings.js +4 -1
- package/dist/src/web/components/git/Git.js +3 -1
- package/dist/src/web/components/typeInputs/NestedEntityMapTypeInput.js +3 -2
- package/dist/src/web/components/typeInputs/TypeInput.d.ts +1 -0
- package/dist/src/web/context/settings.js +1 -0
- package/dist/src/web/hooks/useSettings.d.ts +1 -0
- package/dist/src/web/hooks/useSettings.js +1 -0
- package/package.json +1 -1
- package/public/css/styles.css +21 -2
|
@@ -2,10 +2,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
|
2
2
|
import { useLocation, useRoute } from "preact-iso";
|
|
3
3
|
import { useCallback, useContext, useEffect, useState } from "preact/hooks";
|
|
4
4
|
import { removeAt } from "../../shared/utils/array.js";
|
|
5
|
+
import { deepEqual } from "../../shared/utils/compare.js";
|
|
5
6
|
import { getSerializedDisplayNameFromEntityInstance } from "../../shared/utils/displayName.js";
|
|
6
7
|
import { toTitleCase } from "../../shared/utils/string.js";
|
|
7
8
|
import { validateLocaleIdentifier } from "../../shared/validation/identifier.js";
|
|
8
9
|
import { deleteInstanceByEntityNameAndId, getChildInstancesForInstanceByEntityName, } from "../api/declarations.js";
|
|
10
|
+
import { EntitiesContext } from "../context/entities.js";
|
|
9
11
|
import { GitClientContext } from "../context/gitClient.js";
|
|
10
12
|
import { useEntityFromRoute } from "../hooks/useEntityFromRoute.js";
|
|
11
13
|
import { useInstanceNamesByEntity } from "../hooks/useInstanceNamesByEntity.js";
|
|
@@ -17,17 +19,35 @@ import { runWithLoading } from "../signals/loading.js";
|
|
|
17
19
|
import { Layout } from "./Layout.js";
|
|
18
20
|
import { TypeInput } from "./typeInputs/TypeInput.js";
|
|
19
21
|
import { ValidationErrors } from "./typeInputs/utils/ValidationErrors.js";
|
|
22
|
+
const onBeforeUnload = (event) => {
|
|
23
|
+
event.preventDefault();
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- best practice according to MDN
|
|
25
|
+
event.returnValue = "unsaved changes";
|
|
26
|
+
};
|
|
20
27
|
export const InstanceRouteSkeleton = ({ mode, buttons, init, titleBuilder, onSubmit, }) => {
|
|
21
28
|
const { params: { name, id }, } = useRoute();
|
|
22
29
|
const [locales] = useSetting("displayedLocales");
|
|
23
30
|
const [getDeclFromDeclName, declsLoaded] = useGetDeclFromDeclName();
|
|
24
31
|
const { declaration: entity, isLocaleEntity } = useEntityFromRoute() ?? {};
|
|
32
|
+
const { entities } = useContext(EntitiesContext);
|
|
25
33
|
const [instanceNamesByEntity] = useInstanceNamesByEntity();
|
|
26
34
|
const [instanceContent, setInstanceContent] = useState();
|
|
35
|
+
const [savedInstanceContent, setSavedInstanceContent] = useState();
|
|
27
36
|
const [childInstances, setChildInstances] = useState([]);
|
|
28
37
|
const [customId, setCustomId] = useState("");
|
|
29
38
|
const client = useContext(GitClientContext);
|
|
30
39
|
const { route } = useLocation();
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (deepEqual(instanceContent, savedInstanceContent)) {
|
|
42
|
+
window.removeEventListener("beforeunload", onBeforeUnload);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
window.addEventListener("beforeunload", onBeforeUnload);
|
|
46
|
+
}
|
|
47
|
+
return () => {
|
|
48
|
+
window.removeEventListener("beforeunload", onBeforeUnload);
|
|
49
|
+
};
|
|
50
|
+
}, [instanceContent, savedInstanceContent]);
|
|
31
51
|
useEffect(() => {
|
|
32
52
|
document.title =
|
|
33
53
|
(entity && titleBuilder({ locales, entity, instanceContent, instanceId: id })) ??
|
|
@@ -35,7 +55,16 @@ export const InstanceRouteSkeleton = ({ mode, buttons, init, titleBuilder, onSub
|
|
|
35
55
|
}, [entity, id, instanceContent, locales, titleBuilder]);
|
|
36
56
|
useEffect(() => {
|
|
37
57
|
if (entity && instanceContent === undefined && declsLoaded) {
|
|
38
|
-
runWithLoading(() => init({
|
|
58
|
+
runWithLoading(() => init({
|
|
59
|
+
locales,
|
|
60
|
+
entity,
|
|
61
|
+
instanceId: id,
|
|
62
|
+
setInstanceContent: value => {
|
|
63
|
+
setInstanceContent(value);
|
|
64
|
+
setSavedInstanceContent(value);
|
|
65
|
+
},
|
|
66
|
+
getDeclFromDeclName,
|
|
67
|
+
}))
|
|
39
68
|
.then(() => id
|
|
40
69
|
? getChildInstancesForInstanceByEntityName(locales, entity.name, id).then(result => {
|
|
41
70
|
setChildInstances(result.instances);
|
|
@@ -46,6 +75,7 @@ export const InstanceRouteSkeleton = ({ mode, buttons, init, titleBuilder, onSub
|
|
|
46
75
|
});
|
|
47
76
|
}
|
|
48
77
|
}, [entity, declsLoaded, getDeclFromDeclName, id, init, instanceContent, locales, name]);
|
|
78
|
+
const checkIsLocaleEntity = useCallback((entityName) => entities.some(entity => entity.declaration.name === entityName && entity.isLocaleEntity), [entities]);
|
|
49
79
|
const handleSubmit = (event) => {
|
|
50
80
|
event.preventDefault();
|
|
51
81
|
if (entity && instanceContent !== undefined) {
|
|
@@ -61,7 +91,10 @@ export const InstanceRouteSkeleton = ({ mode, buttons, init, titleBuilder, onSub
|
|
|
61
91
|
getDeclFromDeclName,
|
|
62
92
|
isLocaleEntity,
|
|
63
93
|
setCustomId,
|
|
64
|
-
setInstanceContent
|
|
94
|
+
setInstanceContent: value => {
|
|
95
|
+
setInstanceContent(value);
|
|
96
|
+
setSavedInstanceContent(value);
|
|
97
|
+
},
|
|
65
98
|
childInstances,
|
|
66
99
|
updateLocalGitState: client?.updateLocalState,
|
|
67
100
|
})).catch((error) => {
|
|
@@ -114,5 +147,5 @@ export const InstanceRouteSkeleton = ({ mode, buttons, init, titleBuilder, onSub
|
|
|
114
147
|
}
|
|
115
148
|
}, children: "Delete" }))] }), !id && isLocaleEntity && (_jsxs("div", { class: "field field--id", children: [_jsx("label", { htmlFor: "id", children: "ID" }), _jsx("p", { className: "comment", children: "The instance\u2019s identifier. An IETF language tag (BCP47)." }), _jsx("input", { type: "text", id: "id", value: customId, required: true, pattern: "[a-z]{2,3}(-[A-Z]{2,3})?", placeholder: "en-US, de-DE, \u2026", onInput: event => {
|
|
116
149
|
setCustomId(event.currentTarget.value);
|
|
117
|
-
}, "aria-invalid": idErrors.length > 0 }), _jsx(ValidationErrors, { errors: idErrors })] })), _jsxs("form", { onSubmit: handleSubmit, children: [_jsx(TypeInput, { type: entity.type, value: instanceContent, path: undefined, instanceNamesByEntity: instanceNamesByEntity, childInstances: childInstances, getDeclFromDeclName: getDeclFromDeclName, onChange: setInstanceContent, onChildChange: handleOnChildChange, onChildAdd: handleOnChildAdd, onChildRemove: handleOnChildRemove }), _jsx("div", { class: "form-footer btns", children: buttons.map(button => (_jsx("button", { type: "submit", name: button.name, class: button.primary ? "primary" : undefined, children: button.label }, button.name))) })] })] }));
|
|
150
|
+
}, "aria-invalid": idErrors.length > 0 }), _jsx(ValidationErrors, { errors: idErrors })] })), _jsxs("form", { onSubmit: handleSubmit, children: [_jsx(TypeInput, { type: entity.type, value: instanceContent, path: undefined, instanceNamesByEntity: instanceNamesByEntity, childInstances: childInstances, getDeclFromDeclName: getDeclFromDeclName, onChange: setInstanceContent, onChildChange: handleOnChildChange, onChildAdd: handleOnChildAdd, onChildRemove: handleOnChildRemove, checkIsLocaleEntity: checkIsLocaleEntity }), _jsx("div", { class: "form-footer btns", children: buttons.map(button => (_jsx("button", { type: "submit", name: button.name, class: button.primary ? "primary" : undefined, children: button.label }, button.name))) })] })] }));
|
|
118
151
|
};
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "preact/jsx-runtime";
|
|
2
2
|
import { useContext } from "preact/hooks";
|
|
3
3
|
import { GitContext } from "../context/git.js";
|
|
4
|
+
import { useSetting } from "../hooks/useSettings.js";
|
|
4
5
|
import { Settings } from "./Settings.js";
|
|
5
6
|
export const Layout = ({ breadcrumbs, children }) => {
|
|
6
|
-
const [
|
|
7
|
-
|
|
7
|
+
const [_1, setIsGitOpen] = useContext(GitContext);
|
|
8
|
+
const [isGitAlwaysOpen, _2] = useSetting("gitSidebar");
|
|
9
|
+
return (_jsxs(_Fragment, { children: [_jsxs("header", { children: [_jsx("nav", { children: _jsx("ol", { children: breadcrumbs.map(({ url, label }) => (_jsx("li", { children: _jsx("a", { href: url, children: label }) }, url))) }) }), _jsxs("div", { class: "nav-buttons", children: [_jsx("button", { class: `git-toggle${!isGitAlwaysOpen ? " git-toggle--no-sidebar" : ""}`, onClick: () => {
|
|
8
10
|
setIsGitOpen(b => !b);
|
|
9
11
|
}, children: "File changes" }), _jsx(Settings, {})] })] }), _jsx("main", { children: children })] }));
|
|
10
12
|
};
|
|
@@ -11,6 +11,7 @@ const localeMapper = (result) => result.instances;
|
|
|
11
11
|
export const Settings = () => {
|
|
12
12
|
const [locales, setLocales] = useSetting("displayedLocales");
|
|
13
13
|
const [enumDisplay, setEnumDisplay] = useSetting("enumDisplay");
|
|
14
|
+
const [isGitAlwaysOpen, setIsGitAlwaysOpen] = useSetting("gitSidebar");
|
|
14
15
|
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
|
15
16
|
const config = useContext(ConfigContext);
|
|
16
17
|
const [localeInstances] = useMappedAPIResource(getLocaleInstances, localeMapper, locales, config.localeEntityName);
|
|
@@ -43,5 +44,7 @@ export const Settings = () => {
|
|
|
43
44
|
setEnumDisplay("select");
|
|
44
45
|
} }), _jsx("label", { htmlFor: "enum-display-select", children: "Compact (Dropdowns)" })] }), _jsxs("div", { className: "field--option", children: [_jsx("input", { type: "radio", name: "enum-display", id: "enum-display-radio", value: "radio", checked: enumDisplay === "radio", onChange: () => {
|
|
45
46
|
setEnumDisplay("radio");
|
|
46
|
-
} }), _jsx("label", { htmlFor: "enum-display-radio", children: "Expanded (all nested form fields in radio lists)" })] })
|
|
47
|
+
} }), _jsx("label", { htmlFor: "enum-display-radio", children: "Expanded (all nested form fields in radio lists)" })] }), _jsx("h3", { children: "Version Control" }), _jsxs("div", { className: "field--option", children: [_jsx("input", { type: "checkbox", name: "git-sidebar-always-open", id: "git-sidebar-always-open", checked: isGitAlwaysOpen, onChange: () => {
|
|
48
|
+
setIsGitAlwaysOpen(v => !v);
|
|
49
|
+
} }), _jsx("label", { htmlFor: "git-sidebar-always-open", children: "Display as sidebar in larger viewports" })] })] })] }));
|
|
47
50
|
};
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "preact/jsx-ru
|
|
|
2
2
|
import { useContext, useState } from "preact/hooks";
|
|
3
3
|
import { GitContext } from "../../context/git.js";
|
|
4
4
|
import { GitClientContext } from "../../context/gitClient.js";
|
|
5
|
+
import { useSetting } from "../../hooks/useSettings.js";
|
|
5
6
|
import { ModalDialog } from "../ModalDialog.js";
|
|
6
7
|
import { GitBranchManager } from "./GitBranchManager.js";
|
|
7
8
|
import { GitFileManager } from "./GitFileManager.js";
|
|
@@ -9,6 +10,7 @@ export const Git = () => {
|
|
|
9
10
|
const [isOpen, setIsOpen] = useContext(GitContext);
|
|
10
11
|
const [mode, setMode] = useState("files");
|
|
11
12
|
const client = useContext(GitClientContext);
|
|
13
|
+
const [isGitAlwaysOpen] = useSetting("gitSidebar");
|
|
12
14
|
if (!client || !client.isRepo) {
|
|
13
15
|
return null;
|
|
14
16
|
}
|
|
@@ -40,7 +42,7 @@ export const Git = () => {
|
|
|
40
42
|
default:
|
|
41
43
|
return null;
|
|
42
44
|
}
|
|
43
|
-
})()] }), isOpen && mode === "files" ? null : (_jsxs("aside", { class: "git", children: [_jsx("h2", { class: "h1-faded", children: "Version Control" }), _jsx(GitFileManager, { client: client, manageBranches: () => {
|
|
45
|
+
})()] }), !isGitAlwaysOpen || (isOpen && mode === "files") ? null : (_jsxs("aside", { class: "git", children: [_jsx("h2", { class: "h1-faded", children: "Version Control" }), _jsx(GitFileManager, { client: client, manageBranches: () => {
|
|
44
46
|
setIsOpen(true);
|
|
45
47
|
setMode("branches");
|
|
46
48
|
} })] }))] }));
|
|
@@ -7,7 +7,7 @@ import { Select } from "../Select.js";
|
|
|
7
7
|
import { TypeInput } from "./TypeInput.js";
|
|
8
8
|
import { MismatchingTypeError } from "./utils/MismatchingTypeError.js";
|
|
9
9
|
export const NestedEntityMapTypeInput = props => {
|
|
10
|
-
const { type, path, value, instanceNamesByEntity, disabled, getDeclFromDeclName, onChange } = props;
|
|
10
|
+
const { type, path, value, instanceNamesByEntity, disabled, getDeclFromDeclName, checkIsLocaleEntity, onChange, } = props;
|
|
11
11
|
const [newKey, setNewKey] = useState("");
|
|
12
12
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
13
13
|
return _jsx(MismatchingTypeError, { expected: "object", actual: value });
|
|
@@ -17,10 +17,11 @@ export const NestedEntityMapTypeInput = props => {
|
|
|
17
17
|
.slice()
|
|
18
18
|
.filter(instance => !existingKeys.includes(instance.id))
|
|
19
19
|
.sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true }));
|
|
20
|
+
const isLocaleEntity = checkIsLocaleEntity(type.secondaryEntity);
|
|
20
21
|
return (_jsxs("div", { class: "field field--container field--nestedentitymap", children: [existingKeys.length > 0 && (_jsx("ul", { children: Object.entries(value).map(([key, item]) => {
|
|
21
22
|
const name = instanceNamesByEntity[type.secondaryEntity]?.find(instance => instance.id === key)
|
|
22
23
|
?.name ?? key;
|
|
23
|
-
return (_jsxs("li", { class: "container-item dict-item", children: [_jsxs("div", { className: "container-item-header", children: [_jsx("div", { className: "container-item-title", children: _jsxs("span", { children: [_jsx("strong", { children: name }), " ", _jsx("span", { className: "id", children: key })] }) }), _jsx("div", { className: "btns", children: _jsxs("button", { class: "destructive", onClick: () => {
|
|
24
|
+
return (_jsxs("li", { class: "container-item dict-item", ...(isLocaleEntity ? { lang: key } : {}), children: [_jsxs("div", { className: "container-item-header", children: [_jsx("div", { className: "container-item-title", children: _jsxs("span", { children: [_jsx("strong", { children: name }), " ", _jsx("span", { className: "id", children: key })] }) }), _jsx("div", { className: "btns", children: _jsxs("button", { class: "destructive", onClick: () => {
|
|
24
25
|
const newObj = { ...value };
|
|
25
26
|
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
26
27
|
delete newObj[key];
|
|
@@ -16,6 +16,7 @@ export type TypeInputProps<T, V = unknown> = {
|
|
|
16
16
|
onChildChange: (index: number, value: unknown) => void;
|
|
17
17
|
onChildAdd: (entityName: string, value: unknown) => void;
|
|
18
18
|
onChildRemove: (index: number) => void;
|
|
19
|
+
checkIsLocaleEntity: (entityName: string) => boolean;
|
|
19
20
|
};
|
|
20
21
|
type Props = TypeInputProps<SerializedType>;
|
|
21
22
|
declare const MemoizedTypeInput: FunctionComponent<Props>;
|
|
@@ -5,6 +5,7 @@ import { SettingsContext } from "../context/settings.ts";
|
|
|
5
5
|
export type UserSettings = {
|
|
6
6
|
displayedLocales: string[];
|
|
7
7
|
enumDisplay: "select" | "radio";
|
|
8
|
+
gitSidebar: boolean;
|
|
8
9
|
};
|
|
9
10
|
export declare const useSettings: (config: WebConfig) => SettingsContext;
|
|
10
11
|
export declare const useSetting: <K extends keyof UserSettings>(key: K) => [UserSettings[K], Dispatch<SetStateAction<UserSettings[K]>>];
|
|
@@ -3,6 +3,7 @@ import { defaultSettings, SettingsContext } from "../context/settings.js";
|
|
|
3
3
|
const settingsGuards = {
|
|
4
4
|
displayedLocales: (v) => Array.isArray(v) && v.every(e => typeof e === "string") && v.length > 0,
|
|
5
5
|
enumDisplay: (v) => typeof v === "string" && ["select", "radio"].includes(v),
|
|
6
|
+
gitSidebar: (v) => typeof v === "boolean",
|
|
6
7
|
};
|
|
7
8
|
const defaultSettingsFromConfig = (config) => ({
|
|
8
9
|
...defaultSettings,
|
package/package.json
CHANGED
package/public/css/styles.css
CHANGED
|
@@ -196,6 +196,24 @@ h3 {
|
|
|
196
196
|
line-height: 1.2;
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
h4 {
|
|
200
|
+
margin: 1.2rem 0 0.6rem;
|
|
201
|
+
font-size: 1.1rem;
|
|
202
|
+
line-height: 1.3;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
h5 {
|
|
206
|
+
margin: 1rem 0 0.5rem;
|
|
207
|
+
font-size: 1rem;
|
|
208
|
+
line-height: 1.4;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
h6 {
|
|
212
|
+
margin: 1rem 0 0.5rem;
|
|
213
|
+
font-size: 0.9rem;
|
|
214
|
+
line-height: 1.6;
|
|
215
|
+
}
|
|
216
|
+
|
|
199
217
|
p {
|
|
200
218
|
margin: 0.5rem 0;
|
|
201
219
|
}
|
|
@@ -747,9 +765,10 @@ form > .field--container {
|
|
|
747
765
|
padding: 1rem;
|
|
748
766
|
background: var(--color-background-secondary);
|
|
749
767
|
flex: 1 1 0;
|
|
768
|
+
line-height: 1.6;
|
|
750
769
|
}
|
|
751
770
|
|
|
752
|
-
.preview p {
|
|
771
|
+
.preview :is(p, ul, ol, table) {
|
|
753
772
|
margin: 0.5rem 0 0;
|
|
754
773
|
}
|
|
755
774
|
|
|
@@ -934,7 +953,7 @@ aside.git h2 {
|
|
|
934
953
|
display: block;
|
|
935
954
|
}
|
|
936
955
|
|
|
937
|
-
.git-toggle {
|
|
956
|
+
.git-toggle:not(.git-toggle--no-sidebar) {
|
|
938
957
|
display: none;
|
|
939
958
|
}
|
|
940
959
|
}
|