feat: Implement view management features with a new UI for editing and listing views
- Added a resizable panel layout in the studio app to display the view list alongside the main application. - Refactored the studio store to include new methods for fetching and managing route views. - Introduced a new DataItemForm component for configuring data items in views. - Created a ViewEditor component for adding and editing views, including data items and queries. - Enhanced the ViewList component to support searching, adding, editing, and deleting views. - Updated UI components (Button, Checkbox, Dialog, Input, Label, Table) for better styling and functionality. - Added environment configuration for API URL. - Introduced a new workspace configuration for pnpm.
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"nanoid": "^5.1.6"
|
||||
"nanoid": "^5.1.6",
|
||||
"zod": "^4.2.1",
|
||||
"zod-to-json-schema": "^3.25.1"
|
||||
}
|
||||
}
|
||||
|
||||
20
pnpm-lock.yaml
generated
20
pnpm-lock.yaml
generated
@@ -11,6 +11,12 @@ importers:
|
||||
nanoid:
|
||||
specifier: ^5.1.6
|
||||
version: 5.1.6
|
||||
zod:
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1
|
||||
zod-to-json-schema:
|
||||
specifier: ^3.25.1
|
||||
version: 3.25.1(zod@4.2.1)
|
||||
|
||||
packages:
|
||||
|
||||
@@ -19,6 +25,20 @@ packages:
|
||||
engines: {node: ^18 || >=20}
|
||||
hasBin: true
|
||||
|
||||
zod-to-json-schema@3.25.1:
|
||||
resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==}
|
||||
peerDependencies:
|
||||
zod: ^3.25 || ^4
|
||||
|
||||
zod@4.2.1:
|
||||
resolution: {integrity: sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==}
|
||||
|
||||
snapshots:
|
||||
|
||||
nanoid@5.1.6: {}
|
||||
|
||||
zod-to-json-schema@3.25.1(zod@4.2.1):
|
||||
dependencies:
|
||||
zod: 4.2.1
|
||||
|
||||
zod@4.2.1: {}
|
||||
|
||||
1
web/.env.example
Normal file
1
web/.env.example
Normal file
@@ -0,0 +1 @@
|
||||
VITE_API_URL='http://localhost:4005'
|
||||
3
web/.gitignore
vendored
3
web/.gitignore
vendored
@@ -4,3 +4,6 @@ node_modules
|
||||
.astro
|
||||
|
||||
dist
|
||||
|
||||
.env
|
||||
!.env*example
|
||||
@@ -11,6 +11,7 @@ dotenv.config();
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
let target = process.env.VITE_API_URL || 'http://localhost:51515';
|
||||
console.log('API Proxy Target:', target);
|
||||
const apiProxy = { target: target, changeOrigin: true, ws: true, rewriteWsOrigin: true, secure: false, cookieDomainRewrite: 'localhost' };
|
||||
let proxy = {
|
||||
'/root/': apiProxy,
|
||||
|
||||
@@ -23,11 +23,14 @@
|
||||
"@astrojs/react": "^4.4.2",
|
||||
"@astrojs/sitemap": "^3.6.0",
|
||||
"@astrojs/vue": "^5.1.3",
|
||||
"@kevisual/cache": "^0.0.5",
|
||||
"@kevisual/context": "^0.0.4",
|
||||
"@kevisual/query": "^0.0.33",
|
||||
"@kevisual/query-login": "^0.0.7",
|
||||
"@kevisual/registry": "^0.0.1",
|
||||
"@kevisual/router": "^0.0.51",
|
||||
"@kevisual/router": "^0.0.52",
|
||||
"@radix-ui/react-checkbox": "^1.3.3",
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@uiw/react-md-editor": "^4.0.11",
|
||||
@@ -39,13 +42,17 @@
|
||||
"dayjs": "^1.11.19",
|
||||
"es-toolkit": "^1.43.0",
|
||||
"github-markdown-css": "^5.8.1",
|
||||
"handsontable": "^16.2.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"lucide-react": "^0.562.0",
|
||||
"marked": "^17.0.1",
|
||||
"marked-highlight": "^2.2.3",
|
||||
"nanoid": "^5.1.6",
|
||||
"papaparse": "^5.5.3",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
"react-hook-form": "^7.69.0",
|
||||
"react-resizable-panels": "^4.1.0",
|
||||
"react-toastify": "^11.0.5",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"vue": "^3.5.26",
|
||||
@@ -55,7 +62,7 @@
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kevisual/api": "^0.0.10",
|
||||
"@kevisual/api": "^0.0.14",
|
||||
"@kevisual/types": "^0.0.10",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
@@ -63,7 +70,7 @@
|
||||
"tailwindcss": "^4.1.18",
|
||||
"tw-animate-css": "^1.4.0"
|
||||
},
|
||||
"packageManager": "pnpm@10.26.2",
|
||||
"packageManager": "pnpm@10.27.0",
|
||||
"onlyBuiltDependencies": [
|
||||
"@tailwindcss/oxide",
|
||||
"esbuild",
|
||||
|
||||
265
web/pnpm-lock.yaml
generated
265
web/pnpm-lock.yaml
generated
@@ -20,6 +20,9 @@ importers:
|
||||
'@astrojs/vue':
|
||||
specifier: ^5.1.3
|
||||
version: 5.1.3(@types/node@24.7.2)(astro@5.16.6(@types/node@24.7.2)(idb-keyval@6.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.52.4)(typescript@5.9.3))(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.52.4)(vue@3.5.26(typescript@5.9.3))
|
||||
'@kevisual/cache':
|
||||
specifier: ^0.0.5
|
||||
version: 0.0.5
|
||||
'@kevisual/context':
|
||||
specifier: ^0.0.4
|
||||
version: 0.0.4
|
||||
@@ -33,8 +36,14 @@ importers:
|
||||
specifier: ^0.0.1
|
||||
version: 0.0.1(typescript@5.9.3)
|
||||
'@kevisual/router':
|
||||
specifier: ^0.0.51
|
||||
version: 0.0.51
|
||||
specifier: ^0.0.52
|
||||
version: 0.0.52
|
||||
'@radix-ui/react-checkbox':
|
||||
specifier: ^1.3.3
|
||||
version: 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@radix-ui/react-label':
|
||||
specifier: ^2.1.8
|
||||
version: 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@radix-ui/react-slot':
|
||||
specifier: ^1.2.4
|
||||
version: 1.2.4(@types/react@19.2.7)(react@19.2.3)
|
||||
@@ -46,7 +55,7 @@ importers:
|
||||
version: 4.0.11(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
antd:
|
||||
specifier: ^6.1.3
|
||||
version: 6.1.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
version: 6.1.3(moment@2.30.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
astro:
|
||||
specifier: ^5.16.6
|
||||
version: 5.16.6(@types/node@24.7.2)(idb-keyval@6.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.52.4)(typescript@5.9.3)
|
||||
@@ -68,6 +77,9 @@ importers:
|
||||
github-markdown-css:
|
||||
specifier: ^5.8.1
|
||||
version: 5.8.1
|
||||
handsontable:
|
||||
specifier: ^16.2.0
|
||||
version: 16.2.0
|
||||
highlight.js:
|
||||
specifier: ^11.11.1
|
||||
version: 11.11.1
|
||||
@@ -83,12 +95,21 @@ importers:
|
||||
nanoid:
|
||||
specifier: ^5.1.6
|
||||
version: 5.1.6
|
||||
papaparse:
|
||||
specifier: ^5.5.3
|
||||
version: 5.5.3
|
||||
react:
|
||||
specifier: ^19.2.3
|
||||
version: 19.2.3
|
||||
react-dom:
|
||||
specifier: ^19.2.3
|
||||
version: 19.2.3(react@19.2.3)
|
||||
react-hook-form:
|
||||
specifier: ^7.69.0
|
||||
version: 7.69.0(react@19.2.3)
|
||||
react-resizable-panels:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
react-toastify:
|
||||
specifier: ^11.0.5
|
||||
version: 11.0.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
@@ -103,8 +124,8 @@ importers:
|
||||
version: 5.0.9(@types/react@19.2.7)(react@19.2.3)
|
||||
devDependencies:
|
||||
'@kevisual/api':
|
||||
specifier: ^0.0.10
|
||||
version: 0.0.10
|
||||
specifier: ^0.0.14
|
||||
version: 0.0.14
|
||||
'@kevisual/types':
|
||||
specifier: ^0.0.10
|
||||
version: 0.0.10
|
||||
@@ -539,6 +560,9 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@handsontable/pikaday@1.0.0':
|
||||
resolution: {integrity: sha512-1VN6N38t5/DcjJ7y7XUYrDx1LuzvvzlrFdBdMG90Qo1xc8+LXHqbWbsTEm5Ec5gXTEbDEO53vUT35R+2COmOyg==}
|
||||
|
||||
'@img/colour@1.0.0':
|
||||
resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -681,17 +705,20 @@ packages:
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||
|
||||
'@kevisual/api@0.0.10':
|
||||
resolution: {integrity: sha512-AF5DcXPfVEZtvIJw9EC8EXkhU33dS08v9+b4mIrzCi0ETRvwAlQ2cg8WgfY6exJYzbFg6M4h+POhXvPujrk9mA==}
|
||||
'@kevisual/api@0.0.14':
|
||||
resolution: {integrity: sha512-GOs61Jvjxs+7PB8+iSPko9/RGeWENxltHueV75M6W0psRsnx/J+06I48/cO413FwCoqSOqpOoivdRgSENdHM9g==}
|
||||
|
||||
'@kevisual/cache@0.0.3':
|
||||
resolution: {integrity: sha512-BWEck69KYL96/ywjYVkML974RHjDJTj2ITQND1zFPR+hlBV1H1p55QZgSYRJCObg3EAV1S9Zic/fR2T4pfe8yg==}
|
||||
|
||||
'@kevisual/cache@0.0.5':
|
||||
resolution: {integrity: sha512-fgtUYGUUq/DY0KFV4CkWszNqvQUaA8XvMTUjoR9ZXRpau5IIDolD/Wen2TFsZ7G3Rfy+lef5dnaiZVDkZwdVKg==}
|
||||
|
||||
'@kevisual/context@0.0.4':
|
||||
resolution: {integrity: sha512-HJeLeZQLU+7tCluSfOyvkgKLs0HjCZrdJlZgEgKRSa8XTwZfMAUt6J7qZTbrZAHBlPtX68EPu/PI8JMCeu3WAQ==}
|
||||
|
||||
'@kevisual/js-filter@0.0.2':
|
||||
resolution: {integrity: sha512-SS8diRpjrAIEQKT8YMTa1XTucQKuPbG04UChXtp7wd1jPsvQaNKYapErRA8qx4igwoVQt6eAYADwYzXhB1fN2A==}
|
||||
'@kevisual/js-filter@0.0.3':
|
||||
resolution: {integrity: sha512-vgUB2fUAWS75GUFr/a/tGSSDrPUUmVDktO38k3hIKwU3ZE4tpuhcVxrpUbkXlFS5i0rbL2mAQeID1C6kIlMGRg==}
|
||||
|
||||
'@kevisual/load@0.0.6':
|
||||
resolution: {integrity: sha512-+3YTFehRcZ1haGel5DKYMUwmi5i6f2psyaPZlfkKU/cOXgkpwoG9/BEqPCnPjicKqqnksEpixVRkyHJ+5bjLVA==}
|
||||
@@ -707,8 +734,8 @@ packages:
|
||||
'@kevisual/registry@0.0.1':
|
||||
resolution: {integrity: sha512-//OHu9m4JDrMjgP8o8dcjZd3D3IAUkRVlkTSviouZEH7r5m7mccA3Hvzw0XJ/lelx6exC6LWsyv6c4uV0Dp+gw==}
|
||||
|
||||
'@kevisual/router@0.0.51':
|
||||
resolution: {integrity: sha512-i9qYBeS/um78oC912oWJD3iElB+5NTKyTrz1Hzf4DckiUFnjLL81UPwjIh5I2l9+ul0IZ/Pxx+sFSF99fJkzKg==}
|
||||
'@kevisual/router@0.0.52':
|
||||
resolution: {integrity: sha512-Qiv3P1XjzD813Tm79S+atrDb2eickGCI9tuy/aCu512LcoYYJqZhwwkeT4ES0DinnA13Ckqd43QWBR6UmuYkHQ==}
|
||||
|
||||
'@kevisual/types@0.0.10':
|
||||
resolution: {integrity: sha512-Q73uzzjk9UidumnmCvOpgzqDDvQxsblz22bIFuoiioUFJWwaparx8bpd8ArRyFojicYL1YJoFDzDZ9j9NN8grA==}
|
||||
@@ -763,6 +790,19 @@ packages:
|
||||
'@radix-ui/primitive@1.1.3':
|
||||
resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
|
||||
|
||||
'@radix-ui/react-checkbox@1.3.3':
|
||||
resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-compose-refs@1.1.2':
|
||||
resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
|
||||
peerDependencies:
|
||||
@@ -838,6 +878,19 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-label@2.1.8':
|
||||
resolution: {integrity: sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-portal@1.1.9':
|
||||
resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==}
|
||||
peerDependencies:
|
||||
@@ -953,6 +1006,24 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-previous@1.1.1':
|
||||
resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-size@1.1.1':
|
||||
resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@rc-component/async-validator@5.0.4':
|
||||
resolution: {integrity: sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==}
|
||||
engines: {node: '>=14.x'}
|
||||
@@ -1541,6 +1612,9 @@ packages:
|
||||
'@types/sax@1.2.7':
|
||||
resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==}
|
||||
|
||||
'@types/trusted-types@2.0.7':
|
||||
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
||||
|
||||
'@types/unist@2.0.11':
|
||||
resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
|
||||
|
||||
@@ -1741,6 +1815,9 @@ packages:
|
||||
bcp-47-match@2.0.3:
|
||||
resolution: {integrity: sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==}
|
||||
|
||||
bignumber.js@9.3.1:
|
||||
resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==}
|
||||
|
||||
birpc@2.8.0:
|
||||
resolution: {integrity: sha512-Bz2a4qD/5GRhiHSwj30c/8kC8QGj12nNDwz3D4ErQ4Xhy35dsSDvF+RA/tWpjyU0pdGtSDiEk6B5fBGE1qNVhw==}
|
||||
|
||||
@@ -1793,6 +1870,9 @@ packages:
|
||||
character-reference-invalid@2.0.1:
|
||||
resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==}
|
||||
|
||||
chevrotain@6.5.0:
|
||||
resolution: {integrity: sha512-BwqQ/AgmKJ8jcMEjaSnfMybnKMgGTrtDKowfTP3pX4jwVy0kNjRsT/AP6h+wC3+3NC+X8X15VWBnTCQlX+wQFg==}
|
||||
|
||||
chokidar@4.0.3:
|
||||
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
||||
engines: {node: '>= 14.16.0'}
|
||||
@@ -1855,6 +1935,9 @@ packages:
|
||||
resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
core-js@3.47.0:
|
||||
resolution: {integrity: sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==}
|
||||
|
||||
cross-fetch@4.0.0:
|
||||
resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==}
|
||||
|
||||
@@ -1977,6 +2060,9 @@ packages:
|
||||
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
dompurify@3.3.1:
|
||||
resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==}
|
||||
|
||||
domutils@3.2.2:
|
||||
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
|
||||
|
||||
@@ -2157,6 +2243,9 @@ packages:
|
||||
h3@1.15.4:
|
||||
resolution: {integrity: sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==}
|
||||
|
||||
handsontable@16.2.0:
|
||||
resolution: {integrity: sha512-4zhMQON9DPyip/6YIPH2G7jN+QEJ0uabCZruhrhOqTqr3Qf/FDjsTInUaEzMCmhhdii5MbA6PGyLfUad6t1sXA==}
|
||||
|
||||
hast-util-from-html@2.0.3:
|
||||
resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==}
|
||||
|
||||
@@ -2241,6 +2330,9 @@ packages:
|
||||
resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==}
|
||||
engines: {node: '>=18.18.0'}
|
||||
|
||||
hyperformula@3.1.1:
|
||||
resolution: {integrity: sha512-v+yvRPZGL73KinH2lvS4/1QMe2xNviTfgIcVgKjzKGi66xEuvuoDRgQ48ODc4XhD+c+JLNfs9Ln1GnHQ5TDNGA==}
|
||||
|
||||
i18next-browser-languagedetector@8.2.0:
|
||||
resolution: {integrity: sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==}
|
||||
|
||||
@@ -2432,6 +2524,10 @@ packages:
|
||||
lru-cache@10.4.3:
|
||||
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||
|
||||
lru-cache@11.2.4:
|
||||
resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
lru-cache@5.1.1:
|
||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||
|
||||
@@ -2641,6 +2737,9 @@ packages:
|
||||
mitt@3.0.1:
|
||||
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
|
||||
|
||||
moment@2.30.1:
|
||||
resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==}
|
||||
|
||||
mrmime@2.0.1:
|
||||
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -2694,6 +2793,9 @@ packages:
|
||||
nth-check@2.1.1:
|
||||
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
|
||||
|
||||
numbro@2.5.0:
|
||||
resolution: {integrity: sha512-xDcctDimhzko/e+y+Q2/8i3qNC9Svw1QgOkSkQoO0kIPI473tR9QRbo2KP88Ty9p8WbPy+3OpTaAIzehtuHq+A==}
|
||||
|
||||
ofetch@1.5.1:
|
||||
resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==}
|
||||
|
||||
@@ -2732,6 +2834,9 @@ packages:
|
||||
pako@0.2.9:
|
||||
resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==}
|
||||
|
||||
papaparse@5.5.3:
|
||||
resolution: {integrity: sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==}
|
||||
|
||||
parse-entities@4.0.2:
|
||||
resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==}
|
||||
|
||||
@@ -2824,6 +2929,12 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^19.2.3
|
||||
|
||||
react-hook-form@7.69.0:
|
||||
resolution: {integrity: sha512-yt6ZGME9f4F6WHwevrvpAjh42HMvocuSnSIHUGycBqXIJdhqGSPQzTpGF+1NLREk/58IdPxEMfPcFCjlMhclGw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17 || ^18 || ^19
|
||||
|
||||
react-i18next@15.7.4:
|
||||
resolution: {integrity: sha512-nyU8iKNrI5uDJch0z9+Y5XEr34b0wkyYj3Rp+tfbahxtlswxSCjcUL9H0nqXo9IR3/t5Y5PKIA3fx3MfUyR9Xw==}
|
||||
peerDependencies:
|
||||
@@ -2873,6 +2984,12 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
react-resizable-panels@4.1.0:
|
||||
resolution: {integrity: sha512-8ZpOwdKQz6bCs2LGnfS6HuBITxkOLelSMzBX4DrWsgHaU3ukTPxmBNAeK8Bsp3LAEdtXeG6ll6UPN7OJNua4sw==}
|
||||
peerDependencies:
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
react-dom: ^18.0.0 || ^19.0.0
|
||||
|
||||
react-style-singleton@2.2.3:
|
||||
resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -2926,6 +3043,9 @@ packages:
|
||||
regex@6.0.1:
|
||||
resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==}
|
||||
|
||||
regexp-to-ast@0.4.0:
|
||||
resolution: {integrity: sha512-4qf/7IsIKfSNHQXSwial1IFmfM1Cc/whNBQqRwe0V2stPe7KmN1U0tWQiIx6JiirgSrisjE0eECdNf7Tav1Ntw==}
|
||||
|
||||
rehype-attr@3.0.3:
|
||||
resolution: {integrity: sha512-Up50Xfra8tyxnkJdCzLBIBtxOcB2M1xdeKe1324U06RAvSjYm7ULSeoM+b/nYPQPVd7jsXJ9+39IG1WAJPXONw==}
|
||||
engines: {node: '>=16'}
|
||||
@@ -3159,6 +3279,9 @@ packages:
|
||||
resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==}
|
||||
engines: {node: '>=12.22'}
|
||||
|
||||
tiny-emitter@2.1.0:
|
||||
resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==}
|
||||
|
||||
tiny-inflate@1.0.3:
|
||||
resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
|
||||
|
||||
@@ -4037,6 +4160,8 @@ snapshots:
|
||||
'@esbuild/win32-x64@0.25.10':
|
||||
optional: true
|
||||
|
||||
'@handsontable/pikaday@1.0.0': {}
|
||||
|
||||
'@img/colour@1.0.0':
|
||||
optional: true
|
||||
|
||||
@@ -4145,20 +4270,27 @@ snapshots:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
'@kevisual/api@0.0.10':
|
||||
'@kevisual/api@0.0.14':
|
||||
dependencies:
|
||||
'@kevisual/js-filter': 0.0.2
|
||||
'@kevisual/js-filter': 0.0.3
|
||||
'@kevisual/load': 0.0.6
|
||||
es-toolkit: 1.43.0
|
||||
eventemitter3: 5.0.1
|
||||
nanoid: 5.1.6
|
||||
|
||||
'@kevisual/cache@0.0.3':
|
||||
dependencies:
|
||||
idb-keyval: 6.2.2
|
||||
|
||||
'@kevisual/cache@0.0.5':
|
||||
dependencies:
|
||||
idb-keyval: 6.2.2
|
||||
lru-cache: 11.2.4
|
||||
nanoid: 5.1.6
|
||||
|
||||
'@kevisual/context@0.0.4': {}
|
||||
|
||||
'@kevisual/js-filter@0.0.2': {}
|
||||
'@kevisual/js-filter@0.0.3': {}
|
||||
|
||||
'@kevisual/load@0.0.6':
|
||||
dependencies:
|
||||
@@ -4190,8 +4322,9 @@ snapshots:
|
||||
- react-native
|
||||
- typescript
|
||||
|
||||
'@kevisual/router@0.0.51':
|
||||
'@kevisual/router@0.0.52':
|
||||
dependencies:
|
||||
eventemitter3: 5.0.1
|
||||
path-to-regexp: 8.3.0
|
||||
selfsigned: 5.4.0
|
||||
send: 1.2.1
|
||||
@@ -4328,6 +4461,22 @@ snapshots:
|
||||
|
||||
'@radix-ui/primitive@1.1.3': {}
|
||||
|
||||
'@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.3)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.3)
|
||||
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.3)
|
||||
'@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.7)(react@19.2.3)
|
||||
'@radix-ui/react-use-size': 1.1.1(@types/react@19.2.7)(react@19.2.3)
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.7
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.7)
|
||||
|
||||
'@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.7)(react@19.2.3)':
|
||||
dependencies:
|
||||
react: 19.2.3
|
||||
@@ -4399,6 +4548,15 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.7
|
||||
|
||||
'@radix-ui/react-label@2.1.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||
dependencies:
|
||||
'@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.7
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.7)
|
||||
|
||||
'@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||
dependencies:
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
@@ -4485,6 +4643,19 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.7
|
||||
|
||||
'@radix-ui/react-use-previous@1.1.1(@types/react@19.2.7)(react@19.2.3)':
|
||||
dependencies:
|
||||
react: 19.2.3
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.7
|
||||
|
||||
'@radix-ui/react-use-size@1.1.1(@types/react@19.2.7)(react@19.2.3)':
|
||||
dependencies:
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.3)
|
||||
react: 19.2.3
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.7
|
||||
|
||||
'@rc-component/async-validator@5.0.4':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
@@ -4648,7 +4819,7 @@ snapshots:
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
|
||||
'@rc-component/picker@1.9.0(dayjs@1.11.19)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||
'@rc-component/picker@1.9.0(dayjs@1.11.19)(moment@2.30.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||
dependencies:
|
||||
'@rc-component/overflow': 1.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@rc-component/resize-observer': 1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
@@ -4659,6 +4830,7 @@ snapshots:
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
optionalDependencies:
|
||||
dayjs: 1.11.19
|
||||
moment: 2.30.1
|
||||
|
||||
'@rc-component/portal@2.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||
dependencies:
|
||||
@@ -5091,6 +5263,9 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/node': 17.0.45
|
||||
|
||||
'@types/trusted-types@2.0.7':
|
||||
optional: true
|
||||
|
||||
'@types/unist@2.0.11': {}
|
||||
|
||||
'@types/unist@3.0.3': {}
|
||||
@@ -5317,7 +5492,7 @@ snapshots:
|
||||
|
||||
ansi-styles@6.2.3: {}
|
||||
|
||||
antd@6.1.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
|
||||
antd@6.1.3(moment@2.30.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
|
||||
dependencies:
|
||||
'@ant-design/colors': 8.0.0
|
||||
'@ant-design/cssinjs': 2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
@@ -5343,7 +5518,7 @@ snapshots:
|
||||
'@rc-component/mutate-observer': 2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@rc-component/notification': 1.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@rc-component/pagination': 1.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@rc-component/picker': 1.9.0(dayjs@1.11.19)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@rc-component/picker': 1.9.0(dayjs@1.11.19)(moment@2.30.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@rc-component/progress': 1.0.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@rc-component/qrcode': 1.1.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@rc-component/rate': 1.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
@@ -5513,6 +5688,8 @@ snapshots:
|
||||
|
||||
bcp-47-match@2.0.3: {}
|
||||
|
||||
bignumber.js@9.3.1: {}
|
||||
|
||||
birpc@2.8.0: {}
|
||||
|
||||
boolbase@1.0.0: {}
|
||||
@@ -5562,6 +5739,11 @@ snapshots:
|
||||
|
||||
character-reference-invalid@2.0.1: {}
|
||||
|
||||
chevrotain@6.5.0:
|
||||
dependencies:
|
||||
regexp-to-ast: 0.4.0
|
||||
optional: true
|
||||
|
||||
chokidar@4.0.3:
|
||||
dependencies:
|
||||
readdirp: 4.1.2
|
||||
@@ -5612,6 +5794,8 @@ snapshots:
|
||||
dependencies:
|
||||
is-what: 5.5.0
|
||||
|
||||
core-js@3.47.0: {}
|
||||
|
||||
cross-fetch@4.0.0:
|
||||
dependencies:
|
||||
node-fetch: 2.7.0
|
||||
@@ -5719,6 +5903,10 @@ snapshots:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
|
||||
dompurify@3.3.1:
|
||||
optionalDependencies:
|
||||
'@types/trusted-types': 2.0.7
|
||||
|
||||
domutils@3.2.2:
|
||||
dependencies:
|
||||
dom-serializer: 2.0.0
|
||||
@@ -5930,6 +6118,16 @@ snapshots:
|
||||
ufo: 1.6.1
|
||||
uncrypto: 0.1.3
|
||||
|
||||
handsontable@16.2.0:
|
||||
dependencies:
|
||||
'@handsontable/pikaday': 1.0.0
|
||||
core-js: 3.47.0
|
||||
dompurify: 3.3.1
|
||||
moment: 2.30.1
|
||||
numbro: 2.5.0
|
||||
optionalDependencies:
|
||||
hyperformula: 3.1.1
|
||||
|
||||
hast-util-from-html@2.0.3:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
@@ -6126,6 +6324,12 @@ snapshots:
|
||||
|
||||
human-signals@8.0.1: {}
|
||||
|
||||
hyperformula@3.1.1:
|
||||
dependencies:
|
||||
chevrotain: 6.5.0
|
||||
tiny-emitter: 2.1.0
|
||||
optional: true
|
||||
|
||||
i18next-browser-languagedetector@8.2.0:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
@@ -6266,6 +6470,8 @@ snapshots:
|
||||
|
||||
lru-cache@10.4.3: {}
|
||||
|
||||
lru-cache@11.2.4: {}
|
||||
|
||||
lru-cache@5.1.1:
|
||||
dependencies:
|
||||
yallist: 3.1.1
|
||||
@@ -6743,6 +6949,8 @@ snapshots:
|
||||
|
||||
mitt@3.0.1: {}
|
||||
|
||||
moment@2.30.1: {}
|
||||
|
||||
mrmime@2.0.1: {}
|
||||
|
||||
ms@2.1.3: {}
|
||||
@@ -6778,6 +6986,10 @@ snapshots:
|
||||
dependencies:
|
||||
boolbase: 1.0.0
|
||||
|
||||
numbro@2.5.0:
|
||||
dependencies:
|
||||
bignumber.js: 9.3.1
|
||||
|
||||
ofetch@1.5.1:
|
||||
dependencies:
|
||||
destr: 2.0.5
|
||||
@@ -6820,6 +7032,8 @@ snapshots:
|
||||
|
||||
pako@0.2.9: {}
|
||||
|
||||
papaparse@5.5.3: {}
|
||||
|
||||
parse-entities@4.0.2:
|
||||
dependencies:
|
||||
'@types/unist': 2.0.11
|
||||
@@ -6910,6 +7124,10 @@ snapshots:
|
||||
react: 19.2.3
|
||||
scheduler: 0.27.0
|
||||
|
||||
react-hook-form@7.69.0(react@19.2.3):
|
||||
dependencies:
|
||||
react: 19.2.3
|
||||
|
||||
react-i18next@15.7.4(i18next@25.6.0(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
@@ -6960,6 +7178,11 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.7
|
||||
|
||||
react-resizable-panels@4.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
|
||||
dependencies:
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
|
||||
react-style-singleton@2.2.3(@types/react@19.2.7)(react@19.2.3):
|
||||
dependencies:
|
||||
get-nonce: 1.0.1
|
||||
@@ -7026,6 +7249,9 @@ snapshots:
|
||||
dependencies:
|
||||
regex-utilities: 2.3.0
|
||||
|
||||
regexp-to-ast@0.4.0:
|
||||
optional: true
|
||||
|
||||
rehype-attr@3.0.3:
|
||||
dependencies:
|
||||
unified: 11.0.5
|
||||
@@ -7396,6 +7622,9 @@ snapshots:
|
||||
|
||||
throttle-debounce@5.0.2: {}
|
||||
|
||||
tiny-emitter@2.1.0:
|
||||
optional: true
|
||||
|
||||
tiny-inflate@1.0.3: {}
|
||||
|
||||
tinyexec@1.0.2: {}
|
||||
|
||||
2
web/pnpm-workspace.yaml
Normal file
2
web/pnpm-workspace.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
onlyBuiltDependencies:
|
||||
- core-js
|
||||
@@ -2,9 +2,18 @@ import { toast, ToastContainer } from 'react-toastify';
|
||||
import { useStudioStore } from './store.ts';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { MonitorPlay, Play } from 'lucide-react';
|
||||
import { Panel, Group } from 'react-resizable-panels'
|
||||
import { ViewList } from '../view/list.tsx';
|
||||
export const AppProvider = () => {
|
||||
return <main className='w-full'>
|
||||
<App />
|
||||
return <main className='w-full h-screen flex flex-col overflow-hidden'>
|
||||
<Group className="h-full flex-1 overflow-hidden">
|
||||
<Panel defaultSize={300} minSize={250} maxSize={500} className="border-r overflow-auto">
|
||||
<ViewList />
|
||||
</Panel>
|
||||
<Panel>
|
||||
<App />
|
||||
</Panel>
|
||||
</Group>
|
||||
<ToastContainer
|
||||
position="top-right"
|
||||
autoClose={3000}
|
||||
@@ -28,12 +37,12 @@ interface RouteItem {
|
||||
}
|
||||
|
||||
export const App = () => {
|
||||
const { routes, getRoutes, run } = useStudioStore();
|
||||
const { routes, getRouteList, run } = useStudioStore();
|
||||
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set());
|
||||
const [visibleIds, setVisibleIds] = useState<Set<string>>(new Set());
|
||||
|
||||
useEffect(() => {
|
||||
getRoutes();
|
||||
getRouteList();
|
||||
}, []);
|
||||
|
||||
const toggleDescription = (id: string) => {
|
||||
@@ -58,7 +67,7 @@ export const App = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-5xl mx-auto p-6">
|
||||
<div className="max-w-5xl mx-auto p-6 h-full overflow-auto">
|
||||
<div className="space-y-1">
|
||||
{routes.map((route: RouteItem) => {
|
||||
const isExpanded = expandedIds.has(route.id);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { create } from 'zustand';
|
||||
import { QueryProxy, ProxyItem } from '@kevisual/api'
|
||||
// import { query } from '@/modules/query.ts'
|
||||
import { QueryClient } from '@kevisual/query';
|
||||
import { QueryProxy, RouterViewData } from '@kevisual/api'
|
||||
import { query } from '@/modules/query.ts'
|
||||
import { toast } from 'react-toastify';
|
||||
import { QueryRouterServer } from '@kevisual/router/src/route.ts'
|
||||
|
||||
import { use } from '@kevisual/context'
|
||||
import { MyCache } from '@kevisual/cache'
|
||||
|
||||
type RouteItem = {
|
||||
id: string;
|
||||
@@ -13,58 +12,81 @@ type RouteItem = {
|
||||
description?: string;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
// type ProxyItem = {
|
||||
// title?: string;
|
||||
// type?: 'api' | 'context' | 'page';
|
||||
// description?: string;
|
||||
// api?: {
|
||||
// url: string;
|
||||
// },
|
||||
// context?: {
|
||||
// key: string;
|
||||
// },
|
||||
// page?: {},
|
||||
// where?: string;
|
||||
// whereList?: Array<{ title: string; where: string }>;
|
||||
// }
|
||||
|
||||
type RouteViewList = Array<RouterViewData>;
|
||||
|
||||
interface StudioState {
|
||||
routes: Array<RouteItem>;
|
||||
getRoutes: () => Promise<void>;
|
||||
getRouteList: () => Promise<void>;
|
||||
run: (route: RouteItem) => Promise<void>;
|
||||
queryProxy?: QueryProxy;
|
||||
router?: QueryRouterServer;
|
||||
init: (opts?: { url?: string }) => Promise<{ router: QueryRouterServer; queryProxy: QueryProxy }>;
|
||||
|
||||
proxy?: ProxyItem;
|
||||
setProxy?: (proxy: ProxyItem) => void;
|
||||
proxyList?: ProxyItem[];
|
||||
setProxyList?: (list: ProxyItem[]) => void;
|
||||
init: (force?: boolean) => Promise<{ queryProxy: QueryProxy }>;
|
||||
routeViewList: RouteViewList;
|
||||
getViewList: () => Promise<void>;
|
||||
getCurrentView: () => Promise<void>;
|
||||
updateRouteView: (view: RouterViewData) => Promise<void>;
|
||||
deleteRouteView: (id: string) => Promise<void>;
|
||||
currentView?: RouterViewData;
|
||||
}
|
||||
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
export const useStudioStore = create<StudioState>((set, get) => ({
|
||||
routes: [],
|
||||
getRoutes: async () => {
|
||||
const state = get();
|
||||
getRouteList: async () => {
|
||||
await get().getCurrentView();
|
||||
const state = await get().init();
|
||||
|
||||
let queryProxy = state.queryProxy;
|
||||
if (!queryProxy) {
|
||||
const init = await state.init();
|
||||
queryProxy = init.queryProxy;
|
||||
}
|
||||
console.log('query proxy', queryProxy.router);
|
||||
const routes: any[] = await queryProxy.listRoutes(() => true, "")
|
||||
console.log('fetched routes', routes);
|
||||
const url = new URL(window.location.href);
|
||||
const viewId = url.searchParams.get('viewId') || 'default';
|
||||
const routes: any[] = await queryProxy.listRoutes(() => true, { viewId });
|
||||
set({ routes });
|
||||
get().getViewList();
|
||||
},
|
||||
getViewList: async () => {
|
||||
const res = await query.post({ path: 'views', key: 'list' });
|
||||
if (res.code === 200) {
|
||||
const list = res.data.list as RouteViewList || [];
|
||||
set({ routeViewList: list });
|
||||
}
|
||||
},
|
||||
getCurrentView: async () => {
|
||||
const url = new URL(window.location.href);
|
||||
const viewId = url.searchParams.get('viewId');
|
||||
if (!viewId) {
|
||||
return;
|
||||
}
|
||||
const res = await query.post({ path: 'views', key: 'current', data: { viewId: viewId } });
|
||||
if (res.code === 200) {
|
||||
const view = res.data as RouterViewData;
|
||||
set({ currentView: view });
|
||||
} else {
|
||||
set({ currentView: undefined });
|
||||
}
|
||||
},
|
||||
routeViewList: [],
|
||||
updateRouteView: async (view: RouterViewData) => {
|
||||
const res = await query.post({ path: 'views', key: 'update', data: view });
|
||||
if (res.code !== 200) {
|
||||
toast.error(`视图更新失败:${res.message || '未知错误'}`);
|
||||
return;
|
||||
} else {
|
||||
get().getViewList();
|
||||
toast.success('视图更新成功');
|
||||
}
|
||||
},
|
||||
deleteRouteView: async (id: string) => {
|
||||
const res = await query.post({ path: 'views', key: 'delete', data: { id } });
|
||||
if (res.code !== 200) {
|
||||
toast.error(`视图删除失败:${res.message || '未知错误'}`);
|
||||
return;
|
||||
}
|
||||
get().getViewList();
|
||||
toast.success('视图删除成功');
|
||||
},
|
||||
run: async (route: RouteItem) => {
|
||||
const state = get();
|
||||
let queryProxy = state.queryProxy!;
|
||||
if (!state.queryProxy) {
|
||||
const init = await state.init();
|
||||
queryProxy = init.queryProxy;
|
||||
}
|
||||
console.log('running route', route, queryProxy.query.url);
|
||||
const state = await get().init();
|
||||
let queryProxy = state.queryProxy;
|
||||
const res = await queryProxy.run({ path: route.path, key: route.key });
|
||||
console.log('route run result', res);
|
||||
if (res.code !== 200) {
|
||||
toast.error(`运行失败:${res.message || '未知错误'}`);
|
||||
} else if (res.code === 200) {
|
||||
@@ -73,51 +95,38 @@ export const useStudioStore = create<StudioState>((set, get) => ({
|
||||
},
|
||||
queryProxy: undefined,
|
||||
router: undefined,
|
||||
init: async () => {
|
||||
const proxy = get().proxy || localStorageProxy.get();
|
||||
const url = proxy.type === 'api' && proxy.api ? proxy.api.url : '/client/router';
|
||||
init: async (force?: boolean) => {
|
||||
// let _url = 'http://localhost:52002/api/router';
|
||||
let _url = 'http://localhost:52000/api/router';
|
||||
// let _url = '/api/router';
|
||||
|
||||
const query = new QueryClient({
|
||||
url: _url,
|
||||
let queryProxy = get().queryProxy;
|
||||
if (queryProxy && !force) {
|
||||
return { queryProxy };
|
||||
}
|
||||
let currentView: RouterViewData | undefined = get().currentView;
|
||||
console.log('currentView in init', currentView);
|
||||
const routerViewData: RouterViewData = currentView || {
|
||||
views: [{
|
||||
id: 'default',
|
||||
title: '默认视图',
|
||||
query: `WHERE path = 'file' `
|
||||
}],
|
||||
data: {
|
||||
items: []
|
||||
},
|
||||
viewId: 'default',
|
||||
}
|
||||
console.log('initializing query proxy with view', routerViewData);
|
||||
queryProxy = new QueryProxy({
|
||||
routerViewData
|
||||
});
|
||||
const router = new QueryRouterServer();
|
||||
const queryProxy = new QueryProxy({ query, router });
|
||||
await queryProxy.init();
|
||||
set({ queryProxy, router });
|
||||
return { router, queryProxy }
|
||||
},
|
||||
proxy: undefined,
|
||||
setProxy: (proxy: ProxyItem) => {
|
||||
localStorageProxy.set(proxy);
|
||||
set({ proxy });
|
||||
},
|
||||
proxyList: [],
|
||||
setProxyList: (list: ProxyItem[]) => {
|
||||
set({ proxyList: list });
|
||||
await sleep(500);
|
||||
set({ queryProxy });
|
||||
return { queryProxy }
|
||||
},
|
||||
}));
|
||||
|
||||
export const localStorageProxy = {
|
||||
get: (): ProxyItem => {
|
||||
const data = localStorage.getItem('PROXY_CONFIG')
|
||||
if (data) {
|
||||
return JSON.parse(data)
|
||||
}
|
||||
const defult: ProxyItem = {
|
||||
title: '默认',
|
||||
description: '默认',
|
||||
type: 'api',
|
||||
api: {
|
||||
url: '/client/router'
|
||||
},
|
||||
}
|
||||
localStorageProxy.set(defult)
|
||||
return defult;
|
||||
},
|
||||
set: (proxy: ProxyItem) => {
|
||||
localStorage.setItem('PROXY_CONFIG', JSON.stringify(proxy))
|
||||
}
|
||||
}
|
||||
use('studioStore', () => {
|
||||
return useStudioStore.getState();
|
||||
});
|
||||
200
web/src/apps/view/components/DataItemForm.tsx
Normal file
200
web/src/apps/view/components/DataItemForm.tsx
Normal file
@@ -0,0 +1,200 @@
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { Query } from "@kevisual/query"
|
||||
import { QueryRouterServer } from "@kevisual/router"
|
||||
import { nanoid } from "nanoid"
|
||||
|
||||
export type RouterViewItem = RouterViewApi | RouterViewContext | RouterViewWorker;
|
||||
type RouteViewBase = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
enabled?: boolean;
|
||||
}
|
||||
export type RouterViewApi = {
|
||||
type: 'api',
|
||||
api: {
|
||||
url: string,
|
||||
// 已初始化的query实例,不需要编辑配置
|
||||
query?: Query
|
||||
}
|
||||
} & RouteViewBase;
|
||||
|
||||
export type RouterViewContext = {
|
||||
type: 'context',
|
||||
context: {
|
||||
key: string,
|
||||
// 从context中获取router,不需要编辑配置
|
||||
router?: QueryRouterServer
|
||||
}
|
||||
} & RouteViewBase;
|
||||
export type RouterViewWorker = {
|
||||
type: 'worker',
|
||||
worker: {
|
||||
type: 'Worker' | 'SharedWorker' | 'serviceWorker',
|
||||
url: string,
|
||||
// 已初始化的worker实例,不需要编辑配置
|
||||
worker?: Worker | SharedWorker | ServiceWorker,
|
||||
/**
|
||||
* worker选项
|
||||
* default: { type: 'module' }
|
||||
*/
|
||||
workerOptions?: {
|
||||
type: 'module' | 'classic'
|
||||
}
|
||||
}
|
||||
} & RouteViewBase;
|
||||
interface DataItemFormProps {
|
||||
item: RouterViewItem
|
||||
onChange: (item: any) => void
|
||||
onRemove: () => void
|
||||
}
|
||||
|
||||
export const DataItemForm = ({ item, onChange, onRemove }: DataItemFormProps) => {
|
||||
const handleChange = (field: string, value: any) => {
|
||||
if (field === 'type') {
|
||||
const newItem: RouterViewItem = { ...item, type: value }
|
||||
if (value === 'api' && !('api' in item)) {
|
||||
(newItem as RouterViewApi).api = { url: '' }
|
||||
} else if (value === 'context' && !('context' in item)) {
|
||||
(newItem as RouterViewContext).context = { key: '' }
|
||||
} else if (value === 'worker' && !('worker' in item)) {
|
||||
(newItem as RouterViewWorker).worker = { type: 'Worker', url: '', workerOptions: { type: 'module' } }
|
||||
}
|
||||
if (!newItem.id) {
|
||||
newItem.id = nanoid(16)
|
||||
}
|
||||
onChange(newItem)
|
||||
} else {
|
||||
onChange({ ...item, [field]: value })
|
||||
}
|
||||
}
|
||||
|
||||
const handleNestedChange = (parent: string, field: string, value: any) => {
|
||||
const parentValue = item[parent as keyof RouterViewItem] as Record<string, any> | undefined
|
||||
const newParentValue: Record<string, any> = {
|
||||
...(parentValue || {}),
|
||||
[field]: value
|
||||
}
|
||||
onChange({ ...item, [parent]: newParentValue })
|
||||
}
|
||||
|
||||
const handleNestedDeepChange = (parent: string, nestedParent: string, field: string, value: any) => {
|
||||
const parentValue = item[parent as keyof RouterViewItem] as Record<string, any> | undefined
|
||||
const nestedValue = parentValue?.[nestedParent] as Record<string, any> | undefined
|
||||
const newNestedValue: Record<string, any> = {
|
||||
...(nestedValue || {}),
|
||||
[field]: value
|
||||
}
|
||||
const newParentValue: Record<string, any> = {
|
||||
...(parentValue || {}),
|
||||
[nestedParent]: newNestedValue
|
||||
}
|
||||
onChange({ ...item, [parent]: newParentValue })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="border rounded-lg p-4 mb-4 space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<h3 className="font-medium">数据项配置</h3>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onRemove}
|
||||
className="text-sm text-red-500 hover:text-red-700"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>标题</Label>
|
||||
<Input
|
||||
value={item.title || ''}
|
||||
onChange={(e) => handleChange('title', e.target.value)}
|
||||
placeholder="输入标题"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>类型</Label>
|
||||
<select
|
||||
value={item.type}
|
||||
onChange={(e) => handleChange('type', e.target.value)}
|
||||
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<option value="api">API</option>
|
||||
<option value="context">Context</option>
|
||||
<option value="worker">Worker</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="enabled"
|
||||
checked={item.enabled !== false}
|
||||
onCheckedChange={(checked) => handleChange('enabled', checked)}
|
||||
/>
|
||||
<Label htmlFor="enabled" className="cursor-pointer">启用</Label>
|
||||
</div>
|
||||
|
||||
{(item.type === 'api') && (
|
||||
<div className="space-y-2">
|
||||
<Label>API URL</Label>
|
||||
<Input
|
||||
value={item.api?.url || ''}
|
||||
onChange={(e) => handleNestedChange('api', 'url', e.target.value)}
|
||||
placeholder="输入 API 地址"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{item.type === 'context' && (
|
||||
<div className="space-y-2">
|
||||
<Label>Context Key</Label>
|
||||
<Input
|
||||
value={item.context?.key || ''}
|
||||
onChange={(e) => handleNestedChange('context', 'key', e.target.value)}
|
||||
placeholder="输入 Context Key"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{item.type === 'worker' && (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Worker Type</Label>
|
||||
<select
|
||||
value={item.worker?.type || 'Worker'}
|
||||
onChange={(e) => handleNestedChange('worker', 'type', e.target.value)}
|
||||
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<option value="Worker">Worker</option>
|
||||
<option value="SharedWorker">SharedWorker</option>
|
||||
<option value="serviceWorker">ServiceWorker</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Worker URL</Label>
|
||||
<Input
|
||||
value={item.worker?.url || ''}
|
||||
onChange={(e) => handleNestedChange('worker', 'url', e.target.value)}
|
||||
placeholder="输入 Worker URL"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Worker Options Type</Label>
|
||||
<select
|
||||
value={item.worker?.workerOptions?.type || 'module'}
|
||||
onChange={(e) => handleNestedDeepChange('worker', 'workerOptions', 'type', e.target.value)}
|
||||
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<option value="module">Module</option>
|
||||
<option value="classic">Classic</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
146
web/src/apps/view/components/ViewEditor.tsx
Normal file
146
web/src/apps/view/components/ViewEditor.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import { useState, useEffect } from "react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||
import { DataItemForm } from "@/apps/view/components/DataItemForm"
|
||||
import { ViewFormItem } from "@/apps/view/components/ViewFormItem"
|
||||
import { nanoid } from "nanoid"
|
||||
|
||||
interface ViewEditorProps {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
data?: {
|
||||
id?: string
|
||||
title?: string
|
||||
data?: { items: any[] }
|
||||
views?: any[]
|
||||
}
|
||||
onSave: (data: any) => void
|
||||
}
|
||||
|
||||
export const ViewEditor = ({ open, onOpenChange, data, onSave }: ViewEditorProps) => {
|
||||
const [title, setTitle] = useState('')
|
||||
const [dataItems, setDataItems] = useState<any[]>([])
|
||||
const [views, setViews] = useState<any[]>([])
|
||||
|
||||
const isUpdate = !!data?.id
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setTitle(data?.title || '')
|
||||
setDataItems(data?.data?.items || [])
|
||||
setViews(data?.views || [])
|
||||
}
|
||||
}, [open, data])
|
||||
|
||||
const handleAddDataItem = () => {
|
||||
setDataItems([...dataItems, { type: 'api', api: { url: '' } }])
|
||||
}
|
||||
|
||||
const handleUpdateDataItem = (index: number, item: any) => {
|
||||
const newItems = [...dataItems]
|
||||
newItems[index] = item
|
||||
setDataItems(newItems)
|
||||
}
|
||||
|
||||
const handleRemoveDataItem = (index: number) => {
|
||||
setDataItems(dataItems.filter((_, i) => i !== index))
|
||||
}
|
||||
|
||||
const handleAddView = () => {
|
||||
setViews([...views, { id: nanoid(16), title: '', query: '' }])
|
||||
}
|
||||
|
||||
const handleUpdateView = (index: number, view: any) => {
|
||||
const newViews = [...views]
|
||||
newViews[index] = view
|
||||
setViews(newViews)
|
||||
}
|
||||
|
||||
const handleRemoveView = (index: number) => {
|
||||
setViews(views.filter((_, i) => i !== index))
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
const viewData = {
|
||||
id: data?.id,
|
||||
title,
|
||||
data: {
|
||||
items: dataItems
|
||||
},
|
||||
views
|
||||
}
|
||||
onSave(viewData)
|
||||
onOpenChange(false)
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
onOpenChange(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{isUpdate ? '编辑视图' : '新增视图'}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-6 max-h-[70vh] overflow-y-auto">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="title">视图标题</Label>
|
||||
<Input
|
||||
id="title"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder="输入视图标题"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<h3 className="font-medium">数据项配置 (data.items)</h3>
|
||||
<Button type="button" variant="outline" size="sm" onClick={handleAddDataItem}>
|
||||
添加数据项
|
||||
</Button>
|
||||
</div>
|
||||
{dataItems.map((item, index) => (
|
||||
<DataItemForm
|
||||
key={index}
|
||||
item={item}
|
||||
onChange={(newItem) => handleUpdateDataItem(index, newItem)}
|
||||
onRemove={() => handleRemoveDataItem(index)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<h3 className="font-medium">视图配置 (views)</h3>
|
||||
<Button type="button" variant="outline" size="sm" onClick={handleAddView}>
|
||||
添加视图
|
||||
</Button>
|
||||
</div>
|
||||
{views.map((view, index) => (
|
||||
<ViewFormItem
|
||||
key={view.id || index}
|
||||
view={view}
|
||||
onChange={(newView) => handleUpdateView(index, newView)}
|
||||
onRemove={() => handleRemoveView(index)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="outline" onClick={handleClose}>
|
||||
取消
|
||||
</Button>
|
||||
<Button type="button" onClick={handleSave}>
|
||||
保存
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
58
web/src/apps/view/components/ViewFormItem.tsx
Normal file
58
web/src/apps/view/components/ViewFormItem.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Input } from "@/components/ui/input"
|
||||
|
||||
interface ViewFormProps {
|
||||
view: any
|
||||
onChange: (view: any) => void
|
||||
onRemove: () => void
|
||||
}
|
||||
|
||||
export const ViewFormItem = ({ view, onChange, onRemove }: ViewFormProps) => {
|
||||
const handleChange = (field: string, value: any) => {
|
||||
onChange({ ...view, [field]: value })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="border rounded-lg p-4 mb-4 space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<h3 className="font-medium">视图配置</h3>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onRemove}
|
||||
className="text-sm text-red-500 hover:text-red-700"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>ID</Label>
|
||||
<Input
|
||||
value={view.id || ''}
|
||||
onChange={(e) => handleChange('id', e.target.value)}
|
||||
placeholder="自动生成"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>标题</Label>
|
||||
<Input
|
||||
value={view.title || ''}
|
||||
onChange={(e) => handleChange('title', e.target.value)}
|
||||
placeholder="输入视图标题"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>查询语句</Label>
|
||||
<Input
|
||||
value={view.query || ''}
|
||||
onChange={(e) => handleChange('query', e.target.value)}
|
||||
placeholder="输入查询语句"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
10
web/src/apps/view/form.ts
Normal file
10
web/src/apps/view/form.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { RouterViewQuery, RouterViewItem } from '@kevisual/api';
|
||||
type ViewFormData = {
|
||||
id?: string;
|
||||
title: string;
|
||||
data?: {
|
||||
items: RouterViewItem[];
|
||||
},
|
||||
views: RouterViewQuery[];
|
||||
};
|
||||
129
web/src/apps/view/list.tsx
Normal file
129
web/src/apps/view/list.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
import { useState } from "react";
|
||||
import { useStudioStore } from '../studio/store.ts';
|
||||
import { Search, RotateCw, Plus, MoreHorizontal, Layout, Edit2, Trash2 } from "lucide-react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { ViewEditor } from "@/apps/view/components/ViewEditor.tsx";
|
||||
|
||||
export const ViewList = () => {
|
||||
const { routeViewList, updateRouteView, deleteRouteView } = useStudioStore();
|
||||
const [selectedItems, setSelectedItems] = useState<string[]>([]);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [editorOpen, setEditorOpen] = useState(false);
|
||||
const [editingView, setEditingView] = useState<any>(null);
|
||||
|
||||
const filteredViews = routeViewList.filter(view =>
|
||||
(view.title || '未命名视图').toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
(view.description || '').toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
|
||||
const handleRefresh = () => {
|
||||
// 刷新逻辑
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
handleEdit({});
|
||||
};
|
||||
|
||||
const handleEdit = (view: any) => {
|
||||
setEditingView(view);
|
||||
setEditorOpen(true);
|
||||
};
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
if (confirm('确定要删除这个视图吗?')) {
|
||||
deleteRouteView(id);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveView = (viewData: any) => {
|
||||
updateRouteView(viewData);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full h-full max-w-4xl p-4 border rounded-md shadow-sm">
|
||||
<div className="flex items-center space-x-2 mb-4">
|
||||
<div className="relative flex-1">
|
||||
<Input
|
||||
placeholder="搜索视图..."
|
||||
className="pl-3 pr-8"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
<Search className="absolute right-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
||||
</div>
|
||||
<Button variant="outline" size="icon" className="h-10 w-10" onClick={handleRefresh}>
|
||||
<RotateCw className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon" className="h-10 w-10" onClick={handleAdd}>
|
||||
<Plus className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="font-medium">视图名称</TableHead>
|
||||
<TableHead className="w-10"></TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredViews.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} className="text-center py-4 text-gray-500">
|
||||
{searchTerm ? '未找到匹配的视图' : '暂无视图'}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
filteredViews.map((view) => (
|
||||
<TableRow key={view.id} className={selectedItems.includes(view.id) ? "bg-gray-100" : ""}>
|
||||
<TableCell className="font-medium">
|
||||
<div className="flex items-center">
|
||||
<Layout className="h-4 w-4 mr-2 text-gray-500" />
|
||||
{view.title || '未命名视图'}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center space-x-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8"
|
||||
onClick={() => handleEdit(view)}
|
||||
>
|
||||
<Edit2 className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8 text-red-500 hover:text-red-700"
|
||||
onClick={() => handleDelete(view.id)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<ViewEditor
|
||||
open={editorOpen}
|
||||
onOpenChange={setEditorOpen}
|
||||
data={editingView}
|
||||
onSave={handleSaveView}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -38,8 +38,8 @@ const buttonVariants = cva(
|
||||
|
||||
function Button({
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
variant = "default",
|
||||
size = "default",
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"button"> &
|
||||
@@ -51,6 +51,8 @@ function Button({
|
||||
return (
|
||||
<Comp
|
||||
data-slot="button"
|
||||
data-variant={variant}
|
||||
data-size={size}
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
30
web/src/components/ui/checkbox.tsx
Normal file
30
web/src/components/ui/checkbox.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import * as React from "react"
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||
import { CheckIcon } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Checkbox({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
|
||||
return (
|
||||
<CheckboxPrimitive.Root
|
||||
data-slot="checkbox"
|
||||
className={cn(
|
||||
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<CheckboxPrimitive.Indicator
|
||||
data-slot="checkbox-indicator"
|
||||
className="grid place-content-center text-current transition-none"
|
||||
>
|
||||
<CheckIcon className="size-3.5" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
)
|
||||
}
|
||||
|
||||
export { Checkbox }
|
||||
42
web/src/components/ui/dialog.tsx
Normal file
42
web/src/components/ui/dialog.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import * as React from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
const Dialog = ({ open, onOpenChange, children }: { open: boolean; onOpenChange: (open: boolean) => void; children: React.ReactNode }) => {
|
||||
if (!open) return null
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||
<div className="fixed inset-0 bg-black/50" onClick={() => onOpenChange(false)} />
|
||||
<div className="relative z-50 w-full max-w-2xl max-h-[90vh] overflow-auto bg-white rounded-lg shadow-lg p-6">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const DialogHeader = ({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn("flex flex-col space-y-1.5 text-center sm:text-left mb-4", className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
const DialogTitle = ({ className, children, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (
|
||||
<h2 className={cn("text-lg font-semibold leading-none tracking-tight", className)} {...props}>
|
||||
{children}
|
||||
</h2>
|
||||
)
|
||||
|
||||
const DialogContent = ({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn("", className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
const DialogFooter = ({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 mt-4", className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
export { Dialog, DialogHeader, DialogTitle, DialogContent, DialogFooter }
|
||||
21
web/src/components/ui/input.tsx
Normal file
21
web/src/components/ui/input.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
data-slot="input"
|
||||
className={cn(
|
||||
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
||||
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Input }
|
||||
22
web/src/components/ui/label.tsx
Normal file
22
web/src/components/ui/label.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Label({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
||||
return (
|
||||
<LabelPrimitive.Root
|
||||
data-slot="label"
|
||||
className={cn(
|
||||
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Label }
|
||||
114
web/src/components/ui/table.tsx
Normal file
114
web/src/components/ui/table.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Table({ className, ...props }: React.ComponentProps<"table">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="table-container"
|
||||
className="relative w-full overflow-x-auto"
|
||||
>
|
||||
<table
|
||||
data-slot="table"
|
||||
className={cn("w-full caption-bottom text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
||||
return (
|
||||
<thead
|
||||
data-slot="table-header"
|
||||
className={cn("[&_tr]:border-b", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
||||
return (
|
||||
<tbody
|
||||
data-slot="table-body"
|
||||
className={cn("[&_tr:last-child]:border-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
||||
return (
|
||||
<tfoot
|
||||
data-slot="table-footer"
|
||||
className={cn(
|
||||
"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
||||
return (
|
||||
<tr
|
||||
data-slot="table-row"
|
||||
className={cn(
|
||||
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
||||
return (
|
||||
<th
|
||||
data-slot="table-head"
|
||||
className={cn(
|
||||
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
||||
return (
|
||||
<td
|
||||
data-slot="table-cell"
|
||||
className={cn(
|
||||
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function TableCaption({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"caption">) {
|
||||
return (
|
||||
<caption
|
||||
data-slot="table-caption"
|
||||
className={cn("text-muted-foreground mt-4 text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableFooter,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableCaption,
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Query } from '@kevisual/query'
|
||||
import { QueryClient } from '@kevisual/query'
|
||||
|
||||
const getUrl = () => {
|
||||
const host = window.location.host
|
||||
@@ -10,6 +10,10 @@ const getUrl = () => {
|
||||
return '/client/router'
|
||||
}
|
||||
|
||||
export const query = new Query({
|
||||
url: getUrl()
|
||||
export const query = new QueryClient({
|
||||
url: '/api/router',
|
||||
});
|
||||
|
||||
export const queryClient = new QueryClient({
|
||||
url: getUrl(),
|
||||
});
|
||||
Reference in New Issue
Block a user