@sealcode/sealgen 0.14.0 → 0.14.1
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.
|
@@ -7,6 +7,7 @@ import { Mountable } from "./mountable.js";
|
|
|
7
7
|
export type StatefulPageActionDescription<ActionName> = {
|
|
8
8
|
action: ActionName;
|
|
9
9
|
label?: string;
|
|
10
|
+
content?: string;
|
|
10
11
|
} | ActionName;
|
|
11
12
|
export type StateAndMetadata<State, Actions> = {
|
|
12
13
|
state: State;
|
|
@@ -34,6 +35,7 @@ export declare abstract class StatefulPage<State extends Record<string, unknown>
|
|
|
34
35
|
extractActionAndLabel<ActionName extends keyof Actions>(action_description: StatefulPageActionDescription<ActionName>): {
|
|
35
36
|
action: string;
|
|
36
37
|
label: string;
|
|
38
|
+
content: string;
|
|
37
39
|
};
|
|
38
40
|
makeActionURL<ActionName extends keyof Actions>(action_description: StatefulPageActionDescription<ActionName>, ...args: ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>): string;
|
|
39
41
|
makeActionButton<ActionName extends keyof Actions>(_state: State, action_description: StatefulPageActionDescription<ActionName>, ...args: ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>): string;
|
|
@@ -42,7 +44,8 @@ export declare abstract class StatefulPage<State extends Record<string, unknown>
|
|
|
42
44
|
label?: string;
|
|
43
45
|
} | ActionName, ...args: ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>): string;
|
|
44
46
|
rerender(): string;
|
|
45
|
-
|
|
47
|
+
preprocessState(values: State): Promise<State>;
|
|
48
|
+
preprocessOverrides(state: State, values: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
46
49
|
extractState(ctx: BaseContext): Promise<StateAndMetadata<State, Actions>>;
|
|
47
50
|
mount(router: Router, path: string): void;
|
|
48
51
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { predicates, hasShape, is
|
|
1
|
+
import { predicates, hasShape, is } from "@sealcode/ts-predicates";
|
|
2
2
|
import deepmerge from "deepmerge";
|
|
3
3
|
import { tempstream } from "tempstream";
|
|
4
4
|
import { from_base64, to_base64 } from "../utils/base64.js";
|
|
@@ -31,15 +31,17 @@ class StatefulPage extends Mountable {
|
|
|
31
31
|
</form>`;
|
|
32
32
|
}
|
|
33
33
|
extractActionAndLabel(action_description) {
|
|
34
|
-
let label, action;
|
|
34
|
+
let label, action, content;
|
|
35
35
|
if (is(action_description, predicates.object)) {
|
|
36
36
|
action = action_description.action.toString();
|
|
37
37
|
label = action_description.label || action;
|
|
38
|
+
content = action_description.content || label;
|
|
38
39
|
} else {
|
|
39
40
|
action = action_description.toString();
|
|
40
41
|
label = action;
|
|
42
|
+
content = label;
|
|
41
43
|
}
|
|
42
|
-
return { action, label };
|
|
44
|
+
return { action, label, content };
|
|
43
45
|
}
|
|
44
46
|
makeActionURL(action_description, ...args) {
|
|
45
47
|
const { action } = this.extractActionAndLabel(action_description);
|
|
@@ -48,15 +50,17 @@ class StatefulPage extends Mountable {
|
|
|
48
50
|
)}`;
|
|
49
51
|
}
|
|
50
52
|
makeActionButton(_state, action_description, ...args) {
|
|
51
|
-
|
|
53
|
+
let { label, content } = this.extractActionAndLabel(action_description);
|
|
52
54
|
return (
|
|
53
55
|
/* HTML */
|
|
54
56
|
`
|
|
55
|
-
<
|
|
57
|
+
<button
|
|
56
58
|
type="submit"
|
|
57
|
-
value="${label}"
|
|
58
59
|
formaction="${this.makeActionURL(action_description, ...args)}"
|
|
59
|
-
|
|
60
|
+
title="${label}"
|
|
61
|
+
>
|
|
62
|
+
${content}
|
|
63
|
+
</button>
|
|
60
64
|
`
|
|
61
65
|
);
|
|
62
66
|
}
|
|
@@ -69,7 +73,10 @@ class StatefulPage extends Mountable {
|
|
|
69
73
|
rerender() {
|
|
70
74
|
return "this.closest('form').requestSubmit()";
|
|
71
75
|
}
|
|
72
|
-
async
|
|
76
|
+
async preprocessState(values) {
|
|
77
|
+
return values;
|
|
78
|
+
}
|
|
79
|
+
async preprocessOverrides(state, values) {
|
|
73
80
|
return values;
|
|
74
81
|
}
|
|
75
82
|
async extractState(ctx) {
|
|
@@ -85,31 +92,50 @@ class StatefulPage extends Mountable {
|
|
|
85
92
|
console.error("Wrong data: ", ctx.$body);
|
|
86
93
|
throw new Error("wrong formdata shape");
|
|
87
94
|
}
|
|
88
|
-
const prepared_body = await this.preprocessRequestBody(ctx.$body);
|
|
89
95
|
const inputs = Object.fromEntries(
|
|
90
|
-
Object.entries(
|
|
96
|
+
Object.entries(ctx.$body).filter(
|
|
91
97
|
([key]) => !["action", "state", "args", "$"].includes(key)
|
|
92
98
|
)
|
|
93
99
|
);
|
|
94
|
-
const original_state_string =
|
|
100
|
+
const original_state_string = ctx.$body.state;
|
|
95
101
|
const original_state = JSON.parse(
|
|
96
102
|
typeof original_state_string == "string" ? from_base64(original_state_string) : "{}"
|
|
97
103
|
);
|
|
98
|
-
const
|
|
104
|
+
const $body = ctx.$body;
|
|
105
|
+
let state_overrides = $body.$ || {};
|
|
106
|
+
state_overrides = await this.preprocessOverrides(
|
|
99
107
|
original_state,
|
|
100
|
-
|
|
101
|
-
{
|
|
102
|
-
arrayMerge: (_target, source, _options) => {
|
|
103
|
-
return source;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
108
|
+
state_overrides
|
|
106
109
|
);
|
|
107
|
-
|
|
110
|
+
let modified_state = deepmerge(original_state, state_overrides, {
|
|
111
|
+
arrayMerge: (target, source, options) => {
|
|
112
|
+
const destination = target.slice();
|
|
113
|
+
source.forEach((item, index) => {
|
|
114
|
+
if (typeof destination[index] === "undefined") {
|
|
115
|
+
destination[index] = options.cloneUnlessOtherwiseSpecified(
|
|
116
|
+
item,
|
|
117
|
+
options
|
|
118
|
+
);
|
|
119
|
+
} else if (options.isMergeableObject(item)) {
|
|
120
|
+
destination[index] = deepmerge(
|
|
121
|
+
target[index],
|
|
122
|
+
item,
|
|
123
|
+
options
|
|
124
|
+
);
|
|
125
|
+
} else if (target.indexOf(item) === -1) {
|
|
126
|
+
destination.push(item);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
return destination;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
modified_state = await this.preprocessState(modified_state);
|
|
133
|
+
if (ctx.$body.action && ctx.$body.action_args) {
|
|
108
134
|
return {
|
|
109
135
|
state: modified_state,
|
|
110
|
-
action:
|
|
136
|
+
action: ctx.$body.action,
|
|
111
137
|
inputs,
|
|
112
|
-
action_args:
|
|
138
|
+
action_args: ctx.$body.action_args
|
|
113
139
|
};
|
|
114
140
|
} else {
|
|
115
141
|
return {
|
|
@@ -117,7 +143,7 @@ class StatefulPage extends Mountable {
|
|
|
117
143
|
action: null,
|
|
118
144
|
inputs,
|
|
119
145
|
action_args: null,
|
|
120
|
-
$:
|
|
146
|
+
$: ctx.$body.$ || {}
|
|
121
147
|
};
|
|
122
148
|
}
|
|
123
149
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sealcode/sealgen",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "Module to automate adding routes and collections to a sealious application",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -71,6 +71,6 @@
|
|
|
71
71
|
"peerDependencies": {
|
|
72
72
|
"koa": "^2.13.0",
|
|
73
73
|
"koa-responsive-image-router": "^0.2.6",
|
|
74
|
-
"sealious": "^0.18.
|
|
74
|
+
"sealious": "^0.18.1"
|
|
75
75
|
}
|
|
76
76
|
}
|
|
@@ -11,6 +11,7 @@ export type StatefulPageActionDescription<ActionName> =
|
|
|
11
11
|
| {
|
|
12
12
|
action: ActionName;
|
|
13
13
|
label?: string;
|
|
14
|
+
content?: string;
|
|
14
15
|
}
|
|
15
16
|
| ActionName;
|
|
16
17
|
|
|
@@ -85,16 +86,18 @@ export abstract class StatefulPage<
|
|
|
85
86
|
|
|
86
87
|
extractActionAndLabel<ActionName extends keyof Actions>(
|
|
87
88
|
action_description: StatefulPageActionDescription<ActionName>
|
|
88
|
-
): { action: string; label: string } {
|
|
89
|
-
let label, action: string;
|
|
89
|
+
): { action: string; label: string; content: string } {
|
|
90
|
+
let label, action, content: string;
|
|
90
91
|
if (is(action_description, predicates.object)) {
|
|
91
92
|
action = action_description.action.toString();
|
|
92
93
|
label = action_description.label || action;
|
|
94
|
+
content = action_description.content || label;
|
|
93
95
|
} else {
|
|
94
96
|
action = action_description.toString();
|
|
95
97
|
label = action;
|
|
98
|
+
content = label;
|
|
96
99
|
}
|
|
97
|
-
return { action, label };
|
|
100
|
+
return { action, label, content };
|
|
98
101
|
}
|
|
99
102
|
|
|
100
103
|
makeActionURL<ActionName extends keyof Actions>(
|
|
@@ -112,13 +115,15 @@ export abstract class StatefulPage<
|
|
|
112
115
|
action_description: StatefulPageActionDescription<ActionName>,
|
|
113
116
|
...args: ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>
|
|
114
117
|
) {
|
|
115
|
-
|
|
118
|
+
let { label, content } = this.extractActionAndLabel(action_description);
|
|
116
119
|
return /* HTML */ `
|
|
117
|
-
<
|
|
120
|
+
<button
|
|
118
121
|
type="submit"
|
|
119
|
-
value="${label}"
|
|
120
122
|
formaction="${this.makeActionURL(action_description, ...args)}"
|
|
121
|
-
|
|
123
|
+
title="${label}"
|
|
124
|
+
>
|
|
125
|
+
${content}
|
|
126
|
+
</button>
|
|
122
127
|
`;
|
|
123
128
|
}
|
|
124
129
|
|
|
@@ -141,10 +146,15 @@ export abstract class StatefulPage<
|
|
|
141
146
|
return "this.closest('form').requestSubmit()";
|
|
142
147
|
}
|
|
143
148
|
|
|
144
|
-
async
|
|
149
|
+
async preprocessState(values: State): Promise<State> {
|
|
150
|
+
return values;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async preprocessOverrides(
|
|
154
|
+
state: State,
|
|
145
155
|
values: Record<string, unknown>
|
|
146
|
-
): Promise<
|
|
147
|
-
return values
|
|
156
|
+
): Promise<Record<string, unknown>> {
|
|
157
|
+
return values;
|
|
148
158
|
}
|
|
149
159
|
|
|
150
160
|
async extractState(
|
|
@@ -164,43 +174,66 @@ export abstract class StatefulPage<
|
|
|
164
174
|
console.error("Wrong data: ", ctx.$body);
|
|
165
175
|
throw new Error("wrong formdata shape");
|
|
166
176
|
}
|
|
167
|
-
|
|
168
|
-
const prepared_body = await this.preprocessRequestBody(ctx.$body);
|
|
177
|
+
|
|
169
178
|
const inputs = Object.fromEntries(
|
|
170
|
-
Object.entries(
|
|
179
|
+
Object.entries(ctx.$body).filter(
|
|
171
180
|
([key]) => !["action", "state", "args", "$"].includes(key)
|
|
172
181
|
)
|
|
173
182
|
) as Record<string, string>;
|
|
174
183
|
// the "$" key is parsed as dot notation and overrides the state
|
|
175
184
|
|
|
176
|
-
const original_state_string =
|
|
185
|
+
const original_state_string = ctx.$body.state;
|
|
177
186
|
const original_state = JSON.parse(
|
|
178
187
|
typeof original_state_string == "string"
|
|
179
188
|
? from_base64(original_state_string)
|
|
180
189
|
: "{}"
|
|
181
190
|
);
|
|
182
191
|
|
|
183
|
-
const
|
|
192
|
+
const $body = ctx.$body;
|
|
193
|
+
let state_overrides = $body.$ || {};
|
|
194
|
+
state_overrides = await this.preprocessOverrides(
|
|
184
195
|
original_state,
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
196
|
+
state_overrides
|
|
197
|
+
);
|
|
198
|
+
let modified_state = deepmerge(original_state, state_overrides, {
|
|
199
|
+
arrayMerge: (
|
|
200
|
+
target: any[],
|
|
201
|
+
source: any[],
|
|
202
|
+
options: ArrayMergeOptions
|
|
203
|
+
) => {
|
|
204
|
+
// https://github.com/TehShrike/deepmerge?tab=readme-ov-file#arraymerge-example-combine-arrays
|
|
205
|
+
const destination = target.slice();
|
|
206
|
+
|
|
207
|
+
source.forEach((item, index) => {
|
|
208
|
+
if (typeof destination[index] === "undefined") {
|
|
209
|
+
destination[index] =
|
|
210
|
+
options.cloneUnlessOtherwiseSpecified(
|
|
211
|
+
item,
|
|
212
|
+
options
|
|
213
|
+
);
|
|
214
|
+
} else if (options.isMergeableObject(item)) {
|
|
215
|
+
destination[index] = deepmerge(
|
|
216
|
+
target[index],
|
|
217
|
+
item,
|
|
218
|
+
options
|
|
219
|
+
);
|
|
220
|
+
} else if (target.indexOf(item) === -1) {
|
|
221
|
+
destination.push(item);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
return destination;
|
|
225
|
+
},
|
|
226
|
+
}) as State;
|
|
227
|
+
|
|
228
|
+
// giving extending classes a change to modify the state before furhter processing
|
|
229
|
+
modified_state = await this.preprocessState(modified_state);
|
|
197
230
|
|
|
198
|
-
if (
|
|
231
|
+
if (ctx.$body.action && ctx.$body.action_args) {
|
|
199
232
|
return {
|
|
200
233
|
state: modified_state,
|
|
201
|
-
action:
|
|
234
|
+
action: ctx.$body.action as string,
|
|
202
235
|
inputs,
|
|
203
|
-
action_args:
|
|
236
|
+
action_args: ctx.$body.action_args,
|
|
204
237
|
};
|
|
205
238
|
} else {
|
|
206
239
|
return {
|
|
@@ -208,7 +241,7 @@ export abstract class StatefulPage<
|
|
|
208
241
|
action: null,
|
|
209
242
|
inputs,
|
|
210
243
|
action_args: null,
|
|
211
|
-
$:
|
|
244
|
+
$: ctx.$body.$ || {},
|
|
212
245
|
};
|
|
213
246
|
}
|
|
214
247
|
}
|