@platforma-sdk/ui-vue 1.3.18 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platforma-sdk/ui-vue",
3
- "version": "1.3.18",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "main": "dist/lib.umd.cjs",
6
6
  "module": "dist/lib.js",
@@ -19,8 +19,8 @@
19
19
  },
20
20
  "dependencies": {
21
21
  "vue": "^3.5.9",
22
- "@milaboratories/uikit": "^1.2.12",
23
- "@platforma-sdk/model": "^1.2.29"
22
+ "@platforma-sdk/model": "^1.2.29",
23
+ "@milaboratories/uikit": "^1.2.13"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@ag-grid-community/client-side-row-model": "^32.2.1",
@@ -2,6 +2,7 @@ import { reactive, computed, ref, watch, unref } from 'vue';
2
2
  import type { ZodError } from 'zod';
3
3
  import type { ModelOptions, Model } from './types';
4
4
  import { deepEqual, deepClone } from '@milaboratories/helpers';
5
+ import { isJsonEqual } from './utils';
5
6
 
6
7
  const identity = <T, V = T>(v: T): V => v as unknown as V;
7
8
 
@@ -51,6 +52,15 @@ export function createModel<M, V = unknown>(options: ModelOptions<M, V>): Model<
51
52
  error.value = undefined;
52
53
  };
53
54
 
55
+ const setError = (cause: unknown) => {
56
+ const err = ensureError(cause);
57
+ if (isZodError(err)) {
58
+ error.value = Error(formatZodError(err)); // @todo temp
59
+ } else {
60
+ error.value = err;
61
+ }
62
+ };
63
+
54
64
  const setValue = (v: M) => {
55
65
  error.value = undefined;
56
66
  try {
@@ -59,12 +69,7 @@ export function createModel<M, V = unknown>(options: ModelOptions<M, V>): Model<
59
69
  save();
60
70
  }
61
71
  } catch (cause: unknown) {
62
- const err = ensureError(cause);
63
- if (isZodError(err)) {
64
- error.value = Error(formatZodError(err)); // @todo temp
65
- } else {
66
- error.value = err as Error; // @todo ensureError
67
- }
72
+ setError(cause);
68
73
  }
69
74
  };
70
75
 
@@ -81,7 +86,7 @@ export function createModel<M, V = unknown>(options: ModelOptions<M, V>): Model<
81
86
  watch(
82
87
  local,
83
88
  (v) => {
84
- if (!deepEqual(options.get(), v)) {
89
+ if (!isJsonEqual(options.get(), v)) {
85
90
  setValue(v as M);
86
91
  }
87
92
  },
@@ -104,5 +109,6 @@ export function createModel<M, V = unknown>(options: ModelOptions<M, V>): Model<
104
109
  errorString,
105
110
  save,
106
111
  revert,
112
+ setError,
107
113
  });
108
114
  }
package/src/defineApp.ts CHANGED
@@ -2,7 +2,7 @@ import { notEmpty } from '@milaboratories/helpers';
2
2
  import { type BlockOutputsBase, type Platforma } from '@platforma-sdk/model';
3
3
  import type { Component, Reactive } from 'vue';
4
4
  import { inject, markRaw, reactive } from 'vue';
5
- import { createApp, type BaseApp } from './createApp';
5
+ import { createApp, type BaseApp } from './internal/createApp';
6
6
  import type { LocalState, Routes } from './types';
7
7
 
8
8
  const pluginKey = Symbol('sdk-vue');
@@ -40,6 +40,7 @@ export function defineApp<
40
40
  } as unknown as App<Args, Outputs, UiState, Href, Local>);
41
41
  })
42
42
  .catch((err) => {
43
+ console.error('load initial state error', err);
43
44
  plugin.error = err;
44
45
  });
45
46
  };
@@ -1,11 +1,12 @@
1
- import { deepClone } from '@milaboratories/helpers';
1
+ import { deepClone, throttle } from '@milaboratories/helpers';
2
2
  import type { Mutable } from '@milaboratories/helpers';
3
3
  import type { NavigationState, BlockOutputsBase, BlockState, Platforma } from '@platforma-sdk/model';
4
4
  import { reactive, nextTick, computed, watch } from 'vue';
5
- import type { UnwrapValueOrErrors, StateModelOptions, UnwrapOutputs, OptionalResult, OutputValues, OutputErrors } from './types';
6
- import { createModel } from './createModel';
7
- import { parseQuery } from './urls';
8
- import { MultiError, unwrapValueOrErrors } from './utils';
5
+ import type { UnwrapValueOrErrors, StateModelOptions, UnwrapOutputs, OptionalResult, OutputValues, OutputErrors } from '../types';
6
+ import { createModel } from '../createModel';
7
+ import { parseQuery } from '../urls';
8
+ import { MultiError, unwrapValueOrErrors } from '../utils';
9
+ import { pick } from 'lodash';
9
10
 
