diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3be5db4..d1173e5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,39 @@
## Unreleased
+## 0.3.0
+
+- Add the Basic editor. When you create a dashboard you choose the editor: Advanced is the
+ full editor you know and stays the default, Basic cuts the screen into regions and puts
+ one widget in each, with nothing to drag or align. A dashboard keeps the editor it was
+ made in. In Basic you set the grid (columns, rows, the gap and the outer padding),
+ click one cell and another to make a region of the cells between them, and double-click
+ a region to remove it. A region can be any rectangle of cells, from one cell to the whole
+ grid, and there is no limit to how many. Regions are lettered in the order you made them.
+ On the Widgets tab you drag a widget into a region, or click one to fill the selected
+ region, and its properties are on the right. Only empty regions are outlined, and a click
+ beside the regions lets go of the selection, so you see the picture as it will be
+ rendered.
+- Mark each dashboard in the list as Basic or Advanced.
+- Rebuild the New dashboard window: pick where the display comes from (a device, a
+ predefined display or a custom size), then the display and the name, and see the display
+ drawn in the proportions of its resolution. Two arrows turn it a quarter at a time, with a
+ small lean back and a settle at the end, and the canvas turns with it.
+- Draw each dashboard in the list as its display: in the proportions of its resolution,
+ turned as it is set up, with the colors of its palette and its resolution at the bottom,
+ and cards no taller than they need to be.
+- Add a setting to Display settings, "Media Source picture as designed". Off, as before,
+ the picture the Media Source gives is turned for the display. On, it keeps the
+ orientation you see in the designer, which suits a preview or a card; to show it with
+ `opendisplay.upload_image` give the rotation of the dashboard as `rotation`, which the
+ text under the switch names. What the editor sends to a device is always turned for it.
+- Fix a text that starts outside the display and is turned: the Media Source and the
+ picture sent to a device lost its first letters while the editor showed it whole. The
+ display now receives the same pixels the editor shows, at every rotation.
+- Fix the cells of a grid in widgets, which stopped short of the edge of their box by the
+ width of the gaps; the weather widget moved by a few pixels.
+- Give Display settings a cog in the menu of a dashboard.
+
## 0.2.0
- Add a Settings view, opened from the list of dashboards, with the fonts, the widgets and
diff --git a/CLAUDE.md b/CLAUDE.md
index 3306b36..8127400 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -141,7 +141,9 @@ dashboard document ─▶ validate ─▶ resolve widget data ─▶ resolve exp
(`preview_margin`; elements are shifted into it just before rendering), so what hangs
out of the display is seen (the empty margin is transparent) and the canvas outlines
the display.
- Item bounds stay in display coordinates, and the Media Source picture has no margin.
+ Item bounds stay in display coordinates. The Media Source picture is drawn on that same
+ picture, margin and all, and the margin is cut off afterwards, so the display receives
+ exactly the pixels the editor shows (a test compares them, at every rotation).
- **The dashboard document is the only persisted state** (HA `Store`,
versioned). No `localStorage`/IndexedDB for dashboards. Per-viewer UI
conveniences (panel widths, collapsed sections, clipboard) may use memory or
@@ -160,6 +162,10 @@ dashboard document ─▶ validate ─▶ resolve widget data ─▶ resolve exp
tested with a stored fixture of the previous version. Never rely on "records
that fail validation are skipped".
- TypeScript types and Python validation change together, in one change.
+- A dashboard has a `mode`, `advanced` (the default) or `basic`, chosen when it is created
+ and never changed. A basic dashboard has a `layout` (a grid and its regions) and widget
+ items with a `region`; the backend sets their frames from the layout. An advanced one has
+ neither. Never add a conversion between the modes.
- `items` is a **tree**. Sibling order is z-order: first drawn first; the last
child is on top. Item kinds: `primitive` (exactly one ODL element), `widget`
(zero or more ODL elements from its renderer), `container` (editor-side
diff --git a/README.md b/README.md
index 21c10f9..fd7a66e 100644
--- a/README.md
+++ b/README.md
@@ -41,11 +41,11 @@ display can fetch.
## Start from the display you own
Create a dashboard from a display Home Assistant already knows, from a list of
-common panels, or at any custom size. Rotation is part of the dashboard, so a
-portrait display just works.
+common panels, or at any custom size. Rotation is part of the dashboard: turn the
+display in the preview and the canvas turns with it, so a portrait display just works.
-
+
All your dashboards live in one list, with their size, colours and state
@@ -56,6 +56,70 @@ device with one click.
+## Two editors: Basic and Advanced
+
+You choose the editor when you create a dashboard, and the dashboard keeps it.
+**Advanced** is the full editor described below: place and arrange every element
+freely. **Basic** cuts the screen into regions and puts one widget in each; there is
+nothing to align, so a screen is made in a few clicks. Advanced is the default, and
+the list of dashboards marks each one as Basic or Advanced. A dashboard is never
+converted from one to the other.
+
+
+
+
+
+**1. Set the grid.** On the *Layout* tab pick the number of columns and rows, and the gap
+between the cells.
+
+
+
+
+
+**2. Make the regions.** Click one cell, then another: the cells between them become one
+region. A region that would be cut in two is refused. Double-click a region to remove it; its cells are free again. Regions are lettered A, B, C … in the order you made them.
+
+
+
+
+
+
+
+
+
+**3. Fill them with widgets.** On the *Widgets* tab drag a widget into a region, or select
+a region and click a widget. Selecting a widget shows its properties on the right; with no
+tree and no alignment tools there is more room for them.
+
+
+
+
+
+
+
+
+
+Only empty regions are drawn with a border. Click beside the regions to let go of the
+selection and you see the picture as it will be rendered.
+
+
+
+
+
+There is no limit to the layout but the size of the grid: a region may be any rectangle
+of cells, so a tall one, a square one and a row of single cells can share a screen.
+
+
+
+
+
+
+
+
+
+A Basic dashboard is rendered by the same `odl-renderer` as an Advanced one, and its image
+address works the same way.
+
## Shapes you can shape
Select a polygon and drag its points. Click the **+** on an edge to add a point,
diff --git a/ROADMAP.md b/ROADMAP.md
index afa0557..bb524a9 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -33,6 +33,7 @@ Tick a step's box in the same change that completes it.
| 9 | Widget catalog (Sensor card, Agenda, Weather, …) | 8 |
| 10 | Finishing | all |
| 11 | Import and export of dashboards | 1, 8 |
+| 12 | Basic editor (a grid of regions, a widget in each) | 8, 9 |
Phases 3 and 5 may run in parallel once phase 2 has landed the inspector shell.
Phase 8 may start in parallel with phases 4–7.
@@ -947,6 +948,52 @@ and out of backups. Deliberately simple: export writes a file, import reads a fi
---
+## Phase 12 — Basic editor
+
+A second editor beside the full one. A dashboard is created in one of two **modes** and
+keeps it: `advanced` (everything above, the default, and what a stored dashboard without a
+`mode` is) or `basic`. There is no conversion in either direction. A basic dashboard is a
+grid cut into **regions**, with one widget in each; nothing is dragged, resized or aligned.
+It renders through the same pipeline as an advanced one, and everything that reaches a
+display is still ODL drawn by `odl-renderer`.
+
+- [x] **12.1 Model and validation.** `mode` (`basic` | `advanced`) and, for a basic
+ dashboard, `layout: { columns, rows, gap, regions }`; a widget item carries `region`. A
+ region is a rectangle of cells (`row`, `column`, `rowSpan`, `columnSpan`, any size up to
+ the whole grid), and the regions cover every cell exactly once. The backend derives each
+ widget's `frame` from its region (`basic_layout.py`, through the SDK's `grid()`), so the
+ layout is the one place that says where a widget is; `schemaVersion` stays 1 and the new
+ fields are optional. An advanced dashboard may not have a `layout` or a `region`; a basic
+ one holds widgets only, at most one to a region. `bootstrap` sends the limits of a grid
+ (`layoutLimits`) and `opendisplay_studio/layout_frames` returns where the cells and
+ regions of a layout are on a display, so the panel never lays a grid out itself.
+- [x] **12.2 Choosing the editor.** The new-dashboard dialog starts with the editor
+ (Advanced by default), then where the display comes from (a device, a predefined display
+ or a custom size), the display and the name, a preview of the display with arrows that
+ turn it, and the advanced display options (advanced mode only). The list of dashboards
+ marks each card Basic or Advanced and opens it in its own editor.
+- [x] **12.3 Display preview.** One drawing of a display (`ods-display-preview`) for the
+ cards and the dialog: the proportions of the resolution, the housing turned as the display
+ is set up, its light and button where they are on the device. In the dialog a change of
+ rotation is animated: a lean back, a swing past and a settle (`rotation-motion.ts`).
+- [x] **12.4 Layout tab.** The left panel has the grid (columns, rows, gap, outer padding).
+ One click on a cell and one on another make a region of the cells between them; a region
+ that would cut another in two is refused. A double click removes a region. Regions are
+ lettered A, B, C … in the order they were made, with their size in cells. Resizing the
+ grid keeps the regions that still fit.
+- [x] **12.5 Widgets tab.** The left panel is the library with widgets only. A widget goes
+ into a region by dragging, or into the selected one (or the first empty one) by a click.
+ Properties are on the right; there is no structure tree and no arrange bar, and the
+ canvas keeps its zoom bar. Only an empty region is drawn with a border; a click beside
+ the regions lets go of the selection, so what is left is the rendered picture.
+- [x] **12.6 Tests.** Backend: validation (any rectangles, a region as wide as the grid,
+ gaps in the tiling refused), frames of a layout, compile and render of a basic dashboard.
+ Frontend unit: region operations and lettering, placing a widget, hit testing, display
+ shape and rotation motion. E2E: choosing the editor, the grid, joining and removing
+ regions, filling them, saving, the gallery marks, selection and borders, the preview.
+
+---
+
## Deviations from LVGL (deliberate)
| LVGL behaviour | Ours | Why |
@@ -965,3 +1012,4 @@ and out of backups. Deliberately simple: export writes a file, import reads a fi
| Fixed 5 px grid | Grid step is the dashboard `snapSize`; all other thresholds as measured | Displays differ in resolution; user already sets snap size |
| — (not an LVGL matter) | Document `schemaVersion` stays 1 and shape changes ship without migrations until the first public release of the dashboard model | Pre-release; the model changes shape every phase (flat list → tree → containers) |
| One field holds either a literal or an expression | Item `expressions` map beside the literals | Literals keep their shape (typed TS/Python, `ha-form`), switching the `{}` toggle off restores the value without loss |
+| — (not an LVGL matter) | A dashboard has a `mode`; a basic one is a grid of regions with a widget in each, edited in its own editor | A simpler way to make a screen for those who do not need free placement; no conversion between the editors |
diff --git a/custom_components/opendisplay_studio/basic_layout.py b/custom_components/opendisplay_studio/basic_layout.py
new file mode 100644
index 0000000..b97c06a
--- /dev/null
+++ b/custom_components/opendisplay_studio/basic_layout.py
@@ -0,0 +1,130 @@
+"""The layout of a basic dashboard: a grid cut into regions, a widget in each."""
+
+from __future__ import annotations
+
+from typing import Any, Final
+
+from .odl import Box
+from .sdk.layout import grid
+from .validation import fail, integer, string
+
+MIN_LINES: Final = 1
+MAX_LINES: Final = 12
+DEFAULT_GAP: Final = 8
+MAX_GAP: Final = 64
+MAX_ID_LENGTH: Final = 64
+
+type Layout = dict[str, Any]
+
+LAYOUT_LIMITS: Final = {
+ "minLines": MIN_LINES,
+ "maxLines": MAX_LINES,
+ "defaultGap": DEFAULT_GAP,
+ "maxGap": MAX_GAP,
+}
+
+
+def working_area(width: int, height: int, padding: int) -> Box:
+ """Return what is left of the display inside its padding."""
+ return Box(padding, padding, width - 2 * padding, height - 2 * padding)
+
+
+def grid_cells(area: Box, columns: int, rows: int, gap: int) -> list[Box]:
+ """Return the cells of the grid in reading order, laid out by the SDK."""
+ return grid(area, columns, rows, gap)
+
+
+def region_frame(
+ cells: list[Box], columns: int, region: dict[str, Any]
+) -> dict[str, int]:
+ """Return the frame of a region: from its first cell to its last."""
+ first = cells[region["row"] * columns + region["column"]]
+ last = cells[
+ (region["row"] + region["rowSpan"] - 1) * columns
+ + region["column"]
+ + region["columnSpan"]
+ - 1
+ ]
+ return {
+ "x": first.x,
+ "y": first.y,
+ "width": last.x + last.width - first.x,
+ "height": last.y + last.height - first.y,
+ }
+
+
+def _region(value: object, index: int, columns: int, rows: int) -> dict[str, Any]:
+ name = f"layout.regions[{index}]"
+ if not isinstance(value, dict):
+ fail(f"{name} must be an object")
+ region: dict[str, Any] = {
+ "id": string(value.get("id"), f"{name}.id", MAX_ID_LENGTH),
+ "row": integer(value.get("row"), f"{name}.row", 0, rows - 1),
+ "column": integer(value.get("column"), f"{name}.column", 0, columns - 1),
+ "rowSpan": integer(value.get("rowSpan"), f"{name}.rowSpan", 1, rows),
+ "columnSpan": integer(
+ value.get("columnSpan"), f"{name}.columnSpan", 1, columns
+ ),
+ }
+ if (
+ region["row"] + region["rowSpan"] > rows
+ or region["column"] + region["columnSpan"] > columns
+ ):
+ fail(f"{name} reaches beyond the grid")
+ return region
+
+
+def _check_tiling(regions: list[dict[str, Any]], columns: int, rows: int) -> None:
+ """Every cell of the grid belongs to exactly one region."""
+ owners = [[0] * columns for _ in range(rows)]
+ for region in regions:
+ for row in range(region["row"], region["row"] + region["rowSpan"]):
+ for column in range(
+ region["column"], region["column"] + region["columnSpan"]
+ ):
+ owners[row][column] += 1
+ if any(count != 1 for line in owners for count in line):
+ fail("layout.regions must cover every cell of the grid exactly once")
+
+
+def validate_layout(value: object) -> Layout:
+ """Check the grid and its regions and return them in normal form."""
+ if not isinstance(value, dict):
+ fail("layout is required for a basic dashboard")
+ columns = integer(value.get("columns"), "layout.columns", MIN_LINES, MAX_LINES)
+ rows = integer(value.get("rows"), "layout.rows", MIN_LINES, MAX_LINES)
+ gap = integer(value.get("gap", DEFAULT_GAP), "layout.gap", 0, MAX_GAP)
+ raw = value.get("regions")
+ if not isinstance(raw, list) or not raw:
+ fail("layout.regions must be a list of regions")
+ regions = [_region(item, index, columns, rows) for index, item in enumerate(raw)]
+ if len({region["id"] for region in regions}) != len(regions):
+ fail("layout.regions must have unique ids")
+ _check_tiling(regions, columns, rows)
+ return {"columns": columns, "rows": rows, "gap": gap, "regions": regions}
+
+
+def apply_layout(
+ items: list[dict[str, Any]], layout: Layout, display: dict[str, int]
+) -> None:
+ """
+ Put each widget of a basic dashboard in the frame of its region.
+
+ A basic dashboard holds widgets only, each in a region of its own. The frame a
+ client sent is replaced: the layout is the one place that says where a widget is.
+ """
+ columns = layout["columns"]
+ area = working_area(display["width"], display["height"], display["padding"])
+ cells = grid_cells(area, columns, layout["rows"], layout["gap"])
+ regions = {region["id"]: region for region in layout["regions"]}
+ taken: set[str] = set()
+ for item in items:
+ if item["kind"] != "widget":
+ fail("a basic dashboard holds widgets only")
+ region_id = item.get("region")
+ if not isinstance(region_id, str) or region_id not in regions:
+ fail(f"{item['id']} needs a region of the layout")
+ if region_id in taken:
+ fail("a region holds one widget")
+ taken.add(region_id)
+ item["frame"] = region_frame(cells, columns, regions[region_id])
diff --git a/custom_components/opendisplay_studio/const.py b/custom_components/opendisplay_studio/const.py
index 31564ea..a6d6824 100644
--- a/custom_components/opendisplay_studio/const.py
+++ b/custom_components/opendisplay_studio/const.py
@@ -6,7 +6,7 @@
DOMAIN = "opendisplay_studio"
NAME = "OpenDisplay Studio"
-INTEGRATION_VERSION = "0.2.0"
+INTEGRATION_VERSION = "0.3.0"
DEFAULT_WIDTH = 800
DEFAULT_HEIGHT = 480
diff --git a/custom_components/opendisplay_studio/dashboards.py b/custom_components/opendisplay_studio/dashboards.py
index 496c956..7ef86c9 100644
--- a/custom_components/opendisplay_studio/dashboards.py
+++ b/custom_components/opendisplay_studio/dashboards.py
@@ -13,12 +13,14 @@
from homeassistant.core import HomeAssistant
from homeassistant.helpers.storage import Store
+from .basic_layout import apply_layout, validate_layout
from .const import SCHEMA_VERSION, STORAGE_KEY, STORAGE_VERSION
from .items import validate_items
from .palette import PALETTE_COLORS
from .primitives import DEFAULT_PRIMITIVES, PrimitiveRegistry
from .validation import (
DashboardValidationError,
+ boolean,
color,
device_id,
integer,
@@ -33,6 +35,8 @@
MAX_NAME_LENGTH = 100
LANGUAGE_PATTERN = re.compile(r"^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$")
PALETTES = frozenset(PALETTE_COLORS)
+# Advanced: a free canvas. Basic: a grid of regions with a widget in each.
+MODES = frozenset({"advanced", "basic"})
def validate_dashboard(
@@ -57,6 +61,9 @@ def validate_dashboard(
if padding * 2 >= min(width, height):
raise DashboardValidationError("display.padding leaves no working area")
snap_size = integer(display.get("snapSize", 5), "display.snapSize", 1, 256)
+ media_as_designed = boolean(
+ display.get("mediaAsDesigned", False), "display.mediaAsDesigned"
+ )
items = validate_items(value.get("items", []), registry, primitives, width, height)
language = value.get("language", "en")
if not isinstance(language, str) or LANGUAGE_PATTERN.fullmatch(language) is None:
@@ -64,10 +71,15 @@ def validate_dashboard(
status = value.get("status", "draft")
if status not in {"draft", "ready"}:
raise DashboardValidationError("status must be draft or ready")
+ # A dashboard stored before the modes existed is an advanced one.
+ mode = value.get("mode", "advanced")
+ if mode not in MODES:
+ raise DashboardValidationError("mode must be advanced or basic")
normalized: Dashboard = {
"schemaVersion": SCHEMA_VERSION,
"name": string(value.get("name"), "name", MAX_NAME_LENGTH),
"status": status,
+ "mode": mode,
"language": language,
"display": {
"profileId": display.get("profileId")
@@ -83,15 +95,29 @@ def validate_dashboard(
"snapSize": snap_size,
"rotation": rotation(display.get("rotation", 0)),
"deviceId": device_id(display.get("deviceId")),
+ "mediaAsDesigned": media_as_designed,
},
"items": items,
}
+ _apply_mode(normalized, value)
for key in ("id", "createdAt", "updatedAt"):
if key in value:
normalized[key] = value[key]
return normalized
+def _apply_mode(normalized: Dashboard, value: dict[str, Any]) -> None:
+ """Check what only a basic dashboard has, and put its widgets in their regions."""
+ if normalized["mode"] == "advanced":
+ if "layout" in value:
+ raise DashboardValidationError("layout belongs to a basic dashboard")
+ if any("region" in item for item in normalized["items"]):
+ raise DashboardValidationError("region belongs to a basic dashboard")
+ return
+ normalized["layout"] = validate_layout(value.get("layout"))
+ apply_layout(normalized["items"], normalized["layout"], normalized["display"])
+
+
class DashboardStore:
"""Serialize dashboard mutations and persist them through HA Store."""
diff --git a/custom_components/opendisplay_studio/delivery.py b/custom_components/opendisplay_studio/delivery.py
index 4ef05ce..004a47d 100644
--- a/custom_components/opendisplay_studio/delivery.py
+++ b/custom_components/opendisplay_studio/delivery.py
@@ -39,7 +39,8 @@ def preview_margin(display: dict[str, Any]) -> int:
Elements may lie partly outside the display; seeing them makes moving them back
easy. A margin of two fifths of the shorter side, at most 256 px, keeps the picture
- small enough to render quickly.
+ small enough to render quickly. The picture for a display is drawn with the same
+ margin and then cut to the display, see `async_render_dashboard`.
"""
shorter_side: int = min(display["width"], display["height"])
return min(PREVIEW_MARGIN_LIMIT, round(shorter_side * 0.4))
@@ -59,16 +60,22 @@ async def async_render_dashboard(
from the Media Source or sent to a device, is turned by the dashboard's rotation
so it has the display's own width and height. With `with_margin` the picture also
shows a margin around the display, where elements that hang out of it are drawn.
+
+ Either way the dashboard is drawn once, on the picture with its margin, and the
+ margin is cut off afterwards when it is not wanted. The renderer draws an element
+ that hangs out of the picture only as far as the picture goes, and a turned text is
+ turned after it is drawn, so drawing the display on its own would give another
+ picture than the editor shows. The display receives exactly what the editor shows.
"""
started = monotonic()
data = hass.data[DOMAIN]
compiled = await async_compile_dashboard(hass, dashboard, data.widgets)
display = dashboard["display"]
- margin = preview_margin(display) if with_margin else 0
- elements = compiled.elements
- if margin:
- explicit = expand_debug_grids(elements, display["width"], display["height"])
- elements = translate_elements(explicit, margin, margin)
+ margin = preview_margin(display)
+ explicit = expand_debug_grids(
+ compiled.elements, display["width"], display["height"]
+ )
+ elements = translate_elements(explicit, margin, margin)
accent_color = accent_color_for_palette(display["palette"])
pivots = (
await async_find_pivots(
@@ -79,7 +86,7 @@ async def async_render_dashboard(
margin=margin,
accent_color=accent_color,
)
- if margin
+ if with_margin
else {}
)
picture = await data.renderer.async_render(
@@ -91,6 +98,7 @@ async def async_render_dashboard(
accent_color=accent_color,
rotation=display["rotation"] if for_device else 0,
margin=margin,
+ show_margin=with_margin,
)
pipeline_ms = round((monotonic() - started) * 1000, 2)
LOGGER.info(
@@ -108,4 +116,5 @@ async def async_render_dashboard(
pipeline_ms,
len(picture.png),
)
- return RenderedDashboard(compiled, picture, pipeline_ms, margin, pivots)
+ shown_margin = margin if with_margin else 0
+ return RenderedDashboard(compiled, picture, pipeline_ms, shown_margin, pivots)
diff --git a/custom_components/opendisplay_studio/frontend/opendisplay-studio.js b/custom_components/opendisplay_studio/frontend/opendisplay-studio.js
index f75626d..104edd3 100644
--- a/custom_components/opendisplay_studio/frontend/opendisplay-studio.js
+++ b/custom_components/opendisplay_studio/frontend/opendisplay-studio.js
@@ -138,6 +138,9 @@ var e = (e, t) => `${e} × ${t}`, t = {
deviceDetails: "Dashboard size and colors",
colors: "Colors",
create: "Create dashboard",
+ previewLabel: "Display preview",
+ turnLeft: "Turn 90° to the left",
+ turnRight: "Turn 90° to the right",
creating: "Creating…"
},
header: {
@@ -345,6 +348,14 @@ var e = (e, t) => `${e} × ${t}`, t = {
advanced: "Advanced display options",
padding: "Outer padding",
snapSize: "Snap size",
+ mediaAsDesigned: "Media Source picture as designed",
+ mediaAsDesignedHelp: (e) => e === 0 ? "The picture of the Media Source is the same either way: this dashboard is not turned." : [
+ "The picture of the Media Source keeps the orientation you see in the designer,",
+ "which suits a preview or a card. It is not turned for the display, so to show",
+ `it with opendisplay.upload_image set rotation to ${e}.`,
+ "Left off, the picture is turned for the display.",
+ "What the editor sends to a device is always turned for it."
+ ].join(" "),
x: "X",
y: "Y",
innerPadding: "Inner padding"
@@ -528,6 +539,39 @@ var e = (e, t) => `${e} × ${t}`, t = {
seven_color: "Seven colors · Spectra 6 and orange",
grayscale4: "Grayscale · 4 levels",
grayscale16: "Grayscale · 16 levels"
+ },
+ basic: {
+ modeTitle: "Editor",
+ basic: "Basic",
+ basicHint: "A grid of regions, a widget in each.",
+ advanced: "Advanced",
+ advancedHint: "Place every element freely.",
+ badgeBasic: "Basic",
+ badgeAdvanced: "Advanced",
+ stepsLabel: "Editor steps",
+ stepLayout: "Layout",
+ stepWidgets: "Widgets",
+ widgetsHeading: "Widgets",
+ layoutTitle: "Layout",
+ layoutHeading: "Regions",
+ gridHeading: "Grid",
+ columns: "Columns",
+ rows: "Rows",
+ gap: "Gap",
+ regionsHeading: "Regions",
+ regionsHint: "Click one cell, then another, to join the cells between them into a region. Double-click a region to remove it.",
+ joinStarted: "Click a second cell to finish the region.",
+ joinInvalid: "That would cut a region in two.",
+ cancelJoin: "Cancel",
+ regionCount: (e) => `${e} ${e === 1 ? "region" : "regions"}`,
+ cell: (e, t) => `Cell in row ${e} and column ${t}`,
+ regionSize: (e, t) => `${e}×${t} region`,
+ region: (e) => `Region ${e}`,
+ emptyRegion: "Empty region",
+ emptyRegionHint: "Pick a widget from the list.",
+ noEmptyRegion: "Every region already holds a widget.",
+ layoutStepHint: "Draw the regions first, then fill them on the Widgets tab.",
+ widgetsStepHint: "Drag a widget into a region, or click one to fill the selected region."
}
}, n = {
common: {
@@ -664,6 +708,9 @@ var e = (e, t) => `${e} × ${t}`, t = {
deviceDetails: "Größe und Farben des Dashboards",
colors: "Farben",
create: "Dashboard erstellen",
+ previewLabel: "Vorschau des Displays",
+ turnLeft: "Um 90° nach links drehen",
+ turnRight: "Um 90° nach rechts drehen",
creating: "Wird erstellt…"
},
header: {
@@ -854,6 +901,14 @@ var e = (e, t) => `${e} × ${t}`, t = {
background: "Hintergrund",
advanced: "Erweiterte Display-Optionen",
padding: "Äußerer Rand",
+ mediaAsDesigned: "Bild der Media Source wie im Editor",
+ mediaAsDesignedHelp: (e) => e === 0 ? "Das Bild der Media Source ist in beiden Fällen gleich: Dieses Dashboard ist nicht gedreht." : [
+ "Das Bild der Media Source behält die Ausrichtung aus dem Editor,",
+ "passend für eine Vorschau oder eine Karte. Es ist nicht für das Display gedreht;",
+ `um es mit opendisplay.upload_image zu zeigen, setze rotation auf ${e}.`,
+ "Ausgeschaltet wird das Bild für das Display gedreht.",
+ "Was der Editor an ein Gerät sendet, ist immer für dieses gedreht."
+ ].join(" "),
snapSize: "Rasterschritt",
innerPadding: "Innerer Rand"
},
@@ -1046,6 +1101,39 @@ var e = (e, t) => `${e} × ${t}`, t = {
seven_color: "Sieben Farben · Spectra 6 und Orange",
grayscale4: "Graustufen · 4 Stufen",
grayscale16: "Graustufen · 16 Stufen"
+ },
+ basic: {
+ modeTitle: "Editor",
+ basic: "Einfach",
+ basicHint: "Ein Raster aus Bereichen, in jedem ein Widget.",
+ advanced: "Erweitert",
+ advancedHint: "Jedes Element frei platzieren.",
+ badgeBasic: "Einfach",
+ badgeAdvanced: "Erweitert",
+ stepsLabel: "Schritte des Editors",
+ stepLayout: "Layout",
+ stepWidgets: "Widgets",
+ widgetsHeading: "Widgets",
+ layoutTitle: "Layout",
+ layoutHeading: "Bereiche",
+ gridHeading: "Raster",
+ columns: "Spalten",
+ rows: "Zeilen",
+ gap: "Abstand",
+ regionsHeading: "Bereiche",
+ regionsHint: "Klicke eine Zelle und dann eine zweite, um die Zellen dazwischen zu einem Bereich zu verbinden. Klicke einen Bereich doppelt an, um ihn zu entfernen.",
+ joinStarted: "Klicke eine zweite Zelle, um den Bereich abzuschließen.",
+ joinInvalid: "Das würde einen Bereich zerschneiden.",
+ cancelJoin: "Abbrechen",
+ regionCount: (e) => `${e} ${e === 1 ? "Bereich" : "Bereiche"}`,
+ cell: (e, t) => `Zelle in Zeile ${e} und Spalte ${t}`,
+ regionSize: (e, t) => `Bereich ${e}×${t}`,
+ region: (e) => `Bereich ${e}`,
+ emptyRegion: "Leerer Bereich",
+ emptyRegionHint: "Wähle ein Widget aus der Liste.",
+ noEmptyRegion: "Jeder Bereich enthält schon ein Widget.",
+ layoutStepHint: "Zeichne zuerst die Bereiche und fülle sie dann im Tab Widgets.",
+ widgetsStepHint: "Ziehe ein Widget in einen Bereich oder klicke es an, um den gewählten Bereich zu füllen."
}
}, r = (e, t, n, r) => {
if (e === 1) return t;
@@ -1187,6 +1275,9 @@ var e = (e, t) => `${e} × ${t}`, t = {
deviceDetails: "Rozmiar i kolory dashboardu",
colors: "Kolory",
create: "Utwórz dashboard",
+ previewLabel: "Podgląd wyświetlacza",
+ turnLeft: "Obróć o 90° w lewo",
+ turnRight: "Obróć o 90° w prawo",
creating: "Tworzenie…"
},
header: {
@@ -1377,6 +1468,14 @@ var e = (e, t) => `${e} × ${t}`, t = {
background: "Tło",
advanced: "Zaawansowane ustawienia wyświetlacza",
padding: "Margines zewnętrzny",
+ mediaAsDesigned: "Obraz Media Source tak jak w edytorze",
+ mediaAsDesignedHelp: (e) => e === 0 ? "Obraz Media Source jest taki sam w obu przypadkach: ten dashboard nie jest obrócony." : [
+ "Obraz Media Source zachowuje orientację z edytora, co dobrze nadaje się",
+ "do podglądu albo karty. Nie jest obrócony dla wyświetlacza, więc aby pokazać go",
+ `przez opendisplay.upload_image, ustaw rotation na ${e}.`,
+ "Gdy wyłączone, obraz jest obrócony dla wyświetlacza.",
+ "To, co edytor wysyła na urządzenie, jest zawsze obrócone dla niego."
+ ].join(" "),
snapSize: "Krok przyciągania",
innerPadding: "Margines wewnętrzny"
},
@@ -1569,6 +1668,39 @@ var e = (e, t) => `${e} × ${t}`, t = {
seven_color: "Siedem kolorów · Spectra 6 i pomarańczowy",
grayscale4: "Skala szarości · 4 poziomy",
grayscale16: "Skala szarości · 16 poziomów"
+ },
+ basic: {
+ modeTitle: "Edytor",
+ basic: "Podstawowy",
+ basicHint: "Siatka regionów, w każdym widżet.",
+ advanced: "Zaawansowany",
+ advancedHint: "Każdy element ustawiasz swobodnie.",
+ badgeBasic: "Podstawowy",
+ badgeAdvanced: "Zaawansowany",
+ stepsLabel: "Kroki edytora",
+ stepLayout: "Układ",
+ stepWidgets: "Widżety",
+ widgetsHeading: "Widżety",
+ layoutTitle: "Układ",
+ layoutHeading: "Regiony",
+ gridHeading: "Siatka",
+ columns: "Kolumny",
+ rows: "Wiersze",
+ gap: "Odstęp",
+ regionsHeading: "Regiony",
+ regionsHint: "Kliknij jedną komórkę, potem drugą, aby połączyć komórki między nimi w region. Kliknij region dwukrotnie, aby go usunąć.",
+ joinStarted: "Kliknij drugą komórkę, aby zakończyć region.",
+ joinInvalid: "To przecięłoby region na pół.",
+ cancelJoin: "Anuluj",
+ regionCount: (e) => `${e} ${r(e, "region", "regiony", "regionów")}`,
+ cell: (e, t) => `Komórka w wierszu ${e} i kolumnie ${t}`,
+ regionSize: (e, t) => `region ${e}×${t}`,
+ region: (e) => `Region ${e}`,
+ emptyRegion: "Pusty region",
+ emptyRegionHint: "Wybierz widżet z listy.",
+ noEmptyRegion: "Każdy region ma już widżet.",
+ layoutStepHint: "Najpierw narysuj regiony, potem wypełnij je na karcie Widżety.",
+ widgetsStepHint: "Przeciągnij widżet do regionu albo kliknij go, aby wypełnić zaznaczony region."
}
}, a = "en", o = {
de: n,
@@ -7059,16 +7191,16 @@ var jc = class extends HTMLElement {
jc.elementStyles = [], jc.shadowRootOptions = { mode: "open" }, jc[Dc("elementProperties")] = /* @__PURE__ */ new Map(), jc[Dc("finalized")] = /* @__PURE__ */ new Map(), Ec?.({ ReactiveElement: jc }), (Cc.reactiveElementVersions ??= []).push("2.1.2");
//#endregion
//#region node_modules/lit-html/lit-html.js
-var Mc = globalThis, Nc = (e) => e, Pc = Mc.trustedTypes, Fc = Pc ? Pc.createPolicy("lit-html", { createHTML: (e) => e }) : void 0, Ic = "$lit$", Lc = `lit$${Math.random().toFixed(9).slice(2)}$`, Rc = "?" + Lc, zc = `<${Rc}>`, Bc = document, Vc = () => Bc.createComment(""), Hc = (e) => e === null || typeof e != "object" && typeof e != "function", Uc = Array.isArray, Wc = (e) => Uc(e) || typeof e?.[Symbol.iterator] == "function", Gc = "[ \n\f\r]", Kc = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, qc = /-->/g, Jc = />/g, Yc = RegExp(`>|${Gc}(?:([^\\s"'>=/]+)(${Gc}*=${Gc}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`, "g"), Xc = /'/g, Zc = /"/g, Qc = /^(?:script|style|textarea|title)$/i, F = ((e) => (t, ...n) => ({
+var Mc = globalThis, Nc = (e) => e, Pc = Mc.trustedTypes, Fc = Pc ? Pc.createPolicy("lit-html", { createHTML: (e) => e }) : void 0, Ic = "$lit$", Lc = `lit$${Math.random().toFixed(9).slice(2)}$`, Rc = "?" + Lc, zc = `<${Rc}>`, Bc = document, Vc = () => Bc.createComment(""), Hc = (e) => e === null || typeof e != "object" && typeof e != "function", Uc = Array.isArray, Wc = (e) => Uc(e) || typeof e?.[Symbol.iterator] == "function", Gc = "[ \n\f\r]", Kc = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, qc = /-->/g, Jc = />/g, Yc = RegExp(`>|${Gc}(?:([^\\s"'>=/]+)(${Gc}*=${Gc}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`, "g"), Xc = /'/g, Zc = /"/g, Qc = /^(?:script|style|textarea|title)$/i, $c = (e) => (t, ...n) => ({
_$litType$: e,
strings: t,
values: n
-}))(1), $c = Symbol.for("lit-noChange"), I = Symbol.for("lit-nothing"), el = /* @__PURE__ */ new WeakMap(), tl = Bc.createTreeWalker(Bc, 129);
-function nl(e, t) {
+}), F = $c(1), el = $c(2), tl = Symbol.for("lit-noChange"), I = Symbol.for("lit-nothing"), nl = /* @__PURE__ */ new WeakMap(), rl = Bc.createTreeWalker(Bc, 129);
+function il(e, t) {
if (!Uc(e) || !e.hasOwnProperty("raw")) throw Error("invalid template strings array");
return Fc === void 0 ? t : Fc.createHTML(t);
}
-var rl = (e, t) => {
+var al = (e, t) => {
let n = e.length - 1, r = [], i, a = t === 2 ? "" : t === 3 ? "" : "")), r];
-}, il = class e {
+ return [il(e, a + (e[n] || ">") + (t === 2 ? "" : t === 3 ? "" : "")), r];
+}, ol = class e {
constructor({ strings: t, _$litType$: n }, r) {
let i;
this.parts = [];
- let a = 0, o = 0, s = t.length - 1, c = this.parts, [l, u] = rl(t, n);
- if (this.el = e.createElement(l, r), tl.currentNode = this.el.content, n === 2 || n === 3) {
+ let a = 0, o = 0, s = t.length - 1, c = this.parts, [l, u] = al(t, n);
+ if (this.el = e.createElement(l, r), rl.currentNode = this.el.content, n === 2 || n === 3) {
let e = this.el.content.firstChild;
e.replaceWith(...e.childNodes);
}
- for (; (i = tl.nextNode()) !== null && c.length < s;) {
+ for (; (i = rl.nextNode()) !== null && c.length < s;) {
if (i.nodeType === 1) {
if (i.hasAttributes()) for (let e of i.getAttributeNames()) if (e.endsWith(Ic)) {
let t = u[o++], n = i.getAttribute(e).split(Lc), r = /([.?@])?(.*)/.exec(t);
@@ -7095,7 +7227,7 @@ var rl = (e, t) => {
index: a,
name: r[2],
strings: n,
- ctor: r[1] === "." ? ll : r[1] === "?" ? ul : r[1] === "@" ? dl : cl
+ ctor: r[1] === "." ? dl : r[1] === "?" ? fl : r[1] === "@" ? pl : ul
}), i.removeAttribute(e);
} else e.startsWith(Lc) && (c.push({
type: 6,
@@ -7105,7 +7237,7 @@ var rl = (e, t) => {
let e = i.textContent.split(Lc), t = e.length - 1;
if (t > 0) {
i.textContent = Pc ? Pc.emptyScript : "";
- for (let n = 0; n < t; n++) i.append(e[n], Vc()), tl.nextNode(), c.push({
+ for (let n = 0; n < t; n++) i.append(e[n], Vc()), rl.nextNode(), c.push({
type: 2,
index: ++a
});
@@ -7133,12 +7265,12 @@ var rl = (e, t) => {
return n.innerHTML = e, n;
}
};
-function al(e, t, n = e, r) {
- if (t === $c) return t;
+function sl(e, t, n = e, r) {
+ if (t === tl) return t;
let i = r === void 0 ? n._$Cl : n._$Co?.[r], a = Hc(t) ? void 0 : t._$litDirective$;
- return i?.constructor !== a && (i?._$AO?.(!1), a === void 0 ? i = void 0 : (i = new a(e), i._$AT(e, n, r)), r === void 0 ? n._$Cl = i : (n._$Co ??= [])[r] = i), i !== void 0 && (t = al(e, i._$AS(e, t.values), i, r)), t;
+ return i?.constructor !== a && (i?._$AO?.(!1), a === void 0 ? i = void 0 : (i = new a(e), i._$AT(e, n, r)), r === void 0 ? n._$Cl = i : (n._$Co ??= [])[r] = i), i !== void 0 && (t = sl(e, i._$AS(e, t.values), i, r)), t;
}
-var ol = class {
+var cl = class {
constructor(e, t) {
this._$AV = [], this._$AN = void 0, this._$AD = e, this._$AM = t;
}
@@ -7150,22 +7282,22 @@ var ol = class {
}
u(e) {
let { el: { content: t }, parts: n } = this._$AD, r = (e?.creationScope ?? Bc).importNode(t, !0);
- tl.currentNode = r;
- let i = tl.nextNode(), a = 0, o = 0, s = n[0];
+ rl.currentNode = r;
+ let i = rl.nextNode(), a = 0, o = 0, s = n[0];
for (; s !== void 0;) {
if (a === s.index) {
let t;
- s.type === 2 ? t = new sl(i, i.nextSibling, this, e) : s.type === 1 ? t = new s.ctor(i, s.name, s.strings, this, e) : s.type === 6 && (t = new fl(i, this, e)), this._$AV.push(t), s = n[++o];
+ s.type === 2 ? t = new ll(i, i.nextSibling, this, e) : s.type === 1 ? t = new s.ctor(i, s.name, s.strings, this, e) : s.type === 6 && (t = new ml(i, this, e)), this._$AV.push(t), s = n[++o];
}
- a !== s?.index && (i = tl.nextNode(), a++);
+ a !== s?.index && (i = rl.nextNode(), a++);
}
- return tl.currentNode = Bc, r;
+ return rl.currentNode = Bc, r;
}
p(e) {
let t = 0;
for (let n of this._$AV) n !== void 0 && (n.strings === void 0 ? n._$AI(e[t]) : (n._$AI(e, n, t), t += n.strings.length - 2)), t++;
}
-}, sl = class e {
+}, ll = class e {
get _$AU() {
return this._$AM?._$AU ?? this._$Cv;
}
@@ -7183,7 +7315,7 @@ var ol = class {
return this._$AB;
}
_$AI(e, t = this) {
- e = al(this, e, t), Hc(e) ? e === I || e == null || e === "" ? (this._$AH !== I && this._$AR(), this._$AH = I) : e !== this._$AH && e !== $c && this._(e) : e._$litType$ === void 0 ? e.nodeType === void 0 ? Wc(e) ? this.k(e) : this._(e) : this.T(e) : this.$(e);
+ e = sl(this, e, t), Hc(e) ? e === I || e == null || e === "" ? (this._$AH !== I && this._$AR(), this._$AH = I) : e !== this._$AH && e !== tl && this._(e) : e._$litType$ === void 0 ? e.nodeType === void 0 ? Wc(e) ? this.k(e) : this._(e) : this.T(e) : this.$(e);
}
O(e) {
return this._$AA.parentNode.insertBefore(e, this._$AB);
@@ -7195,16 +7327,16 @@ var ol = class {
this._$AH !== I && Hc(this._$AH) ? this._$AA.nextSibling.data = e : this.T(Bc.createTextNode(e)), this._$AH = e;
}
$(e) {
- let { values: t, _$litType$: n } = e, r = typeof n == "number" ? this._$AC(e) : (n.el === void 0 && (n.el = il.createElement(nl(n.h, n.h[0]), this.options)), n);
+ let { values: t, _$litType$: n } = e, r = typeof n == "number" ? this._$AC(e) : (n.el === void 0 && (n.el = ol.createElement(il(n.h, n.h[0]), this.options)), n);
if (this._$AH?._$AD === r) this._$AH.p(t);
else {
- let e = new ol(r, this), n = e.u(this.options);
+ let e = new cl(r, this), n = e.u(this.options);
e.p(t), this.T(n), this._$AH = e;
}
}
_$AC(e) {
- let t = el.get(e.strings);
- return t === void 0 && el.set(e.strings, t = new il(e)), t;
+ let t = nl.get(e.strings);
+ return t === void 0 && nl.set(e.strings, t = new ol(e)), t;
}
k(t) {
Uc(this._$AH) || (this._$AH = [], this._$AR());
@@ -7221,7 +7353,7 @@ var ol = class {
setConnected(e) {
this._$AM === void 0 && (this._$Cv = e, this._$AP?.(e));
}
-}, cl = class {
+}, ul = class {
get tagName() {
return this.element.tagName;
}
@@ -7233,43 +7365,43 @@ var ol = class {
}
_$AI(e, t = this, n, r) {
let i = this.strings, a = !1;
- if (i === void 0) e = al(this, e, t, 0), a = !Hc(e) || e !== this._$AH && e !== $c, a && (this._$AH = e);
+ if (i === void 0) e = sl(this, e, t, 0), a = !Hc(e) || e !== this._$AH && e !== tl, a && (this._$AH = e);
else {
let r = e, o, s;
- for (e = i[0], o = 0; o < i.length - 1; o++) s = al(this, r[n + o], t, o), s === $c && (s = this._$AH[o]), a ||= !Hc(s) || s !== this._$AH[o], s === I ? e = I : e !== I && (e += (s ?? "") + i[o + 1]), this._$AH[o] = s;
+ for (e = i[0], o = 0; o < i.length - 1; o++) s = sl(this, r[n + o], t, o), s === tl && (s = this._$AH[o]), a ||= !Hc(s) || s !== this._$AH[o], s === I ? e = I : e !== I && (e += (s ?? "") + i[o + 1]), this._$AH[o] = s;
}
a && !r && this.j(e);
}
j(e) {
e === I ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, e ?? "");
}
-}, ll = class extends cl {
+}, dl = class extends ul {
constructor() {
super(...arguments), this.type = 3;
}
j(e) {
this.element[this.name] = e === I ? void 0 : e;
}
-}, ul = class extends cl {
+}, fl = class extends ul {
constructor() {
super(...arguments), this.type = 4;
}
j(e) {
this.element.toggleAttribute(this.name, !!e && e !== I);
}
-}, dl = class extends cl {
+}, pl = class extends ul {
constructor(e, t, n, r, i) {
super(e, t, n, r, i), this.type = 5;
}
_$AI(e, t = this) {
- if ((e = al(this, e, t, 0) ?? I) === $c) return;
+ if ((e = sl(this, e, t, 0) ?? I) === tl) return;
let n = this._$AH, r = e === I && n !== I || e.capture !== n.capture || e.once !== n.once || e.passive !== n.passive, i = e !== I && (n === I || r);
r && this.element.removeEventListener(this.name, this, n), i && this.element.addEventListener(this.name, this, e), this._$AH = e;
}
handleEvent(e) {
typeof this._$AH == "function" ? this._$AH.call(this.options?.host ?? this.element, e) : this._$AH.handleEvent(e);
}
-}, fl = class {
+}, ml = class {
constructor(e, t, n) {
this.element = e, this.type = 6, this._$AN = void 0, this._$AM = t, this.options = n;
}
@@ -7277,18 +7409,18 @@ var ol = class {
return this._$AM._$AU;
}
_$AI(e) {
- al(this, e);
+ sl(this, e);
}
-}, pl = Mc.litHtmlPolyfillSupport;
-pl?.(il, sl), (Mc.litHtmlVersions ??= []).push("3.3.3");
-var ml = (e, t, n) => {
+}, hl = Mc.litHtmlPolyfillSupport;
+hl?.(ol, ll), (Mc.litHtmlVersions ??= []).push("3.3.3");
+var gl = (e, t, n) => {
let r = n?.renderBefore ?? t, i = r._$litPart$;
if (i === void 0) {
let e = n?.renderBefore ?? null;
- r._$litPart$ = i = new sl(t.insertBefore(Vc(), e), e, void 0, n ?? {});
+ r._$litPart$ = i = new ll(t.insertBefore(Vc(), e), e, void 0, n ?? {});
}
return i._$AI(e), i;
-}, hl = globalThis, L = class extends jc {
+}, _l = globalThis, L = class extends jc {
constructor() {
super(...arguments), this.renderOptions = { host: this }, this._$Do = void 0;
}
@@ -7298,7 +7430,7 @@ var ml = (e, t, n) => {
}
update(e) {
let t = this.render();
- this.hasUpdated || (this.renderOptions.isConnected = this.isConnected), super.update(e), this._$Do = ml(t, this.renderRoot, this.renderOptions);
+ this.hasUpdated || (this.renderOptions.isConnected = this.isConnected), super.update(e), this._$Do = gl(t, this.renderRoot, this.renderOptions);
}
connectedCallback() {
super.connectedCallback(), this._$Do?.setConnected(!0);
@@ -7307,25 +7439,25 @@ var ml = (e, t, n) => {
super.disconnectedCallback(), this._$Do?.setConnected(!1);
}
render() {
- return $c;
+ return tl;
}
};
-L._$litElement$ = !0, L.finalized = !0, hl.litElementHydrateSupport?.({ LitElement: L });
-var gl = hl.litElementPolyfillSupport;
-gl?.({ LitElement: L }), (hl.litElementVersions ??= []).push("4.2.2");
+L._$litElement$ = !0, L.finalized = !0, _l.litElementHydrateSupport?.({ LitElement: L });
+var vl = _l.litElementPolyfillSupport;
+vl?.({ LitElement: L }), (_l.litElementVersions ??= []).push("4.2.2");
//#endregion
//#region node_modules/@lit/reactive-element/decorators/custom-element.js
var R = (e) => (t, n) => {
n === void 0 ? customElements.define(e, t) : n.addInitializer(() => {
customElements.define(e, t);
});
-}, _l = {
+}, yl = {
attribute: !0,
type: String,
converter: Oc,
reflect: !1,
hasChanged: kc
-}, vl = (e = _l, t, n) => {
+}, bl = (e = yl, t, n) => {
let { kind: r, metadata: i } = n, a = globalThis.litPropertyMetadata.get(i);
if (a === void 0 && globalThis.litPropertyMetadata.set(i, a = /* @__PURE__ */ new Map()), r === "setter" && ((e = Object.create(e)).wrapped = !0), a.set(n.name, e), r === "accessor") {
let { name: r } = n;
@@ -7349,7 +7481,7 @@ var R = (e) => (t, n) => {
throw Error("Unsupported decorator location: " + r);
};
function z(e) {
- return (t, n) => typeof n == "object" ? vl(e, t, n) : ((e, t, n) => {
+ return (t, n) => typeof n == "object" ? bl(e, t, n) : ((e, t, n) => {
let r = t.hasOwnProperty(n);
return t.constructor.createProperty(n, e), r ? Object.getOwnPropertyDescriptor(t, n) : void 0;
})(e, t, n);
@@ -7365,10 +7497,10 @@ function B(e) {
}
//#endregion
//#region node_modules/@lit/reactive-element/decorators/base.js
-var yl = (e, t, n) => (n.configurable = !0, n.enumerable = !0, Reflect.decorate && typeof t != "object" && Object.defineProperty(e, t, n), n);
+var xl = (e, t, n) => (n.configurable = !0, n.enumerable = !0, Reflect.decorate && typeof t != "object" && Object.defineProperty(e, t, n), n);
//#endregion
//#region node_modules/@lit/reactive-element/decorators/query.js
-function bl(e, t) {
+function Sl(e, t) {
return (n, r, i) => {
let a = (t) => t.renderRoot?.querySelector(e) ?? null;
if (t) {
@@ -7383,29 +7515,29 @@ function bl(e, t) {
}
};
})();
- return yl(n, r, { get() {
+ return xl(n, r, { get() {
let n = e.call(this);
return n === void 0 && (n = a(this), (n !== null || this.hasUpdated) && t.call(this, n)), n;
} });
}
- return yl(n, r, { get() {
+ return xl(n, r, { get() {
return a(this);
} });
};
}
//#endregion
//#region node_modules/lit-html/directive.js
-var xl = {
+var Cl = {
ATTRIBUTE: 1,
CHILD: 2,
PROPERTY: 3,
BOOLEAN_ATTRIBUTE: 4,
EVENT: 5,
ELEMENT: 6
-}, Sl = (e) => (...t) => ({
+}, wl = (e) => (...t) => ({
_$litDirective$: e,
values: t
-}), Cl = class {
+}), Tl = class {
constructor(e) {}
get _$AU() {
return this._$AM._$AU;
@@ -7419,9 +7551,9 @@ var xl = {
update(e, t) {
return this.render(...t);
}
-}, wl = "important", Tl = " !" + wl, V = Sl(class extends Cl {
+}, El = "important", Dl = " !" + El, V = wl(class extends Tl {
constructor(e) {
- if (super(e), e.type !== xl.ATTRIBUTE || e.name !== "style" || e.strings?.length > 2) throw Error("The `styleMap` directive must be used in the `style` attribute and must be the only part in the attribute.");
+ if (super(e), e.type !== Cl.ATTRIBUTE || e.name !== "style" || e.strings?.length > 2) throw Error("The `styleMap` directive must be used in the `style` attribute and must be the only part in the attribute.");
}
render(e) {
return Object.keys(e).reduce((t, n) => {
@@ -7437,13 +7569,13 @@ var xl = {
let r = t[e];
if (r != null) {
this.ft.add(e);
- let t = typeof r == "string" && r.endsWith(Tl);
- e.includes("-") || t ? n.setProperty(e, t ? r.slice(0, -11) : r, t ? wl : "") : n[e] = r;
+ let t = typeof r == "string" && r.endsWith(Dl);
+ e.includes("-") || t ? n.setProperty(e, t ? r.slice(0, -11) : r, t ? El : "") : n[e] = r;
}
}
- return $c;
+ return tl;
}
-}), El = (e, t, n, r, i, a) => {
+}), Ol = (e, t, n, r, i, a) => {
let o = e.primitive, s = a?.fields.find((e) => e.key === t);
if (s?.shape === "coordinate") {
let i = x(e), a = { ...o }, c = Number(Reflect.get(a, t)), l = s.axis === "x", u = (l ? i.x : i.y) - c, [d, f] = gt(l ? r.x : r.y, l ? r.width : r.height, l ? i.width : i.height);
@@ -7454,9 +7586,9 @@ var xl = {
let e = et(s.min, i, n), r = et(s.max, i, n);
Object.assign(o, { [t]: _(n, e, r) });
}
-}, Dl = (e, t, n, r, i) => {
+}, kl = (e, t, n, r, i) => {
t === "x" && (e.x = _(n, ...gt(r.x, r.width, e.width))), t === "y" && (e.y = _(n, ...gt(r.y, r.height, e.height))), t === "width" && (e.width = _(n, 1, Qe(i.width))), t === "height" && (e.height = _(n, 1, Qe(i.height)));
-}, Ol = (e, t, n, r, i) => {
+}, Al = (e, t, n, r, i) => {
if (t === "x") {
let t = e.x_end - e.x_start;
e.x_start = _(n, ...gt(r.x, r.width, t + 1)), e.x_end = e.x_start + t;
@@ -7466,10 +7598,10 @@ var xl = {
e.y_start = _(n, ...gt(r.y, r.height, t + 1)), e.y_end = e.y_start + t;
}
t === "width" && (e.x_end = _(e.x_start + Math.max(1, n) - 1, e.x_start + 1, e.x_start + Qe(i.width) - 1)), t === "height" && (e.y_end = _(e.y_start + Math.max(1, n) - 1, e.y_start + 1, e.y_start + Qe(i.height) - 1));
-}, kl = (e, t, n, r, i) => {
+}, jl = (e, t, n, r, i) => {
let a = pt(r);
if (e.kind === "widget") {
- t === "padding" && (e.layout.padding = _(n, 0, 128)), Dl(e.frame, t, n, a, r.display);
+ t === "padding" && (e.layout.padding = _(n, 0, 128)), kl(e.frame, t, n, a, r.display);
return;
}
if (e.kind === "container") {
@@ -7477,47 +7609,47 @@ var xl = {
width: e.width,
height: e.height
};
- Dl(e, t, n, a, r.display), e.grouped && ft(e, e.width / Math.max(1, o.width), e.height / Math.max(1, o.height), i, r.display);
+ kl(e, t, n, a, r.display), e.grouped && ft(e, e.width / Math.max(1, o.width), e.height / Math.max(1, o.height), i, r.display);
return;
}
let o = e.primitive;
- ue(o) ? Ol(o, t, n, a, r.display) : El(e, t, n, a, r.display, Bo(e, i));
-}, Al = (e, t, n, r, i) => {
+ ue(o) ? Al(o, t, n, a, r.display) : Ol(e, t, n, a, r.display, Bo(e, i));
+}, Ml = (e, t, n, r, i) => {
let a = w(e.items, t);
if (!a || a.item.locked) return;
let { item: o, offset: s } = a;
- S(o, s.x, s.y), kl(o, n, jl(n, r, s), e, i), S(o, -s.x, -s.y);
-}, jl = (e, t, n) => e === "x" ? t + n.x : e === "y" ? t + n.y : t, Ml = (e) => e.items.forEach((t) => ht(t, e)), Nl = (e, t, n) => {
- t === "padding" && (e.display.padding = _(n, 0, Math.floor((Math.min(e.display.width, e.display.height) - 1) / 2))), t === "snapSize" && (e.display.snapSize = _(n, 1, 256)), Ml(e);
-}, Pl = (e, t) => {
+ S(o, s.x, s.y), jl(o, n, Nl(n, r, s), e, i), S(o, -s.x, -s.y);
+}, Nl = (e, t, n) => e === "x" ? t + n.x : e === "y" ? t + n.y : t, Pl = (e) => e.items.forEach((t) => ht(t, e)), Fl = (e, t, n) => {
+ t === "padding" && (e.display.padding = _(n, 0, Math.floor((Math.min(e.display.width, e.display.height) - 1) / 2))), t === "snapSize" && (e.display.snapSize = _(n, 1, 256)), Pl(e);
+}, Il = (e, t) => {
xo[e.display.palette].includes(t) && (e.display.background = t);
-}, Fl = (e, t, n) => {
+}, Ll = (e, t, n) => {
let r = T(e.items, t);
r && (r[n] = !r[n]);
-}, Il = (e, t) => {
+}, Rl = (e, t) => {
Pt(e, t);
-}, Ll = (e, t, n, r) => {
+}, zl = (e, t, n, r) => {
if (r === "inside") {
Nt(e, t, n || void 0, S);
return;
}
Rt(e, t, n, r, S);
-}, Rl = (e, t, n) => {
+}, Bl = (e, t, n) => {
let r = T(e.items, t), i = n.trim();
r && i && (r.name = i.slice(0, 100));
-}, zl = (e, t, n) => {
+}, Vl = (e, t, n) => {
let r = T(e.items, t);
r?.kind === "widget" && (r.widget.options = {
...r.widget.options,
...n
});
-}, Bl = (e, t, n, r) => {
+}, Hl = (e, t, n, r) => {
let i = T(e.items, t);
i?.kind === "widget" && (i.widget.sources = {
...i.widget.sources,
[n]: r
});
-}, Vl = (e, t, n, r, i) => {
+}, Ul = (e, t, n, r, i) => {
let a = w(e.items, t);
if (a?.item.kind !== "primitive" || a.item.locked) return;
let o = a.item, s = ["x", "y"].some((e) => o.expressions?.[e]);
@@ -7529,7 +7661,7 @@ var xl = {
...o.primitive,
[n]: r
};
-}, Hl = (e, t, n) => {
+}, Wl = (e, t, n) => {
let r = T(e.items, t);
if (!(r?.kind !== "primitive" || r.locked) && (r.primitive = {
...r.primitive,
@@ -7538,7 +7670,7 @@ var xl = {
for (let e of Object.keys(n)) delete r.expressions[e];
Object.keys(r.expressions).length === 0 && delete r.expressions;
}
-}, Ul = (e, t, n, r) => {
+}, Gl = (e, t, n, r) => {
let i = T(e.items, t);
if (i?.kind !== "primitive") return;
let a = ts(n, Bo(i, r));
@@ -7546,7 +7678,7 @@ var xl = {
...i.primitive,
...a
};
-}, Wl = (e, t, n, r) => {
+}, Kl = (e, t, n, r) => {
let i = pt(r), a = Math.min(e.layout.defaultSize?.width ?? 240, i.width), o = Math.min(e.layout.defaultSize?.height ?? 144, i.height);
return {
id: g(),
@@ -7568,7 +7700,7 @@ var xl = {
},
layout: { padding: 0 }
};
-}, Gl = (e, t, n, r, i) => {
+}, ql = (e, t, n, r, i) => {
let { width: a, height: o } = i.display, s = at(e.find((e) => e.type === t.trim()), {
x: Math.round(a / 2),
y: Math.round(o / 2),
@@ -7585,21 +7717,21 @@ var xl = {
primitive: s
}, l = x(c);
return S(c, Math.round(n - (l.x + l.width / 2)), Math.round(r - (l.y + l.height / 2))), ht(c, i), c;
-}, Kl = (e, t) => {
+}, Jl = (e, t) => {
let n = pt(e), r = E(e.items).length * Math.max(e.display.snapSize, 5) * 3 % Math.max(1, Math.min(n.width, n.height) / 3);
return {
x: mt(n.x + Math.min(24 + r, Math.max(0, n.width - 1)), e, t),
y: mt(n.y + Math.min(24 + r, Math.max(0, n.height - 1)), e, t)
};
-}, ql = (e, t) => {
+}, Yl = (e, t) => {
if (t === "visible") return !e.hidden;
if (e.kind === "primitive") return Object.entries(e.primitive).find(([e]) => e === t)?.[1];
-}, Jl = (e, t, n, r) => {
+}, Xl = (e, t, n, r) => {
let i = T(e.items, t);
if (!i || i.locked) return;
let a = { ...i.expressions };
- r === null ? delete a[n] : a[n] = r ?? kn(ql(i, n)), Object.keys(a).length === 0 ? delete i.expressions : i.expressions = a;
-}, Yl = {
+ r === null ? delete a[n] : a[n] = r ?? kn(Yl(i, n)), Object.keys(a).length === 0 ? delete i.expressions : i.expressions = a;
+}, Zl = {
align: {
selection: 2,
parent: 1
@@ -7608,7 +7740,7 @@ var xl = {
selection: 3,
parent: 2
}
-}, Xl = (e, t) => t === 1 ? "parent" : e, Zl = {
+}, Ql = (e, t) => t === 1 ? "parent" : e, $l = {
left: {
axis: "x",
fraction: 0
@@ -7633,11 +7765,11 @@ var xl = {
axis: "y",
fraction: 1
}
-}, Ql = (e, t) => t === "x" ? e.x : e.y, $l = (e, t) => t === "x" ? e.width : e.height, eu = (e, t) => Ql(e, t) + $l(e, t), tu = (e, t, n) => ({
+}, eu = (e, t) => t === "x" ? e.x : e.y, tu = (e, t) => t === "x" ? e.width : e.height, nu = (e, t) => eu(e, t) + tu(e, t), ru = (e, t, n) => ({
id: e,
dx: t === "x" ? n : 0,
dy: t === "y" ? n : 0
-}), nu = (e, t) => {
+}), iu = (e, t) => {
let { parent: n, offset: r } = t;
return n ? {
x: r.x,
@@ -7645,48 +7777,48 @@ var xl = {
width: n.width,
height: n.height
} : pt(e);
-}, ru = (e, t, n) => {
+}, au = (e, t, n) => {
let r = new Set(t);
return [...r].flatMap((t) => {
let i = w(e.items, t);
- return !i || i.item.locked || !n.movable(i.item) || iu(e, t, r) ? [] : [{
+ return !i || i.item.locked || !n.movable(i.item) || ou(e, t, r) ? [] : [{
id: t,
box: At(x(i.item, n.measure(i.item)), i.offset),
- area: nu(e, i)
+ area: iu(e, i)
}];
});
-}, iu = (e, t, n) => {
+}, ou = (e, t, n) => {
let r = w(e.items, t)?.parent;
for (; r;) {
if (n.has(r.id)) return !0;
r = w(e.items, r.id)?.parent;
}
return !1;
-}, au = (e) => e.filter((e) => e.dx !== 0 || e.dy !== 0), ou = (e, t, n, r) => {
- let i = ru(e, t, r), a = Xl(r.target, i.length);
- if (i.length < Yl.align[a]) return [];
- let { axis: o, fraction: s } = Zl[n], c = Qt(i.map((e) => e.box));
- return au(i.map((e) => {
- let t = a === "parent" ? e.area : c, n = $l(t, o) - $l(e.box, o), r = Ql(t, o) + n * s;
- return tu(e.id, o, Math.round(r - Ql(e.box, o)));
+}, su = (e) => e.filter((e) => e.dx !== 0 || e.dy !== 0), cu = (e, t, n, r) => {
+ let i = au(e, t, r), a = Ql(r.target, i.length);
+ if (i.length < Zl.align[a]) return [];
+ let { axis: o, fraction: s } = $l[n], c = Qt(i.map((e) => e.box));
+ return su(i.map((e) => {
+ let t = a === "parent" ? e.area : c, n = tu(t, o) - tu(e.box, o), r = eu(t, o) + n * s;
+ return ru(e.id, o, Math.round(r - eu(e.box, o)));
}));
-}, su = (e, t) => {
+}, lu = (e, t) => {
let [n, ...r] = t.map((e) => e.area);
return r.every((e) => e.x === n.x && e.y === n.y && e.width === n.width && e.height === n.height) ? n : pt(e);
-}, cu = (e, t, n, r) => {
- let i = ru(e, t, r);
- if (i.length < Yl.distribute[r.target]) return [];
- let a = n === "horizontal" ? "x" : "y", o = [...i].sort((e, t) => Ql(e.box, a) - Ql(t.box, a) || eu(e.box, a) - eu(t.box, a)), s = r.target === "parent" ? su(e, i) : Qt(i.map((e) => e.box)), c = o.reduce((e, t) => e + $l(t.box, a), 0), l = ($l(s, a) - c) / (o.length - 1), u = 0;
- return au(o.map((e, t) => {
- let n = Math.round(Ql(s, a) + u + l * t);
- return u += $l(e.box, a), tu(e.id, a, n - Ql(e.box, a));
+}, uu = (e, t, n, r) => {
+ let i = au(e, t, r);
+ if (i.length < Zl.distribute[r.target]) return [];
+ let a = n === "horizontal" ? "x" : "y", o = [...i].sort((e, t) => eu(e.box, a) - eu(t.box, a) || nu(e.box, a) - nu(t.box, a)), s = r.target === "parent" ? lu(e, i) : Qt(i.map((e) => e.box)), c = o.reduce((e, t) => e + tu(t.box, a), 0), l = (tu(s, a) - c) / (o.length - 1), u = 0;
+ return su(o.map((e, t) => {
+ let n = Math.round(eu(s, a) + u + l * t);
+ return u += tu(e.box, a), ru(e.id, a, n - eu(e.box, a));
}));
-}, lu = (e, t) => {
+}, du = (e, t) => {
for (let n of t) {
let t = w(e.items, n.id);
t && S(t.item, n.dx, n.dy);
}
-}, uu = (e) => ({
+}, fu = (e) => ({
canUndo: !1,
canRedo: !1,
item: e,
@@ -7694,13 +7826,13 @@ var xl = {
canGroup: e.kind === "container" && !e.grouped,
canUngroup: e.kind === "container" && e.grouped,
canEnter: e.kind === "container" && e.grouped
-}), du = (e) => e.targets ?? (e.item ? [e.item] : []), fu = (e) => du(e).length > 0, pu = (e) => du(e).filter((e) => !e.locked).length, mu = (e) => {
- let t = pu(e), n = Xl(e.alignTo ?? "selection", t);
- return t >= Yl.align[n];
-}, hu = (e) => pu(e) >= Yl.distribute[e.alignTo ?? "selection"], H = (e, t) => {
- let n = du(e).map((e) => e.id);
+}), pu = (e) => e.targets ?? (e.item ? [e.item] : []), mu = (e) => pu(e).length > 0, hu = (e) => pu(e).filter((e) => !e.locked).length, gu = (e) => {
+ let t = hu(e), n = Ql(e.alignTo ?? "selection", t);
+ return t >= Zl.align[n];
+}, _u = (e) => hu(e) >= Zl.distribute[e.alignTo ?? "selection"], H = (e, t) => {
+ let n = pu(e).map((e) => e.id);
n.length > 0 && t(n);
-}, gu = {
+}, vu = {
left: {
key: "ArrowLeft",
icon: "mdi:arrow-left"
@@ -7717,45 +7849,45 @@ var xl = {
key: "ArrowDown",
icon: "mdi:arrow-down"
}
-}, _u = () => Object.keys(gu).flatMap((e) => [!1, !0].map((n) => ({
+}, yu = () => Object.keys(vu).flatMap((e) => [!1, !0].map((n) => ({
id: n ? `nudge-${e}-snap` : `nudge-${e}`,
group: "arrange",
label: () => t.commands.nudge[e],
- icon: () => gu[e].icon,
+ icon: () => vu[e].icon,
shortcuts: [{
- key: gu[e].key,
+ key: vu[e].key,
shift: n
}],
- isEnabled: fu,
+ isEnabled: mu,
isRelevant: () => !1,
run: (t, r) => H(t, (t) => r.nudge(t, e, n))
-}))), vu = {
+}))), bu = {
left: "mdi:align-horizontal-left",
center: "mdi:align-horizontal-center",
right: "mdi:align-horizontal-right",
top: "mdi:align-vertical-top",
middle: "mdi:align-vertical-center",
bottom: "mdi:align-vertical-bottom"
-}, yu = {
+}, xu = {
horizontal: "mdi:arrow-expand-horizontal",
vertical: "mdi:arrow-expand-vertical"
-}, bu = () => Object.keys(vu).map((e) => ({
+}, Su = () => Object.keys(bu).map((e) => ({
id: `align-${e}`,
group: "arrange",
label: () => t.commands.align[e],
- icon: () => vu[e],
+ icon: () => bu[e],
shortcuts: [],
- isEnabled: mu,
+ isEnabled: gu,
run: (t, n) => H(t, (t) => n.align(t, e))
-})), xu = () => Object.keys(yu).map((e) => ({
+})), Cu = () => Object.keys(xu).map((e) => ({
id: `distribute-${e}`,
group: "arrange",
label: () => t.commands.distribute[e],
- icon: () => yu[e],
+ icon: () => xu[e],
shortcuts: [],
- isEnabled: hu,
+ isEnabled: _u,
run: (t, n) => H(t, (t) => n.distribute(t, e))
-})), Su = [
+})), wu = [
{
id: "undo",
group: "history",
@@ -7790,7 +7922,7 @@ var xl = {
label: () => t.commands.delete,
icon: () => "mdi:delete-outline",
shortcuts: [{ key: "Delete" }, { key: "Backspace" }],
- isEnabled: fu,
+ isEnabled: mu,
run: (e, t) => H(e, (e) => t.requestDelete(e))
},
{
@@ -7799,7 +7931,7 @@ var xl = {
label: ({ item: e }) => e?.hidden ? t.commands.show : t.commands.hide,
icon: ({ item: e }) => e?.hidden ? "mdi:eye-off-outline" : "mdi:eye-outline",
shortcuts: [],
- isEnabled: fu,
+ isEnabled: mu,
run: (e, t) => H(e, (e) => t.toggleFlag(e, "hidden"))
},
{
@@ -7808,7 +7940,7 @@ var xl = {
label: ({ item: e }) => e?.locked ? t.commands.unlock : t.commands.lock,
icon: ({ item: e }) => e?.locked ? "mdi:lock" : "mdi:lock-open-variant-outline",
shortcuts: [],
- isEnabled: fu,
+ isEnabled: mu,
run: (e, t) => H(e, (e) => t.toggleFlag(e, "locked"))
},
{
@@ -7820,7 +7952,7 @@ var xl = {
key: "c",
mod: !0
}],
- isEnabled: fu,
+ isEnabled: mu,
run: (e, t) => H(e, (e) => t.copy(e))
},
{
@@ -7832,7 +7964,7 @@ var xl = {
key: "x",
mod: !0
}],
- isEnabled: fu,
+ isEnabled: mu,
run: (e, t) => H(e, (e) => t.cut(e))
},
{
@@ -7865,7 +7997,7 @@ var xl = {
key: "d",
mod: !0
}],
- isEnabled: fu,
+ isEnabled: mu,
run: (e, t) => H(e, (e) => t.duplicate(e))
},
{
@@ -7885,7 +8017,7 @@ var xl = {
label: () => t.commands.bringToFront,
icon: () => "mdi:arrange-bring-to-front",
shortcuts: [],
- isEnabled: fu,
+ isEnabled: mu,
run: (e, t) => H(e, (e) => t.arrange(e, "front"))
},
{
@@ -7894,7 +8026,7 @@ var xl = {
label: () => t.commands.sendToBack,
icon: () => "mdi:arrange-send-to-back",
shortcuts: [],
- isEnabled: fu,
+ isEnabled: mu,
run: (e, t) => H(e, (e) => t.arrange(e, "back"))
},
{
@@ -7903,7 +8035,7 @@ var xl = {
label: () => t.commands.moveUp,
icon: () => "mdi:arrow-up",
shortcuts: [],
- isEnabled: fu,
+ isEnabled: mu,
run: (e, t) => H(e, (e) => t.arrange(e, "up"))
},
{
@@ -7912,12 +8044,12 @@ var xl = {
label: () => t.commands.moveDown,
icon: () => "mdi:arrow-down",
shortcuts: [],
- isEnabled: fu,
+ isEnabled: mu,
run: (e, t) => H(e, (e) => t.arrange(e, "down"))
},
- ...bu(),
- ...xu(),
- ..._u(),
+ ...Su(),
+ ...Cu(),
+ ...yu(),
{
id: "group",
group: "group",
@@ -7975,7 +8107,7 @@ var xl = {
label: () => t.commands.deselect,
icon: () => "mdi:selection-off",
shortcuts: [{ key: "Escape" }],
- isEnabled: fu,
+ isEnabled: mu,
isRelevant: () => !1,
run: (e, t) => t.deselect()
},
@@ -8054,17 +8186,31 @@ var xl = {
isEnabled: () => !0,
run: (e, t) => t.showShortcuts()
}
-], Cu = (e) => Su.some((t) => t.id === e), wu = (e) => {
- let t = Su.find((t) => t.id === e);
+], Tu = /* @__PURE__ */ new Set([
+ "undo",
+ "redo",
+ "delete-item",
+ "deselect",
+ "save",
+ "toggle-code",
+ "zoom-in",
+ "zoom-out",
+ "zoom-reset",
+ "show-shortcuts"
+]), Eu = wu.map((e) => Tu.has(e.id) ? e : {
+ ...e,
+ isEnabled: (t) => !t.basic && e.isEnabled(t)
+}), Du = (e) => Eu.some((t) => t.id === e), Ou = (e) => {
+ let t = Eu.find((t) => t.id === e);
if (!t) throw Error(`Unknown command ${e}`);
return t;
-}, Tu = (e, t) => {
+}, ku = (e, t) => {
let n = t.ctrlKey || t.metaKey;
- return t.key.toLowerCase() === e.key.toLowerCase() && n === !!e.mod && (t.shiftKey === !!e.shift || Eu(e)) && !t.altKey;
-}, Eu = (e) => e.key === "?" || e.key === "+", Du = (e, t) => {
- let n = Su.filter((t) => t.shortcuts.some((t) => Tu(t, e)));
+ return t.key.toLowerCase() === e.key.toLowerCase() && n === !!e.mod && (t.shiftKey === !!e.shift || Au(e)) && !t.altKey;
+}, Au = (e) => e.key === "?" || e.key === "+", ju = (e, t) => {
+ let n = Eu.filter((t) => t.shortcuts.some((t) => ku(t, e)));
return t ? n.find((e) => e.isEnabled(t)) : n[0];
-}, Ou = {
+}, Mu = {
Delete: "Del",
Backspace: "⌫",
Escape: "Esc",
@@ -8072,15 +8218,15 @@ var xl = {
ArrowRight: "→",
ArrowUp: "↑",
ArrowDown: "↓"
-}, ku = (e, t) => {
- let n = Ou[e.key] ?? e.key.toUpperCase();
+}, Nu = (e, t) => {
+ let n = Mu[e.key] ?? e.key.toUpperCase();
return t ? `${e.mod ? "⌘" : ""}${e.shift ? "⇧" : ""}${n}` : [
e.mod ? "Ctrl" : "",
e.shift ? "Shift" : "",
n
].filter(Boolean).join("+");
-}, Au = (e, t, n = !1) => {
- let r = e.label(t), [i] = e.shortcuts, a = i ? ku(i, n) : "";
+}, Pu = (e, t, n = !1) => {
+ let r = e.label(t), [i] = e.shortcuts, a = i ? Nu(i, n) : "";
return {
label: r,
icon: e.icon(t),
@@ -8088,7 +8234,7 @@ var xl = {
shortcut: a,
enabled: e.isEnabled(t)
};
-}, ju = [
+}, Fu = [
[
"copy",
"cut",
@@ -8103,7 +8249,7 @@ var xl = {
"enter-group",
"exit-group"
]
-], Mu = [
+], Iu = [
[
"copy",
"cut",
@@ -8119,8 +8265,8 @@ var xl = {
"enter-group",
"exit-group"
]
-], Nu = [["paste", "paste-here"]], Pu = (e, t, n) => e.flatMap((e, r) => e.map((e) => wu(e)).filter((e) => e.isRelevant?.(t) ?? !0).map((e, i) => {
- let a = Au(e, t, n);
+], Lu = [["paste", "paste-here"]], Ru = (e, t, n) => e.flatMap((e, r) => e.map((e) => Ou(e)).filter((e) => e.isRelevant?.(t) ?? !0).map((e, i) => {
+ let a = Pu(e, t, n);
return {
id: e.id,
label: a.label,
@@ -8130,29 +8276,30 @@ var xl = {
danger: e.id === "delete-item",
separatorBefore: i === 0 && r > 0
};
-})), Fu = [
+})), zu = [
0,
90,
180,
270
-], Iu = (e) => Fu.some((t) => t === e), Lu = (e) => e === 90 || e === 270, Ru = (e, t) => Lu(t) ? {
+], Bu = (e, t) => zu[(zu.indexOf(e) + t + zu.length) % zu.length], Vu = (e) => zu.some((t) => t === e), Hu = (e) => e === 90 || e === 270, Uu = (e, t) => Hu(t) ? {
width: e.height,
height: e.width
} : {
width: e.width,
height: e.height
-}, zu = (e, t, n) => Lu(t) === Lu(n) ? e : {
+}, Wu = (e, t, n) => Hu(t) === Hu(n) ? e : {
width: e.height,
height: e.width
-}, Bu = (e, t) => {
+}, Gu = (e, t) => {
let n = Number(e);
- return Iu(n) ? n : t;
-}, Vu = (e, t = "custom") => {
+ return Vu(n) ? n : t;
+}, Ku = (e, t = "custom") => {
let n = Oo(t);
return {
id: "",
schemaVersion: 1,
name: "",
+ mode: "advanced",
status: "draft",
language: e || "en",
display: {
@@ -8164,13 +8311,14 @@ var xl = {
padding: 0,
snapSize: 5,
rotation: 0,
- deviceId: null
+ deviceId: null,
+ mediaAsDesigned: !1
},
items: [],
createdAt: "",
updatedAt: ""
};
-}, Hu = (e) => ({
+}, qu = (e) => ({
name: e.name,
width: e.display.width,
height: e.display.height,
@@ -8178,51 +8326,52 @@ var xl = {
background: e.display.background,
rotation: String(e.display.rotation),
padding: e.display.padding,
- snapSize: e.display.snapSize
-}), Uu = (e, t) => {
+ snapSize: e.display.snapSize,
+ mediaAsDesigned: e.display.mediaAsDesigned
+}), Ju = (e, t) => {
let n = structuredClone(e);
return Object.assign(n.display, t), xo[t.palette].includes(n.display.background) || (n.display.background = "white"), n;
-}, Wu = (e, t) => Uu(e, {
+}, Yu = (e, t) => Ju(e, {
profileId: "custom",
deviceId: t.id,
- ...Ru(t, e.display.rotation),
+ ...Uu(t, e.display.rotation),
palette: t.palette
-}), Gu = (e, t, n = t.defaultPalette) => Uu(e, {
+}), Xu = (e, t, n = t.defaultPalette) => Ju(e, {
profileId: t.id,
deviceId: null,
- ...Ru(t, e.display.rotation),
+ ...Uu(t, e.display.rotation),
palette: t.palettes.includes(n) ? n : t.defaultPalette
-}), Ku = (e, t) => {
+}), Zu = (e, t) => {
let n = {
- ...Hu(e),
+ ...qu(e),
...t
}, r = structuredClone(e);
r.name = String(n.name);
let i = {
width: Math.round(Number(n.width) || 0),
height: Math.round(Number(n.height) || 0)
- }, a = i.width !== e.display.width || i.height !== e.display.height, o = Bu(n.rotation, e.display.rotation), s = a ? i : zu(i, e.display.rotation, o);
- a && (r.display.profileId = "custom", r.display.deviceId = null), r.display.width = s.width, r.display.height = s.height, r.display.rotation = o, r.display.palette = n.palette in bo ? n.palette : "bw", r.display.padding = Math.round(Number(n.padding) || 0), r.display.snapSize = Math.round(Number(n.snapSize) || 0);
+ }, a = i.width !== e.display.width || i.height !== e.display.height, o = Gu(n.rotation, e.display.rotation), s = a ? i : Wu(i, e.display.rotation, o);
+ a && (r.display.profileId = "custom", r.display.deviceId = null), r.display.width = s.width, r.display.height = s.height, r.display.rotation = o, r.display.palette = n.palette in bo ? n.palette : "bw", r.display.padding = Math.round(Number(n.padding) || 0), r.display.snapSize = Math.round(Number(n.snapSize) || 0), r.display.mediaAsDesigned = n.mediaAsDesigned === !0;
let c = xo[r.display.palette];
return r.display.background = c.includes(n.background) ? n.background : "white", r;
-}, qu = (e) => {
+}, Qu = (e) => {
let { width: t, height: n, padding: r, snapSize: i } = e.display;
return !!e.name.trim() && t >= 64 && t <= 4096 && n >= 64 && n <= 4096 && r >= 0 && r * 2 < Math.min(t, n) && i >= 1 && i <= 256;
-}, Ju = (e, t, n, r) => {
+}, $u = (e, t, n, r) => {
let i = t.trim().toLocaleLowerCase(r);
return e.filter((e) => !i || e.name.toLocaleLowerCase(r).includes(i)).sort((e, t) => n === "name" ? e.name.localeCompare(t.name, r) : t.updatedAt.localeCompare(e.updatedAt) || e.name.localeCompare(t.name, r));
-}, Yu = (e, t, n) => {
+}, ed = (e, t, n) => {
let r = new Set(t.map((e) => e.name.toLocaleLowerCase(n))), i = `${e.name} copy`, a = i, o = 2;
for (; r.has(a.toLocaleLowerCase(n));) a = `${i} ${o++}`;
return a;
-}, Xu = Object.keys(bo).filter(ko), Zu = Do.filter((e) => e.id !== "custom"), Qu = {
+}, td = Object.keys(bo).filter(ko), nd = Do.filter((e) => e.id !== "custom"), rd = {
size: !0,
- palettes: Xu,
+ palettes: td,
backgrounds: []
-}, $u = (e) => ({
- ...Qu,
+}, id = (e) => ({
+ ...rd,
backgrounds: xo[e.display.palette]
-}), ed = (e, t) => ({
+}), ad = (e, t) => ({
name: e,
label: t,
required: !0,
@@ -8232,12 +8381,12 @@ var xl = {
max: 4096,
unit_of_measurement: "px"
} }
-}), td = () => [{
+}), od = () => [{
name: "dimensions",
type: "grid",
flatten: !0,
- schema: [ed("width", t.fields.width), ed("height", t.fields.height)]
-}], nd = (e) => [{
+ schema: [ad("width", t.fields.width), ad("height", t.fields.height)]
+}], sd = (e) => [{
name: "palette",
label: t.fields.palette,
required: !0,
@@ -8248,7 +8397,7 @@ var xl = {
label: bo[e]
}))
} }
-}], rd = (e) => [{
+}], cd = (e) => [{
name: "background",
label: t.fields.background,
required: !0,
@@ -8259,18 +8408,22 @@ var xl = {
label: `${e[0].toUpperCase()}${e.slice(1)}`
}))
} }
-}], id = () => [{
+}], ld = () => [{
name: "rotation",
label: t.fields.rotation,
required: !0,
selector: { select: {
mode: "dropdown",
- options: Fu.map((e) => ({
+ options: zu.map((e) => ({
value: String(e),
label: t.rotations[e]
}))
} }
-}], ad = () => [{
+}], ud = "mediaAsDesigned", dd = () => [{
+ name: ud,
+ label: t.fields.mediaAsDesigned,
+ selector: { boolean: {} }
+}], fd = (e) => (n) => "name" in n && n.name === "mediaAsDesigned" ? t.fields.mediaAsDesignedHelp(e) : "", pd = () => [{
name: "advanced",
type: "expandable",
flatten: !0,
@@ -8295,97 +8448,225 @@ var xl = {
unit_of_measurement: "px"
} }
}]
-}], od = (e = Qu) => [
- {
- name: "name",
- label: t.fields.name,
- required: !0,
- selector: { text: {} }
- },
- ...e.size ? td() : [],
- ...e.palettes.length > 1 ? nd(e.palettes) : [],
- ...e.backgrounds.length > 0 ? rd(e.backgrounds) : [],
- ...id(),
- ...ad()
-], sd = (e) => "label" in e ? e.label : e.title ?? "", cd = {
+}], md = () => [{
+ name: "name",
+ label: t.fields.name,
+ required: !0,
+ selector: { text: {} }
+}], hd = (e = rd) => [
+ ...md(),
+ ...e.size ? od() : [],
+ ...e.palettes.length > 1 ? sd(e.palettes) : [],
+ ...e.backgrounds.length > 0 ? cd(e.backgrounds) : [],
+ ...ld(),
+ ...dd(),
+ ...pd()
+], gd = (e) => "label" in e ? e.label : e.title ?? "", _d = {
bwr: "#d32f2f",
bwry: "#d32f2f",
bwy: "#d6a800",
spectra6: "#246bfd",
seven_color: "#ff8000"
-}, ld = (e) => cd[e] ?? "#202124", ud = (e, t) => {
+}, vd = (e) => _d[e] ?? "#202124", yd = (e, t) => {
let n = new Date(e.updatedAt);
return Number.isNaN(n.getTime()) ? "" : new Intl.DateTimeFormat(t, { dateStyle: "medium" }).format(n);
-}, dd = (e) => e.target.value, fd = (e) => e.composedPath().some((e) => e instanceof HTMLElement && (e.matches("input, textarea, select") || e.isContentEditable)), pd = () => /Mac|iPhone|iPad/.test(navigator.platform), md = (e) => e.shiftKey || e.ctrlKey || e.metaKey, hd = (e, t, n = "application/json") => {
+}, bd = (e) => e.target.value, xd = (e) => e.composedPath().some((e) => e instanceof HTMLElement && (e.matches("input, textarea, select") || e.isContentEditable)), Sd = () => /Mac|iPhone|iPad/.test(navigator.platform), Cd = (e) => e.shiftKey || e.ctrlKey || e.metaKey, wd = (e, t, n = "application/json") => {
let r = URL.createObjectURL(new Blob([t], { type: n })), i = document.createElement("a");
i.href = r, i.download = e, i.click(), URL.revokeObjectURL(r);
-}, gd = (e) => new Promise((t) => {
+}, Td = (e) => new Promise((t) => {
let n = document.createElement("input");
n.type = "file", n.accept = e, n.addEventListener("change", () => t(n.files?.[0])), n.addEventListener("cancel", () => t(void 0)), n.click();
-}), _d = "dashboard", vd = (e) => `${e.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/ł/g, "l").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || _d}.json`, yd = (e) => Object.fromEntries(e.map((e) => [e.source, e.suggestion])), bd = (e) => {
+}), Ed = "dashboard", Dd = (e) => `${e.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/ł/g, "l").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || Ed}.json`, Od = (e) => Object.fromEntries(e.map((e) => [e.source, e.suggestion])), kd = (e) => {
try {
return JSON.parse(e);
} catch {
return;
}
-}, xd = (e) => typeof e == "string" && e !== "" ? e : void 0, U = (e, t) => e instanceof Error || typeof e == "object" && e && "message" in e ? xd(e.message) ?? t : xd(e) ?? t, Sd = (e) => {
- if (typeof e == "object" && e && "code" in e) return xd(e.code);
-}, Cd = (e) => e.callWS({
+}, Ad = (e) => typeof e == "string" && e !== "" ? e : void 0, U = (e, t) => e instanceof Error || typeof e == "object" && e && "message" in e ? Ad(e.message) ?? t : Ad(e) ?? t, jd = (e) => {
+ if (typeof e == "object" && e && "code" in e) return Ad(e.code);
+}, Md = (e) => e.callWS({
type: "opendisplay_studio/bootstrap",
language: e.language
-}), wd = (e) => e.callWS({
+}), Nd = (e, t, n) => e.callWS({
+ type: "opendisplay_studio/layout_frames",
+ width: t.width,
+ height: t.height,
+ padding: t.padding,
+ layout: n
+}), Pd = (e) => e.callWS({
type: "opendisplay_studio/reload_widgets",
language: e.language
-}), Td = async (e, t) => (await e.callWS({
+}), Fd = async (e, t) => (await e.callWS({
type: "opendisplay_studio/create_dashboard",
dashboard: t
-})).dashboard, Ed = async (e, t) => (await e.callWS({
+})).dashboard, Id = async (e, t) => (await e.callWS({
type: "opendisplay_studio/update_dashboard",
dashboard_id: t.id,
dashboard: t
-})).dashboard, Dd = async (e, t) => {
+})).dashboard, Ld = async (e, t) => {
await e.callWS({
type: "opendisplay_studio/delete_dashboard",
dashboard_id: t
});
-}, Od = (e, t) => e.callWS({
+}, Rd = (e, t) => e.callWS({
type: "opendisplay_studio/compose_preview",
dashboard: structuredClone(t)
-}), kd = async (e) => (await e.callWS({ type: "opendisplay_studio/list_devices" })).devices, Ad = async (e, t) => {
+}), zd = async (e) => (await e.callWS({ type: "opendisplay_studio/list_devices" })).devices, Bd = async (e, t) => {
await e.callWS({
type: "opendisplay_studio/send_to_device",
dashboard: structuredClone(t)
});
-}, jd = async (e, t) => (await e.callWS({
+}, Vd = async (e, t) => (await e.callWS({
type: "opendisplay_studio/export_dashboard",
dashboard: structuredClone(t)
-})).file, Md = (e, t, n, r = {}) => e.callWS({
+})).file, Hd = (e, t, n, r = {}) => e.callWS({
type: "opendisplay_studio/prepare_import",
file: t,
display: n,
colorMap: r
-}), Nd, Pd = (e) => (Nd ??= e.callWS({ type: "opendisplay_studio/list_icons" }).then((e) => ({
+}), Ud, Wd = (e) => (Ud ??= e.callWS({ type: "opendisplay_studio/list_icons" }).then((e) => ({
names: e.icons,
glyphs: Object.fromEntries(Object.entries(e.glyphs).map(([e, t]) => [e, String.fromCodePoint(t)]))
-})), Nd), Fd = async (e) => (await e.callWS({ type: "opendisplay_studio/list_fonts" })).fonts, Id = async (e, t, n) => (await e.callWS({
+})), Ud), Gd = async (e) => (await e.callWS({ type: "opendisplay_studio/list_fonts" })).fonts, Kd = async (e, t, n) => (await e.callWS({
type: "opendisplay_studio/install_font",
filename: t,
data: n
-})).fonts, Ld = async (e, t, n) => (await e.callWS({
+})).fonts, qd = async (e, t, n) => (await e.callWS({
type: "opendisplay_studio/delete_font",
name: t,
force: n
-})).fonts, Rd, zd = (e) => (Rd ??= Fd(e).catch((e) => {
- throw Rd = void 0, e;
-}), Rd), Bd = (e) => {
- Rd = Promise.resolve(e);
-}, Vd = (e) => [e.family, e.style].filter((e) => e !== "" && e !== "Regular").join(" "), Hd = (e, t) => e.find((e) => e.name === t), W = (e, t, ...n) => {
+})).fonts, Jd, Yd = (e) => (Jd ??= Gd(e).catch((e) => {
+ throw Jd = void 0, e;
+}), Jd), Xd = (e) => {
+ Jd = Promise.resolve(e);
+}, Zd = (e) => [e.family, e.style].filter((e) => e !== "" && e !== "Regular").join(" "), Qd = (e, t) => e.find((e) => e.name === t), W = (e, t, ...n) => {
e.dispatchEvent(new CustomEvent(t, {
detail: n[0],
bubbles: !0,
composed: !0
}));
-}, Ud = 100, Wd = class {
+}, $d = 26, ef = (e) => {
+ let t = String.fromCharCode(65 + e % $d);
+ return e < $d ? t : ef(Math.floor(e / $d) - 1) + t;
+}, tf = "region-", nf = (e) => {
+ let t = 1;
+ for (; e.has(`${tf}${t}`);) t += 1;
+ let n = `${tf}${t}`;
+ return e.add(n), n;
+}, rf = (e) => e.rowSpan > 1 || e.columnSpan > 1, af = (e, t) => e.row - t.row || e.column - t.column, of = (e) => {
+ let t = [];
+ for (let n = e.row; n < e.row + e.rowSpan; n += 1) for (let r = e.column; r < e.column + e.columnSpan; r += 1) t.push({
+ row: n,
+ column: r
+ });
+ return t;
+}, sf = (e, t) => e.regions.find((e) => of(e).some((e) => e.row === t.row && e.column === t.column)), cf = (e, t) => ({
+ row: Math.min(e.row, t.row),
+ column: Math.min(e.column, t.column),
+ rowSpan: Math.abs(e.row - t.row) + 1,
+ columnSpan: Math.abs(e.column - t.column) + 1
+}), lf = (e) => {
+ let t = [...e.regions], n = new Set(t.map((e) => e.id));
+ for (let r = 0; r < e.rows; r += 1) for (let i = 0; i < e.columns; i += 1) sf({
+ ...e,
+ regions: t
+ }, {
+ row: r,
+ column: i
+ }) || t.push({
+ id: nf(n),
+ row: r,
+ column: i,
+ rowSpan: 1,
+ columnSpan: 1
+ });
+ return uf(t);
+}, uf = (e) => [...e.filter(rf), ...e.filter((e) => !rf(e)).sort(af)], df = (e, t, n) => ({
+ columns: e,
+ rows: t,
+ gap: n,
+ regions: lf({
+ columns: e,
+ rows: t,
+ gap: n,
+ regions: []
+ })
+}), ff = (e, t) => e.row >= t.row && e.column >= t.column && e.row + e.rowSpan <= t.row + t.rowSpan && e.column + e.columnSpan <= t.column + t.columnSpan, pf = (e, t) => e.row < t.row + t.rowSpan && e.row + e.rowSpan > t.row && e.column < t.column + t.columnSpan && e.column + e.columnSpan > t.column, mf = (e, t) => e.regions.filter((e) => pf(e, t)).sort(af), hf = (e, t) => {
+ let n = mf(e, t);
+ return n.length > 1 && n.every((e) => ff(e, t));
+}, gf = (e, t, n) => {
+ let r = mf(e, t), i = r.find((e) => n.has(e.id)) ?? r[0], a = r.filter((e) => e.id !== i.id), o = e.regions.filter((e) => !r.includes(e)).concat({
+ id: i.id,
+ ...t
+ });
+ return {
+ layout: {
+ ...e,
+ regions: uf(o)
+ },
+ removedRegionIds: a.map((e) => e.id)
+ };
+}, _f = (e, t) => {
+ let n = e.regions.find((e) => e.id === t);
+ if (!n) return e;
+ let r = e.regions.filter((e) => e !== n), i = {
+ id: n.id,
+ row: n.row,
+ column: n.column,
+ rowSpan: 1,
+ columnSpan: 1
+ }, a = lf({
+ ...e,
+ regions: [...r, i]
+ });
+ return {
+ ...e,
+ regions: a
+ };
+}, vf = (e, t, n) => {
+ let r = {
+ row: 0,
+ column: 0,
+ rowSpan: n,
+ columnSpan: t
+ }, i = e.regions.filter((e) => ff(e, r)), a = e.regions.filter((e) => !i.includes(e)), o = {
+ ...e,
+ columns: t,
+ rows: n,
+ regions: i
+ };
+ return {
+ layout: {
+ ...o,
+ regions: lf(o)
+ },
+ removedRegionIds: a.map((e) => e.id)
+ };
+}, yf = (e, t) => e.regions.find((e) => t.x >= e.x && t.x < e.x + e.width && t.y >= e.y && t.y < e.y + e.height)?.id, bf = (e, t, n) => {
+ let r = structuredClone(e);
+ return r.mode = t, t === "basic" ? r.layout = df(2, 2, n) : delete r.layout, r;
+}, xf = (e) => e.items.filter((e) => e.kind === "widget"), Sf = (e, t) => xf(e).find((e) => e.region === t), Cf = (e) => new Set(xf(e).flatMap((e) => e.region ?? [])), wf = (e, t, n) => {
+ let r = Cf(e), i = t.regions.filter((e) => !r.has(e.id));
+ return i.find((e) => e.id === n) ?? i[0];
+}, Tf = (e, t, n, r) => {
+ let i = Kl(t, 0, 0, e);
+ return i.region = n.id, i.frame = { ...r }, e.items.push(i), i;
+}, Ef = (e, t) => e.filter((e) => e.kind !== "widget" || !t.includes(e.region ?? "")), Df = (e, t) => {
+ e.layout = t.layout, e.items = Ef(e.items, t.removedRegionIds);
+}, Of = (e, t) => {
+ if (!e.layout) return;
+ let n = Cf(e);
+ Df(e, gf(e.layout, t, n));
+}, kf = (e, t) => {
+ e.layout &&= _f(e.layout, t);
+}, Af = (e, t) => {
+ e.layout && Df(e, vf(e.layout, t.columns, t.rows));
+}, jf = (e, t) => {
+ e.layout &&= {
+ ...e.layout,
+ gap: t
+ };
+}, Mf = 100, Nf = class {
constructor() {
this.undoStack = [], this.redoStack = [];
}
@@ -8396,7 +8677,7 @@ var xl = {
return this.redoStack.length;
}
record(e) {
- this.undoStack.push(structuredClone(e)), this.undoStack.length > Ud && this.undoStack.shift(), this.redoStack = [];
+ this.undoStack.push(structuredClone(e)), this.undoStack.length > Mf && this.undoStack.shift(), this.redoStack = [];
}
undo(e) {
let t = this.undoStack.pop();
@@ -8409,7 +8690,7 @@ var xl = {
clear() {
this.undoStack = [], this.redoStack = [];
}
-}, Gd = (e, t) => {
+}, Pf = (e, t) => {
let n = w(e.items, t);
if (!n) return;
let { parent: r } = n;
@@ -8419,8 +8700,8 @@ var xl = {
width: r.width,
height: r.height
} : pt(e);
-}, Kd = (e, t, n, r) => {
- let i = w(e.items, t), a = Gd(e, t);
+}, Ff = (e, t, n, r) => {
+ let i = w(e.items, t), a = Pf(e, t);
if (!i || !a || i.item.locked) return;
let o = x(i.item, r), s = ce(n, "lt"), c = a.x + (a.width - o.width) * s.x, l = a.y + (a.height - o.height) * s.y;
S(i.item, Math.round(c - o.x), Math.round(l - o.y));
@@ -8495,11 +8776,10 @@ var xl = {
ha-dialog {
--dialog-content-padding: 0;
}
-`, qd = P`
+`, If = P`
.dashboard-card,
.dashboard-add-card {
min-width: 0;
- min-height: 236px;
padding: 0;
border: 1px solid var(--studio-border);
border-radius: 13px;
@@ -8507,6 +8787,10 @@ var xl = {
background: var(--studio-surface);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
}
+ /* A card is as tall as its picture and its two lines; the add card matches it. */
+ .dashboard-add-card {
+ min-height: 200px;
+ }
.dashboard-card:hover,
.dashboard-card:focus-within,
.dashboard-add-card:hover,
@@ -8520,7 +8804,7 @@ var xl = {
outline: 0;
transform: translateY(-1px);
}
-`, Jd = P`
+`, Lf = P`
.box {
display: flex;
align-items: center;
@@ -8614,9 +8898,9 @@ var xl = {
background: var(--primary-color);
color: var(--text-primary-color, #fff);
}
-`, Yd = Sl(class extends Cl {
+`, Rf = wl(class extends Tl {
constructor(e) {
- if (super(e), e.type !== xl.ATTRIBUTE || e.name !== "class" || e.strings?.length > 2) throw Error("`classMap()` can only be used in the `class` attribute and must be the only part in the attribute.");
+ if (super(e), e.type !== Cl.ATTRIBUTE || e.name !== "class" || e.strings?.length > 2) throw Error("`classMap()` can only be used in the `class` attribute and must be the only part in the attribute.");
}
render(e) {
return " " + Object.keys(e).filter((t) => e[t]).join(" ") + " ";
@@ -8633,18 +8917,18 @@ var xl = {
let r = !!t[e];
r === this.st.has(e) || this.nt?.has(e) || (r ? (n.add(e), this.st.add(e)) : (n.remove(e), this.st.delete(e)));
}
- return $c;
+ return tl;
}
-}), Xd = 2, q = (e, t) => t === "x" ? e.x : e.y, J = (e, t) => t === "x" ? e.width : e.height, Zd = (e, t) => {
+}), zf = 2, q = (e, t) => t === "x" ? e.x : e.y, J = (e, t) => t === "x" ? e.width : e.height, Bf = (e, t) => {
let n = q(e, t), r = J(e, t);
return [
n,
n + r / 2,
n + r
];
-}, Qd = (e, t, n) => {
+}, Vf = (e, t, n) => {
let r = [], i = (t, i, a) => {
- for (let o of Zd(t, n)) Zd(e, n).forEach((e, t) => {
+ for (let o of Bf(t, n)) Bf(e, n).forEach((e, t) => {
let n = Math.abs(o - e);
n <= i && r.push({
offset: Math.round(o - e),
@@ -8655,9 +8939,9 @@ var xl = {
});
});
};
- return t.siblings.forEach((e) => i(e, 5, "sibling")), i(t.parent, 8, "parent"), r.push(...ef(e, t.siblings, n)), r.sort(nf)[0];
-}, $d = (e, t, n) => q(e, n) < q(t, n) + J(t, n) && q(t, n) < q(e, n) + J(e, n), ef = (e, t, n) => {
- let r = n === "x" ? "y" : "x", i = t.filter((t) => $d(t, e, r)), a = [];
+ return t.siblings.forEach((e) => i(e, 5, "sibling")), i(t.parent, 8, "parent"), r.push(...Uf(e, t.siblings, n)), r.sort(Gf)[0];
+}, Hf = (e, t, n) => q(e, n) < q(t, n) + J(t, n) && q(t, n) < q(e, n) + J(e, n), Uf = (e, t, n) => {
+ let r = n === "x" ? "y" : "x", i = t.filter((t) => Hf(t, e, r)), a = [];
for (let t of i) for (let r of i) {
let i = q(r, n) - (q(t, n) + J(t, n)) - J(e, n);
if (i < 0) continue;
@@ -8669,22 +8953,22 @@ var xl = {
});
}
return a;
-}, tf = (e) => e.distance - (e.source === "parent" ? Xd : 0), nf = (e, t) => tf(e) - tf(t) || Number(t.source === "parent") - Number(e.source === "parent"), rf = (e, t, n) => ({
- dx: af(e, t, "x", n),
- dy: af(e, t, "y", n)
-}), af = (e, t, n, r) => {
- let i = Qd(e, t, n), a = r?.[n];
+}, Wf = (e) => e.distance - (e.source === "parent" ? zf : 0), Gf = (e, t) => Wf(e) - Wf(t) || Number(t.source === "parent") - Number(e.source === "parent"), Kf = (e, t, n) => ({
+ dx: qf(e, t, "x", n),
+ dy: qf(e, t, "y", n)
+}), qf = (e, t, n, r) => {
+ let i = Vf(e, t, n), a = r?.[n];
if (a) {
- let t = Zd(e, n)[a.own], r = Math.abs(a.line - t);
+ let t = Bf(e, n)[a.own], r = Math.abs(a.line - t);
if (r <= 12 && (!i || i.distance >= r)) return Math.round(a.line - t);
}
return r && (i?.source === "parent" && i.own !== void 0 && i.line !== void 0 ? r[n] = {
own: i.own,
line: i.line
} : delete r[n]), i?.offset ?? 0;
-}, of = (e, t, n) => {
+}, Jf = (e, t, n) => {
let r = [], i = (t, i, a) => {
- for (let o of Zd(t, n)) {
+ for (let o of Bf(t, n)) {
let t = Math.abs(o - e);
t <= i && r.push({
offset: Math.round(o - e),
@@ -8694,25 +8978,25 @@ var xl = {
}
};
return t.siblings.forEach((e) => i(e, 5, "sibling")), i(t.parent, 8, "parent"), r;
-}, sf = (e, t, n) => {
+}, Yf = (e, t, n) => {
let [r, i] = n === "x" ? ["w", "e"] : ["n", "s"];
if (t.includes(r)) return q(e, n);
if (t.includes(i)) return q(e, n) + J(e, n);
-}, cf = (e, t, n) => {
+}, Xf = (e, t, n) => {
let r = (r) => {
- let i = sf(e, t, r);
- return i === void 0 ? 0 : of(i, n, r).sort(nf)[0]?.offset ?? 0;
+ let i = Yf(e, t, r);
+ return i === void 0 ? 0 : Jf(i, n, r).sort(Gf)[0]?.offset ?? 0;
};
return {
dx: r("x"),
dy: r("y")
};
-}, lf = .5, uf = (e, t) => {
+}, Zf = .5, Qf = (e, t) => {
let n = [], r = [...t.siblings, t.parent];
for (let t of ["x", "y"]) {
let i = t === "x" ? "y" : "x";
- for (let a of Zd(e, t)) {
- let o = r.filter((e) => Zd(e, t).some((e) => Math.abs(e - a) <= lf));
+ for (let a of Bf(e, t)) {
+ let o = r.filter((e) => Bf(e, t).some((e) => Math.abs(e - a) <= Zf));
if (o.length === 0) continue;
let s = [e, ...o].map((e) => [q(e, i), q(e, i) + J(e, i)]);
n.push({
@@ -8723,17 +9007,17 @@ var xl = {
});
}
}
- return pf(n);
-}, df = (e, t, n) => {
- let r = n === "x" ? "y" : "x", i = t.filter((t) => $d(t, e, r)), a = (e) => q(e, n) + J(e, n);
+ return tp(n);
+}, $f = (e, t, n) => {
+ let r = n === "x" ? "y" : "x", i = t.filter((t) => Hf(t, e, r)), a = (e) => q(e, n) + J(e, n);
return {
before: i.filter((t) => a(t) <= q(e, n)).sort((e, t) => a(t) - a(e))[0],
after: i.filter((t) => q(t, n) >= q(e, n) + J(e, n)).sort((e, t) => q(e, n) - q(t, n))[0]
};
-}, ff = (e, t) => {
+}, ep = (e, t) => {
let n = [];
for (let r of ["x", "y"]) {
- let { before: i, after: a } = df(e, t, r);
+ let { before: i, after: a } = $f(e, t, r);
if (!i || !a) continue;
let o = q(i, r) + J(i, r), s = q(a, r), c = q(e, r) - o, l = s - (q(e, r) + J(e, r));
if (Math.abs(c - l) > 3) continue;
@@ -8751,19 +9035,19 @@ var xl = {
});
}
return n;
-}, pf = (e) => e.filter((t, n) => e.findIndex((e) => e.axis === t.axis && Math.abs(e.position - t.position) <= lf) === n), mf = (e, t, n) => n?.absolute ? {
+}, tp = (e) => e.filter((t, n) => e.findIndex((e) => e.axis === t.axis && Math.abs(e.position - t.position) <= Zf) === n), np = (e, t, n) => n?.absolute ? {
x: n.x,
y: n.y,
width: n.width,
height: n.height
-} : At(x(e, n), t), hf = (e, t, n) => t.flatMap((t) => {
+} : At(x(e, n), t), rp = (e, t, n) => t.flatMap((t) => {
let r = w(e.items, t);
return r ? [{
original: structuredClone(r.item),
offset: r.offset,
measured: n(r.item)
}] : [];
-}), gf = (e) => Qt(e.map((e) => At(x(e.original, e.measured), e.offset))), _f = (e, t, n, r) => At(x(t, n && e.kind === "primitive" && t.kind === "primitive" ? je(e.primitive, t.primitive, n) : n), r), vf = (e, t, n, r, i, a, o) => {
+}), ip = (e) => Qt(e.map((e) => At(x(e.original, e.measured), e.offset))), ap = (e, t, n, r) => At(x(t, n && e.kind === "primitive" && t.kind === "primitive" ? je(e.primitive, t.primitive, n) : n), r), op = (e, t, n, r, i, a, o) => {
let s = a.offset ?? {
x: 0,
y: 0
@@ -8774,36 +9058,36 @@ var xl = {
}, r, o, i, {
...a,
snapEnabled: s
- }), l = (t) => _f(e, t, a.measured, s), u = c(r.dx, r.dy, a.snapEnabled);
+ }), l = (t) => ap(e, t, a.measured, s), u = c(r.dx, r.dy, a.snapEnabled);
if (!o || !a.snapEnabled) return {
item: u,
guides: []
};
- let d = l(u), f = cf(d, t, o);
+ let d = l(u), f = Xf(d, t, o);
if (f.dx === 0 && f.dy === 0) return {
item: u,
- guides: uf(d, o)
+ guides: Qf(d, o)
};
- let p = l(e), m = (e) => (sf(d, t, e) ?? 0) - (sf(p, t, e) ?? 0), h = c(m("x") + f.dx, m("y") + f.dy, !1);
+ let p = l(e), m = (e) => (Yf(d, t, e) ?? 0) - (Yf(p, t, e) ?? 0), h = c(m("x") + f.dx, m("y") + f.dy, !1);
return {
item: h,
- guides: uf(l(h), o)
+ guides: Qf(l(h), o)
};
-}, yf = (e, t, n, r) => {
+}, sp = (e, t, n, r) => {
let i = w(e.items, n);
if (!i) return;
let a = i.parent?.id, o = zt(e.items).filter((e) => e.parent?.id === a).filter((e) => !e.item.hidden).filter((e) => !t.includes(e.item.id)).flatMap((e) => {
let t = C(e.item) ? Bt(e) : At(x(e.item, r(e.item)), e.offset);
return t ? [t] : [];
- }), s = i.parent ? bf(e, i.parent.id) : pt(e);
+ }), s = i.parent ? cp(e, i.parent.id) : pt(e);
return s ? {
siblings: o,
parent: s
} : void 0;
-}, bf = (e, t) => {
+}, cp = (e, t) => {
let n = w(e.items, t);
return n && C(n.item) ? At(x(n.item), n.offset) : void 0;
-}, xf = (e, t, n, r, i, a, o, s) => {
+}, lp = (e, t, n, r, i, a, o, s) => {
let c = e.find((e) => e.original.id === t);
if (!c) return [];
let l = St(c.original, { mode: "move" }, n, r, i, {
@@ -8812,7 +9096,7 @@ var xl = {
measured: c.measured
}), u = x(c.original, c.measured), d = x(l, c.measured), f = d.x - u.x, p = d.y - u.y;
if (o && a && !c.original.locked) {
- let t = gf(e), i = rf({
+ let t = ip(e), i = Kf({
...t,
x: t.x + n,
y: t.y + r
@@ -8824,36 +9108,36 @@ var xl = {
let n = structuredClone(e.original);
return n.locked || S(n, f, p), n;
});
-}, Sf = (e, t, n) => {
+}, up = (e, t, n) => {
let r = e.flatMap((e) => {
let n = t.find((t) => t.id === e.original.id);
return n ? [At(x(n, e.measured), e.offset)] : [];
});
- return r.length > 0 ? uf(Qt(r), n) : [];
-}, Cf = (e, t, n) => {
+ return r.length > 0 ? Qf(Qt(r), n) : [];
+}, dp = (e, t, n) => {
let r = e.flatMap((e) => {
let n = t.find((t) => t.id === e.original.id);
return n ? [At(x(n, e.measured), e.offset)] : [];
});
- return r.length > 0 ? ff(Qt(r), n.siblings) : [];
-}, wf = (e, t, n) => {
+ return r.length > 0 ? ep(Qt(r), n.siblings) : [];
+}, fp = (e, t, n) => {
let r = w(e.items, t);
- if (r) return mf(r.item, r.offset, n(r.item));
-}, Tf = (e, t, n) => {
- let r = t.flatMap((t) => wf(e, t, n) ?? []);
+ if (r) return np(r.item, r.offset, n(r.item));
+}, pp = (e, t, n) => {
+ let r = t.flatMap((t) => fp(e, t, n) ?? []);
return r.length > 0 ? Qt(r) : void 0;
-}, Ef = (e, t, n, r) => {
+}, mp = (e, t, n, r) => {
let i = n ? w(e.items, n)?.item : void 0, a = i && C(i) ? i.id : void 0;
return zt(e.items).filter((e) => (e.parent?.id ?? void 0) === a).filter((e) => !e.item.hidden).filter((e) => {
- let n = C(e.item) ? Bt(e) : mf(e.item, e.offset, r(e.item));
+ let n = C(e.item) ? Bt(e) : np(e.item, e.offset, r(e.item));
return n !== void 0 && Mt(n, t);
}).map((e) => e.item.id);
-}, Df = (e, t) => ({
+}, hp = (e, t) => ({
x: Math.min(e.x, t.x),
y: Math.min(e.y, t.y),
width: Math.abs(t.x - e.x),
height: Math.abs(t.y - e.y)
-}), Of = (e) => {
+}), gp = (e) => {
let { origin: t, threshold: n = 0, target: r = window, onActivate: i, onMove: a, onEnd: o, onCancel: s } = e, c = !1, l = () => {
r.removeEventListener("pointermove", u), r.removeEventListener("pointerup", d), r.removeEventListener("pointercancel", f);
}, u = (e) => {
@@ -8868,18 +9152,18 @@ var xl = {
l(), s?.();
};
return r.addEventListener("pointermove", u), r.addEventListener("pointerup", d), r.addEventListener("pointercancel", f), l;
-}, kf = (e) => Array.isArray(e) && e.length === 2 && e.every((e) => typeof e == "number"), Af = (e) => Array.isArray(e) ? e.filter(kf).map(([e, t]) => [e, t]) : [], jf = (e) => e.length < 256, Mf = (e) => e.length > 3, Nf = (e) => {
- if (!jf(e) || e.length === 0) return e;
+}, _p = (e) => Array.isArray(e) && e.length === 2 && e.every((e) => typeof e == "number"), vp = (e) => Array.isArray(e) ? e.filter(_p).map(([e, t]) => [e, t]) : [], yp = (e) => e.length < 256, bp = (e) => e.length > 3, xp = (e) => {
+ if (!yp(e) || e.length === 0) return e;
let [t, n] = e[e.length - 1], [r, i] = e[0];
return [...e, [Math.round((t + r) / 2), Math.round((n + i) / 2)]];
-}, Pf = (e, t) => Mf(e) ? e.filter((e, n) => n !== t) : e, Ff = (e, t, n, r) => e.map((e, i) => i === t ? n === 0 ? [r, e[1]] : [e[0], r] : e), If = (e, t, n) => e.map((e, r) => r === t ? n : e), Lf = (e, t, n) => jf(e) ? [
+}, Sp = (e, t) => bp(e) ? e.filter((e, n) => n !== t) : e, Cp = (e, t, n, r) => e.map((e, i) => i === t ? n === 0 ? [r, e[1]] : [e[0], r] : e), wp = (e, t, n) => e.map((e, r) => r === t ? n : e), Tp = (e, t, n) => yp(e) ? [
...e.slice(0, t + 1),
n,
...e.slice(t + 1)
-] : e, Rf = (e, t) => [Math.round((e[0] + t[0]) / 2), Math.round((e[1] + t[1]) / 2)], zf = (e) => {
- if (e.type === "polygon") return Af(e.points);
+] : e, Ep = (e, t) => [Math.round((e[0] + t[0]) / 2), Math.round((e[1] + t[1]) / 2)], Dp = (e) => {
+ if (e.type === "polygon") return vp(e.points);
if (e.type === "line") return [[e.x_start, e.y_start], [e.x_end, e.y_end]];
-}, Bf = (e, t) => {
+}, Op = (e, t) => {
if (e.type === "polygon") return {
...e,
points: t
@@ -8905,13 +9189,303 @@ function Y(e, t, n, r) {
return i > 3 && a && Object.defineProperty(t, n, a), a;
}
//#endregion
+//#region src/ods-region-layer.ts
+var kp = (e) => ({
+ left: `${e.x}px`,
+ top: `${e.y}px`,
+ width: `${e.width}px`,
+ height: `${e.height}px`
+}), Ap = class extends L {
+ constructor(...e) {
+ super(...e), this.step = "layout", this.selectedRegionId = "", this.onKeyDown = (e) => {
+ e.key === "Escape" && this.corner && this.stopJoining();
+ };
+ }
+ static {
+ this.styles = [G, P`
+ :host {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ }
+ button {
+ position: absolute;
+ box-sizing: border-box;
+ padding: 0;
+ pointer-events: auto;
+ cursor: pointer;
+ background: transparent;
+ color: var(--studio-text);
+ }
+ .cell {
+ border: calc(1px * var(--ui, 1)) dashed var(--studio-accent);
+ }
+ .cell.joined {
+ border-color: transparent;
+ }
+ .cell:hover,
+ .cell.in-rectangle {
+ background: var(--studio-accent-soft);
+ }
+ .cell.corner {
+ background: var(--studio-accent-soft);
+ border-style: solid;
+ }
+ .cell.in-rectangle.invalid {
+ background: color-mix(
+ in srgb,
+ var(--error-color, #db4437) 18%,
+ transparent
+ );
+ border-color: var(--error-color, #db4437);
+ }
+ .outline {
+ position: absolute;
+ box-sizing: border-box;
+ border: calc(2px * var(--ui, 1)) solid var(--studio-accent);
+ pointer-events: none;
+ }
+ .outline {
+ display: grid;
+ place-content: center;
+ justify-items: center;
+ gap: calc(2px * var(--ui, 1));
+ color: var(--studio-text);
+ }
+ .letter {
+ font-size: calc(26px * var(--ui, 1));
+ font-weight: 700;
+ }
+ .size {
+ color: var(--studio-muted);
+ font: calc(10px * var(--ui, 1)) var(--code-font-family, monospace);
+ }
+ .outline.selected {
+ background: var(--studio-accent-soft);
+ box-shadow: 0 0 0 calc(2px * var(--ui, 1)) var(--studio-accent);
+ }
+ /* A region with a widget draws nothing of its own, so the picture is what will be
+ rendered; only an empty region is marked, and the selected one is outlined. */
+ .slot {
+ border: 0;
+ }
+ .slot:hover {
+ box-shadow: inset 0 0 0 calc(1px * var(--ui, 1)) var(--studio-accent);
+ }
+ .slot.empty {
+ border: calc(1px * var(--ui, 1)) dashed var(--studio-accent);
+ background: color-mix(in srgb, var(--studio-surface) 70%, transparent);
+ }
+ .slot.empty:hover {
+ background: var(--studio-accent-soft);
+ box-shadow: none;
+ }
+ .slot.selected {
+ border: calc(2px * var(--ui, 1)) solid var(--studio-accent);
+ box-shadow: none;
+ }
+ .slot.empty.selected {
+ background: var(--studio-accent-soft);
+ }
+ .slot-label {
+ position: absolute;
+ top: 0;
+ left: 0;
+ max-width: 100%;
+ padding: calc(2px * var(--ui, 1)) calc(6px * var(--ui, 1));
+ overflow: hidden;
+ background: var(--studio-accent);
+ color: var(--studio-on-accent, #fff);
+ font-size: calc(11px * var(--ui, 1));
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ .slot-empty {
+ display: grid;
+ gap: calc(4px * var(--ui, 1));
+ place-items: center;
+ font-size: calc(12px * var(--ui, 1));
+ }
+ .slot-empty ha-icon {
+ --mdc-icon-size: calc(28px * var(--ui, 1));
+ color: var(--studio-accent);
+ }
+ .status {
+ position: absolute;
+ bottom: calc(8px * var(--ui, 1));
+ left: 50%;
+ padding: calc(4px * var(--ui, 1)) calc(10px * var(--ui, 1));
+ border-radius: 999px;
+ background: var(--studio-text);
+ color: var(--studio-surface);
+ font-size: calc(12px * var(--ui, 1));
+ transform: translateX(-50%);
+ white-space: nowrap;
+ }
+ `];
+ }
+ connectedCallback() {
+ super.connectedCallback(), window.addEventListener("keydown", this.onKeyDown);
+ }
+ disconnectedCallback() {
+ super.disconnectedCallback(), window.removeEventListener("keydown", this.onKeyDown);
+ }
+ willUpdate(e) {
+ e.has("step") && this.stopJoining();
+ }
+ get layout() {
+ return this.dashboard.layout;
+ }
+ hoverCell(e) {
+ this.hover = e;
+ }
+ stopJoining() {
+ this.corner = void 0, this.hover = void 0;
+ }
+ cellAt(e, t) {
+ return {
+ row: Math.floor(e / t.columns),
+ column: e % t.columns
+ };
+ }
+ isCorner(e) {
+ return this.corner?.row === e.row && this.corner.column === e.column;
+ }
+ get joining() {
+ if (!(!this.corner || !this.hover)) return cf(this.corner, this.hover);
+ }
+ isInJoin(e) {
+ let t = this.joining;
+ return t ? e.row >= t.row && e.row < t.row + t.rowSpan && e.column >= t.column && e.column < t.column + t.columnSpan : !1;
+ }
+ isInJoinedRegion(e, t) {
+ let n = sf(t, e);
+ return n !== void 0 && (n.rowSpan > 1 || n.columnSpan > 1);
+ }
+ joinIsValid(e) {
+ let t = this.joining;
+ return t !== void 0 && hf(e, t);
+ }
+ onCellClick(e, t) {
+ let n = sf(t, e);
+ if (!this.corner) {
+ this.corner = e, n && W(this, "region-select", { regionId: n.id });
+ return;
+ }
+ let r = cf(this.corner, e);
+ if (r.rowSpan === 1 && r.columnSpan === 1) {
+ this.stopJoining();
+ return;
+ }
+ hf(t, r) && (this.stopJoining(), W(this, "regions-merge", { rectangle: r }));
+ }
+ onCellDoubleClick(e, t) {
+ let n = sf(t, e);
+ n && this.isInJoinedRegion(e, t) && (this.stopJoining(), W(this, "region-split", { regionId: n.id }));
+ }
+ onSlotClick(e) {
+ W(this, "region-select", { regionId: e });
+ }
+ renderCell(e, n, r) {
+ let i = this.cellAt(n, r), a = this.isInJoin(i);
+ return F`
+
+ `;
+ }
+ renderOutline(e, n, r) {
+ let i = e.regions.find((e) => e.id === n.id);
+ return i ? F`
+