@yuneta/gobj-ui 6.0.0 → 6.1.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.
- package/README.md +105 -2
- package/dist/gobj-ui.cjs.js +5529 -476
- package/dist/gobj-ui.es.js +5529 -477
- package/index.js +1 -0
- package/package.json +1 -1
- package/src/c_yui_schema_editor.css +133 -0
- package/src/c_yui_schema_editor.js +3303 -0
- package/src/c_yui_treedb_schema.js +22 -3
- package/src/schema_descs.js +172 -0
- package/src/schema_descs.test.js +170 -0
- package/src/schema_flags.js +237 -0
- package/src/schema_flags.test.js +133 -0
- package/src/schema_import.js +424 -0
- package/src/schema_import.test.js +255 -0
- package/src/schema_model.js +538 -0
- package/src/schema_model.test.js +258 -0
- package/src/schema_to_c.js +381 -0
- package/src/schema_to_c.test.js +265 -0
- package/src/schema_validate.js +289 -0
- package/src/schema_validate.test.js +265 -0
- package/src/schema_write_options.js +60 -0
- package/src/schema_write_options.test.js +65 -0
- package/src/yui_icons.css +14 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* schema_validate.test.js
|
|
3
|
+
*
|
|
4
|
+
* What must be caught BEFORE the restart, pinned.
|
|
5
|
+
*
|
|
6
|
+
* Each of these is a way to spend an outage finding out something
|
|
7
|
+
* the records already said: a link that saves nothing, a topic
|
|
8
|
+
* that will not open, or — the expensive one — a restart that
|
|
9
|
+
* succeeds and changes nothing because the version did not move.
|
|
10
|
+
***********************************************************************/
|
|
11
|
+
import { describe, test, expect } from "vitest";
|
|
12
|
+
import { build_schema_model } from "./schema_model.js";
|
|
13
|
+
import { validate_schema, validate_model, has_errors } from "./schema_validate.js";
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
/* A treedb of two topics linked by departments.users -> users.departments,
|
|
17
|
+
which is the shape every test below breaks in one place. */
|
|
18
|
+
function schema(overrides)
|
|
19
|
+
{
|
|
20
|
+
let o = overrides || {};
|
|
21
|
+
const records = {
|
|
22
|
+
treedbs: [{id: "db", schema_version: 1}],
|
|
23
|
+
topics: [
|
|
24
|
+
{id: "db.departments", value: "departments", order: 1, pkey: "id",
|
|
25
|
+
topic_version: o.dep_version === undefined ? 1 : o.dep_version,
|
|
26
|
+
treedbs: ["treedbs^db^topics"]},
|
|
27
|
+
{id: "db.users", value: "users", order: 2, pkey: o.users_pkey || "id",
|
|
28
|
+
topic_version: 1, pkey2s: o.users_pkey2s,
|
|
29
|
+
treedbs: ["treedbs^db^topics"]},
|
|
30
|
+
],
|
|
31
|
+
cols: [
|
|
32
|
+
{id: "db.departments.id", value: "id", order: 1, type: "string",
|
|
33
|
+
flag: ["persistent"], topics: ["topics^db.departments^cols"]},
|
|
34
|
+
{id: "db.departments.users", value: "users", order: 2,
|
|
35
|
+
type: o.hook_type === undefined ? "dict" : o.hook_type,
|
|
36
|
+
flag: ["hook"], hook: o.hook === undefined ? {users: "departments"} : o.hook,
|
|
37
|
+
topics: ["topics^db.departments^cols"]},
|
|
38
|
+
{id: "db.users.id", value: "id", order: 1, type: "string",
|
|
39
|
+
flag: ["persistent"], topics: ["topics^db.users^cols"]},
|
|
40
|
+
{id: "db.users.departments", value: "departments", order: 2,
|
|
41
|
+
type: o.fkey_type === undefined ? "array" : o.fkey_type,
|
|
42
|
+
flag: o.fkey_flag === undefined ? ["fkey"] : o.fkey_flag,
|
|
43
|
+
topics: ["topics^db.users^cols"]},
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
if(o.extra_col) {
|
|
47
|
+
records.cols.push(o.extra_col);
|
|
48
|
+
}
|
|
49
|
+
return build_schema_model(records).treedbs[0];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function codes(findings)
|
|
53
|
+
{
|
|
54
|
+
return findings.map(f => f.code);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
describe("a schema with nothing wrong", () => {
|
|
59
|
+
test("says nothing", () => {
|
|
60
|
+
expect(validate_schema(schema())).toEqual([]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("no treedb at all is not a crash", () => {
|
|
64
|
+
expect(validate_schema(null)).toEqual([]);
|
|
65
|
+
expect(validate_schema(undefined, {})).toEqual([]);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("the topic will not open", () => {
|
|
70
|
+
test("a pkey naming no column", () => {
|
|
71
|
+
const f = validate_schema(schema({users_pkey: "user_id"}));
|
|
72
|
+
expect(codes(f)).toContain("pkey names no column");
|
|
73
|
+
expect(f[0].detail).toBe("user_id");
|
|
74
|
+
expect(has_errors(f)).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("a pkey2 naming no column", () => {
|
|
78
|
+
expect(codes(validate_schema(schema({users_pkey2s: ["name"]}))))
|
|
79
|
+
.toContain("pkey2 names no column");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("a pkey2 declared as a bare string is read the same way", () => {
|
|
83
|
+
expect(codes(validate_schema(schema({users_pkey2s: "name"}))))
|
|
84
|
+
.toContain("pkey2 names no column");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("a topic with no column", () => {
|
|
88
|
+
const model = build_schema_model({
|
|
89
|
+
treedbs: [{id: "db"}],
|
|
90
|
+
topics: [{id: "db.empty", value: "empty", treedbs: ["treedbs^db^topics"]}],
|
|
91
|
+
cols: [],
|
|
92
|
+
});
|
|
93
|
+
expect(codes(validate_schema(model.treedbs[0]))).toContain("topic has no column");
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("the link does nothing, and the write succeeded", () => {
|
|
98
|
+
test("a hook with no mapping", () => {
|
|
99
|
+
expect(codes(validate_schema(schema({hook: null})))).toContain("hook has no mapping");
|
|
100
|
+
expect(codes(validate_schema(schema({hook: {}})))).toContain("hook has no mapping");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("a hook naming a topic that is not in this treedb", () => {
|
|
104
|
+
expect(codes(validate_schema(schema({hook: {clients: "departments"}}))))
|
|
105
|
+
.toContain("hook names no topic");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("a hook naming a column the child does not have", () => {
|
|
109
|
+
const f = validate_schema(schema({hook: {users: "department_id"}}));
|
|
110
|
+
expect(codes(f)).toContain("hook names no fkey column");
|
|
111
|
+
expect(f[0].detail).toBe("users.department_id");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("a hook whose target column is NOT flagged fkey", () => {
|
|
115
|
+
expect(codes(validate_schema(schema({fkey_flag: ["persistent"]}))))
|
|
116
|
+
.toContain("hook target is not a fkey");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("a hook typed as a scalar cannot hold children", () => {
|
|
120
|
+
expect(codes(validate_schema(schema({hook_type: "string"}))))
|
|
121
|
+
.toContain("hook must be a collection");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("a fkey typed as an integer", () => {
|
|
125
|
+
expect(codes(validate_schema(schema({fkey_type: "integer"}))))
|
|
126
|
+
.toContain("fkey has a bad type");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("a fkey no hook names is a WARNING — the parent side went away", () => {
|
|
130
|
+
const f = validate_schema(schema({hook: {users: "departments"},
|
|
131
|
+
extra_col: {id: "db.users.boss", value: "boss", order: 3, type: "string",
|
|
132
|
+
flag: ["fkey"], topics: ["topics^db.users^cols"]}}));
|
|
133
|
+
const orphan = f.filter(x => x.code === "fkey with no hook");
|
|
134
|
+
expect(orphan.length).toBe(1);
|
|
135
|
+
expect(orphan[0].col).toBe("boss");
|
|
136
|
+
expect(orphan[0].severity).toBe("warning");
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe("the column itself", () => {
|
|
141
|
+
test("no type", () => {
|
|
142
|
+
expect(codes(validate_schema(schema({
|
|
143
|
+
extra_col: {id: "db.users.x", value: "x", order: 9,
|
|
144
|
+
topics: ["topics^db.users^cols"]}
|
|
145
|
+
})))).toContain("column has no type");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("a type the treedb's own validator does not know", () => {
|
|
149
|
+
expect(codes(validate_schema(schema({
|
|
150
|
+
extra_col: {id: "db.users.x", value: "x", order: 9, type: "text",
|
|
151
|
+
topics: ["topics^db.users^cols"]}
|
|
152
|
+
})))).toContain("unknown column type");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("flagged enum with no enum list", () => {
|
|
156
|
+
expect(codes(validate_schema(schema({
|
|
157
|
+
extra_col: {id: "db.users.x", value: "x", order: 9, type: "string",
|
|
158
|
+
flag: ["enum"], topics: ["topics^db.users^cols"]}
|
|
159
|
+
})))).toContain("enum column has no enum");
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("an enum stored as JSON TEXT counts as an enum", () => {
|
|
163
|
+
expect(codes(validate_schema(schema({
|
|
164
|
+
extra_col: {id: "db.users.x", value: "x", order: 9, type: "string",
|
|
165
|
+
flag: ["enum"], enum: '["a","b"]',
|
|
166
|
+
topics: ["topics^db.users^cols"]}
|
|
167
|
+
})))).not.toContain("enum column has no enum");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("a hook stored as JSON TEXT is still a hook mapping", () => {
|
|
171
|
+
expect(validate_schema(schema({hook: '{"users":"departments"}'}))).toEqual([]);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("a flag stored as a bare string is still a flag", () => {
|
|
175
|
+
expect(codes(validate_schema(schema({fkey_flag: "fkey"}))))
|
|
176
|
+
.not.toContain("hook target is not a fkey");
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe("the version that did not move", () => {
|
|
181
|
+
test("a written topic whose topic_version is unchanged is reported", () => {
|
|
182
|
+
const f = validate_schema(schema(), {
|
|
183
|
+
written_topics: ["db.departments"],
|
|
184
|
+
baseline: {"db.departments": 1}
|
|
185
|
+
});
|
|
186
|
+
const v = f.filter(x => x.code === "topic version not bumped");
|
|
187
|
+
expect(v.length).toBe(1);
|
|
188
|
+
expect(v[0].topic).toBe("departments");
|
|
189
|
+
expect(v[0].severity).toBe("warning");
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("a bumped version says nothing", () => {
|
|
193
|
+
expect(validate_schema(schema({dep_version: 2}), {
|
|
194
|
+
written_topics: ["db.departments"],
|
|
195
|
+
baseline: {"db.departments": 1}
|
|
196
|
+
})).toEqual([]);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("a topic nobody wrote is not asked about its version", () => {
|
|
200
|
+
expect(validate_schema(schema(), {baseline: {"db.departments": 1}})).toEqual([]);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("a version compared across types still matches (2 vs '2')", () => {
|
|
204
|
+
expect(validate_schema(schema({dep_version: "1"}), {
|
|
205
|
+
written_topics: ["db.departments"],
|
|
206
|
+
baseline: {"db.departments": 1}
|
|
207
|
+
}).length).toBe(1);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe("errors come first, and orphans are the model's business", () => {
|
|
212
|
+
test("an error outranks a warning in the list", () => {
|
|
213
|
+
const f = validate_schema(schema({users_pkey: "nope",
|
|
214
|
+
extra_col: {id: "db.users.boss", value: "boss", order: 3, type: "string",
|
|
215
|
+
flag: ["fkey"], topics: ["topics^db.users^cols"]}}));
|
|
216
|
+
expect(f[0].severity).toBe("error");
|
|
217
|
+
expect(f[f.length - 1].severity).toBe("warning");
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("validate_model reports what belongs to nothing", () => {
|
|
221
|
+
const model = build_schema_model({
|
|
222
|
+
treedbs: [{id: "db"}],
|
|
223
|
+
topics: [{id: "gone.users", value: "users", treedbs: ["treedbs^gone^topics"]}],
|
|
224
|
+
cols: [{id: "db.gone.x", value: "x", topics: ["topics^db.gone^cols"]}],
|
|
225
|
+
});
|
|
226
|
+
const c = codes(validate_model(model));
|
|
227
|
+
expect(c).toContain("topic belongs to no treedb");
|
|
228
|
+
expect(c).toContain("column belongs to no topic");
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("has_errors ignores warnings", () => {
|
|
232
|
+
expect(has_errors([{severity: "warning"}])).toBe(false);
|
|
233
|
+
expect(has_errors([{severity: "warning"}, {severity: "error"}])).toBe(true);
|
|
234
|
+
expect(has_errors(null)).toBe(false);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe("only one hook may name a fkey column", () => {
|
|
239
|
+
test("two hooks on the same fkey is what the treedb refuses to open", () => {
|
|
240
|
+
const model = build_schema_model({
|
|
241
|
+
treedbs: [{id: "db"}],
|
|
242
|
+
topics: [
|
|
243
|
+
{id: "db.a", value: "a", order: 1, treedbs: ["treedbs^db^topics"]},
|
|
244
|
+
{id: "db.b", value: "b", order: 2, treedbs: ["treedbs^db^topics"]},
|
|
245
|
+
{id: "db.c", value: "c", order: 3, treedbs: ["treedbs^db^topics"]},
|
|
246
|
+
],
|
|
247
|
+
cols: [
|
|
248
|
+
{id: "db.a.id", value: "id", order: 1, type: "string", topics: ["topics^db.a^cols"]},
|
|
249
|
+
{id: "db.a.cs", value: "cs", order: 2, type: "dict", flag: ["hook"],
|
|
250
|
+
hook: {c: "parent"}, topics: ["topics^db.a^cols"]},
|
|
251
|
+
{id: "db.b.id", value: "id", order: 1, type: "string", topics: ["topics^db.b^cols"]},
|
|
252
|
+
{id: "db.b.cs", value: "cs", order: 2, type: "dict", flag: ["hook"],
|
|
253
|
+
hook: {c: "parent"}, topics: ["topics^db.b^cols"]},
|
|
254
|
+
{id: "db.c.id", value: "id", order: 1, type: "string", topics: ["topics^db.c^cols"]},
|
|
255
|
+
{id: "db.c.parent", value: "parent", order: 2, type: "array", flag: ["fkey"],
|
|
256
|
+
topics: ["topics^db.c^cols"]},
|
|
257
|
+
],
|
|
258
|
+
});
|
|
259
|
+
const f = validate_schema(model.treedbs[0]);
|
|
260
|
+
const dup = f.filter(x => x.code === "fkey named by two hooks");
|
|
261
|
+
expect(dup.length).toBe(1);
|
|
262
|
+
expect(dup[0].col).toBe("parent");
|
|
263
|
+
expect(dup[0].severity).toBe("error");
|
|
264
|
+
});
|
|
265
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* schema_write_options.js
|
|
3
|
+
*
|
|
4
|
+
* The OPTIONS a schema write travels with, and why each one.
|
|
5
|
+
*
|
|
6
|
+
* Three words decide whether a write does what it says, and all
|
|
7
|
+
* three are easy to get wrong in a way that succeeds:
|
|
8
|
+
*
|
|
9
|
+
* `create` a node that is not there yet is written by
|
|
10
|
+
* `update-node` with this, not by `create-node`. The
|
|
11
|
+
* two are not the same call: only the update path
|
|
12
|
+
* carries `autolink`, and without a link a new column
|
|
13
|
+
* belongs to no topic.
|
|
14
|
+
*
|
|
15
|
+
* `autolink` turns the fkey carried IN THE RECORD into a link.
|
|
16
|
+
* It rewrites the node's links from the fkey fields
|
|
17
|
+
* the record has — so on a PARTIAL update, which
|
|
18
|
+
* carries none, it reads that as "no parents" and
|
|
19
|
+
* UNLINKS the node. Raising a topic's version with it
|
|
20
|
+
* on detached that topic from its treedb, and the
|
|
21
|
+
* write returned success. It goes with a create,
|
|
22
|
+
* where the record carries its fkey, and with nothing
|
|
23
|
+
* else.
|
|
24
|
+
*
|
|
25
|
+
* `force` a delete of a node something points at. A column is
|
|
26
|
+
* pointed at by the topic that owns it, which is not
|
|
27
|
+
* a reason to refuse.
|
|
28
|
+
*
|
|
29
|
+
* Pure and tested, because the cost of getting it wrong is a
|
|
30
|
+
* store that has to be repaired by hand, and nothing in the
|
|
31
|
+
* answer says it happened.
|
|
32
|
+
*
|
|
33
|
+
* Copyright (c) 2026, ArtGins.
|
|
34
|
+
* All Rights Reserved.
|
|
35
|
+
***********************************************************************/
|
|
36
|
+
|
|
37
|
+
/***************************************************************
|
|
38
|
+
* write_command(op) -> the treedb command that performs it
|
|
39
|
+
***************************************************************/
|
|
40
|
+
function write_command(op)
|
|
41
|
+
{
|
|
42
|
+
return (op === "delete") ? "delete-node" : "update-node";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/***************************************************************
|
|
46
|
+
* write_options(op) -> the options it travels with
|
|
47
|
+
***************************************************************/
|
|
48
|
+
function write_options(op)
|
|
49
|
+
{
|
|
50
|
+
if(op === "delete") {
|
|
51
|
+
return {force: true};
|
|
52
|
+
}
|
|
53
|
+
if(op === "create") {
|
|
54
|
+
return {list_dict: true, create: true, autolink: true};
|
|
55
|
+
}
|
|
56
|
+
return {list_dict: true};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
export {write_command, write_options};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* schema_write_options.test.js
|
|
3
|
+
*
|
|
4
|
+
* The three words that decide whether a schema write does what it
|
|
5
|
+
* says, pinned.
|
|
6
|
+
*
|
|
7
|
+
* This file exists because of one afternoon: `autolink` on a
|
|
8
|
+
* partial update detached a topic from its treedb — the version
|
|
9
|
+
* was raised, the write answered success, and the topic vanished
|
|
10
|
+
* from the schema. Nothing in the answer said so.
|
|
11
|
+
***********************************************************************/
|
|
12
|
+
import { describe, test, expect } from "vitest";
|
|
13
|
+
import { write_command, write_options } from "./schema_write_options.js";
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
describe("which command performs which write", () => {
|
|
17
|
+
test("a create goes through update-node, NOT create-node", () => {
|
|
18
|
+
/* Only the update path carries `autolink`, and without a link a
|
|
19
|
+
new column belongs to no topic. */
|
|
20
|
+
expect(write_command("create")).toBe("update-node");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("an update goes through update-node", () => {
|
|
24
|
+
expect(write_command("update")).toBe("update-node");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("a delete goes through delete-node", () => {
|
|
28
|
+
expect(write_command("delete")).toBe("delete-node");
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("autolink goes with a create and with nothing else", () => {
|
|
33
|
+
test("a create asks for it — its record carries the fkey", () => {
|
|
34
|
+
const o = write_options("create");
|
|
35
|
+
expect(o.autolink).toBe(true);
|
|
36
|
+
expect(o.create).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("an update NEVER asks for it", () => {
|
|
40
|
+
/* The editor writes only what changed, so an update carries no
|
|
41
|
+
fkey — and autolink reads an absent fkey as "no parents". */
|
|
42
|
+
expect(write_options("update").autolink).toBeUndefined();
|
|
43
|
+
expect(write_options("update").create).toBeUndefined();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("a delete never asks for it either", () => {
|
|
47
|
+
expect(write_options("delete").autolink).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("the rest", () => {
|
|
52
|
+
test("a delete forces: a column is pointed at by its own topic", () => {
|
|
53
|
+
expect(write_options("delete")).toEqual({force: true});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("reads come back as dicts, so a write answers in the same shape", () => {
|
|
57
|
+
expect(write_options("create").list_dict).toBe(true);
|
|
58
|
+
expect(write_options("update").list_dict).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("an unknown op is treated as an update — the safe one", () => {
|
|
62
|
+
expect(write_options("")).toEqual({list_dict: true});
|
|
63
|
+
expect(write_options(undefined).autolink).toBeUndefined();
|
|
64
|
+
});
|
|
65
|
+
});
|
package/src/yui_icons.css
CHANGED
|
@@ -349,3 +349,17 @@
|
|
|
349
349
|
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512'%3E%3Cpath d='M192 0C192 0 48 176 48 304a144 144 0 1 0 288 0C336 176 192 0 192 0z'/%3E%3C/svg%3E");
|
|
350
350
|
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512'%3E%3Cpath d='M192 0C192 0 48 176 48 304a144 144 0 1 0 288 0C336 176 192 0 192 0z'/%3E%3C/svg%3E");
|
|
351
351
|
}
|
|
352
|
+
|
|
353
|
+
/* The drag handle of a reorderable row, and the icon of a treedb: both
|
|
354
|
+
* are wanted by the schema editor and neither was in the set. A missing
|
|
355
|
+
* glyph renders as a solid black square, so a mask is added rather than
|
|
356
|
+
* a class referenced on hope. */
|
|
357
|
+
.yi-grip-vertical::before {
|
|
358
|
+
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E%3Cpath d='M40 352l48 0c22.1 0 40 17.9 40 40l0 48c0 22.1-17.9 40-40 40l-48 0c-22.1 0-40-17.9-40-40l0-48c0-22.1 17.9-40 40-40zm192 0l48 0c22.1 0 40 17.9 40 40l0 48c0 22.1-17.9 40-40 40l-48 0c-22.1 0-40-17.9-40-40l0-48c0-22.1 17.9-40 40-40zM40 192l48 0c22.1 0 40 17.9 40 40l0 48c0 22.1-17.9 40-40 40l-48 0c-22.1 0-40-17.9-40-40l0-48c0-22.1 17.9-40 40-40zm192 0l48 0c22.1 0 40 17.9 40 40l0 48c0 22.1-17.9 40-40 40l-48 0c-22.1 0-40-17.9-40-40l0-48c0-22.1 17.9-40 40-40zM40 32l48 0c22.1 0 40 17.9 40 40l0 48c0 22.1-17.9 40-40 40l-48 0c-22.1 0-40-17.9-40-40l0-48c0-22.1 17.9-40 40-40zM232 32l48 0c22.1 0 40 17.9 40 40l0 48c0 22.1-17.9 40-40 40l-48 0c-22.1 0-40-17.9-40-40l0-48c0-22.1 17.9-40 40-40z'/%3E%3C/svg%3E");
|
|
359
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E%3Cpath d='M40 352l48 0c22.1 0 40 17.9 40 40l0 48c0 22.1-17.9 40-40 40l-48 0c-22.1 0-40-17.9-40-40l0-48c0-22.1 17.9-40 40-40zm192 0l48 0c22.1 0 40 17.9 40 40l0 48c0 22.1-17.9 40-40 40l-48 0c-22.1 0-40-17.9-40-40l0-48c0-22.1 17.9-40 40-40zM40 192l48 0c22.1 0 40 17.9 40 40l0 48c0 22.1-17.9 40-40 40l-48 0c-22.1 0-40-17.9-40-40l0-48c0-22.1 17.9-40 40-40zm192 0l48 0c22.1 0 40 17.9 40 40l0 48c0 22.1-17.9 40-40 40l-48 0c-22.1 0-40-17.9-40-40l0-48c0-22.1 17.9-40 40-40zM40 32l48 0c22.1 0 40 17.9 40 40l0 48c0 22.1-17.9 40-40 40l-48 0c-22.1 0-40-17.9-40-40l0-48c0-22.1 17.9-40 40-40zM232 32l48 0c22.1 0 40 17.9 40 40l0 48c0 22.1-17.9 40-40 40l-48 0c-22.1 0-40-17.9-40-40l0-48c0-22.1 17.9-40 40-40z'/%3E%3C/svg%3E");
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.yi-database::before {
|
|
363
|
+
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M448 80l0 48c0 44.2-100.3 80-224 80S0 172.2 0 128L0 80C0 35.8 100.3 0 224 0S448 35.8 448 80zM393.2 214.7c20.8-7.4 39.9-16.9 54.8-28.6L448 288c0 44.2-100.3 80-224 80S0 332.2 0 288L0 186.1c14.9 11.8 34 21.2 54.8 28.6C99.7 230.7 159.5 240 224 240s124.3-9.3 169.2-25.3zM0 346.1c14.9 11.8 34 21.2 54.8 28.6C99.7 390.7 159.5 400 224 400s124.3-9.3 169.2-25.3c20.8-7.4 39.9-16.9 54.8-28.6l0 85.9c0 44.2-100.3 80-224 80S0 476.2 0 432l0-85.9z'/%3E%3C/svg%3E");
|
|
364
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M448 80l0 48c0 44.2-100.3 80-224 80S0 172.2 0 128L0 80C0 35.8 100.3 0 224 0S448 35.8 448 80zM393.2 214.7c20.8-7.4 39.9-16.9 54.8-28.6L448 288c0 44.2-100.3 80-224 80S0 332.2 0 288L0 186.1c14.9 11.8 34 21.2 54.8 28.6C99.7 230.7 159.5 240 224 240s124.3-9.3 169.2-25.3zM0 346.1c14.9 11.8 34 21.2 54.8 28.6C99.7 390.7 159.5 400 224 400s124.3-9.3 169.2-25.3c20.8-7.4 39.9-16.9 54.8-28.6l0 85.9c0 44.2-100.3 80-224 80S0 476.2 0 432l0-85.9z'/%3E%3C/svg%3E");
|
|
365
|
+
}
|