diff --git a/bun.lockb b/bun.lockb index cbf4d54e..27636da0 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/nodedevpkg/vue-vite-presets/package.json b/nodedevpkg/vue-vite-presets/package.json index 069ce9f7..ae964978 100644 --- a/nodedevpkg/vue-vite-presets/package.json +++ b/nodedevpkg/vue-vite-presets/package.json @@ -15,13 +15,13 @@ "@innoai-tech/vuecomponentcompleter": "^0.1.3", "@mapbox/rehype-prism": "^0.9.0", "@mdx-js/rollup": "^3.1.0", - "@swc/core": "^1.8.0", - "@vitejs/plugin-vue": "^5.1.4", + "@swc/core": "^1.9.2", + "@vitejs/plugin-vue": "^5.2.0", "hastscript": "^9.0.0", "unist-util-visit": "^5.0.0", - "vite": "^5.4.10", + "vite": "^5.4.11", "vite-plugin-pages": "^0.32.3", - "vite-tsconfig-paths": "^5.1.0" + "vite-tsconfig-paths": "^5.1.2" }, "peerDependencies": {}, "exports": { diff --git a/nodepkg/jsoneditor/example/jsoneditor.tsx b/nodepkg/jsoneditor/example/jsoneditor.tsx index 345f647b..46e8cc04 100644 --- a/nodepkg/jsoneditor/example/jsoneditor.tsx +++ b/nodepkg/jsoneditor/example/jsoneditor.tsx @@ -23,7 +23,7 @@ class JSONSchema { @t.nativeEnum(Kind) kind!: Kind; - @t.annotate({ title: "名称" }) + @t.annotate({ title: "名称", description: "详细描述" }) @t.string() name!: string; @@ -44,17 +44,25 @@ class JSONSchema { @t.annotate({ title: "端口" }) @t.array(t.object(Port)) ports!: Port[]; + + @t.annotate({ title: "其他配置" }) + @t.record(t.string(), t.any()) + @t.optional() + manifests!: Record; } export default component(() => { const x = t.object(JSONSchema); const editor$ = JSONEditor.of(x, { + name: "name", annotations: { - text: "name", - longtext: new Array(100).fill("longtext").join(""), + longtext: new Array(100).fill("longtext").join("") }, ports: [], + manifests: { + "x": {} + } }); rx( @@ -68,7 +76,7 @@ export default component(() => { } console.log(JSON.stringify(v, null, 2)); }), - subscribeUntilUnmount(), + subscribeUntilUnmount() ); return () => ( diff --git a/nodepkg/jsoneditor/package.json b/nodepkg/jsoneditor/package.json index 5c67500f..a4ffdc40 100644 --- a/nodepkg/jsoneditor/package.json +++ b/nodepkg/jsoneditor/package.json @@ -1,6 +1,6 @@ { "name": "@innoai-tech/jsoneditor", - "version": "0.3.2", + "version": "0.3.4", "monobundle": { "build": { "clean": true @@ -11,6 +11,7 @@ }, "dependencies": { "@innoai-tech/vuekit": "workspace:^", + "@innoai-tech/vuemarkdown": "workspace:^", "@innoai-tech/vuematerial": "workspace:^", "@innoai-tech/vueuikit": "workspace:^", "@mdi/js": "^7.4.47", diff --git a/nodepkg/jsoneditor/src/JSONEditorView.tsx b/nodepkg/jsoneditor/src/JSONEditorView.tsx index d0abdaa5..eb2dc7b1 100644 --- a/nodepkg/jsoneditor/src/JSONEditorView.tsx +++ b/nodepkg/jsoneditor/src/JSONEditorView.tsx @@ -1,24 +1,10 @@ -import { - component$, - type Context, - EmptyContext, - rx, - Schema, - type Type, -} from "@innoai-tech/vuekit"; +import { component$, type Context, EmptyContext, rx, Schema, type Type } from "@innoai-tech/vuekit"; import { JSONEditorProvider, JSONEditorSlotsProvider } from "./models"; -import { - ArrayInput, - LayoutContextProvider, - Line, - ObjectInput, - OneEditingProvider, - RecordInput, - ValueInput, -} from "./views"; +import { LayoutContextProvider, Line } from "./views"; import { styled } from "@innoai-tech/vueuikit"; import { ref } from "vue"; import { isUndefined } from "@innoai-tech/lodash"; +import { AnyInput, ArrayInput, ObjectInput, OneEditingProvider, RecordInput, ValueInput } from "./inputs"; export const defaultValueRender = (typedef: Type, value: any, ctx: Context) => { if ( @@ -44,6 +30,10 @@ export const defaultValueRender = (typedef: Type, value: any, ctx: Context) => { return ; } + if (typedef.type == "any" || typedef.type == "unknown") { + return ; + } + return ; }; @@ -62,7 +52,7 @@ export const JSONEditorView = component$(({}, { render }) => { @@ -71,7 +61,7 @@ export const JSONEditorView = component$(({}, { render }) => { @@ -83,7 +73,7 @@ export const JSONEditorView = component$(({}, { render }) => { ); - }), + }) ); }); @@ -95,6 +85,6 @@ const JSONEditorContainer = styled("div")({ section: { display: "flex", flexDirection: "column", - minWidth: "max-content", - }, + minWidth: "max-content" + } }); diff --git a/nodepkg/jsoneditor/src/views/JSONRaw.tsx b/nodepkg/jsoneditor/src/actions/JSONRaw.tsx similarity index 86% rename from nodepkg/jsoneditor/src/views/JSONRaw.tsx rename to nodepkg/jsoneditor/src/actions/JSONRaw.tsx index b8b3aa49..581cd477 100644 --- a/nodepkg/jsoneditor/src/views/JSONRaw.tsx +++ b/nodepkg/jsoneditor/src/actions/JSONRaw.tsx @@ -1,9 +1,8 @@ import { component$ } from "@innoai-tech/vuekit"; import { Icon } from "@innoai-tech/vuematerial"; -import { ActionBtn } from "./Actions.tsx"; import copyToClipboard from "copy-to-clipboard"; import { mdiContentCopy } from "@mdi/js"; -import { Tooltip } from "./Tooltip.tsx"; +import { ActionBtn, Tooltip } from "../views"; export const CopyAsJSONIconBtn = component$<{ value?: any; diff --git a/nodepkg/jsoneditor/src/actions/index.ts b/nodepkg/jsoneditor/src/actions/index.ts new file mode 100644 index 00000000..2209e88b --- /dev/null +++ b/nodepkg/jsoneditor/src/actions/index.ts @@ -0,0 +1 @@ +export * from "./JSONRaw.tsx" \ No newline at end of file diff --git a/nodepkg/jsoneditor/src/index.ts b/nodepkg/jsoneditor/src/index.ts index 6589d320..fb05aa08 100644 --- a/nodepkg/jsoneditor/src/index.ts +++ b/nodepkg/jsoneditor/src/index.ts @@ -1,3 +1,4 @@ +export * from "./inputs"; export * from "./models"; export * from "./views"; export * from "./JSONEditorView.tsx"; diff --git a/nodepkg/jsoneditor/src/inputs/AnyInput.tsx b/nodepkg/jsoneditor/src/inputs/AnyInput.tsx new file mode 100644 index 00000000..63febfc1 --- /dev/null +++ b/nodepkg/jsoneditor/src/inputs/AnyInput.tsx @@ -0,0 +1,26 @@ +import { component$, type Context, rx, t, type Type } from "@innoai-tech/vuekit"; +import { isArray, isObject } from "@innoai-tech/lodash"; +import { ArrayInput } from "./ArrayInput.tsx"; +import { RecordInput } from "./RecordInput.tsx"; +import { ValueInput } from "./ValueInput.tsx"; + +export const AnyInput = component$<{ + typedef: Type; + ctx: Context; + value: string | boolean | number | null | undefined; +}>((props, { render }) => { + return rx( + props.value$, + render((value) => { + if (isArray(value)) { + return ; + } + + if (isObject(value)) { + return ; + } + + return ; + }) + ); +}); diff --git a/nodepkg/jsoneditor/src/views/ArrayInput.tsx b/nodepkg/jsoneditor/src/inputs/ArrayInput.tsx similarity index 86% rename from nodepkg/jsoneditor/src/views/ArrayInput.tsx rename to nodepkg/jsoneditor/src/inputs/ArrayInput.tsx index 48aedb1a..f61d0640 100644 --- a/nodepkg/jsoneditor/src/views/ArrayInput.tsx +++ b/nodepkg/jsoneditor/src/inputs/ArrayInput.tsx @@ -6,24 +6,16 @@ import { Schema, subscribeUntilUnmount, type Type, - type VNodeChild, + type VNodeChild } from "@innoai-tech/vuekit"; import { Icon } from "@innoai-tech/vuematerial"; import { mdiCancel, mdiCheckBold, mdiMinusBoxOutline } from "@mdi/js"; -import { Block, Line, PropName } from "./TokenView.tsx"; import { JSONEditorProvider, JSONEditorSlotsProvider } from "../models"; -import { ActionBtn, Actions } from "./Actions.tsx"; -import { CopyAsJSONIconBtn } from "./JSONRaw.tsx"; -import { Tooltip } from "./Tooltip.tsx"; import { Box, Popper } from "@innoai-tech/vueuikit"; -import { - InputActionSubject, - InputText, - ValueContainer, - ValueInputActions, -} from "./ValueInput.tsx"; +import { InputActionSubject, InputText, ValueContainer, ValueInputActions } from "./ValueInput.tsx"; import { tap } from "rxjs"; -import { PopupStatus } from "./Menu.tsx"; +import { CopyAsJSONIconBtn } from "../actions"; +import { ActionBtn, Actions, Block, Line, PopupStatus, PropName, Tooltip } from "../views"; export const ArrayInput = component$<{ ctx: Context; @@ -55,7 +47,7 @@ export const ArrayInput = component$<{ (values: any) => { values.push(value); }, - [], + [] ); }} /> @@ -81,15 +73,15 @@ export const ArrayInput = component$<{ {slots.$value?.(propSchema, itemValue, { ...props.ctx, path: path, - branch: [...props.ctx.branch, itemValue], + branch: [...props.ctx.branch, itemValue] })} ); - }, + } )} ); - }), + }) ); }); @@ -121,12 +113,10 @@ const AddItemIconBtn = component$<{ const items = Schema.schemaProp(props.typedef, "items") as Type; const [err, value] = items.validate(inputValue, { coerce: true }); - if (!!err) { editor$.setError(props.ctx.path, err); return; } - emit("add", value); } else { emit("add", undefined); @@ -137,17 +127,27 @@ const AddItemIconBtn = component$<{ rx( inputText$, tap((input) => { - if (input.trim().startsWith("[")) { + const raw = input.trim(); + + if (raw.startsWith("[") && raw.endsWith("]")) { try { - const v = JSON.parse(input); + const v = JSON.parse(raw); editor$.update(props.ctx.path, v); reset(); } catch (err) { editor$.setError(props.ctx.path, "无效的 JSON 字符串"); } } + + if (raw.startsWith("{") && raw.endsWith("}")) { + try { + commit(JSON.parse(raw)); + } catch (err) { + editor$.setError(props.ctx.path, "无效的 JSON 字符串"); + } + } }), - subscribeUntilUnmount(), + subscribeUntilUnmount() ); rx( @@ -162,7 +162,7 @@ const AddItemIconBtn = component$<{ break; } }), - subscribeUntilUnmount(), + subscribeUntilUnmount() ); const $input = rx( @@ -196,7 +196,7 @@ const AddItemIconBtn = component$<{ /> ); - }), + }) ); return () => ( diff --git a/nodepkg/jsoneditor/src/views/ObjectInput.tsx b/nodepkg/jsoneditor/src/inputs/ObjectInput.tsx similarity index 91% rename from nodepkg/jsoneditor/src/views/ObjectInput.tsx rename to nodepkg/jsoneditor/src/inputs/ObjectInput.tsx index 3353375d..4bae4862 100644 --- a/nodepkg/jsoneditor/src/views/ObjectInput.tsx +++ b/nodepkg/jsoneditor/src/inputs/ObjectInput.tsx @@ -8,23 +8,27 @@ import { Schema, subscribeUntilUnmount, type Type, - type VNodeChild, + type VNodeChild } from "@innoai-tech/vuekit"; import { Popper, styled } from "@innoai-tech/vueuikit"; import { Icon } from "@innoai-tech/vuematerial"; import { mdiCancel, mdiCheckBold, mdiMinusBoxOutline } from "@mdi/js"; -import { Block, Description, Line, PropName, Token } from "./TokenView.tsx"; -import { JSONEditorProvider, JSONEditorSlotsProvider } from "../models"; -import { Menu, MenuItem, PopupStatus } from "./Menu.tsx"; -import { ActionBtn, Actions } from "./Actions.tsx"; -import { CopyAsJSONIconBtn } from "./JSONRaw.tsx"; -import { Tooltip } from "./Tooltip.tsx"; import { - InputActionSubject, - InputText, - ValueContainer, - ValueInputActions, -} from "./ValueInput.tsx"; + ActionBtn, + Actions, + Block, + Description, + Line, + Menu, + MenuItem, + PopupStatus, + PropName, + Token, + Tooltip +} from "../views"; +import { JSONEditorProvider, JSONEditorSlotsProvider } from "../models"; +import { CopyAsJSONIconBtn } from "../actions"; +import { InputActionSubject, InputText, ValueContainer, ValueInputActions } from "./ValueInput.tsx"; import { combineLatest, tap } from "rxjs"; export const RemovePropIconBtn = component$<{ @@ -74,7 +78,7 @@ export const ObjectInput = component$<{ .map(([propName, _propValue, propSchema]) => { return { propName: String(propName), - typedef: propSchema, + typedef: propSchema }; })} /> @@ -111,15 +115,15 @@ export const ObjectInput = component$<{ {slots.$value?.(propSchema, propValue, { ...props.ctx, path: path, - branch: [...props.ctx.branch, propValue], + branch: [...props.ctx.branch, propValue] })} ); - }, + } )} ); - }), + }) ); }); @@ -140,7 +144,7 @@ export const PropValueInput = component$<{ const editor$ = JSONEditorProvider.use(); const selectFocus$ = new ImmerBehaviorSubject({ - index: 0, + index: 0 }); const reset = () => { @@ -154,16 +158,18 @@ export const PropValueInput = component$<{ } selectFocus$.next({ - index: 0, + index: 0 }); }; rx( inputText$, tap((input) => { - if (input.trim().startsWith("{")) { + const raw = input.trim(); + + if (raw.startsWith("{") && raw.endsWith("}")) { try { - const v = JSON.parse(input); + const v = JSON.parse(raw); editor$.update(props.ctx.path, v); reset(); } catch (err) { @@ -171,7 +177,7 @@ export const PropValueInput = component$<{ } } }), - subscribeUntilUnmount(), + subscribeUntilUnmount() ); const commit = (prop?: string) => { @@ -208,7 +214,7 @@ export const PropValueInput = component$<{ break; } }), - subscribeUntilUnmount(), + subscribeUntilUnmount() ); const $inputForProp = rx( @@ -222,7 +228,7 @@ export const PropValueInput = component$<{ data-options={isOpen} /> ); - }), + }) ); return rx( @@ -296,7 +302,7 @@ export const PropValueInput = component$<{ ); - }), + }) ); }); @@ -320,6 +326,6 @@ const PropMenuItem = component<{ const AddPropMenuItemContainer = styled(MenuItem)({ [`& ${PropName}`]: { - textAlign: "left", - }, + textAlign: "left" + } }); diff --git a/nodepkg/jsoneditor/src/views/RecordInput.tsx b/nodepkg/jsoneditor/src/inputs/RecordInput.tsx similarity index 85% rename from nodepkg/jsoneditor/src/views/RecordInput.tsx rename to nodepkg/jsoneditor/src/inputs/RecordInput.tsx index 206eea18..3ebe6a6c 100644 --- a/nodepkg/jsoneditor/src/views/RecordInput.tsx +++ b/nodepkg/jsoneditor/src/inputs/RecordInput.tsx @@ -1,9 +1,8 @@ import { component$, type Context, rx, type Type } from "@innoai-tech/vuekit"; -import { Block, Line, PropName, Token } from "./TokenView.tsx"; import { JSONEditorProvider, JSONEditorSlotsProvider } from "../models"; -import { Actions } from "./Actions.tsx"; -import { CopyAsJSONIconBtn } from "./JSONRaw.tsx"; import { PropValueInput, RemovePropIconBtn } from "./ObjectInput.tsx"; +import { CopyAsJSONIconBtn } from "../actions"; +import { Actions, Block, Line, PropName, Token } from "../views"; export const RecordInput = component$<{ ctx: Context; @@ -44,7 +43,10 @@ export const RecordInput = component$<{ const path = [...props.ctx.path, propName]; return ( - + ); - }, + } )} ); - }), + }) ); }); diff --git a/nodepkg/jsoneditor/src/views/ValueInput.tsx b/nodepkg/jsoneditor/src/inputs/ValueInput.tsx similarity index 88% rename from nodepkg/jsoneditor/src/views/ValueInput.tsx rename to nodepkg/jsoneditor/src/inputs/ValueInput.tsx index 9b600f10..3029bc65 100644 --- a/nodepkg/jsoneditor/src/views/ValueInput.tsx +++ b/nodepkg/jsoneditor/src/inputs/ValueInput.tsx @@ -10,7 +10,7 @@ import { Schema, subscribeUntilUnmount, tapEffect, - type Type, + type Type } from "@innoai-tech/vuekit"; import { JSONEditorProvider } from "../models"; import { @@ -23,15 +23,13 @@ import { Observable, Subject, switchMap, - tap, + tap } from "rxjs"; import { alpha, Popper, styled, variant } from "@innoai-tech/vueuikit"; import { Icon } from "@innoai-tech/vuematerial"; import { mdiCancel, mdiCheckBold } from "@mdi/js"; -import { ActionBtn } from "./Actions.tsx"; -import { Menu, MenuItem, PopupStatus } from "./Menu.tsx"; -import { Description, PropName } from "./TokenView.tsx"; import { isUndefined } from "@innoai-tech/lodash"; +import { ActionBtn, Description, Menu, MenuItem, PopupStatus, PropName } from "../views"; export class InputText extends BehaviorSubject { static from(inputEl$: Observable) { @@ -48,10 +46,10 @@ export class InputText extends BehaviorSubject { fromEvent(inputEl, "input"), tap((e) => { input$.next((e.target as HTMLInputElement).value.trim()); - }), + }) ); }), - subscribeUntilUnmount(), + subscribeUntilUnmount() ); return input$; @@ -60,15 +58,15 @@ export class InputText extends BehaviorSubject { export type InputAction = | { - type: "COMMIT"; - } + type: "COMMIT"; +} | { - type: "CANCEL"; - } + type: "CANCEL"; +} | { - type: "SELECT"; - direction: number; - }; + type: "SELECT"; + direction: number; +}; export class InputActionSubject extends Subject { static from(inputEl$: Observable) { @@ -89,7 +87,7 @@ export class InputActionSubject extends Subject { e.preventDefault(); inputAction$.next({ type: "COMMIT" }); } - }), + }) ), rx( fromEvent(inputEl, "keydown"), @@ -110,7 +108,7 @@ export class InputActionSubject extends Subject { inputAction$.next({ type: "SELECT", direction: -1 }); break; } - }), + }) ), rx( fromEvent(inputEl, "keyup"), @@ -118,11 +116,11 @@ export class InputActionSubject extends Subject { if (e.key === "Escape") { inputAction$.next({ type: "CANCEL" }); } - }), - ), + }) + ) ); }), - subscribeUntilUnmount(), + subscribeUntilUnmount() ); return inputAction$; @@ -136,7 +134,7 @@ class OneEditing extends Observable { value$: Observable; editing$: PopupStatus; path: () => Array; - }, + } ) { return rx( merge( @@ -146,7 +144,7 @@ class OneEditing extends Observable { if (p && p == JSONPointer.create(opt.path())) { opt.editing$.show(); } - }), + }) ), rx( @@ -156,7 +154,7 @@ class OneEditing extends Observable { if (!editing) { oneEditing$.disable(opt.path()); } - }), + }) ), rx( @@ -166,10 +164,10 @@ class OneEditing extends Observable { if (isUndefined(value) && !anyEditing) { oneEditing$.enable(opt.path()); } - }), - ), + }) + ) ), - subscribeUntilUnmount(), + subscribeUntilUnmount() ); } @@ -202,12 +200,13 @@ export const ValueInput = component$<{ typedef: Type; ctx: Context; value: string | boolean | number | null | undefined; + allowRawJSON?: boolean }>((props, { render }) => { const actionsEl$ = observableRef(null); const containerEl$ = observableRef(null); const inputEl$ = observableRef(null); - const inputAction$ = InputActionSubject.from(inputEl$); + const inputText$ = InputText.from(inputEl$); const oneEditing$ = OneEditingProvider.use(); const editing$ = PopupStatus.from(inputEl$); @@ -216,7 +215,7 @@ export const ValueInput = component$<{ OneEditing.sync(oneEditing$, { editing$, value$: props.value$, - path: () => props.ctx.path, + path: () => props.ctx.path }); const selectedIndex = () => { @@ -233,11 +232,16 @@ export const ValueInput = component$<{ }; const selectFocus$ = new ImmerBehaviorSubject({ - index: selectedIndex(), + index: selectedIndex() }); - const cancel = () => { + const reset = () => { + inputText$.next(""); editing$.hide(); + }; + + const cancel = () => { + reset(); selectFocus$.next({ index: selectedIndex() }); @@ -268,9 +272,29 @@ export const ValueInput = component$<{ editor$.update(props.ctx.path, v); - editing$.hide(); + reset(); }; + if (props.allowRawJSON) { + rx( + inputText$, + tap((input) => { + const raw = input.trim(); + + if ((raw.startsWith("{") && raw.endsWith("}")) || raw.startsWith("[") && raw.endsWith("]")) { + try { + const v = JSON.parse(raw); + editor$.update(props.ctx.path, v); + reset(); + } catch (err) { + editor$.setError(props.ctx.path, "无效的 JSON 字符串"); + } + } + }), + subscribeUntilUnmount() + ); + } + rx( inputAction$, tap((action) => { @@ -288,7 +312,7 @@ export const ValueInput = component$<{ break; } }), - subscribeUntilUnmount(), + subscribeUntilUnmount() ); let containerHeight: number | undefined; @@ -314,8 +338,9 @@ export const ValueInput = component$<{ }; } - return () => {}; - }), + return () => { + }; + }) ), rx( @@ -330,7 +355,7 @@ export const ValueInput = component$<{ inputEl.selectionEnd = inputEl.value.length; } } - }), + }) ), rx( @@ -354,7 +379,7 @@ export const ValueInput = component$<{ if (e.relatedTarget) { if ( containerEl$.value?.contains( - e.relatedTarget as HTMLElement, + e.relatedTarget as HTMLElement ) || actionsEl$.value?.contains(e.relatedTarget as HTMLElement) ) { @@ -364,22 +389,23 @@ export const ValueInput = component$<{ e.preventDefault(); commit(inputEl.value); - }), + }) ), rx( fromEvent(inputEl, "input"), tap((e) => { updateHeight(e.target as HTMLTextAreaElement); - }), - ), + inputText$.next((e.target as HTMLTextAreaElement).value); + }) + ) ); } return EMPTY; - }), - ), + }) + ) ), - subscribeUntilUnmount(), + subscribeUntilUnmount() ); if (props.typedef.type == "enums") { @@ -431,7 +457,7 @@ export const ValueInput = component$<{ )} ); - }), + }) ); } @@ -482,7 +508,7 @@ export const ValueInput = component$<{ )} ); - }), + }) ); }); @@ -495,7 +521,7 @@ export const ValueInputActions = styled("div")({ roundedRight: "sm", display: "flex", px: 2, - ml: -4, + ml: -4 }); export const ValueContainer = styled("div")({ @@ -522,8 +548,8 @@ export const ValueContainer = styled("div")({ _hover: { textOverflow: "clip", whiteSpace: "normal", - wordBreak: "break-all", - }, + wordBreak: "break-all" + } }, "& textarea,input": { @@ -545,31 +571,31 @@ export const ValueContainer = styled("div")({ resize: "none", "&[data-options]:focus": { - roundedBottom: 0, - }, + roundedBottom: 0 + } }, _type__string: { - color: "sys.primary", + color: "sys.primary" }, _type__number: { - color: "sys.primary", + color: "sys.primary" }, _type__boolean: { - color: "sys.warning", + color: "sys.warning" }, _type__undefined: { - color: "sys.error", - }, + color: "sys.error" + } }); const EnumMenuItemContainer = styled(MenuItem)({ [`& ${PropName}`]: { - textAlign: "left", - }, + textAlign: "left" + } }); const EnumMenuItem = component<{ diff --git a/nodepkg/jsoneditor/src/inputs/index.ts b/nodepkg/jsoneditor/src/inputs/index.ts new file mode 100644 index 00000000..7890c153 --- /dev/null +++ b/nodepkg/jsoneditor/src/inputs/index.ts @@ -0,0 +1,5 @@ +export * from "./ObjectInput.tsx"; +export * from "./ArrayInput.tsx"; +export * from "./RecordInput.tsx"; +export * from "./ValueInput.tsx"; +export * from "./AnyInput.tsx"; \ No newline at end of file diff --git a/nodepkg/jsoneditor/src/views/TokenView.tsx b/nodepkg/jsoneditor/src/views/TokenView.tsx index f3080acb..3ebde2cb 100644 --- a/nodepkg/jsoneditor/src/views/TokenView.tsx +++ b/nodepkg/jsoneditor/src/views/TokenView.tsx @@ -7,10 +7,14 @@ import { ref, rx, Teleport, - type VNodeChild, + type VNodeChild } from "@innoai-tech/vuekit"; import { JSONEditorProvider } from "../models"; import { Actions, ActionToolbar } from "./Actions.tsx"; +import { Icon } from "@innoai-tech/vuematerial"; +import { Markdown } from "@innoai-tech/vuemarkdown"; +import { mdiHelpBox } from "@mdi/js"; +import { Tooltip } from "./Tooltip.tsx"; export const Token = styled("span")({ textStyle: "sys.label-small", @@ -21,7 +25,7 @@ export const Token = styled("span")({ wordBreak: "keep-all", whiteSpace: "nowrap", font: "code", - opacity: 0.3, + opacity: 0.3 }); export const PropName = component$<{ @@ -49,7 +53,7 @@ export const PropName = component$<{ const PropLeading = styled(ActionToolbar)({ position: "absolute", - ml: -28, + ml: -28 }); const PropNameView = styled("div")({ @@ -68,34 +72,40 @@ const PropNameView = styled("div")({ lineHeight: 24, _deprecated: { - textDecoration: "line-through", + textDecoration: "line-through" }, _optional: { - "&:after": { content: `"?"`, color: "sys.secondary", opacity: 0.58 }, - }, + "&:after": { content: `"?"`, color: "sys.secondary", opacity: 0.58 } + } }); export const LineTitle = styled("div")({ position: "absolute", + zIndex: 1, left: 0, - display: "block", opacity: 0.58, fontSize: 10, lineHeight: 10, top: 0, - "&::before": { - content: `"// "`, - font: "code", - }, + "& > *": { + display: "flex", + alignItems: "center", + gap: 4, + + "&::before": { + content: `"// "`, + font: "code" + } + } }); export const LineError = styled("div")({ display: "block", fontSize: 10, lineHeight: 14, - color: "sys.error", + color: "sys.error" }); export const LineContainer = styled("div")({ @@ -106,43 +116,43 @@ export const LineContainer = styled("div")({ containerStyle: "sys.surface-container", "& [data-visible-on-hover]": { - visibility: "visible", - }, + visibility: "visible" + } }, "&:focus-within": { - containerStyle: "sys.surface-container", + containerStyle: "sys.surface-container" }, _dirty: { - bgColor: variant("sys.warning-container", alpha(0.38)), + bgColor: variant("sys.warning-container", alpha(0.38)) }, _error: { - bgColor: "sys.error-container", + bgColor: "sys.error-container" }, [`&:has(${Actions})`]: { [`& > ${LineError}`]: { - display: "none", + display: "none" }, _error: { - bgColor: "inherit", - }, - }, + bgColor: "inherit" + } + } }); export const LayoutContextProvider = createProvider( () => { return { indent: 0, - $container: ref(null), + $container: ref(null) }; }, { - name: "IntentContext", - }, + name: "IntentContext" + } ); export const Block = component<{ @@ -169,7 +179,7 @@ export const Block = component<{ {slots.trailing?.()} @@ -179,7 +189,7 @@ export const Block = component<{ {props.closeToken} @@ -189,7 +199,7 @@ export const Block = component<{ {slots.default?.()} @@ -221,23 +231,37 @@ export const Line = component$<{ data-error={hasError} data-dirty={props.dirty} style={{ - paddingLeft: `${ctx.indent * 32}px`, + paddingLeft: `${ctx.indent * 32}px` }} > - {props.title && ( + {(props.title || props.description) && ( - {props.title} + {props.description ? ( + + + + }> +
+ {props.title} +
+
+ ) : ( + + {props.title} + + )}
)} {slots.default?.()} {hasError && {`${errors[pointer]}`}} ); - }), + }) ); return () => {$line}; @@ -252,9 +276,26 @@ const LinesTrailing = styled("div")({}); export const LineRow = styled("div")({ display: "flex", alignItems: "start", - pr: 10, + pr: 10 }); export const Description = styled("span")({ display: "block", + + textStyle: "sys.body-small", + + "& p": { + my: 1, + + wordBreak: "keep-all", + whiteSpace: "nowrap", + opacity: 0.7 + }, + + "& code": { + wordBreak: "keep-all", + whiteSpace: "nowrap" + } }); + + diff --git a/nodepkg/jsoneditor/src/views/index.ts b/nodepkg/jsoneditor/src/views/index.ts index 15c06a7a..140c2927 100644 --- a/nodepkg/jsoneditor/src/views/index.ts +++ b/nodepkg/jsoneditor/src/views/index.ts @@ -1,6 +1,5 @@ export * from "./TokenView.tsx"; export * from "./Menu.tsx"; -export * from "./ObjectInput.tsx"; -export * from "./ArrayInput.tsx"; -export * from "./RecordInput.tsx"; -export * from "./ValueInput.tsx"; +export * from "./Actions.tsx"; +export * from "./Tooltip.tsx"; +export * from "./Form.tsx"; diff --git a/nodepkg/typedef/package.json b/nodepkg/typedef/package.json index 03842d7b..fc08bf32 100644 --- a/nodepkg/typedef/package.json +++ b/nodepkg/typedef/package.json @@ -10,7 +10,7 @@ "immer": "^10.1.1" }, "devDependencies": { - "reflect-metadata": "^0.2.x" + "reflect-metadata": "^0.2.2" }, "peerDependencies": {}, "exports": { diff --git a/nodepkg/typedef/src/core/TypeUnknown.ts b/nodepkg/typedef/src/core/TypeUnknown.ts index 8742330c..ba01343d 100644 --- a/nodepkg/typedef/src/core/TypeUnknown.ts +++ b/nodepkg/typedef/src/core/TypeUnknown.ts @@ -13,17 +13,16 @@ import { type TypeDefineObject, TypedError, type TypeModifier, - validate, + validate } from "./Type.ts"; import { Schema } from "./Schema.ts"; import { isString } from "./util.ts"; export class TypeUnknown - implements Type -{ + implements Type { static define = defineType( ( - validator: (value: unknown, ctx: Context) => Result = () => true, + validator: (value: unknown, ctx: Context) => Result = () => true ): Type => { class CustomType extends TypeUnknown { override validator(value: unknown, ctx: Context): Result { @@ -32,7 +31,7 @@ export class TypeUnknown } return new CustomType(null); - }, + } ); static fromTypeObject = (x: TypeDefineObject, baseType?: Type) => { @@ -81,12 +80,14 @@ export class TypeUnknown if (t.startsWith("{") && t.endsWith("}")) { try { return JSON.parse(t); - } catch (e) {} + } catch (e) { + } } if (t.startsWith("[") && t.endsWith("]")) { try { return JSON.parse(t); - } catch (e) {} + } catch (e) { + } } } } @@ -94,17 +95,18 @@ export class TypeUnknown return value as T; } - *entries( + * entries( _value: unknown, - _context: Context = EmptyContext, - ): Iterable {} + _context: Context = EmptyContext + ): Iterable { + } public validate( value: unknown, options: { coerce?: boolean; message?: string; - } = {}, + } = {} ): [TypedError, undefined] | [undefined, T] { return validate(value, this, options); } @@ -143,24 +145,24 @@ export class TypeUnknown export class TypeWrapper extends TypeUnknown { static of>( t: U, - extra: ExtraSchema, + extra: ExtraSchema ): Type, MergeSchema, ExtraSchema>> { return new TypeWrapper, ExtraSchema>({ ...extra, - [Schema.underlying]: t, + [Schema.underlying]: t }); } static refine>( t: U, refiner: (v: Infer, ctx: Context) => Result, - schema: S, + schema: S ): Type, MergeSchema, S>> { class Refiner< U extends Type, S extends Record, > extends TypeWrapper, S> { - override *refiner(value: Infer, ctx: Context): Result { + override* refiner(value: Infer, ctx: Context): Result { yield* this.unwrap.refiner(value, ctx); const result = refiner(value, ctx); @@ -174,7 +176,7 @@ export class TypeWrapper extends TypeUnknown { return new Refiner({ ...schema, - [Schema.underlying]: t, + [Schema.underlying]: t }); } @@ -188,13 +190,13 @@ export class TypeWrapper extends TypeUnknown { return this.unwrap.type; } - override *entries( + override* entries( value: unknown, - context: Context = EmptyContext, + context: Context = EmptyContext ): Iterable { yield* this.unwrap.entries(value, { ...context, - node: { current: this, parent: context.node }, + node: { current: this, parent: context.node } }); } @@ -203,7 +205,7 @@ export class TypeWrapper extends TypeUnknown { this.unwrap.validator(value, context), context, this, - value, + value ); } @@ -212,7 +214,7 @@ export class TypeWrapper extends TypeUnknown { this.unwrap.refiner(value, context), context, this, - value, + value ); } @@ -228,18 +230,18 @@ export class DefaultedType extends TypeWrapper< static create = defineType( ( t: T, - defaultValue: Infer, + defaultValue: Infer ): Type< Infer | undefined, InferSchema & { - default: Infer; - } + default: Infer; + } > => { return new DefaultedType({ default: defaultValue, - [Schema.underlying]: t, + [Schema.underlying]: t }); - }, + } ); override coercer(value: unknown, context: Context): Infer | undefined { @@ -257,9 +259,9 @@ export class OptionalType extends TypeWrapper< (t: T): Type | undefined, InferSchema> => { return new OptionalType({ [Schema.underlying]: t, - [Schema.optional]: t, + [Schema.optional]: t }); - }, + } ); override refiner(value: T | undefined, context: Context): Result { diff --git a/nodepkg/vuematerial/src/Icons/Icon.tsx b/nodepkg/vuematerial/src/Icons/Icon.tsx index 514274c5..8b733f29 100644 --- a/nodepkg/vuematerial/src/Icons/Icon.tsx +++ b/nodepkg/vuematerial/src/Icons/Icon.tsx @@ -3,17 +3,35 @@ import { styled } from "@innoai-tech/vueuikit"; export const Icon = styled< { path: string; + size?: number; placement?: "start" | "end"; }, "span" >("span", (props, _) => { - return (Wrapper) => ( - - - - - - ); + return (Wrapper) => { + const size = props.size ?? 24; + + return ( + + + + + + ); + }; })({ - boxSize: "1.2em", + display: "inline-block", + + _data_has_size__false: { + boxSize: "1.2em", + + "& svg": { + w: "100%", + h: "100%" + } + } }); diff --git a/package.json b/package.json index 97b55aac..37255a75 100644 --- a/package.json +++ b/package.json @@ -22,14 +22,14 @@ "prettier": {}, "dependencies": { "@innoai-tech/fetcher": "^0.5.5", + "@innoai-tech/jsoneditor": "workspace:*", "@innoai-tech/lodash": "^0.2.5", "@innoai-tech/vuekit": "workspace:*", - "@innoai-tech/jsoneditor": "workspace:*", "@innoai-tech/vueuikit": "workspace:*", "rxjs": "^7.8.1" }, "devDependencies": { - "@happy-dom/global-registrator": "^15.10.1", + "@happy-dom/global-registrator": "^15.11.6", "@innoai-tech/config": "^0.5.7", "@innoai-tech/devconfig": "^0.5.0", "@innoai-tech/monobundle": "^0.14.5", @@ -42,13 +42,13 @@ "core-js": "^3.39.0", "normalize.css": "^8.0.1", "prettier": "^3.3.3", - "turbo": "^2.2.3", + "turbo": "^2.3.0", "typescript": "^5.6.3", - "vite": "^5.4.10", + "vite": "^5.4.11", "vue": "^3.5.12" }, "resolutions": { - "vite": "5.4.10", + "vite": "5.4.11", "vue": "3.5.12" } } diff --git a/webapp/openapi-playground/mod/openapi/RequestBuilder.tsx b/webapp/openapi-playground/mod/openapi/RequestBuilder.tsx index 6a9a94ff..08b187fb 100644 --- a/webapp/openapi-playground/mod/openapi/RequestBuilder.tsx +++ b/webapp/openapi-playground/mod/openapi/RequestBuilder.tsx @@ -49,6 +49,10 @@ export const RequestBuilder = component$<{ x = x.optional(); } + x.use( + f.hint(p.schema["title"]) + ); + if (["object", "array"].includes(p.schema.type ?? "")) { propSchemas[p.name] = x.use(f.inputBy(JSONEditorInput)); } else { @@ -147,11 +151,10 @@ export const RequestBuilder = component$<{ py: 24, display: "flex", flexDirection: "column", - gap: 16, + gap: 32, height: "100%", overflow: "auto" }}> - {[...form$.fields(form$.typedef)].map((f) => { return ( diff --git a/webapp/openapi-playground/mod/openapi/SchemaView.tsx b/webapp/openapi-playground/mod/openapi/SchemaView.tsx index fe4af2a6..90311db6 100644 --- a/webapp/openapi-playground/mod/openapi/SchemaView.tsx +++ b/webapp/openapi-playground/mod/openapi/SchemaView.tsx @@ -2,14 +2,17 @@ import { component, component$, createProvider, - t, RouterLink, - type VNodeChild, - type Type, Schema + Schema, + t, + type Type, + type VNodeChild } from "@innoai-tech/vuekit"; import { isUndefined } from "@innoai-tech/lodash"; import { styled } from "@innoai-tech/vueuikit"; import { Markdown } from "@innoai-tech/vuemarkdown"; +import { Icon, Tooltip } from "@innoai-tech/vuematerial"; +import { mdiHelpBox } from "@mdi/js"; export const Token = styled("div")({ display: "inline-table", @@ -72,31 +75,57 @@ export const Description = styled<{ schema: Type }, "div">("div", (props, {}) => { return (Root) => { + const title = Schema.metaProp(props.schema, "title") ?? ""; const description = Schema.metaProp(props.schema, "description") ?? ""; - if (description.length == 0) { + if (!(title || description)) { return null; } return ( - + {title} {description ? ( + + + + }> + + + ) : null} ); }; } )({ position: "relative", - pt: 4, + pt: 8, + display: "flex", + alignItems: "center", + gap: 4, + + "&::before": { + content: `"// "`, + fontFamily: "code" + }, + + textStyle: "sys.body-small", + fontSize: 10, + lineHeight: 12, + + [`${Icon}`]: { + width: 12, + height: 12, + overflow: "hidden" + } +}); + +const MarkdownContainer = styled("div")({ + textStyle: "sys.body-small", "& p": { my: 1, - "&::before": { - content: `"// "`, - fontFamily: "code" - }, - wordBreak: "keep-all", whiteSpace: "nowrap", opacity: 0.7 @@ -105,11 +134,7 @@ export const Description = styled<{ "& code": { wordBreak: "keep-all", whiteSpace: "nowrap" - }, - - textStyle: "sys.body-small", - fontSize: 10, - lineHeight: 12 + } });