update sounds ang transtions
Some checks failed
Test / test (push) Has been cancelled

This commit is contained in:
2026-06-22 17:16:36 +07:00
parent d70a4e45af
commit 523f98b74c
28 changed files with 2449 additions and 138 deletions

View File

@@ -6,6 +6,7 @@
{ type: "move_to_position", label: "Go to position" },
{ type: "move_to_marker", label: "Go to marker" },
{ type: "adjust_localization", label: "Adjust localization" },
{ type: "switch_map", label: "Switch map" },
{ type: "wait", label: "Wait" },
{ type: "set_speed", label: "Set speed" },
],
@@ -38,6 +39,60 @@
const SAMPLE_IO_MODULES = ["GPIO module 1", "PLC I/O 1"];
const SAMPLE_CARTS = ["Any valid cart", "Cart A", "Cart B"];
let missionPositionCatalog = [];
function buildPositionCatalogFromMaps(maps) {
const out = [];
(maps || []).forEach((map) => {
if (!map?.id) return;
const zones = Array.isArray(map.zones) ? map.zones : [];
zones
.filter((z) => z && z.type === "position" && typeof z.id === "string" && z.id)
.forEach((z) => {
const mapName = map.name || map.id;
const pname = z.name || z.id;
out.push({
position_id: z.id,
map_id: map.id,
name: pname,
label: `${mapName} / ${pname}`,
});
});
});
return out;
}
function positionOptions() {
return missionPositionCatalog.map((p) => ({ value: p.position_id, label: p.label }));
}
function positionIds() {
return missionPositionCatalog.map((p) => p.position_id);
}
function positionLabel(ref) {
if (!ref) return "—";
const hit = missionPositionCatalog.find((p) => p.position_id === ref || p.name === ref);
return hit?.label || ref;
}
function positionSelectOptions() {
const opts = positionOptions();
if (opts.length) return opts;
return SAMPLE_POSITIONS.map((n) => ({ value: n, label: n }));
}
function syncPositionFieldDefs() {
const ids = positionIds();
["move_to_position", "adjust_localization", "if", "pick_cart", "drop_cart"].forEach((type) => {
const defs = VARIABLE_FIELD_DEFS[type];
if (!defs) return;
defs.forEach((def) => {
if (def.key === "position") def.options = ids.length ? ids : SAMPLE_POSITIONS;
});
});
}
const el = (id) => document.getElementById(id);
const t = (key, vars) => window.I18n?.t(key, vars) ?? key;
@@ -75,6 +130,7 @@
{ key: "cart", label: "Cart", options: SAMPLE_CARTS },
],
drop_cart: [{ key: "position", label: "Position", options: SAMPLE_POSITIONS }],
switch_map: [{ key: "map_id", label: "Map", options: [] }],
};
const store = {
@@ -102,17 +158,19 @@
function defaultParams(type) {
switch (type) {
case "move_to_position":
return { position: SAMPLE_POSITIONS[0], check_free: true };
return { position: positionIds()[0] || SAMPLE_POSITIONS[0], check_free: true };
case "move_to_marker":
return { marker: SAMPLE_MARKERS[0] };
case "adjust_localization":
return { position: SAMPLE_POSITIONS[0] };
return { position: positionIds()[0] || SAMPLE_POSITIONS[0] };
case "wait":
return { seconds: 1 };
case "set_speed":
return { speed: "normal" };
case "switch_map":
return { map_id: "", entry_position_id: "" };
case "if":
return { condition: "position_free", position: SAMPLE_POSITIONS[0] };
return { condition: "position_free", position: positionIds()[0] || SAMPLE_POSITIONS[0] };
case "loop":
return { count: 1, mode: "count" };
case "set_digital_output":
@@ -122,13 +180,13 @@
case "set_plc_register":
return { register: 1, action: "set", value: 0 };
case "pick_cart":
return { position: SAMPLE_POSITIONS[0], cart: SAMPLE_CARTS[0] };
return { position: positionIds()[0] || SAMPLE_POSITIONS[0], cart: SAMPLE_CARTS[0] };
case "drop_cart":
return { position: SAMPLE_POSITIONS[0], collision_check: true };
return { position: positionIds()[0] || SAMPLE_POSITIONS[0], collision_check: true };
case "user_log":
return { message: "Mission step" };
case "play_sound":
return { sound: "beep" };
return { sound: "sys_beep", volume: 100 };
default:
return {};
}
@@ -277,7 +335,7 @@
const fmtVar = (key, val) => (p[`${key}_var`] ? `${val} (biến)` : val);
switch (action.type) {
case "move_to_position":
return `Position: ${fmtVar("position", p.position)}${p.check_free ? " • kiểm tra trống" : ""}`;
return `Position: ${fmtVar("position", positionLabel(p.position))}${p.check_free ? " • kiểm tra trống" : ""}`;
case "move_to_marker":
return `Marker: ${fmtVar("marker", p.marker)}`;
case "wait":
@@ -287,7 +345,7 @@
case "loop":
return p.mode === "endless" ? "Lặp vô hạn" : `Lặp ${p.count} lần • ${action.children?.length || 0} bước`;
case "if":
return `If ${p.condition} @ ${p.position || "—"}`;
return `If ${p.condition} @ ${positionLabel(p.position)}`;
case "set_digital_output":
return `${p.module} pin ${p.pin}${p.value ? "ON" : "OFF"}`;
case "wait_digital_input":
@@ -296,11 +354,18 @@
return `Reg ${p.register}: ${p.action} ${p.value}`;
case "pick_cart":
case "drop_cart":
return `${action.type === "pick_cart" ? "Pick" : "Drop"} @ ${fmtVar("position", p.position)}${action.type === "pick_cart" ? `${fmtVar("cart", p.cart)}` : ""}`;
return `${action.type === "pick_cart" ? "Pick" : "Drop"} @ ${fmtVar("position", positionLabel(p.position))}${action.type === "pick_cart" ? `${fmtVar("cart", p.cart)}` : ""}`;
case "user_log":
return p.message || "—";
case "play_sound":
return p.sound || "—";
case "switch_map":
return `${p.map_id || "—"}`;
case "play_sound": {
const soundName = (() => {
const s = (window.SoundsApp?.getSounds?.() || []).find((x) => x.id === p.sound);
return s?.name || p.sound || "—";
})();
return `${soundName} · ${p.volume != null ? p.volume : 100}%`;
}
default:
return action.label;
}
@@ -679,9 +744,11 @@
sel.dataset.varKey = v.key;
v.options.forEach((opt) => {
const o = document.createElement("option");
o.value = opt;
o.textContent = opt;
if (opt === v.default) o.selected = true;
const value = opt?.value ?? opt;
const label = opt?.label ?? positionLabel(value);
o.value = value;
o.textContent = label;
if (value === v.default) o.selected = true;
sel.appendChild(o);
});
row.appendChild(lab);
@@ -1170,6 +1237,19 @@
return select;
};
const selectInputLabeled = (key, value, options) => {
const select = document.createElement("select");
select.dataset.param = key;
options.forEach((opt) => {
const o = document.createElement("option");
o.value = opt.value;
o.textContent = opt.label;
if (opt.value === value) o.selected = true;
select.appendChild(o);
});
return select;
};
const addVariableToggle = (paramKey, fieldLabel) => {
const chk = document.createElement("label");
chk.innerHTML = `<input type="checkbox" data-param="${paramKey}_var" ${p[`${paramKey}_var`] ? "checked" : ""} /> Biến — hỏi khi thêm vào queue`;
@@ -1179,7 +1259,7 @@
switch (action.type) {
case "move_to_position":
case "adjust_localization":
addField("Position", selectInput("position", p.position, SAMPLE_POSITIONS));
addField("Position", selectInputLabeled("position", p.position, positionSelectOptions()));
addVariableToggle("position", "Position");
if (action.type === "move_to_position") {
const chk = document.createElement("label");
@@ -1203,7 +1283,7 @@
break;
case "if":
addField("Điều kiện", selectInput("condition", p.condition, ["position_free", "position_occupied", "register_equals"]));
addField("Position", selectInput("position", p.position, SAMPLE_POSITIONS));
addField("Position", selectInputLabeled("position", p.position, positionSelectOptions()));
addVariableToggle("position", "Position");
break;
case "set_digital_output":
@@ -1231,13 +1311,13 @@
addField("Giá trị", textInput("value", p.value, "number"));
break;
case "pick_cart":
addField("Position", selectInput("position", p.position, SAMPLE_POSITIONS));
addField("Position", selectInputLabeled("position", p.position, positionSelectOptions()));
addVariableToggle("position", "Position");
addField("Cart", selectInput("cart", p.cart, SAMPLE_CARTS));
addVariableToggle("cart", "Cart");
break;
case "drop_cart":
addField("Position", selectInput("position", p.position, SAMPLE_POSITIONS));
addField("Position", selectInputLabeled("position", p.position, positionSelectOptions()));
addVariableToggle("position", "Position");
{
const chk = document.createElement("label");
@@ -1248,9 +1328,25 @@
case "user_log":
addField("Message", textInput("message", p.message));
break;
case "play_sound":
addField("Sound", selectInput("sound", p.sound, ["beep", "horn", "chime"]));
case "play_sound": {
const catalog = (window.SoundsApp?.getSounds?.() || []).filter((s) => s.enabled !== false);
const options = catalog.length
? catalog.map((s) => ({ value: s.id, label: s.name }))
: [
{ value: "sys_beep", label: "Beep" },
{ value: "sys_horn", label: "Horn" },
{ value: "sys_chime", label: "Chime" },
];
addField("Sound", selectInputLabeled("sound", p.sound || options[0].value, options));
addField("Volume (0100)", textInput("volume", p.volume != null ? p.volume : 100, "number"));
break;
}
case "switch_map": {
const options = (VARIABLE_FIELD_DEFS.switch_map?.[0]?.options || []).map((id) => ({ value: id, label: id }));
addField("Map", selectInputLabeled("map_id", p.map_id || options[0]?.value || "", options));
addField("Entry position id", textInput("entry_position_id", p.entry_position_id || ""));
break;
}
default:
grid.innerHTML = `<p class="mutedNote">Action này không có tham số cấu hình.</p>`;
}
@@ -1361,6 +1457,26 @@
async function init() {
loadStore();
if (window.SoundsApp?.refreshSounds) {
try {
await window.SoundsApp.refreshSounds();
} catch {
/* ignore */
}
}
try {
const maps = await fetch("/api/maps", { credentials: "include" });
if (maps.ok) {
const data = await maps.json();
const list = Array.isArray(data.maps) ? data.maps : [];
missionPositionCatalog = buildPositionCatalogFromMaps(list);
syncPositionFieldDefs();
const ids = list.map((m) => m.id).filter(Boolean);
VARIABLE_FIELD_DEFS.switch_map[0].options = ids;
}
} catch {
/* ignore */
}
await loadStoreFromBackend();
bindEvents();
renderMissionList();