Enhance UI components for map editing and school creation. Update styles for improved layout and responsiveness, including adjustments to dialog positioning and flex properties. Introduce new localization strings for better user guidance in both English and Russian. Refactor code structure for clarity and maintainability, ensuring a more intuitive user experience.
This commit is contained in:
@@ -23,11 +23,16 @@ export function mapEditor(options: MapEditorOptions): {
|
||||
let selectedId: string | null = map.territory.id;
|
||||
|
||||
const tree = el('ul', { class: 'tree map-editor__tree' });
|
||||
const treeTitle = el('h3', { class: 'editor-section__title' });
|
||||
const details = el('div', { class: 'map-editor__details' });
|
||||
const root = el(
|
||||
'div',
|
||||
{ class: 'map-editor' },
|
||||
el('div', { class: 'map-editor__pane' }, tree),
|
||||
el(
|
||||
'div',
|
||||
{ class: 'map-editor__pane' },
|
||||
el('section', { class: 'editor-section editor-section--grow' }, treeTitle, tree),
|
||||
),
|
||||
details,
|
||||
);
|
||||
|
||||
@@ -37,10 +42,20 @@ export function mapEditor(options: MapEditorOptions): {
|
||||
};
|
||||
|
||||
const render = (): void => {
|
||||
treeTitle.textContent = t('editorStructure');
|
||||
paintTree();
|
||||
paintDetails();
|
||||
};
|
||||
|
||||
/** Every group of controls gets a frame and a heading; loose selects read as debug output. */
|
||||
const section = (title: string, ...children: (Node | null)[]): HTMLElement =>
|
||||
el(
|
||||
'section',
|
||||
{ class: 'editor-section' },
|
||||
el('h3', { class: 'editor-section__title', text: title }),
|
||||
...children,
|
||||
);
|
||||
|
||||
const paintTree = (): void => {
|
||||
clear(tree);
|
||||
appendNode(tree, map.territory.id, labelOf(catalog, map, map.territory.id), 0);
|
||||
@@ -48,10 +63,7 @@ export function mapEditor(options: MapEditorOptions): {
|
||||
for (const building of map.buildings) {
|
||||
appendNode(tree, building.id, defLabel(catalog.buildings, building.def) ?? building.id, 1);
|
||||
for (const floor of map.floors.filter((candidate) => candidate.building === building.id)) {
|
||||
const floorName = floor.label !== undefined && floor.label.length > 0
|
||||
? floor.label
|
||||
: (defLabel(catalog.floors, floor.def) ?? floor.id);
|
||||
appendNode(tree, floor.id, floorName, 2);
|
||||
appendNode(tree, floor.id, floorName(catalog, floor), 2);
|
||||
for (const room of map.rooms.filter((candidate) => candidate.floor === floor.id)) {
|
||||
appendNode(tree, room.id, defLabel(catalog.rooms, room.def) ?? room.id, 3);
|
||||
}
|
||||
@@ -77,42 +89,58 @@ export function mapEditor(options: MapEditorOptions): {
|
||||
|
||||
const paintDetails = (): void => {
|
||||
clear(details);
|
||||
details.append(paintSelected(), paintLinks(), paintRooms());
|
||||
};
|
||||
|
||||
/** Furnishing of the selected room, or the reason there is nothing to furnish. */
|
||||
const paintSelected = (): HTMLElement => {
|
||||
const room = map.rooms.find((candidate) => candidate.id === selectedId);
|
||||
const roomDef = room === undefined ? undefined : catalog.rooms.find((def) => def.defName === room.def);
|
||||
const title = `${t('editorSlots')} — ${labelOf(catalog, map, selectedId ?? map.territory.id)}`;
|
||||
|
||||
if (room !== undefined && roomDef !== undefined) {
|
||||
details.append(el('p', { class: 'field__label', text: t('editorSlots') }));
|
||||
for (const slot of roomDef.slots) {
|
||||
const select = el('select', { class: 'input' });
|
||||
const empty = el('option', { text: t('slotEmpty') });
|
||||
empty.value = '';
|
||||
select.append(empty);
|
||||
for (const thing of catalog.things) {
|
||||
const option = el('option', { text: thing.label });
|
||||
option.value = thing.defName;
|
||||
select.append(option);
|
||||
}
|
||||
if (room === undefined || roomDef === undefined) {
|
||||
return section(title, el('p', { class: 'editor-section__hint', text: t('editorPickRoom') }));
|
||||
}
|
||||
|
||||
const fill = (room.slots ?? []).find((candidate) => candidate.key === slot.key);
|
||||
select.value = fill?.thing ?? '';
|
||||
select.addEventListener('change', () => {
|
||||
const slots = [...(room.slots ?? [])].filter((candidate) => candidate.key !== slot.key);
|
||||
if (select.value !== '') {
|
||||
slots.push({ key: slot.key, thing: select.value });
|
||||
}
|
||||
|
||||
room.slots = slots;
|
||||
emit();
|
||||
});
|
||||
|
||||
details.append(
|
||||
el('label', { class: 'field' }, el('span', { class: 'field__label', text: slot.key }), select),
|
||||
);
|
||||
const rows: HTMLElement[] = [];
|
||||
for (const slot of roomDef.slots) {
|
||||
const select = el('select', { class: 'input' });
|
||||
const empty = el('option', { text: t('slotEmpty') });
|
||||
empty.value = '';
|
||||
select.append(empty);
|
||||
for (const thing of catalog.things) {
|
||||
const option = el('option', { text: thing.label });
|
||||
option.value = thing.defName;
|
||||
select.append(option);
|
||||
}
|
||||
|
||||
const remove = el('button', {
|
||||
class: 'button',
|
||||
const fill = (room.slots ?? []).find((candidate) => candidate.key === slot.key);
|
||||
select.value = fill?.thing ?? '';
|
||||
select.addEventListener('change', () => {
|
||||
const slots = [...(room.slots ?? [])].filter((candidate) => candidate.key !== slot.key);
|
||||
if (select.value !== '') {
|
||||
slots.push({ key: slot.key, thing: select.value });
|
||||
}
|
||||
|
||||
room.slots = slots;
|
||||
emit();
|
||||
});
|
||||
|
||||
rows.push(
|
||||
el(
|
||||
'label',
|
||||
{ class: 'field__row' },
|
||||
el('span', { class: 'field__label', text: slot.key }),
|
||||
select,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return section(
|
||||
title,
|
||||
...rows,
|
||||
el('div', { class: 'dialog__actions' }, el('button', {
|
||||
class: 'button button--danger button--small',
|
||||
type: 'button',
|
||||
text: t('removeRoom'),
|
||||
onClick: () => {
|
||||
@@ -121,11 +149,11 @@ export function mapEditor(options: MapEditorOptions): {
|
||||
selectedId = map.territory.id;
|
||||
emit();
|
||||
},
|
||||
});
|
||||
details.append(remove);
|
||||
}
|
||||
})),
|
||||
);
|
||||
};
|
||||
|
||||
details.append(el('p', { class: 'field__label', text: t('editorLinks') }));
|
||||
const paintLinks = (): HTMLElement => {
|
||||
const linksList = el('ul', { class: 'map-editor__links' });
|
||||
for (const [index, link] of map.links.entries()) {
|
||||
const row = el('li', { class: 'map-editor__link' });
|
||||
@@ -144,12 +172,13 @@ export function mapEditor(options: MapEditorOptions): {
|
||||
linksList.append(row);
|
||||
}
|
||||
|
||||
details.append(linksList);
|
||||
|
||||
const walkable = walkableIds(map);
|
||||
const from = selectOf(walkable, catalog, map);
|
||||
const to = selectOf(walkable, catalog, map);
|
||||
details.append(
|
||||
|
||||
return section(
|
||||
t('editorLinks'),
|
||||
map.links.length > 0 ? linksList : el('p', { class: 'editor-section__hint', text: t('editorNoLinks') }),
|
||||
el(
|
||||
'div',
|
||||
{ class: 'field__row' },
|
||||
@@ -172,7 +201,9 @@ export function mapEditor(options: MapEditorOptions): {
|
||||
}),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const paintRooms = (): HTMLElement => {
|
||||
const roomSelect = el('select', { class: 'input' });
|
||||
for (const def of catalog.rooms) {
|
||||
const option = el('option', { text: def.label });
|
||||
@@ -180,7 +211,8 @@ export function mapEditor(options: MapEditorOptions): {
|
||||
roomSelect.append(option);
|
||||
}
|
||||
|
||||
details.append(
|
||||
return section(
|
||||
t('editorRooms'),
|
||||
el(
|
||||
'div',
|
||||
{ class: 'field__row' },
|
||||
@@ -277,6 +309,22 @@ function slug(defName: string): string {
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* A floor's `label` is its designation, not a replacement name — the vanilla map says "1". Shown
|
||||
* on its own it is a stray digit in the tree, so it follows the def label: "Этаж 1", "Floor 1".
|
||||
* Mirrors `MapView.FloorName` on the server.
|
||||
*/
|
||||
function floorName(catalog: CatalogResponse, floor: MapLayout['floors'][number]): string {
|
||||
const def = defLabel(catalog.floors, floor.def);
|
||||
const label = floor.label ?? '';
|
||||
|
||||
if (def === undefined) {
|
||||
return label.length > 0 ? label : floor.id;
|
||||
}
|
||||
|
||||
return label.length > 0 ? `${def} ${label}` : def;
|
||||
}
|
||||
|
||||
function defLabel(defs: readonly { defName: string; label: string }[], defName: string): string | undefined {
|
||||
return defs.find((def) => def.defName === defName)?.label;
|
||||
}
|
||||
@@ -293,9 +341,7 @@ function labelOf(catalog: CatalogResponse, map: MapLayout, id: string): string {
|
||||
|
||||
const floor = map.floors.find((node) => node.id === id);
|
||||
if (floor !== undefined) {
|
||||
return floor.label !== undefined && floor.label.length > 0
|
||||
? floor.label
|
||||
: (defLabel(catalog.floors, floor.def) ?? id);
|
||||
return floorName(catalog, floor);
|
||||
}
|
||||
|
||||
const room = map.rooms.find((node) => node.id === id);
|
||||
|
||||
Reference in New Issue
Block a user