10
11
  export function createApp<
11
12
  Args = unknown,
@@ -13,7 +14,20 @@ export function createApp<
13
14
  UiState = unknown,
14
15
  Href extends `/${string}` = `/${string}`,
15
16
  >(state: BlockState<Args, Outputs, UiState, Href>, platforma: Platforma<Args, Outputs, UiState, Href>) {
16
- const innerState = reactive({
17
+ type AppModel = {
18
+ args: Args;
19
+ ui: UiState;
20
+ };
21
+
22
+ const throttleSpan = 100; // @todo settings and more flexible
23
+
24
+ const setBlockArgs = throttle(platforma.setBlockArgs, throttleSpan);
25
+
26
+ const setBlockUiState = throttle(platforma.setBlockUiState, throttleSpan);
27
+
28
+ const setBlockArgsAndUiState = throttle(platforma.setBlockArgsAndUiState, throttleSpan);
29
+
30
+ const snapshot = reactive({
17
31
  args: Object.freeze(state.args),
18
32
  outputs: Object.freeze(state.outputs),
19
33
  ui: Object.freeze(state.ui),
@@ -28,43 +42,43 @@ export function createApp<
28
42
  platforma.onStateUpdates(async (updates) => {
29
43
  updates.forEach((patch) => {
30
44
  if (patch.key === 'args') {
31
- innerState.args = Object.freeze(patch.value);
45
+ snapshot.args = Object.freeze(patch.value);
32
46
  }
33
47
 
34
48
  if (patch.key === 'ui') {
35
- innerState.ui = Object.freeze(patch.value);
49
+ snapshot.ui = Object.freeze(patch.value);
36
50
  }
37
51
 
38
52
  if (patch.key === 'outputs') {
39
- innerState.outputs = Object.freeze(patch.value);
53
+ snapshot.outputs = Object.freeze(patch.value);
40
54
  }
41
55
 
42
56
  if (patch.key === 'navigationState') {
43
- innerState.navigationState = Object.freeze(patch.value);
57
+ snapshot.navigationState = Object.freeze(patch.value);
44
58
  }
45
59
  });
46
60
 
47
61
  await nextTick(); // @todo remove
48
62
  });
49
63
 
50
- const cloneArgs = () => deepClone(innerState.args) as Args;
51
- const cloneUiState = () => deepClone(innerState.ui) as UiState;
52
- const cloneNavigationState = () => deepClone(innerState.navigationState) as Mutable<NavigationState<Href>>;
64
+ const cloneArgs = () => deepClone(snapshot.args) as Args;
65
+ const cloneUiState = () => deepClone(snapshot.ui) as UiState;
66
+ const cloneNavigationState = () => deepClone(snapshot.navigationState) as Mutable<NavigationState<Href>>;
53
67
 
54
68
  const methods = {
55
69
  createArgsModel<T = Args>(options: StateModelOptions<Args, T> = {}) {
56
70
  return createModel<T, Args>({
57
71
  get() {
58
72
  if (options.transform) {
59
- return options.transform(innerState.args);
73
+ return options.transform(snapshot.args);
60
74
  }
61
75
 
62
- return innerState.args as T;
76
+ return snapshot.args as T;
63
77
  },
64
78
  validate: options.validate,
65
79
  autoSave: true,
66
80
  onSave(newArgs) {
67
- platforma.setBlockArgs(newArgs);
81
+ setBlockArgs(newArgs);
68
82
  },
69
83
  });
70
84
  },
@@ -75,15 +89,31 @@ export function createApp<
75
89
  return createModel<T, UiState>({
76
90
  get() {
77
91
  if (options.transform) {
78
- return options.transform(innerState.ui);
92
+ return options.transform(snapshot.ui);
93
+ }
94
+
95
+ return (snapshot.ui ?? defaultUiState()) as T;
96
+ },
97
+ validate: options.validate,
98
+ autoSave: true,
99
+ onSave(newData) {
100
+ setBlockUiState(newData);
101
+ },
102
+ });
103
+ },
104
+ createAppModel<T = AppModel>(options: StateModelOptions<AppModel, T> = {}) {
105
+ return createModel<T, AppModel>({
106
+ get() {
107
+ if (options.transform) {
108
+ return options.transform(snapshot);
79
109
  }
80
110
 
81
- return (innerState.ui ?? defaultUiState()) as T;
111
+ return { args: snapshot.args, ui: snapshot.ui } as T;
82
112
  },
83
113
  validate: options.validate,
84
114
  autoSave: true,
85
115
  onSave(newData) {
86
- platforma.setBlockUiState(newData);
116
+ setBlockArgsAndUiState(newData.args, newData.ui);
87
117
  },
88
118
  });
89
119
  },
@@ -99,7 +129,7 @@ export function createApp<
99
129
  });
100
130
 
101
131
  watch(
102
- () => innerState.outputs,
132
+ () => snapshot.outputs,
103
133
  () => {
104
134
  try {
105
135
  Object.assign(data, {
@@ -124,7 +154,7 @@ export function createApp<
124
154
  * @returns
125
155
  */
126
156
  unwrapOutputs<K extends keyof Outputs>(...keys: K[]): UnwrapOutputs<Outputs, K> {
127
- const outputs = innerState.outputs;
157
+ const outputs = snapshot.outputs;
128
158
  const entries = keys.map((key) => [key, unwrapValueOrErrors(outputs[key])]);
129
159
  return Object.fromEntries(entries);
130
160
  },
@@ -133,13 +163,15 @@ export function createApp<
133
163
  * @see outputs
134
164
  */
135
165
  getOutputField(key: keyof Outputs) {
136
- return innerState.outputs[key];
166
+ return snapshot.outputs[key];
137
167
  },
138
168
  /**
139
169
  * @deprecated use outputValues instead
140
170
  * @see outputValues
141
171
  */
142
172
  getOutputFieldOkOptional<K extends keyof Outputs>(key: K): UnwrapValueOrErrors<Outputs[K]> | undefined {
173
+ console.warn('use reactive app.outputValues.fieldName instead instead of getOutputFieldOkOptional(fieldName)');
174
+
143
175
  const result = this.getOutputField(key);
144
176
 
145
177
  if (result && result.ok) {
@@ -149,6 +181,8 @@ export function createApp<
149
181
  return undefined;
150
182
  },
151
183
  getOutputFieldErrorsOptional<K extends keyof Outputs>(key: K): string[] | undefined {
184
+ console.warn('use reactive app.outputErrors.fieldName instead instead of getOutputFieldErrorsOptional(fieldName)');
185
+
152
186
  const result = this.getOutputField(key);
153
187
 
154
188
  if (result && !result.ok) {
@@ -179,30 +213,29 @@ export function createApp<
179
213
  };
180
214
 
181
215
  const getters = {
182
- args: computed(() => innerState.args),
183
- outputs: computed(() => innerState.outputs),
184
- ui: computed(() => innerState.ui),
185
- navigationState: computed(() => innerState.navigationState),
186
- href: computed(() => innerState.navigationState.href),
216
+ args: computed(() => snapshot.args),
217
+ outputs: computed(() => snapshot.outputs),
218
+ ui: computed(() => snapshot.ui),
219
+ navigationState: computed(() => snapshot.navigationState),
220
+ href: computed(() => snapshot.navigationState.href),
187
221
 
188
222
  outputValues: computed<OutputValues<Outputs>>(() => {
189
- const entries = Object.entries(innerState.outputs).map(([k, vOrErr]) => [
190
- k,
191
- vOrErr.ok && vOrErr.value !== undefined ? vOrErr.value : undefined,
192
- ]);
223
+ const entries = Object.entries(snapshot.outputs).map(([k, vOrErr]) => [k, vOrErr.ok && vOrErr.value !== undefined ? vOrErr.value : undefined]);
193
224
  return Object.fromEntries(entries);
194
225
  }),
195
226
 
196
227
  outputErrors: computed<OutputErrors<Outputs>>(() => {
197
- const entries = Object.entries(innerState.outputs).map(([k, vOrErr]) => [k, vOrErr && !vOrErr.ok ? new MultiError(vOrErr.errors) : undefined]);
228
+ const entries = Object.entries(snapshot.outputs).map(([k, vOrErr]) => [k, vOrErr && !vOrErr.ok ? new MultiError(vOrErr.errors) : undefined]);
198
229
  return Object.fromEntries(entries);
199
230
  }),
200
231
 
201
- queryParams: computed(() => parseQuery<Href>(innerState.navigationState.href)),
202
- hasErrors: computed(() => Object.values(innerState.outputs).some((v) => !v?.ok)), // @TODO: there is middle-layer error, v sometimes is undefined
232
+ queryParams: computed(() => parseQuery<Href>(snapshot.navigationState.href)),
233
+ hasErrors: computed(() => Object.values(snapshot.outputs).some((v) => !v?.ok)), // @TODO: there is middle-layer error, v sometimes is undefined
203
234
  };
204
235
 
205
- return reactive(Object.assign(methods, getters));
236
+ const appModel = methods.createAppModel();
237
+
238
+ return reactive(Object.assign(appModel, methods, getters));
206
239
  }
207
240
 
208
241
  export type BaseApp<
@@ -2,7 +2,7 @@ import type { Expect, Equal } from '@milaboratories/helpers';
2
2
  import { z } from 'zod';
3
3
  import type { ModelOptions, Model } from './types';
4
4
  import type { BlockOutputsBase, InferHrefType, InferOutputsType, Platforma } from '@platforma-sdk/model';
5
- import type { BaseApp, createApp } from './createApp';
5
+ import type { BaseApp, createApp } from './internal/createApp';
6
6
  import type { App } from './defineApp';
7
7
  import { computed, type Component } from 'vue';
8
8
 
@@ -22,8 +22,8 @@ const __model1 = __createModel({
22
22
  },
23
23
  validate,
24
24
  autoSave: true,
25
- onSave(seed) {
26
- console.log('save', seed);
25
+ onSave(_seed) {
26
+ //
27
27
  },
28
28
  });
29
29
 
@@ -33,8 +33,8 @@ const __model2 = __createModel({
33
33
  },
34
34
  validate,
35
35
  autoSave: true,
36
- onSave(seed) {
37
- console.log('save', seed);
36
+ onSave(_seed) {
37
+ //
38
38
  },
39
39
  });
40
40
 
package/src/types.ts CHANGED
@@ -18,12 +18,13 @@ export interface ModelOptions<M, V = M> extends ReadableComputed<M> {
18
18
 
19
19
  export type Model<T> = {
20
20
  model: T;
21
- valid: boolean;
22
- isChanged: boolean;
23
- error: Error | undefined;
24
- errorString: string | undefined;
25
- save: () => void;
26
- revert: () => void;
21
+ readonly valid: boolean;
22
+ readonly isChanged: boolean;
23
+ readonly error: Error | undefined;
24
+ readonly errorString: string | undefined;
25
+ readonly save: () => void;
26
+ readonly revert: () => void;
27
+ readonly setError: (cause: unknown) => void;
27
28
  };
28
29
 
29
30
  interface ReadableComputed<T> {
package/src/utils.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { ValueOrErrors } from '@platforma-sdk/model';
2
2
  import type { OptionalResult } from './types';
3
+ import canonicalize from 'canonicalize';
3
4
 
4
5
  export class UnresolvedError extends Error {}
5
6
 
@@ -58,18 +59,6 @@ export function isDefined<T>(v: T | undefined): v is T {
58
59
  return v !== undefined;
59
60
  }
60
61
 
61
- export function getFilePathBreadcrumbs(filePath: string) {
62
- const chunks = filePath.split('/');
63
-
64
- const stack: { index: number; path: string; name: string }[] = [];
65
-
66
- for (let i = 0; i < chunks.length; i++) {
67
- stack.push({
68
- index: i,
69
- name: i === 0 ? 'Root' : chunks[i],
70
- path: chunks.slice(0, i + 1).join('/'),
71
- });
72
- }
73
-
74
- return stack;
62
+ export function isJsonEqual(a: unknown, b: unknown) {
63
+ return canonicalize(a) === canonicalize(b);
75
64
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"createApp.d.ts","sourceRoot":"","sources":["../../src/createApp.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAErG,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAKjI,wBAAgB,SAAS,CACvB,IAAI,GAAG,OAAO,EACd,OAAO,SAAS,gBAAgB,GAAG,gBAAgB,EACnD,OAAO,GAAG,OAAO,EACjB,IAAI,SAAS,IAAI,MAAM,EAAE,GAAG,IAAI,MAAM,EAAE,EACxC,KAAK,EAAE,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;sBAwCjF,CAAC,mBAAkB,iBAAiB,CAAC,IAAI,EAAE,CAAC,CAAC;oBAmB/C,CAAC,qBAAqB,iBAAiB,CAAC,OAAO,EAAE,CAAC,CAAC,8BAAuB,MAAM,OAAO;iBAqB1F,CAAC,SAAS,MAAM,OAAO,WAAW,CAAC,EAAE,KAAG,cAAc,CAAC,aAAa,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;oBA+B9E,CAAC,SAAS,MAAM,OAAO,WAAW,CAAC,EAAE,KAAG,aAAa,CAAC,OAAO,EAAE,CAAC,CAAC;0BAS3D,MAAM,OAAO;+BAOR,CAAC,SAAS,MAAM,OAAO,OAAO,CAAC,KAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS;mCASzE,CAAC,SAAS,MAAM,OAAO,OAAO,CAAC,KAAG,MAAM,EAAE,GAAG,SAAS;qBASpE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI;wBAKjB,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO;gCAIlB,CAAC,IAAI,EAAE,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI;uBAKvD,IAAI;;;;;;;;;;EAgCxB;AAED,MAAM,MAAM,OAAO,CACjB,IAAI,GAAG,OAAO,EACd,OAAO,SAAS,gBAAgB,GAAG,gBAAgB,EACnD,OAAO,GAAG,OAAO,EACjB,IAAI,SAAS,IAAI,MAAM,EAAE,GAAG,IAAI,MAAM,EAAE,IACtC,UAAU,CAAC,OAAO,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC"}