@operato/board 10.29.8 → 10.29.10
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/CHANGELOG.md +28 -0
- package/coverage/lcov-report/index.html +71 -26
- package/coverage/lcov.info +4477 -470
- package/dist/src/figure-provider.d.ts +52 -16
- package/dist/src/figure-provider.js +70 -65
- package/dist/src/figure-provider.js.map +1 -1
- package/dist/src/modeller/component-toolbar/component-menu.js +62 -9
- package/dist/src/modeller/component-toolbar/component-menu.js.map +1 -1
- package/dist/src/ox-board-modeller.d.ts +5 -0
- package/dist/src/ox-board-modeller.js +7 -5
- package/dist/src/ox-board-modeller.js.map +1 -1
- package/dist/src/resolved-scene.js +7 -4
- package/dist/src/resolved-scene.js.map +1 -1
- package/dist/src/types.d.ts +8 -0
- package/dist/src/types.js.map +1 -1
- package/dist/test/component-menu.test.d.ts +1 -0
- package/dist/test/component-menu.test.js +61 -0
- package/dist/test/component-menu.test.js.map +1 -0
- package/dist/test/figure-provider.test.js +108 -99
- package/dist/test/figure-provider.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +13 -13
- package/test/component-menu.test.ts +69 -0
- package/test/figure-provider.test.ts +123 -107
- package/translations/en.json +2 -1
- package/translations/ja.json +2 -1
- package/translations/ko.json +2 -1
|
@@ -1,26 +1,62 @@
|
|
|
1
|
+
/** Which type map a page draws from: the active one, or the candidate in a preview. */
|
|
2
|
+
export type FigureMapChoice = 'ACTIVE' | 'CANDIDATE';
|
|
3
|
+
/** The type map release a page registered its figures from. */
|
|
4
|
+
export interface FigureMapRelease {
|
|
5
|
+
map: FigureMapChoice;
|
|
6
|
+
version: number;
|
|
7
|
+
revision: number;
|
|
8
|
+
}
|
|
9
|
+
/** What `figureBlueprints` answers: one map release, an item or a refusal per name asked. */
|
|
10
|
+
export interface FigureBlueprintsAnswer {
|
|
11
|
+
release: {
|
|
12
|
+
version: number;
|
|
13
|
+
revision: number;
|
|
14
|
+
} | null;
|
|
15
|
+
items: {
|
|
16
|
+
type: string;
|
|
17
|
+
ref: string;
|
|
18
|
+
asset: unknown;
|
|
19
|
+
}[];
|
|
20
|
+
refused: {
|
|
21
|
+
type: string;
|
|
22
|
+
code: string;
|
|
23
|
+
message: string;
|
|
24
|
+
}[];
|
|
25
|
+
}
|
|
26
|
+
export type BlueprintsFetch = (types: readonly string[], map: FigureMapChoice) => Promise<FigureBlueprintsAnswer>;
|
|
27
|
+
/** Why a type was not registered, for the stand-in. The shape things-scene's seat reads. */
|
|
28
|
+
type Refusals = {
|
|
29
|
+
refused: {
|
|
30
|
+
type: string;
|
|
31
|
+
message: string;
|
|
32
|
+
}[];
|
|
33
|
+
};
|
|
34
|
+
export declare const FIGURE_TYPE_MAP_CHANGED = "figure-type-map-changed";
|
|
1
35
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* its version. A payload without an asset is not a figure this board can draw.
|
|
36
|
+
* Asks the server. An active map is asked without the argument, so an authoring server whose door
|
|
37
|
+
* has no `map` (it serves the latest release) answers the same query shape.
|
|
5
38
|
*/
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
export declare function registerFigurePayload(type: string, payload: FigurePayload): void;
|
|
14
|
-
export declare const fetchFigureBlueprint: BlueprintFetch;
|
|
39
|
+
export declare const fetchFigureBlueprints: BlueprintsFetch;
|
|
40
|
+
/** Sets which map this page draws from. A preview page sets CANDIDATE before its board opens. */
|
|
41
|
+
export declare function setFigureMapChoice(next: FigureMapChoice): void;
|
|
42
|
+
/** The map release this page registered its figures from, or null before any (or from an authoring server). */
|
|
43
|
+
export declare function figureMapReleaseOfPage(): FigureMapRelease | null;
|
|
44
|
+
/** For tests: forget the page's map release and draw from the active map again. */
|
|
45
|
+
export declare function resetFigureMapPage(): void;
|
|
15
46
|
/**
|
|
16
47
|
* Builds the resolver that fills things-scene's seat.
|
|
17
48
|
*
|
|
18
49
|
* `fetch` is a seam for tests — the default one talks to the server.
|
|
19
50
|
*/
|
|
20
|
-
export declare function createFigureResolver(fetch?:
|
|
51
|
+
export declare function createFigureResolver(fetch?: BlueprintsFetch): (types: readonly string[]) => Promise<Refusals | undefined>;
|
|
52
|
+
/** Registers one figure under the name the board asked. Anything but a figure asset is refused by name. */
|
|
53
|
+
export declare function registerFigurePayload(type: string, payload: {
|
|
54
|
+
asset?: unknown;
|
|
55
|
+
}): void;
|
|
21
56
|
/**
|
|
22
57
|
* Takes the seat. The package entry (`index.ts`) calls this, so an app that has `@operato/board`
|
|
23
|
-
* gets
|
|
24
|
-
*
|
|
58
|
+
* gets figures on its boards without registering anything itself. Returns the function that takes
|
|
59
|
+
* the seat back.
|
|
25
60
|
*/
|
|
26
|
-
export declare function installFigureResolver(fetch?:
|
|
61
|
+
export declare function installFigureResolver(fetch?: BlueprintsFetch): () => void;
|
|
62
|
+
export {};
|
|
@@ -4,102 +4,107 @@
|
|
|
4
4
|
import gql from 'graphql-tag';
|
|
5
5
|
import { Component, registerFigure, registerTypeResolver } from '@hatiolab/things-scene';
|
|
6
6
|
import { client, gqlContext } from '@operato/graphql';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* who published what. The board package is where a saved model already gets its values from the
|
|
13
|
-
* server — `board-provider.ts` reads a board by id the same way — so this is where the types a
|
|
14
|
-
* model names, and nobody has registered, get fetched. Putting it in the authoring package would
|
|
15
|
-
* make every consumer drag the modeller along, and putting it in each app would make every app
|
|
16
|
-
* build the same thing (ADR-0063 결정 2 · ADR-0045).
|
|
17
|
-
*
|
|
18
|
-
* ── What it asks for, and what it does not ──────────────────────────────────
|
|
19
|
-
* Uppercase names are the figure namespace, so those are the only ones asked about: a model that
|
|
20
|
-
* names `rect` or a mistyped `Conveyor` never reaches the server. A type that is already
|
|
21
|
-
* registered is left alone — a figure never replaces a component that is already drawing.
|
|
22
|
-
*
|
|
23
|
-
* ── An answer of "nobody published this" is not a failure ───────────────────
|
|
24
|
-
* `figureBlueprint` returns null for a type that has no released figure, and that type stays
|
|
25
|
-
* unregistered on purpose: the scene draws it as a stand-in and saves it back untouched
|
|
26
|
-
* (ADR-0063 결정 1). Being unable to ask at all is a different fact, so a transport or server
|
|
27
|
-
* error rejects — `resolveUnknownTypes` reports those in `failed` and the board still opens.
|
|
28
|
-
*/
|
|
29
|
-
/** The figure namespace: `EQ_MIXER`, `CONVEYOR`, `VEH_AGV_01`. */
|
|
30
|
-
const FIGURE_TYPE = /^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$/;
|
|
31
|
-
/** Registers one released figure with the scene. Anything but a figure asset is refused by name. */
|
|
32
|
-
export function registerFigurePayload(type, payload) {
|
|
33
|
-
var _a;
|
|
34
|
-
if (!(payload === null || payload === void 0 ? void 0 : payload.asset) || typeof payload.asset !== 'object')
|
|
35
|
-
throw new Error(`not a figure asset: ${type}`);
|
|
36
|
-
registerFigure((_a = payload.type) !== null && _a !== void 0 ? _a : type, payload.asset);
|
|
37
|
-
}
|
|
38
|
-
const FETCH_FIGURE_BLUEPRINT = gql `
|
|
39
|
-
query FetchFigureBlueprint($type: String!) {
|
|
40
|
-
figureBlueprint(type: $type)
|
|
7
|
+
import { i18next } from '@operato/i18n';
|
|
8
|
+
export const FIGURE_TYPE_MAP_CHANGED = 'figure-type-map-changed';
|
|
9
|
+
const FETCH_FIGURE_BLUEPRINTS = gql `
|
|
10
|
+
query FetchFigureBlueprints($types: [String!]!, $map: FigureTypeMapState) {
|
|
11
|
+
figureBlueprints(types: $types, map: $map)
|
|
41
12
|
}
|
|
42
13
|
`;
|
|
43
|
-
|
|
44
|
-
|
|
14
|
+
const FETCH_FIGURE_BLUEPRINTS_LATEST = gql `
|
|
15
|
+
query FetchFigureBlueprintsLatest($types: [String!]!) {
|
|
16
|
+
figureBlueprints(types: $types)
|
|
17
|
+
}
|
|
18
|
+
`;
|
|
19
|
+
/**
|
|
20
|
+
* Asks the server. An active map is asked without the argument, so an authoring server whose door
|
|
21
|
+
* has no `map` (it serves the latest release) answers the same query shape.
|
|
22
|
+
*/
|
|
23
|
+
export const fetchFigureBlueprints = async (types, map) => {
|
|
45
24
|
const response = await client.query({
|
|
46
|
-
query:
|
|
47
|
-
variables: {
|
|
48
|
-
context: gqlContext()
|
|
25
|
+
query: map === 'CANDIDATE' ? FETCH_FIGURE_BLUEPRINTS : FETCH_FIGURE_BLUEPRINTS_LATEST,
|
|
26
|
+
variables: map === 'CANDIDATE' ? { types: [...types], map } : { types: [...types] },
|
|
27
|
+
context: gqlContext(),
|
|
28
|
+
fetchPolicy: 'network-only'
|
|
49
29
|
});
|
|
50
30
|
/*
|
|
51
31
|
The client runs with errorPolicy 'all', so a rejected query comes back as a resolved response
|
|
52
|
-
carrying `errors
|
|
53
|
-
"the door said no".
|
|
32
|
+
carrying `errors`. Without this the caller cannot tell "the door said no" from an empty answer.
|
|
54
33
|
*/
|
|
55
34
|
const errors = response.errors;
|
|
56
35
|
if (errors === null || errors === void 0 ? void 0 : errors.length) {
|
|
57
36
|
throw new Error(errors.map(e => { var _a; return (_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : String(e); }).join('\n'));
|
|
58
37
|
}
|
|
59
|
-
return
|
|
38
|
+
return response.data.figureBlueprints;
|
|
60
39
|
};
|
|
40
|
+
let choice = 'ACTIVE';
|
|
41
|
+
let pageRelease = null;
|
|
42
|
+
/** Sets which map this page draws from. A preview page sets CANDIDATE before its board opens. */
|
|
43
|
+
export function setFigureMapChoice(next) {
|
|
44
|
+
choice = next;
|
|
45
|
+
}
|
|
46
|
+
/** The map release this page registered its figures from, or null before any (or from an authoring server). */
|
|
47
|
+
export function figureMapReleaseOfPage() {
|
|
48
|
+
return pageRelease;
|
|
49
|
+
}
|
|
50
|
+
/** For tests: forget the page's map release and draw from the active map again. */
|
|
51
|
+
export function resetFigureMapPage() {
|
|
52
|
+
pageRelease = null;
|
|
53
|
+
choice = 'ACTIVE';
|
|
54
|
+
}
|
|
55
|
+
const sameRelease = (a, b) => a.map === b.map && a.version === b.version && a.revision === b.revision;
|
|
61
56
|
/**
|
|
62
57
|
* Builds the resolver that fills things-scene's seat.
|
|
63
58
|
*
|
|
64
59
|
* `fetch` is a seam for tests — the default one talks to the server.
|
|
65
60
|
*/
|
|
66
|
-
export function createFigureResolver(fetch =
|
|
61
|
+
export function createFigureResolver(fetch = fetchFigureBlueprints) {
|
|
67
62
|
return async function resolveFigureTypes(types) {
|
|
68
|
-
const asking = types.filter(type =>
|
|
63
|
+
const asking = types.filter(type => !Component.register(type));
|
|
69
64
|
if (asking.length === 0) {
|
|
70
65
|
return;
|
|
71
66
|
}
|
|
72
|
-
const
|
|
73
|
-
await
|
|
67
|
+
const map = choice;
|
|
68
|
+
const answer = await fetch(asking, map);
|
|
69
|
+
if (answer.release) {
|
|
70
|
+
const now = { map, version: answer.release.version, revision: answer.release.revision };
|
|
71
|
+
if (pageRelease && !sameRelease(pageRelease, now)) {
|
|
72
|
+
/* Another map release than the figures drawn here: none is registered, the app offers a reopen. */
|
|
73
|
+
window.dispatchEvent(new CustomEvent(FIGURE_TYPE_MAP_CHANGED, { detail: { was: pageRelease, now } }));
|
|
74
|
+
const message = i18next.t('text.figure-type-map-changed', { defaultValue: 'the type map changed. reopen to draw it.' });
|
|
75
|
+
return { refused: asking.map(type => ({ type, message })) };
|
|
76
|
+
}
|
|
77
|
+
pageRelease = pageRelease !== null && pageRelease !== void 0 ? pageRelease : now;
|
|
78
|
+
}
|
|
79
|
+
const refused = answer.refused
|
|
80
|
+
.filter(refusal => asking.includes(refusal.type))
|
|
81
|
+
.map(refusal => ({ type: refusal.type, message: refusal.message }));
|
|
82
|
+
for (const item of answer.items) {
|
|
83
|
+
if (!asking.includes(item.type))
|
|
84
|
+
continue;
|
|
74
85
|
try {
|
|
75
|
-
|
|
76
|
-
if (!payload) {
|
|
77
|
-
/* Nobody published this type. It stays a stand-in. */
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
registerFigurePayload(type, payload);
|
|
86
|
+
registerFigurePayload(item.type, item);
|
|
81
87
|
}
|
|
82
88
|
catch (error) {
|
|
83
|
-
|
|
89
|
+
refused.push({ type: item.type, message: error instanceof Error ? error.message : String(error) });
|
|
84
90
|
}
|
|
85
|
-
}));
|
|
86
|
-
if (failures.length > 0) {
|
|
87
|
-
/*
|
|
88
|
-
What did register stays registered — the seat reads the registry back, not what this says.
|
|
89
|
-
The throw is how "could not ask" reaches `TypeResolution.failed`.
|
|
90
|
-
*/
|
|
91
|
-
throw new Error(`Figure 를 가져오지 못했다:\n${failures
|
|
92
|
-
.map(({ type, error }) => ` - ${type}: ${error instanceof Error ? error.message : String(error)}`)
|
|
93
|
-
.join('\n')}`);
|
|
94
91
|
}
|
|
92
|
+
return { refused };
|
|
95
93
|
};
|
|
96
94
|
}
|
|
95
|
+
/** Registers one figure under the name the board asked. Anything but a figure asset is refused by name. */
|
|
96
|
+
export function registerFigurePayload(type, payload) {
|
|
97
|
+
if (!(payload === null || payload === void 0 ? void 0 : payload.asset) || typeof payload.asset !== 'object') {
|
|
98
|
+
throw new Error(`not a figure asset: ${type}`);
|
|
99
|
+
}
|
|
100
|
+
registerFigure(type, payload.asset);
|
|
101
|
+
}
|
|
97
102
|
/**
|
|
98
103
|
* Takes the seat. The package entry (`index.ts`) calls this, so an app that has `@operato/board`
|
|
99
|
-
* gets
|
|
100
|
-
*
|
|
104
|
+
* gets figures on its boards without registering anything itself. Returns the function that takes
|
|
105
|
+
* the seat back.
|
|
101
106
|
*/
|
|
102
|
-
export function installFigureResolver(fetch =
|
|
107
|
+
export function installFigureResolver(fetch = fetchFigureBlueprints) {
|
|
103
108
|
return registerTypeResolver(createFigureResolver(fetch));
|
|
104
109
|
}
|
|
105
110
|
//# sourceMappingURL=figure-provider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figure-provider.js","sourceRoot":"","sources":["../../src/figure-provider.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport gql from 'graphql-tag'\n\nimport { Component, registerFigure, registerTypeResolver } from '@hatiolab/things-scene'\nimport { client, gqlContext } from '@operato/graphql'\n\n/*\n * Where a board gets the figures somebody authored.\n *\n * ── Why the seat is filled here ─────────────────────────────────────────────\n * things-scene keeps the seat (`registerTypeResolver`) and knows nothing about figures, GraphQL or\n * who published what. The board package is where a saved model already gets its values from the\n * server — `board-provider.ts` reads a board by id the same way — so this is where the types a\n * model names, and nobody has registered, get fetched. Putting it in the authoring package would\n * make every consumer drag the modeller along, and putting it in each app would make every app\n * build the same thing (ADR-0063 결정 2 · ADR-0045).\n *\n * ── What it asks for, and what it does not ──────────────────────────────────\n * Uppercase names are the figure namespace, so those are the only ones asked about: a model that\n * names `rect` or a mistyped `Conveyor` never reaches the server. A type that is already\n * registered is left alone — a figure never replaces a component that is already drawing.\n *\n * ── An answer of \"nobody published this\" is not a failure ───────────────────\n * `figureBlueprint` returns null for a type that has no released figure, and that type stays\n * unregistered on purpose: the scene draws it as a stand-in and saves it back untouched\n * (ADR-0063 결정 1). Being unable to ask at all is a different fact, so a transport or server\n * error rejects — `resolveUnknownTypes` reports those in `failed` and the board still opens.\n */\n\n/** The figure namespace: `EQ_MIXER`, `CONVEYOR`, `VEH_AGV_01`. */\nconst FIGURE_TYPE = /^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$/\n\n/**\n * What the server hands over for one released type: the figure asset. The board does not read the asset; the scene's\n * own registration compiles it with the kernel (ADR-0087 decision 5), which refuses an asset of any other format by\n * its version. A payload without an asset is not a figure this board can draw.\n */\nexport type FigurePayload = { type: string; asset: any }\n\n/** Asks for one type's payload. Null when no released figure carries that name. */\nexport type BlueprintFetch = (type: string) => Promise<FigurePayload | null>\n\n/** Registers one released figure with the scene. Anything but a figure asset is refused by name. */\nexport function registerFigurePayload(type: string, payload: FigurePayload): void {\n if (!(payload as { asset?: unknown })?.asset || typeof payload.asset !== 'object') throw new Error(`not a figure asset: ${type}`)\n registerFigure(payload.type ?? type, payload.asset)\n}\n\nconst FETCH_FIGURE_BLUEPRINT = gql`\n query FetchFigureBlueprint($type: String!) {\n figureBlueprint(type: $type)\n }\n`\n\nexport const fetchFigureBlueprint: BlueprintFetch = async (type: string) => {\n const response = await client.query({\n query: FETCH_FIGURE_BLUEPRINT,\n variables: { type },\n context: gqlContext()\n })\n\n /*\n The client runs with errorPolicy 'all', so a rejected query comes back as a resolved response\n carrying `errors` and a null field. Without this the caller cannot tell \"no such figure\" from\n \"the door said no\".\n */\n const errors = (response as { errors?: readonly { message?: string }[] }).errors\n if (errors?.length) {\n throw new Error(errors.map(e => e?.message ?? String(e)).join('\\n'))\n }\n\n return response.data?.figureBlueprint ?? null\n}\n\n/**\n * Builds the resolver that fills things-scene's seat.\n *\n * `fetch` is a seam for tests — the default one talks to the server.\n */\nexport function createFigureResolver(fetch: BlueprintFetch = fetchFigureBlueprint) {\n return async function resolveFigureTypes(types: readonly string[]): Promise<void> {\n const asking = types.filter(type => FIGURE_TYPE.test(type) && !Component.register(type))\n if (asking.length === 0) {\n return\n }\n\n const failures: { type: string; error: unknown }[] = []\n\n await Promise.all(\n asking.map(async type => {\n try {\n const payload = await fetch(type)\n if (!payload) {\n /* Nobody published this type. It stays a stand-in. */\n return\n }\n\n registerFigurePayload(type, payload)\n } catch (error) {\n failures.push({ type, error })\n }\n })\n )\n\n if (failures.length > 0) {\n /*\n What did register stays registered — the seat reads the registry back, not what this says.\n The throw is how \"could not ask\" reaches `TypeResolution.failed`.\n */\n throw new Error(\n `Figure 를 가져오지 못했다:\\n${failures\n .map(({ type, error }) => ` - ${type}: ${error instanceof Error ? error.message : String(error)}`)\n .join('\\n')}`\n )\n }\n }\n}\n\n/**\n * Takes the seat. The package entry (`index.ts`) calls this, so an app that has `@operato/board`\n * gets published figures on its boards without registering anything itself. Returns the function\n * that takes the seat back.\n */\nexport function installFigureResolver(fetch: BlueprintFetch = fetchFigureBlueprint): () => void {\n return registerTypeResolver(createFigureResolver(fetch))\n}\n"]}
|
|
1
|
+
{"version":3,"file":"figure-provider.js","sourceRoot":"","sources":["../../src/figure-provider.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport gql from 'graphql-tag'\n\nimport { Component, registerFigure, registerTypeResolver } from '@hatiolab/things-scene'\nimport { client, gqlContext } from '@operato/graphql'\nimport { i18next } from '@operato/i18n'\n\n/*\n * Where a board gets the figures it names.\n *\n * ── Why the seat is filled here ─────────────────────────────────────────────\n * things-scene keeps the seat (`registerTypeResolver`) and knows nothing about figures, GraphQL or\n * who published what. The board package is where a saved model already gets its values from the\n * server, so this is where the types a model names, and nobody has registered, get fetched\n * (ADR-0063 결정 2 · ADR-0045).\n *\n * ── One question per board, by full name ─────────────────────────────────────\n * A board names a figure by its full name `{namespace}/{type}` (ADR-0114 결정 2). Every type the\n * model names that nobody registered is asked in one `figureBlueprints` query; a type already\n * registered -- a built-in component, a figure already drawing -- is never asked. The server answers\n * each name with an item or a refusal with its reason; items are registered under the name asked,\n * refusals go back to the seat so the stand-in shows why (ADR-0114 A3). Being unable to ask at all\n * rejects, and `resolveUnknownTypes` reports it in `failed`; the board still opens.\n *\n * ── One map release per page ─────────────────────────────────────────────────\n * A board server answers from one type map release `{ version, revision }` (ADR-0114 결정 12). The\n * page keeps the first release it registered from, together with which map it asked (active, or the\n * candidate in a preview). An answer from another release is not registered -- figures already drawn\n * would otherwise mix two maps -- and `figure-type-map-changed` tells the app to offer a reopen. An\n * authoring server answers with no release (its latest-release policy); then nothing is kept.\n */\n\n/** Which type map a page draws from: the active one, or the candidate in a preview. */\nexport type FigureMapChoice = 'ACTIVE' | 'CANDIDATE'\n\n/** The type map release a page registered its figures from. */\nexport interface FigureMapRelease {\n map: FigureMapChoice\n version: number\n revision: number\n}\n\n/** What `figureBlueprints` answers: one map release, an item or a refusal per name asked. */\nexport interface FigureBlueprintsAnswer {\n release: { version: number; revision: number } | null\n items: { type: string; ref: string; asset: unknown }[]\n refused: { type: string; code: string; message: string }[]\n}\n\nexport type BlueprintsFetch = (types: readonly string[], map: FigureMapChoice) => Promise<FigureBlueprintsAnswer>\n\n/** Why a type was not registered, for the stand-in. The shape things-scene's seat reads. */\ntype Refusals = { refused: { type: string; message: string }[] }\n\nexport const FIGURE_TYPE_MAP_CHANGED = 'figure-type-map-changed'\n\nconst FETCH_FIGURE_BLUEPRINTS = gql`\n query FetchFigureBlueprints($types: [String!]!, $map: FigureTypeMapState) {\n figureBlueprints(types: $types, map: $map)\n }\n`\n\nconst FETCH_FIGURE_BLUEPRINTS_LATEST = gql`\n query FetchFigureBlueprintsLatest($types: [String!]!) {\n figureBlueprints(types: $types)\n }\n`\n\n/**\n * Asks the server. An active map is asked without the argument, so an authoring server whose door\n * has no `map` (it serves the latest release) answers the same query shape.\n */\nexport const fetchFigureBlueprints: BlueprintsFetch = async (types, map) => {\n const response = await client.query({\n query: map === 'CANDIDATE' ? FETCH_FIGURE_BLUEPRINTS : FETCH_FIGURE_BLUEPRINTS_LATEST,\n variables: map === 'CANDIDATE' ? { types: [...types], map } : { types: [...types] },\n context: gqlContext(),\n fetchPolicy: 'network-only'\n })\n /*\n The client runs with errorPolicy 'all', so a rejected query comes back as a resolved response\n carrying `errors`. Without this the caller cannot tell \"the door said no\" from an empty answer.\n */\n const errors = (response as any).errors as { message?: string }[] | undefined\n if (errors?.length) {\n throw new Error(errors.map(e => e?.message ?? String(e)).join('\\n'))\n }\n return response.data.figureBlueprints as FigureBlueprintsAnswer\n}\n\nlet choice: FigureMapChoice = 'ACTIVE'\nlet pageRelease: FigureMapRelease | null = null\n\n/** Sets which map this page draws from. A preview page sets CANDIDATE before its board opens. */\nexport function setFigureMapChoice(next: FigureMapChoice): void {\n choice = next\n}\n\n/** The map release this page registered its figures from, or null before any (or from an authoring server). */\nexport function figureMapReleaseOfPage(): FigureMapRelease | null {\n return pageRelease\n}\n\n/** For tests: forget the page's map release and draw from the active map again. */\nexport function resetFigureMapPage(): void {\n pageRelease = null\n choice = 'ACTIVE'\n}\n\nconst sameRelease = (a: FigureMapRelease, b: FigureMapRelease) => a.map === b.map && a.version === b.version && a.revision === b.revision\n\n/**\n * Builds the resolver that fills things-scene's seat.\n *\n * `fetch` is a seam for tests — the default one talks to the server.\n */\nexport function createFigureResolver(fetch: BlueprintsFetch = fetchFigureBlueprints) {\n return async function resolveFigureTypes(types: readonly string[]): Promise<Refusals | undefined> {\n const asking = types.filter(type => !Component.register(type))\n if (asking.length === 0) {\n return\n }\n\n const map = choice\n const answer = await fetch(asking, map)\n\n if (answer.release) {\n const now: FigureMapRelease = { map, version: answer.release.version, revision: answer.release.revision }\n if (pageRelease && !sameRelease(pageRelease, now)) {\n /* Another map release than the figures drawn here: none is registered, the app offers a reopen. */\n window.dispatchEvent(new CustomEvent(FIGURE_TYPE_MAP_CHANGED, { detail: { was: pageRelease, now } }))\n const message = i18next.t('text.figure-type-map-changed', { defaultValue: 'the type map changed. reopen to draw it.' })\n return { refused: asking.map(type => ({ type, message })) }\n }\n pageRelease = pageRelease ?? now\n }\n\n const refused: Refusals['refused'] = answer.refused\n .filter(refusal => asking.includes(refusal.type))\n .map(refusal => ({ type: refusal.type, message: refusal.message }))\n\n for (const item of answer.items) {\n if (!asking.includes(item.type)) continue\n try {\n registerFigurePayload(item.type, item)\n } catch (error) {\n refused.push({ type: item.type, message: error instanceof Error ? error.message : String(error) })\n }\n }\n\n return { refused }\n }\n}\n\n/** Registers one figure under the name the board asked. Anything but a figure asset is refused by name. */\nexport function registerFigurePayload(type: string, payload: { asset?: unknown }): void {\n if (!payload?.asset || typeof payload.asset !== 'object') {\n throw new Error(`not a figure asset: ${type}`)\n }\n registerFigure(type, payload.asset as any)\n}\n\n/**\n * Takes the seat. The package entry (`index.ts`) calls this, so an app that has `@operato/board`\n * gets figures on its boards without registering anything itself. Returns the function that takes\n * the seat back.\n */\nexport function installFigureResolver(fetch: BlueprintsFetch = fetchFigureBlueprints): () => void {\n return registerTypeResolver(createFigureResolver(fetch) as any)\n}\n"]}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @license Copyright © HatioLab Inc. All rights reserved.
|
|
3
3
|
*/
|
|
4
4
|
import { __decorate } from "tslib";
|
|
5
|
-
import { css, html, LitElement } from 'lit';
|
|
5
|
+
import { css, html, LitElement, nothing } from 'lit';
|
|
6
6
|
import { property, query, state } from 'lit/decorators.js';
|
|
7
7
|
import { ScopedElementsMixin } from '@open-wc/scoped-elements';
|
|
8
8
|
import { ScrollbarStyles } from '@operato/styles';
|
|
@@ -37,9 +37,22 @@ export class ComponentMenu extends ScopedElementsMixin(LitElement) {
|
|
|
37
37
|
<div templates @mouseover=${(e) => this.onHoverComponent(e)}>
|
|
38
38
|
${(this.templates || []).map(template => {
|
|
39
39
|
const { type } = template;
|
|
40
|
+
const reason = template.unavailableReason;
|
|
41
|
+
const label = i18next.exists(`component.${type}`) ? i18next.t(`component.${type}`) : template.description || type;
|
|
40
42
|
return html `
|
|
41
|
-
<div
|
|
42
|
-
|
|
43
|
+
<div
|
|
44
|
+
@click=${this.onClickTemplate}
|
|
45
|
+
data-type=${type}
|
|
46
|
+
template
|
|
47
|
+
?unavailable=${!!reason}
|
|
48
|
+
aria-disabled=${reason ? 'true' : 'false'}
|
|
49
|
+
title=${reason || ''}
|
|
50
|
+
>
|
|
51
|
+
<img src=${String(this.templateIcon(template))} draggable="false" />
|
|
52
|
+
<span name>${label}${reason ? html `<span reason>${reason}</span>` : nothing}</span>
|
|
53
|
+
${reason
|
|
54
|
+
? html `<span mark>${template.unavailableLabel || i18next.t('text.unavailable', { defaultValue: 'unavailable' })}</span>`
|
|
55
|
+
: nothing}
|
|
43
56
|
</div>
|
|
44
57
|
`;
|
|
45
58
|
})}
|
|
@@ -58,7 +71,7 @@ export class ComponentMenu extends ScopedElementsMixin(LitElement) {
|
|
|
58
71
|
: html ``;
|
|
59
72
|
}
|
|
60
73
|
updated(changes) {
|
|
61
|
-
var _a;
|
|
74
|
+
var _a, _b, _c;
|
|
62
75
|
if (changes.has('group')) {
|
|
63
76
|
if (!this.group) {
|
|
64
77
|
this.templates = [];
|
|
@@ -70,9 +83,8 @@ export class ComponentMenu extends ScopedElementsMixin(LitElement) {
|
|
|
70
83
|
}
|
|
71
84
|
}
|
|
72
85
|
if (changes.has('template')) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
: this.detail.setAttribute('hidden', '');
|
|
86
|
+
/* no detail panel is rendered while no group is open */
|
|
87
|
+
(_b = this.detail) === null || _b === void 0 ? void 0 : _b.toggleAttribute('hidden', !((_c = this.template) === null || _c === void 0 ? void 0 : _c.about));
|
|
76
88
|
}
|
|
77
89
|
}
|
|
78
90
|
findTemplate(type) {
|
|
@@ -84,14 +96,18 @@ export class ComponentMenu extends ScopedElementsMixin(LitElement) {
|
|
|
84
96
|
this.findTemplate((_a = button.closest('[data-type]')) === null || _a === void 0 ? void 0 : _a.getAttribute('data-type'));
|
|
85
97
|
}
|
|
86
98
|
onClickTemplate(e) {
|
|
87
|
-
var _a, _b;
|
|
99
|
+
var _a, _b, _c;
|
|
88
100
|
var item = e.target;
|
|
89
101
|
var type = (_a = item.closest('[data-type]')) === null || _a === void 0 ? void 0 : _a.getAttribute('data-type');
|
|
90
102
|
if (!type) {
|
|
91
103
|
return;
|
|
92
104
|
}
|
|
105
|
+
/* A template that cannot be placed here does nothing when picked; its reason is on the item. */
|
|
106
|
+
if ((_b = this.templates.find(template => template.type == type)) === null || _b === void 0 ? void 0 : _b.unavailableReason) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
93
109
|
if (this.scene) {
|
|
94
|
-
(
|
|
110
|
+
(_c = this.scene.rootContainer) === null || _c === void 0 ? void 0 : _c.trigger('addstop'); // 새로운 컴포넌트 클릭시 이전 추가 모드 정지
|
|
95
111
|
this.template = this.templates.find(template => template.type == type);
|
|
96
112
|
this.template && this.scene.startAddMode(JSON.parse(JSON.stringify(this.template.model)));
|
|
97
113
|
}
|
|
@@ -167,6 +183,43 @@ ComponentMenu.styles = [
|
|
|
167
183
|
cursor: pointer;
|
|
168
184
|
}
|
|
169
185
|
|
|
186
|
+
/* Shown but not placeable: dimmed, marked, and the reason under the name on hover (ADR-0114 P7). */
|
|
187
|
+
[template][unavailable] {
|
|
188
|
+
opacity: 0.5;
|
|
189
|
+
cursor: not-allowed;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
[template][unavailable]:hover,
|
|
193
|
+
[template][unavailable]:focus {
|
|
194
|
+
font-weight: normal;
|
|
195
|
+
cursor: not-allowed;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
[template] [name] {
|
|
199
|
+
display: flex;
|
|
200
|
+
flex-direction: column;
|
|
201
|
+
min-width: 0;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
[template] [mark] {
|
|
205
|
+
margin-left: auto;
|
|
206
|
+
padding: 0 4px;
|
|
207
|
+
border-radius: 4px;
|
|
208
|
+
background: var(--md-sys-color-surface-container-high);
|
|
209
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
210
|
+
white-space: nowrap;
|
|
211
|
+
text-transform: none;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
[template] [reason] {
|
|
215
|
+
display: none;
|
|
216
|
+
text-transform: none;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
[template][unavailable]:hover [reason] {
|
|
220
|
+
display: block;
|
|
221
|
+
}
|
|
222
|
+
|
|
170
223
|
[template] img {
|
|
171
224
|
margin: 5px;
|
|
172
225
|
width: var(--component-menu-item-icon-size);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component-menu.js","sourceRoot":"","sources":["../../../../src/modeller/component-toolbar/component-menu.ts"],"names":[],"mappings":"AAAA;;GAEG;;AAEH,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAkB,MAAM,KAAK,CAAA;AAC3D,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAG1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAGvC,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAEvD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,2CAA2C,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAA;AAE1F,MAAM,OAAO,aAAc,SAAQ,mBAAmB,CAAC,UAAU,CAAC;IAAlE;;QAuF8B,WAAM,GAAa,EAAE,CAAA;QACrB,UAAK,GAAiB,IAAI,CAAA;QAC1B,UAAK,GAAkB,EAAE,CAAA;QAE5C,cAAS,GAAiB,EAAE,CAAA;IA+FvC,CAAC;IA1FC,MAAM,KAAK,cAAc;QACvB,OAAO;YACL,kBAAkB,EAAE,eAAe;SACpC,CAAA;IACH,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,KAAK;YACf,CAAC,CAAC,IAAI,CAAA;wBACY,CAAC,CAAa,EAAE,EAAE,CAAC,CAAC,CAAC,eAAe,EAAE;cAChD,CAAA;;;kEAGqD,EAAE;cACvD,OAAO,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,KAAK,EAAE,EAAE,EAAE,YAAY,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;cAC9D,OAAO,CAAC,CAAC,CAAC,YAAY,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC;;;sCAGzB,CAAC,CAAa,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC;cACnE,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE;gBACtC,MAAM,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAA;gBAEzB,OAAO,IAAI,CAAA;8BACK,IAAI,CAAC,eAAe,cAAc,IAAI;6BACvC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC,aAAa,IAAI,EAAE,CAAC;;eAErF,CAAA;YACH,CAAC,CAAC;;;;;wBAKU,GAAG,EAAE;gBACf,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;YACtB,CAAC;wBACW,IAAI,CAAC,QAAQ;;;;SAI5B;YACH,CAAC,CAAC,IAAI,CAAA,EAAE,CAAA;IACZ,CAAC;IAED,OAAO,CAAC,OAA6B;;QACnC,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACzB,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;gBAChB,IAAI,CAAC,SAAS,GAAG,EAAE,CAAA;gBACnB,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAA;YACjC,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,SAAS,GAAG,CAAA,MAAA,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,0CAAE,SAAS,KAAI,EAAE,CAAA;gBACxF,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAA;YAChC,CAAC;QACH,CAAC;QAED,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5B,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK;gBAClC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,QAAQ,CAAC;gBACvC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAA;QAC5C,CAAC;IACH,CAAC;IAED,YAAY,CAAC,IAA+B;QAC1C,IAAI,CAAC,QAAQ,GAAG,IAAI,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,IAAI,IAAI,CAAC,CAAA;IAChF,CAAC;IAED,gBAAgB,CAAC,CAAa;;QAC5B,MAAM,MAAM,GAAG,CAAC,CAAC,MAAqB,CAAA;QACtC,IAAI,CAAC,YAAY,CAAC,MAAA,MAAO,CAAC,OAAO,CAAC,aAAa,CAAC,0CAAE,YAAY,CAAC,WAAW,CAAC,CAAC,CAAA;IAC9E,CAAC;IAED,eAAe,CAAC,CAAa;;QAC3B,IAAI,IAAI,GAAG,CAAC,CAAC,MAAqB,CAAA;QAClC,IAAI,IAAI,GAAG,MAAA,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,0CAAE,YAAY,CAAC,WAAW,CAAC,CAAA;QAEjE,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAM;QACR,CAAC;QAED,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,MAAA,IAAI,CAAC,KAAK,CAAC,aAAa,0CAAE,OAAO,CAAC,SAAS,CAAC,CAAA,CAAC,2BAA2B;YACxE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,IAAI,IAAI,CAAC,CAAA;YACtE,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAC3F,CAAC;QAED,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;IACnB,CAAC;IAED,YAAY,CAAC,QAAoB;QAC/B,OAAO,QAAQ,CAAC,IAAI,IAAI,OAAO,CAAA;IACjC,CAAC;;AAxLM,oBAAM,GAAG;IACd,eAAe;IACf,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAiFF;CACF,AApFY,CAoFZ;AAE2B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;6CAAsB;AACrB;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;4CAA2B;AAC1B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;4CAA0B;AAE5C;IAAR,KAAK,EAAE;gDAA6B;AAC5B;IAAR,KAAK,EAAE;+CAA2B;AAER;IAA1B,KAAK,CAAC,kBAAkB,CAAC;6CAAqB","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport { css, html, LitElement, PropertyValues } from 'lit'\nimport { property, query, state } from 'lit/decorators.js'\n\nimport { Scene } from '@hatiolab/things-scene'\nimport { ScopedElementsMixin } from '@open-wc/scoped-elements'\nimport { ScrollbarStyles } from '@operato/styles'\nimport { i18next } from '@operato/i18n'\n\nimport { Pallet, PalletItem } from '../../types.js'\nimport { ComponentDetail } from './component-detail.js'\n\nconst noImage = new URL('../../../../icons/components/no-image.png', import.meta.url).href\n\nexport class ComponentMenu extends ScopedElementsMixin(LitElement) {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n align-content: stretch;\n\n background-color: var(--component-menu-background-color, var(--md-sys-color-secondary-container));\n color: var(--component-menu-color, var(--md-sys-color-on-secondary-container));\n margin: 0px;\n padding: 0px;\n\n width: 180px;\n height: 100%;\n\n overflow: visible;\n\n border: 1px solid var(--component-menu-border-color, var(--md-sys-color-outline-variant));\n box-sizing: border-box;\n\n position: absolute;\n top: 0px;\n\n z-index: 1;\n }\n\n h2 {\n background-color: var(--component-menu-title-background, var(--md-sys-color-surface-container-highest));\n padding: 4px 8px;\n margin: 0;\n font: var(--component-menu-title);\n color: var(--component-menu-title-color, var(--md-sys-color-on-surface));\n text-transform: capitalize;\n }\n\n [templates] {\n flex: 1;\n\n display: block;\n margin: 0;\n padding: 0;\n overflow-y: auto;\n\n background-color: var(--component-menu-background-color, var(--md-sys-color-secondary-container));\n color: var(--component-menu-color, var(--md-sys-color-on-secondary-container));\n }\n\n [template] {\n display: flex;\n align-items: center;\n min-height: var(--component-menu-item-icon-size);\n padding: 0 5px 0 0;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n font-size: 11px;\n color: var(--component-menu-item-color, var(--md-sys-color-on-secondary-container));\n text-transform: capitalize;\n }\n\n [template]:hover,\n [template]:focus {\n color: var(--component-menu-item-hover-color, var(--md-sys-color-secondary));\n font-weight: bold;\n cursor: pointer;\n }\n\n [template] img {\n margin: 5px;\n width: var(--component-menu-item-icon-size);\n height: var(--component-menu-item-icon-size);\n }\n\n component-detail {\n position: absolute;\n top: 0;\n left: 180px;\n height: 100%;\n outline: none;\n }\n\n component-detail[hidden] {\n display: none;\n }\n `\n ]\n\n @property({ type: Object }) groups: Pallet[] = []\n @property({ type: Object }) scene: Scene | null = null\n @property({ type: String }) group: string | null = ''\n\n @state() templates: PalletItem[] = []\n @state() template: PalletItem | any\n\n @query('component-detail') detail!: HTMLElement\n\n static get scopedElements() {\n return {\n 'component-detail': ComponentDetail\n }\n }\n\n render() {\n return this.group\n ? html`\n <h2 onclick=${(e: MouseEvent) => e.stopPropagation()}>\n ${/* group label — translate via group.<name> key, fall back to the\n raw group name so untranslated groups keep working.\n \"list\" is also translatable; defaults to the literal \"list\"\n which is what users have been seeing all along. */ ''}\n ${i18next.t(`group.${this.group}`, { defaultValue: this.group })}\n ${i18next.t('label.list', { defaultValue: 'list' })}\n </h2>\n\n <div templates @mouseover=${(e: MouseEvent) => this.onHoverComponent(e)}>\n ${(this.templates || []).map(template => {\n const { type } = template\n\n return html`\n <div @click=${this.onClickTemplate} data-type=${type} template>\n <img src=${String(this.templateIcon(template))} />${i18next.t(`component.${type}`)}\n </div>\n `\n })}\n </div>\n\n <component-detail\n tabindex=\"-1\"\n @focusout=${() => {\n this.template = null\n }}\n .template=${this.template}\n hidden\n >\n </component-detail>\n `\n : html``\n }\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('group')) {\n if (!this.group) {\n this.templates = []\n this.setAttribute('hidden', '')\n } else {\n this.templates = this.groups.find((g: Pallet) => g.name === this.group)?.templates || []\n this.removeAttribute('active')\n }\n }\n\n if (changes.has('template')) {\n this.template && this.template.about\n ? this.detail.removeAttribute('hidden')\n : this.detail.setAttribute('hidden', '')\n }\n }\n\n findTemplate(type: string | null | undefined) {\n this.template = type && this.templates.find(template => template.type == type)\n }\n\n onHoverComponent(e: MouseEvent) {\n const button = e.target as HTMLElement\n this.findTemplate(button!.closest('[data-type]')?.getAttribute('data-type'))\n }\n\n onClickTemplate(e: MouseEvent) {\n var item = e.target as HTMLElement\n var type = item.closest('[data-type]')?.getAttribute('data-type')\n\n if (!type) {\n return\n }\n\n if (this.scene) {\n this.scene.rootContainer?.trigger('addstop') // 새로운 컴포넌트 클릭시 이전 추가 모드 정지\n this.template = this.templates.find(template => template.type == type)\n this.template && this.scene.startAddMode(JSON.parse(JSON.stringify(this.template.model)))\n }\n\n this.group = null\n }\n\n templateIcon(template: PalletItem) {\n return template.icon || noImage\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"component-menu.js","sourceRoot":"","sources":["../../../../src/modeller/component-toolbar/component-menu.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport { css, html, LitElement, nothing, PropertyValues } from 'lit'\nimport { property, query, state } from 'lit/decorators.js'\n\nimport { Scene } from '@hatiolab/things-scene'\nimport { ScopedElementsMixin } from '@open-wc/scoped-elements'\nimport { ScrollbarStyles } from '@operato/styles'\nimport { i18next } from '@operato/i18n'\n\nimport { Pallet, PalletItem } from '../../types.js'\nimport { ComponentDetail } from './component-detail.js'\n\nconst noImage = new URL('../../../../icons/components/no-image.png', import.meta.url).href\n\nexport class ComponentMenu extends ScopedElementsMixin(LitElement) {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n align-content: stretch;\n\n background-color: var(--component-menu-background-color, var(--md-sys-color-secondary-container));\n color: var(--component-menu-color, var(--md-sys-color-on-secondary-container));\n margin: 0px;\n padding: 0px;\n\n width: 180px;\n height: 100%;\n\n overflow: visible;\n\n border: 1px solid var(--component-menu-border-color, var(--md-sys-color-outline-variant));\n box-sizing: border-box;\n\n position: absolute;\n top: 0px;\n\n z-index: 1;\n }\n\n h2 {\n background-color: var(--component-menu-title-background, var(--md-sys-color-surface-container-highest));\n padding: 4px 8px;\n margin: 0;\n font: var(--component-menu-title);\n color: var(--component-menu-title-color, var(--md-sys-color-on-surface));\n text-transform: capitalize;\n }\n\n [templates] {\n flex: 1;\n\n display: block;\n margin: 0;\n padding: 0;\n overflow-y: auto;\n\n background-color: var(--component-menu-background-color, var(--md-sys-color-secondary-container));\n color: var(--component-menu-color, var(--md-sys-color-on-secondary-container));\n }\n\n [template] {\n display: flex;\n align-items: center;\n min-height: var(--component-menu-item-icon-size);\n padding: 0 5px 0 0;\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n font-size: 11px;\n color: var(--component-menu-item-color, var(--md-sys-color-on-secondary-container));\n text-transform: capitalize;\n }\n\n [template]:hover,\n [template]:focus {\n color: var(--component-menu-item-hover-color, var(--md-sys-color-secondary));\n font-weight: bold;\n cursor: pointer;\n }\n\n /* Shown but not placeable: dimmed, marked, and the reason under the name on hover (ADR-0114 P7). */\n [template][unavailable] {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n [template][unavailable]:hover,\n [template][unavailable]:focus {\n font-weight: normal;\n cursor: not-allowed;\n }\n\n [template] [name] {\n display: flex;\n flex-direction: column;\n min-width: 0;\n }\n\n [template] [mark] {\n margin-left: auto;\n padding: 0 4px;\n border-radius: 4px;\n background: var(--md-sys-color-surface-container-high);\n color: var(--md-sys-color-on-surface-variant);\n white-space: nowrap;\n text-transform: none;\n }\n\n [template] [reason] {\n display: none;\n text-transform: none;\n }\n\n [template][unavailable]:hover [reason] {\n display: block;\n }\n\n [template] img {\n margin: 5px;\n width: var(--component-menu-item-icon-size);\n height: var(--component-menu-item-icon-size);\n }\n\n component-detail {\n position: absolute;\n top: 0;\n left: 180px;\n height: 100%;\n outline: none;\n }\n\n component-detail[hidden] {\n display: none;\n }\n `\n ]\n\n @property({ type: Object }) groups: Pallet[] = []\n @property({ type: Object }) scene: Scene | null = null\n @property({ type: String }) group: string | null = ''\n\n @state() templates: PalletItem[] = []\n @state() template: PalletItem | any\n\n @query('component-detail') detail!: HTMLElement\n\n static get scopedElements() {\n return {\n 'component-detail': ComponentDetail\n }\n }\n\n render() {\n return this.group\n ? html`\n <h2 onclick=${(e: MouseEvent) => e.stopPropagation()}>\n ${/* group label — translate via group.<name> key, fall back to the\n raw group name so untranslated groups keep working.\n \"list\" is also translatable; defaults to the literal \"list\"\n which is what users have been seeing all along. */ ''}\n ${i18next.t(`group.${this.group}`, { defaultValue: this.group })}\n ${i18next.t('label.list', { defaultValue: 'list' })}\n </h2>\n\n <div templates @mouseover=${(e: MouseEvent) => this.onHoverComponent(e)}>\n ${(this.templates || []).map(template => {\n const { type } = template\n\n const reason = template.unavailableReason\n const label = i18next.exists(`component.${type}`) ? i18next.t(`component.${type}`) : template.description || type\n\n return html`\n <div\n @click=${this.onClickTemplate}\n data-type=${type}\n template\n ?unavailable=${!!reason}\n aria-disabled=${reason ? 'true' : 'false'}\n title=${reason || ''}\n >\n <img src=${String(this.templateIcon(template))} draggable=\"false\" />\n <span name>${label}${reason ? html`<span reason>${reason}</span>` : nothing}</span>\n ${reason\n ? html`<span mark>${template.unavailableLabel || i18next.t('text.unavailable', { defaultValue: 'unavailable' })}</span>`\n : nothing}\n </div>\n `\n })}\n </div>\n\n <component-detail\n tabindex=\"-1\"\n @focusout=${() => {\n this.template = null\n }}\n .template=${this.template}\n hidden\n >\n </component-detail>\n `\n : html``\n }\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('group')) {\n if (!this.group) {\n this.templates = []\n this.setAttribute('hidden', '')\n } else {\n this.templates = this.groups.find((g: Pallet) => g.name === this.group)?.templates || []\n this.removeAttribute('active')\n }\n }\n\n if (changes.has('template')) {\n /* no detail panel is rendered while no group is open */\n this.detail?.toggleAttribute('hidden', !this.template?.about)\n }\n }\n\n findTemplate(type: string | null | undefined) {\n this.template = type && this.templates.find(template => template.type == type)\n }\n\n onHoverComponent(e: MouseEvent) {\n const button = e.target as HTMLElement\n this.findTemplate(button!.closest('[data-type]')?.getAttribute('data-type'))\n }\n\n onClickTemplate(e: MouseEvent) {\n var item = e.target as HTMLElement\n var type = item.closest('[data-type]')?.getAttribute('data-type')\n\n if (!type) {\n return\n }\n\n /* A template that cannot be placed here does nothing when picked; its reason is on the item. */\n if (this.templates.find(template => template.type == type)?.unavailableReason) {\n return\n }\n\n if (this.scene) {\n this.scene.rootContainer?.trigger('addstop') // 새로운 컴포넌트 클릭시 이전 추가 모드 정지\n this.template = this.templates.find(template => template.type == type)\n this.template && this.scene.startAddMode(JSON.parse(JSON.stringify(this.template.model)))\n }\n\n this.group = null\n }\n\n templateIcon(template: PalletItem) {\n return template.icon || noImage\n }\n}\n"]}
|
|
@@ -17,6 +17,11 @@ import { ComponentGroup, ComponentTemplate } from './types.js';
|
|
|
17
17
|
declare const BoardModeller_base: typeof LitElement & import("@open-wc/dedupe-mixin").Constructor<import("@open-wc/scoped-elements/types.js").ScopedElementsHost>;
|
|
18
18
|
export declare class BoardModeller extends BoardModeller_base {
|
|
19
19
|
static styles: import("lit").CSSResult[];
|
|
20
|
+
/**
|
|
21
|
+
* Registers a group, or replaces what a group of that name holds. A group filled from a server -- the figures of
|
|
22
|
+
* the active type map (ADR-0114 결정 10) -- registers again when its list changes; this used to assign to a local
|
|
23
|
+
* variable and change nothing.
|
|
24
|
+
*/
|
|
20
25
|
static registerGroup(group: ComponentGroup): void;
|
|
21
26
|
static registerTemplate(templates: ComponentTemplate[]): void;
|
|
22
27
|
static get groups(): ComponentGroup[];
|
|
@@ -28,13 +28,15 @@ const ICON_COLLAPSE = new URL('../../icons/icon-collapse.png', import.meta.url).
|
|
|
28
28
|
const ICON_COLLAPSE_ACTIVE = new URL('../../icons/icon-collapse-active.png', import.meta.url).href;
|
|
29
29
|
var Registry = [];
|
|
30
30
|
let BoardModeller = class BoardModeller extends ScopedElementsMixin(LitElement) {
|
|
31
|
+
/**
|
|
32
|
+
* Registers a group, or replaces what a group of that name holds. A group filled from a server -- the figures of
|
|
33
|
+
* the active type map (ADR-0114 결정 10) -- registers again when its list changes; this used to assign to a local
|
|
34
|
+
* variable and change nothing.
|
|
35
|
+
*/
|
|
31
36
|
static registerGroup(group) {
|
|
32
|
-
|
|
37
|
+
const found = Registry.find(inRegisterGroup => inRegisterGroup.name == group.name);
|
|
33
38
|
if (found) {
|
|
34
|
-
found
|
|
35
|
-
...found,
|
|
36
|
-
...group
|
|
37
|
-
};
|
|
39
|
+
Object.assign(found, group);
|
|
38
40
|
}
|
|
39
41
|
else {
|
|
40
42
|
Registry.push(group);
|