create-aiaui-module 0.1.1-TEST → 0.2.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/README.md +13 -12
- package/bin/create-aiaui-module.js +24 -32
- package/package.json +4 -3
- package/runtime/_runtime/host.py +7 -15
- package/runtime/_runtime/registry.py +4 -1
- package/templates/JobDashboard/CUSTOMIZATION.md +123 -0
- package/templates/{JobInformation → JobDashboard}/README.md +22 -9
- package/templates/JobDashboard/backend/api.py +144 -0
- package/templates/JobDashboard/backend/config.json +46 -0
- package/templates/JobDashboard/client/dist/modules.js +3513 -0
- package/templates/JobDashboard/client/src/contract.ts +146 -0
- package/templates/JobDashboard/client/src/customization.ts +30 -0
- package/templates/JobDashboard/client/src/defaults/DefaultPanel.vue +129 -0
- package/templates/JobDashboard/client/src/defaults/Icons.vue +49 -0
- package/templates/JobDashboard/client/src/defaults/InputCarriers.vue +18 -0
- package/templates/JobDashboard/client/src/defaults/JobBottom.vue +32 -0
- package/templates/JobDashboard/client/src/defaults/JobCard.vue +48 -0
- package/templates/JobDashboard/client/src/defaults/JobHeader.vue +41 -0
- package/templates/JobDashboard/client/src/defaults/JobMiddle.vue +41 -0
- package/templates/JobDashboard/client/src/defaults/JobName.vue +9 -0
- package/templates/JobDashboard/client/src/defaults/LotId.vue +14 -0
- package/templates/JobDashboard/client/src/defaults/OutputCarriers.vue +21 -0
- package/templates/JobDashboard/client/src/defaults/Recipe.vue +12 -0
- package/templates/JobDashboard/client/src/defaults/Status.vue +29 -0
- package/templates/JobDashboard/client/src/defaults/Timeline.vue +65 -0
- package/templates/JobDashboard/client/src/defaults/board-data.ts +36 -0
- package/templates/JobDashboard/client/src/defaults/config-defaults.ts +36 -0
- package/templates/JobDashboard/client/src/defaults/format.ts +51 -0
- package/templates/JobDashboard/client/src/defaults/slots.ts +26 -0
- package/templates/JobDashboard/client/src/defaults/styles.css +619 -0
- package/templates/{JobInformation/resources → JobDashboard/client}/src/index.ts +10 -18
- package/templates/JobInformation/CUSTOMIZATION.md +0 -173
- package/templates/JobInformation/api.py +0 -53
- package/templates/JobInformation/resources/dist/modules.js +0 -2919
- package/templates/JobInformation/resources/src/bus.ts +0 -38
- package/templates/JobInformation/resources/src/contract.ts +0 -74
- package/templates/JobInformation/resources/src/customization.ts +0 -25
- package/templates/JobInformation/resources/src/defaults/DefaultPanel.vue +0 -22
- package/templates/JobInformation/resources/src/defaults/Header.vue +0 -12
- package/templates/JobInformation/resources/src/defaults/InputCarriers.vue +0 -15
- package/templates/JobInformation/resources/src/defaults/OutputCarriers.vue +0 -14
- package/templates/JobInformation/resources/src/defaults/Recipe.vue +0 -18
- package/templates/JobInformation/resources/src/defaults/Timeline.vue +0 -22
- package/templates/JobInformation/resources/src/defaults/slots.ts +0 -16
- package/templates/JobInformation/resources/src/defaults/styles.css +0 -90
- /package/templates/{JobInformation/resources → JobDashboard/client}/build.mjs +0 -0
- /package/templates/{JobInformation/resources → JobDashboard/client}/dist/manifest.json +0 -0
- /package/templates/{JobInformation/resources → JobDashboard/client}/package-lock.json +0 -0
- /package/templates/{JobInformation/resources → JobDashboard/client}/package.json +0 -0
- /package/templates/{JobInformation/resources → JobDashboard/client}/src/vite-env.d.ts +0 -0
- /package/templates/{JobInformation/resources → JobDashboard/client}/vite.config.mjs +0 -0
- /package/templates/{JobInformation → JobDashboard}/contractVersion +0 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { Component } from "vue";
|
|
2
|
+
|
|
3
|
+
export type ModuleApi = {
|
|
4
|
+
call<T>(
|
|
5
|
+
method: string,
|
|
6
|
+
path: string,
|
|
7
|
+
options?: { query?: Record<string, unknown>; body?: unknown },
|
|
8
|
+
): Promise<T>;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/* -------------------------------------------------------------------------- */
|
|
12
|
+
/* Slot names */
|
|
13
|
+
/* -------------------------------------------------------------------------- */
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Every customizable slot of a job card. Leaves override a single piece;
|
|
17
|
+
* region slots override the whole region (leaf overrides then only apply
|
|
18
|
+
* inside the platform's default regions). Replaceable top to bottom:
|
|
19
|
+
*
|
|
20
|
+
* board the whole job card
|
|
21
|
+
* ├── headerRegion job name + lot id + status (slot.board also
|
|
22
|
+
* │ ├── jobName, lotId, status replaces the row)
|
|
23
|
+
* ├── middleRegion input carriers + timeline + output carriers
|
|
24
|
+
* │ └── inputCarriers, timeline, outputCarriers
|
|
25
|
+
* └── bottomRegion recipe area + footer (slot.board also
|
|
26
|
+
* └── recipe replaces footer)
|
|
27
|
+
*/
|
|
28
|
+
export const SlotKey = {
|
|
29
|
+
board: "board",
|
|
30
|
+
headerRegion: "headerRegion",
|
|
31
|
+
jobName: "jobName",
|
|
32
|
+
lotId: "lotId",
|
|
33
|
+
status: "status",
|
|
34
|
+
middleRegion: "middleRegion",
|
|
35
|
+
inputCarriers: "inputCarriers",
|
|
36
|
+
timeline: "timeline",
|
|
37
|
+
outputCarriers: "outputCarriers",
|
|
38
|
+
bottomRegion: "bottomRegion",
|
|
39
|
+
recipe: "recipe",
|
|
40
|
+
} as const;
|
|
41
|
+
|
|
42
|
+
export type SlotKeyType = (typeof SlotKey)[keyof typeof SlotKey];
|
|
43
|
+
|
|
44
|
+
/** Any Vue component that renders one slot. Custom components receive `ModuleSlotProps`. */
|
|
45
|
+
export type SlotComponent = Component;
|
|
46
|
+
|
|
47
|
+
export type SlotMap = Record<string, SlotComponent>;
|
|
48
|
+
|
|
49
|
+
/* -------------------------------------------------------------------------- */
|
|
50
|
+
/* Default panel data contract */
|
|
51
|
+
/* -------------------------------------------------------------------------- */
|
|
52
|
+
|
|
53
|
+
/** One carrier entry rendered by the carrier slots. */
|
|
54
|
+
export type ModuleCarrier = { code: string };
|
|
55
|
+
|
|
56
|
+
/** One step of a job's timeline (rendered by the default `timeline` slot). */
|
|
57
|
+
export type ModuleTimelineStep = {
|
|
58
|
+
label: string;
|
|
59
|
+
state?: "done" | "active" | "pending";
|
|
60
|
+
/** ISO timestamp; durations between steps are computed client-side. */
|
|
61
|
+
at?: string | null;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* One job entry returned by `GET /jobs` (the module's default data contract).
|
|
66
|
+
* Every item is rendered as one job card (the `board` slot).
|
|
67
|
+
*/
|
|
68
|
+
export type ModuleJobRecord = {
|
|
69
|
+
id: string;
|
|
70
|
+
name: string;
|
|
71
|
+
lot?: string | null;
|
|
72
|
+
/** State name; matched against `config.states` for its color and icon. */
|
|
73
|
+
state?: string | null;
|
|
74
|
+
carriers?: { input?: ModuleCarrier[]; output?: ModuleCarrier[] };
|
|
75
|
+
timeline?: ModuleTimelineStep[];
|
|
76
|
+
recipe?: string | null;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** Response of `GET /jobs` consumed by the default panel. */
|
|
80
|
+
export type ModuleJobPage = {
|
|
81
|
+
items: ModuleJobRecord[];
|
|
82
|
+
total: number;
|
|
83
|
+
page?: number;
|
|
84
|
+
pageSize?: number;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/** One state entry returned by `GET /config`. */
|
|
88
|
+
export type ModuleStateVisual = {
|
|
89
|
+
name: string;
|
|
90
|
+
color?: string | null;
|
|
91
|
+
icon?: string | null;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Response of `GET /config` (the module's configuration contract).
|
|
96
|
+
* Every field is optional; any missing field falls back to the template's
|
|
97
|
+
* built-in defaults (all parts visible, default state colors).
|
|
98
|
+
*/
|
|
99
|
+
export type ModuleConfig = {
|
|
100
|
+
display?: {
|
|
101
|
+
lotId?: boolean;
|
|
102
|
+
recipe?: boolean;
|
|
103
|
+
inputCarriers?: boolean;
|
|
104
|
+
outputCarriers?: boolean;
|
|
105
|
+
timeline?: boolean;
|
|
106
|
+
};
|
|
107
|
+
/** State visuals for `<slot>status</slot>`; missing entries use the built-in token map. */
|
|
108
|
+
states?: ModuleStateVisual[];
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/* -------------------------------------------------------------------------- */
|
|
112
|
+
/* Context & customization */
|
|
113
|
+
/* -------------------------------------------------------------------------- */
|
|
114
|
+
|
|
115
|
+
export type ModuleContext = {
|
|
116
|
+
module: string;
|
|
117
|
+
instanceId: string;
|
|
118
|
+
api: ModuleApi;
|
|
119
|
+
signal: AbortSignal;
|
|
120
|
+
place: (spec: {
|
|
121
|
+
kind: "route" | "slot";
|
|
122
|
+
path?: string;
|
|
123
|
+
slot?: string;
|
|
124
|
+
label?: string;
|
|
125
|
+
order?: number;
|
|
126
|
+
}) => void;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* What the host platform actually passes into `mount`. The module owns everything
|
|
131
|
+
* outside this context and applies it to slots itself.
|
|
132
|
+
*/
|
|
133
|
+
export type HostModuleContext = ModuleContext;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Props every slot component receives: the module context, the per-job record
|
|
137
|
+
* being rendered, and the merged module configuration.
|
|
138
|
+
*/
|
|
139
|
+
export type ModuleSlotProps = {
|
|
140
|
+
context: ModuleContext;
|
|
141
|
+
job: ModuleJobRecord;
|
|
142
|
+
config: ModuleConfig;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/** A slot override map. Editing `resources/src/customization.ts` swaps these in. */
|
|
146
|
+
export type Customization = { slots: SlotMap };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Customization } from "./contract";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Developer customization entry point.
|
|
5
|
+
*
|
|
6
|
+
* Default (no change): keeps the built-in job card layout.
|
|
7
|
+
*
|
|
8
|
+
* Replace one piece inside a card (layout stays platform-owned):
|
|
9
|
+
* import MyJobName from "./custom/MyJobName.vue";
|
|
10
|
+
* export const customization: Customization = {
|
|
11
|
+
* slots: { jobName: MyJobName },
|
|
12
|
+
* };
|
|
13
|
+
*
|
|
14
|
+
* Replace a whole region or the whole card:
|
|
15
|
+
* import MyMiddle from "./custom/MyMiddle.vue";
|
|
16
|
+
* export const customization: Customization = {
|
|
17
|
+
* slots: { middleRegion: MyMiddle }, // the three columns region
|
|
18
|
+
* };
|
|
19
|
+
* export const customization: Customization = {
|
|
20
|
+
* slots: { board: MyBoard }, // the entire single-job card
|
|
21
|
+
* };
|
|
22
|
+
*
|
|
23
|
+
* Every custom component receives `{ context, job, config }` as props
|
|
24
|
+
* (see `contract.ts` for the slot table and CUSTOMIZATION.md for the API/data
|
|
25
|
+
* contract). The platform, batch listing, `GET /jobs`/`GET /config` and card
|
|
26
|
+
* frame stay under AIAUI control regardless of which slot you replace.
|
|
27
|
+
*/
|
|
28
|
+
export const customization: Customization = {
|
|
29
|
+
slots: {},
|
|
30
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, onMounted, ref } from "vue";
|
|
3
|
+
import type {
|
|
4
|
+
ModuleConfig,
|
|
5
|
+
ModuleContext,
|
|
6
|
+
ModuleJobPage,
|
|
7
|
+
SlotMap,
|
|
8
|
+
} from "../contract";
|
|
9
|
+
import { mergeConfig } from "./config-defaults";
|
|
10
|
+
import { DEMO_JOBS } from "./board-data";
|
|
11
|
+
import { defaultSlots } from "./slots";
|
|
12
|
+
import { customization } from "../customization";
|
|
13
|
+
import JobCard from "./JobCard.vue";
|
|
14
|
+
import Icons from "./Icons.vue";
|
|
15
|
+
|
|
16
|
+
const props = defineProps<{ context: ModuleContext; slotMap?: SlotMap }>();
|
|
17
|
+
|
|
18
|
+
const slotMap = computed(() =>
|
|
19
|
+
props.slotMap ?? { ...defaultSlots, ...(customization.slots ?? {}) },
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const jobs = ref<ModuleJobPage["items"]>(DEMO_JOBS);
|
|
23
|
+
const config = ref<ModuleConfig>(mergeConfig(null));
|
|
24
|
+
const note = ref("");
|
|
25
|
+
const search = ref("");
|
|
26
|
+
const stateFilter = ref("");
|
|
27
|
+
const loading = ref(true);
|
|
28
|
+
|
|
29
|
+
async function loadConfig() {
|
|
30
|
+
const signal = props.context.signal;
|
|
31
|
+
try {
|
|
32
|
+
const raw = await props.context.api.call<ModuleConfig>("GET", "config");
|
|
33
|
+
if (signal.aborted) return;
|
|
34
|
+
config.value = mergeConfig(raw);
|
|
35
|
+
} catch {
|
|
36
|
+
if (signal.aborted) return;
|
|
37
|
+
// keep whatever config we have (defaults on first mount)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function loadJobs() {
|
|
42
|
+
const signal = props.context.signal;
|
|
43
|
+
loading.value = true;
|
|
44
|
+
try {
|
|
45
|
+
const page = await props.context.api.call<ModuleJobPage>("GET", "jobs", {
|
|
46
|
+
query: { page: 1, pageSize: 10 },
|
|
47
|
+
});
|
|
48
|
+
if (signal.aborted) return;
|
|
49
|
+
jobs.value = page.items;
|
|
50
|
+
note.value = "";
|
|
51
|
+
} catch {
|
|
52
|
+
if (signal.aborted) return;
|
|
53
|
+
jobs.value = DEMO_JOBS;
|
|
54
|
+
note.value = "GET /jobs unavailable — showing demo data";
|
|
55
|
+
} finally {
|
|
56
|
+
if (!signal.aborted) loading.value = false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function refresh() {
|
|
61
|
+
await Promise.all([loadConfig(), loadJobs()]);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
onMounted(refresh);
|
|
65
|
+
|
|
66
|
+
const states = computed(() =>
|
|
67
|
+
Array.from(new Set(jobs.value.map((job) => job.state ?? "—"))),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const visibleJobs = computed(() =>
|
|
71
|
+
jobs.value.filter((job) => {
|
|
72
|
+
const matchesName =
|
|
73
|
+
!search.value.trim() ||
|
|
74
|
+
job.name.toLowerCase().includes(search.value.trim().toLowerCase());
|
|
75
|
+
const matchesState =
|
|
76
|
+
!stateFilter.value || (job.state ?? "—") === stateFilter.value;
|
|
77
|
+
return matchesName && matchesState;
|
|
78
|
+
}),
|
|
79
|
+
);
|
|
80
|
+
</script>
|
|
81
|
+
|
|
82
|
+
<template>
|
|
83
|
+
<section class="legacy-jobs" aria-label="Job Dashboard">
|
|
84
|
+
<header class="legacy-toolbar">
|
|
85
|
+
<div class="legacy-filters">
|
|
86
|
+
<label class="legacy-search">
|
|
87
|
+
<Icons name="search" :size="14" />
|
|
88
|
+
<input
|
|
89
|
+
v-model="search"
|
|
90
|
+
placeholder="Search jobs"
|
|
91
|
+
type="text"
|
|
92
|
+
aria-label="Search jobs"
|
|
93
|
+
/>
|
|
94
|
+
</label>
|
|
95
|
+
<select
|
|
96
|
+
v-model="stateFilter"
|
|
97
|
+
class="legacy-filter"
|
|
98
|
+
aria-label="Status filter"
|
|
99
|
+
>
|
|
100
|
+
<option value="">All statuses</option>
|
|
101
|
+
<option v-for="state in states" :key="state" :value="state">
|
|
102
|
+
{{ state }}
|
|
103
|
+
</option>
|
|
104
|
+
</select>
|
|
105
|
+
<button
|
|
106
|
+
class="legacy-refresh"
|
|
107
|
+
type="button"
|
|
108
|
+
title="Refresh"
|
|
109
|
+
:aria-busy="loading"
|
|
110
|
+
@click="refresh"
|
|
111
|
+
>
|
|
112
|
+
<Icons name="refresh" :size="14" :class="{ 'aia-spinning': loading }" />
|
|
113
|
+
</button>
|
|
114
|
+
</div>
|
|
115
|
+
</header>
|
|
116
|
+
<p v-if="note" class="legacy-job-note" role="note">{{ note }}</p>
|
|
117
|
+
<JobCard
|
|
118
|
+
v-for="job in visibleJobs"
|
|
119
|
+
:key="job.id"
|
|
120
|
+
:context="props.context"
|
|
121
|
+
:slotMap="slotMap"
|
|
122
|
+
:job="job"
|
|
123
|
+
:config="config"
|
|
124
|
+
/>
|
|
125
|
+
<p v-if="visibleJobs.length === 0" class="legacy-job-empty">
|
|
126
|
+
No jobs match the current filters.
|
|
127
|
+
</p>
|
|
128
|
+
</section>
|
|
129
|
+
</template>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/* Minimal inline icon set shared by the built-in slots (no external dependency). */
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{
|
|
5
|
+
name:
|
|
6
|
+
| "clock"
|
|
7
|
+
| "list"
|
|
8
|
+
| "stop"
|
|
9
|
+
| "activity"
|
|
10
|
+
| "flag"
|
|
11
|
+
| "search"
|
|
12
|
+
| "refresh"
|
|
13
|
+
| "chevron-down"
|
|
14
|
+
| "package";
|
|
15
|
+
size?: number;
|
|
16
|
+
}>();
|
|
17
|
+
|
|
18
|
+
const paths: Record<string, string> = {
|
|
19
|
+
clock:
|
|
20
|
+
'<circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline>',
|
|
21
|
+
list: '<line x1="8" y1="6" x2="21" y2="6"></line><line x1="8" y1="12" x2="21" y2="12"></line><line x1="8" y1="18" x2="21" y2="18"></line><line x1="3" y1="6" x2="3.01" y2="6"></line><line x1="3" y1="12" x2="3.01" y2="12"></line><line x1="3" y1="18" x2="3.01" y2="18"></line>',
|
|
22
|
+
stop: '<circle cx="12" cy="12" r="10"></circle><rect x="9" y="9" width="6" height="6" rx="1"></rect>',
|
|
23
|
+
activity: '<path d="M22 12h-4l-3 9L9 3l-3 9H2"></path>',
|
|
24
|
+
flag:
|
|
25
|
+
'<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"></path><line x1="4" y1="22" x2="4" y2="15"></line>',
|
|
26
|
+
search:
|
|
27
|
+
'<circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line>',
|
|
28
|
+
refresh:
|
|
29
|
+
'<polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>',
|
|
30
|
+
"chevron-down": '<polyline points="6 9 12 15 18 9"></polyline>',
|
|
31
|
+
package:
|
|
32
|
+
'<path d="m9 4c0-1.7 1.3-3 3-3s3 1.3 3 3v1h3.4c1.4 0 2.6 1.1 2.6 2.5v10c0 1.4-1.1 2.5-2.6 2.5H5.6C4.1 20 3 18.9 3 17.5v-10C3 6.1 4.1 5 5.6 5H9V4Z"></path><path d="M12 4v16"></path>',
|
|
33
|
+
};
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<template>
|
|
37
|
+
<svg
|
|
38
|
+
:width="props.size ?? 16"
|
|
39
|
+
:height="props.size ?? 16"
|
|
40
|
+
viewBox="0 0 24 24"
|
|
41
|
+
fill="none"
|
|
42
|
+
stroke="currentColor"
|
|
43
|
+
stroke-width="2"
|
|
44
|
+
stroke-linecap="round"
|
|
45
|
+
stroke-linejoin="round"
|
|
46
|
+
aria-hidden="true"
|
|
47
|
+
v-html="paths[props.name]"
|
|
48
|
+
/>
|
|
49
|
+
</template>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ModuleSlotProps } from "../contract";
|
|
3
|
+
|
|
4
|
+
const props = defineProps<ModuleSlotProps>();
|
|
5
|
+
const carriers = props.job.carriers?.input ?? [];
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<aside class="legacy-carriers legacy-carriers-in" :aria-label="`Input carriers for ${job.name}`">
|
|
10
|
+
<span class="legacy-carriers-label">INPUT CARRIER(S)</span>
|
|
11
|
+
<div class="legacy-carriers-list">
|
|
12
|
+
<span v-for="carrier in carriers" :key="carrier.code" class="legacy-carrier-chip">{{
|
|
13
|
+
carrier.code
|
|
14
|
+
}}</span>
|
|
15
|
+
<span v-if="carriers.length === 0" class="legacy-carrier-empty">—</span>
|
|
16
|
+
</div>
|
|
17
|
+
</aside>
|
|
18
|
+
</template>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import type {
|
|
4
|
+
ModuleConfig,
|
|
5
|
+
ModuleContext,
|
|
6
|
+
ModuleJobRecord,
|
|
7
|
+
SlotMap,
|
|
8
|
+
} from "../contract";
|
|
9
|
+
import Recipe from "./Recipe.vue";
|
|
10
|
+
|
|
11
|
+
const props = defineProps<{
|
|
12
|
+
context: ModuleContext;
|
|
13
|
+
job: ModuleJobRecord;
|
|
14
|
+
config: ModuleConfig;
|
|
15
|
+
slotMap?: SlotMap;
|
|
16
|
+
}>();
|
|
17
|
+
|
|
18
|
+
const leafProps = computed(() => ({
|
|
19
|
+
context: props.context,
|
|
20
|
+
job: props.job,
|
|
21
|
+
config: props.config,
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
const showRecipe = computed(() => props.config.display?.recipe !== false);
|
|
25
|
+
const recipe = computed(() => (props.slotMap ?? {}).recipe ?? Recipe);
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<template>
|
|
29
|
+
<div v-if="showRecipe" class="legacy-progress">
|
|
30
|
+
<component :is="recipe" v-bind="leafProps" />
|
|
31
|
+
</div>
|
|
32
|
+
</template>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import type { Component } from "vue";
|
|
4
|
+
import type {
|
|
5
|
+
ModuleConfig,
|
|
6
|
+
ModuleContext,
|
|
7
|
+
ModuleJobRecord,
|
|
8
|
+
SlotMap,
|
|
9
|
+
} from "../contract";
|
|
10
|
+
import JobHeader from "./JobHeader.vue";
|
|
11
|
+
import JobMiddle from "./JobMiddle.vue";
|
|
12
|
+
import JobBottom from "./JobBottom.vue";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Default `board` slot = one job card. Overriding `board` replaces the whole
|
|
16
|
+
* card (header, middle, bottom); leaf/region overrides then only apply when
|
|
17
|
+
* the platform's default board composes them.
|
|
18
|
+
*/
|
|
19
|
+
const props = defineProps<{
|
|
20
|
+
context: ModuleContext;
|
|
21
|
+
job: ModuleJobRecord;
|
|
22
|
+
config: ModuleConfig;
|
|
23
|
+
slotMap?: SlotMap;
|
|
24
|
+
}>();
|
|
25
|
+
|
|
26
|
+
const board = computed(() => props.slotMap?.board ?? null);
|
|
27
|
+
const regionProps = computed(() => ({ context: props.context, job: props.job, config: props.config, slotMap: props.slotMap }));
|
|
28
|
+
const headerRegion = computed(() => props.slotMap?.headerRegion ?? JobHeader);
|
|
29
|
+
const middleRegion = computed(() => props.slotMap?.middleRegion ?? JobMiddle);
|
|
30
|
+
const bottomRegion = computed(() => props.slotMap?.bottomRegion ?? JobBottom);
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<article class="legacy-card" :data-job-id="job.id">
|
|
35
|
+
<component
|
|
36
|
+
v-if="board"
|
|
37
|
+
:is="board as Component"
|
|
38
|
+
:context="context"
|
|
39
|
+
:job="job"
|
|
40
|
+
:config="config"
|
|
41
|
+
/>
|
|
42
|
+
<template v-else>
|
|
43
|
+
<component :is="headerRegion" v-bind="regionProps" />
|
|
44
|
+
<component :is="middleRegion" v-bind="regionProps" />
|
|
45
|
+
<component :is="bottomRegion" v-bind="regionProps" />
|
|
46
|
+
</template>
|
|
47
|
+
</article>
|
|
48
|
+
</template>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import type {
|
|
4
|
+
ModuleConfig,
|
|
5
|
+
ModuleContext,
|
|
6
|
+
ModuleJobRecord,
|
|
7
|
+
SlotMap,
|
|
8
|
+
} from "../contract";
|
|
9
|
+
import JobName from "./JobName.vue";
|
|
10
|
+
import LotId from "./LotId.vue";
|
|
11
|
+
import Status from "./Status.vue";
|
|
12
|
+
|
|
13
|
+
const props = defineProps<{
|
|
14
|
+
context: ModuleContext;
|
|
15
|
+
job: ModuleJobRecord;
|
|
16
|
+
config: ModuleConfig;
|
|
17
|
+
slotMap?: SlotMap;
|
|
18
|
+
}>();
|
|
19
|
+
|
|
20
|
+
const leafProps = computed(() => ({
|
|
21
|
+
context: props.context,
|
|
22
|
+
job: props.job,
|
|
23
|
+
config: props.config,
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
const jobName = computed(() => (props.slotMap ?? {}).jobName ?? JobName);
|
|
27
|
+
const lotId = computed(() =>
|
|
28
|
+
props.config.display?.lotId === false ? null : ((props.slotMap ?? {}).lotId ?? LotId),
|
|
29
|
+
);
|
|
30
|
+
const status = computed(() => (props.slotMap ?? {}).status ?? Status);
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<div class="legacy-job-head">
|
|
35
|
+
<div class="legacy-job-main">
|
|
36
|
+
<component :is="jobName" v-bind="leafProps" />
|
|
37
|
+
<component v-if="lotId" :is="lotId" v-bind="leafProps" />
|
|
38
|
+
</div>
|
|
39
|
+
<component :is="status" v-bind="leafProps" />
|
|
40
|
+
</div>
|
|
41
|
+
</template>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import type {
|
|
4
|
+
ModuleConfig,
|
|
5
|
+
ModuleContext,
|
|
6
|
+
ModuleJobRecord,
|
|
7
|
+
SlotMap,
|
|
8
|
+
} from "../contract";
|
|
9
|
+
import InputCarriers from "./InputCarriers.vue";
|
|
10
|
+
import OutputCarriers from "./OutputCarriers.vue";
|
|
11
|
+
import Timeline from "./Timeline.vue";
|
|
12
|
+
|
|
13
|
+
const props = defineProps<{
|
|
14
|
+
context: ModuleContext;
|
|
15
|
+
job: ModuleJobRecord;
|
|
16
|
+
config: ModuleConfig;
|
|
17
|
+
slotMap?: SlotMap;
|
|
18
|
+
}>();
|
|
19
|
+
|
|
20
|
+
const leafProps = computed(() => ({
|
|
21
|
+
context: props.context,
|
|
22
|
+
job: props.job,
|
|
23
|
+
config: props.config,
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
const showInput = computed(() => props.config.display?.inputCarriers !== false);
|
|
27
|
+
const showTimeline = computed(() => props.config.display?.timeline !== false);
|
|
28
|
+
const showOutput = computed(() => props.config.display?.outputCarriers !== false);
|
|
29
|
+
|
|
30
|
+
const inputCarriers = computed(() => (props.slotMap ?? {}).inputCarriers ?? InputCarriers);
|
|
31
|
+
const timeline = computed(() => (props.slotMap ?? {}).timeline ?? Timeline);
|
|
32
|
+
const outputCarriers = computed(() => (props.slotMap ?? {}).outputCarriers ?? OutputCarriers);
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<template>
|
|
36
|
+
<div class="legacy-body">
|
|
37
|
+
<component v-if="showInput" :is="inputCarriers" v-bind="leafProps" />
|
|
38
|
+
<component v-if="showTimeline" :is="timeline" v-bind="leafProps" />
|
|
39
|
+
<component v-if="showOutput" :is="outputCarriers" v-bind="leafProps" />
|
|
40
|
+
</div>
|
|
41
|
+
</template>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ModuleSlotProps } from "../contract";
|
|
3
|
+
import Icons from "./Icons.vue";
|
|
4
|
+
|
|
5
|
+
defineProps<ModuleSlotProps>();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<span v-if="job.lot" class="legacy-lot" :title="`Lot ID · ${job.lot}`">
|
|
10
|
+
<Icons name="package" :size="12" />
|
|
11
|
+
<span class="legacy-lot-label">Lot ID</span>
|
|
12
|
+
<span class="legacy-lot-value">{{ job.lot }}</span>
|
|
13
|
+
</span>
|
|
14
|
+
</template>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ModuleSlotProps } from "../contract";
|
|
3
|
+
|
|
4
|
+
const props = defineProps<ModuleSlotProps>();
|
|
5
|
+
const carriers = props.job.carriers?.output ?? [];
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<aside
|
|
10
|
+
class="legacy-carriers legacy-carriers-out"
|
|
11
|
+
:aria-label="`Output carriers for ${job.name}`"
|
|
12
|
+
>
|
|
13
|
+
<span class="legacy-carriers-label">OUTPUT CARRIER(S)</span>
|
|
14
|
+
<div class="legacy-carriers-list">
|
|
15
|
+
<span v-for="carrier in carriers" :key="carrier.code" class="legacy-carrier-chip">{{
|
|
16
|
+
carrier.code
|
|
17
|
+
}}</span>
|
|
18
|
+
<span v-if="carriers.length === 0" class="legacy-carrier-empty">—</span>
|
|
19
|
+
</div>
|
|
20
|
+
</aside>
|
|
21
|
+
</template>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ModuleSlotProps } from "../contract";
|
|
3
|
+
|
|
4
|
+
defineProps<ModuleSlotProps>();
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<div class="legacy-recipe-row">
|
|
9
|
+
<span class="legacy-recipe-label">Recipe</span>
|
|
10
|
+
<span class="legacy-recipe">{{ job.recipe ?? "—" }}</span>
|
|
11
|
+
</div>
|
|
12
|
+
</template>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import type { ModuleSlotProps, ModuleStateVisual } from "../contract";
|
|
4
|
+
import { tokenStateIcon } from "./format";
|
|
5
|
+
|
|
6
|
+
const props = defineProps<ModuleSlotProps>();
|
|
7
|
+
|
|
8
|
+
const visual = computed<Partial<ModuleStateVisual>>(() => {
|
|
9
|
+
const hit = props.config.states?.find(
|
|
10
|
+
(s) => s.name.toLowerCase() === (props.job.state ?? "").toLowerCase(),
|
|
11
|
+
);
|
|
12
|
+
return hit ?? { color: null, icon: tokenStateIcon(props.job.state) };
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const chipStyle = computed<Record<string, string> | undefined>(() => {
|
|
16
|
+
if (!visual.value.color) return undefined;
|
|
17
|
+
return {
|
|
18
|
+
color: visual.value.color,
|
|
19
|
+
borderColor: `color-mix(in srgb, ${visual.value.color} 22%, #ffffff)`,
|
|
20
|
+
background: `color-mix(in srgb, ${visual.value.color} 12%, #ffffff)`,
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
<span class="legacy-chip" :style="chipStyle" :data-state="job.state ?? ''">
|
|
27
|
+
{{ job.state ?? "—" }}
|
|
28
|
+
</span>
|
|
29
|
+
</template>
|