Changes
10 changed files (+301/-47)
-
-
@@ -11,6 +11,8 @@ module Main exposing (main)import Browser import Browser.Navigation exposing (Key) import Html exposing (node, p, text) import Html.Attributes exposing (attribute) import Parameters exposing (Parameters) import Template exposing (template) import Url exposing (Url)
-
@@ -86,7 +88,15 @@ view : Model -> Browser.Document Msgview model = { title = "" , body = [ template model.parameters [ node "x-app-layout" [] [ node "x-preview" [ attribute "slot" "preview" ] [ template model.parameters [] ] , node "x-panel" [ attribute "slot" "parameters" ] [ p [] [ text "Parameters" ] ] ] ] }
-
-
-
@@ -40,8 +40,8 @@ BottomRight ( x, y ) ->( x - width, y - height ) template : Parameters -> Svg.Svg msg template params = template : Parameters -> List (Svg.Attribute msg) -> Svg.Svg msg template params attrs = let ( canvasWidth, canvasHeight ) = canvasSizeDimension params.canvasSize
-
@@ -49,9 +49,10 @@ |> Tuple.mapBoth toMM toMM|> Tuple.mapBoth ceiling ceiling in svg [ viewBox (String.join " " [ "0", "0", String.fromInt canvasWidth, String.fromInt canvasHeight ]) , class "print" ] (viewBox (String.join " " [ "0", "0", String.fromInt canvasWidth, String.fromInt canvasHeight ]) :: class "print" :: attrs ) -- TODO: Make margin configurable [ scaleChecker (BottomLeft ( 10, canvasHeight - 10 )) , longPiece params (TopLeft ( 10, 10 ))
-
-
-
@@ -6,6 +6,10 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/.// // SPDX-License-Identifier: MPL-2.0 import { XText } from "./x-text.js"; import { XAppLayout } from "./x-app-layout.js"; import { XPanel } from "./x-panel.js"; import { XPreview } from "./x-preview.js"; customElements.define("x-text", XText); customElements.define("x-app-layout", XAppLayout); customElements.define("x-panel", XPanel); customElements.define("x-preview", XPreview);
-
-
-
@@ -0,0 +1,39 @@/* * Copyright 2026 Shota FUJI * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. * * SPDX-License-Identifier: MPL-2.0 */ :host { position: fixed; inset: 0; display: flex; } ::slotted([slot="preview"]) { flex-grow: 1; flex-shrink: 1; } ::slotted([slot="parameters"]) { position: relative; z-index: 100; } @media print { :host { display: contents; } ::slotted([slot="preview"]) { display: contents; } ::slotted([slot="parameters"]) { display: none; } }
-
-
-
@@ -0,0 +1,31 @@// Copyright 2026 Shota FUJI // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. // // SPDX-License-Identifier: MPL-2.0 import css from "./x-app-layout.css"; export class XAppLayout extends HTMLElement { constructor() { super(); const shadow = this.attachShadow({ mode: "open", }); const style = document.createElement("style"); style.textContent = css; shadow.appendChild(style); const preview = document.createElement("slot"); preview.name = "preview"; shadow.appendChild(preview); const parameters = document.createElement("slot"); parameters.name = "parameters"; shadow.appendChild(parameters); } }
-
-
-
@@ -0,0 +1,67 @@/* * Copyright 2026 Shota FUJI * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. * * SPDX-License-Identifier: MPL-2.0 */ :host { position: relative; display: flex; justify-content: center; padding: min(5dvh, 5vw); } ::slotted(svg) { border: 1px solid #0002; max-width: 100%; max-height: 100%; border-radius: 2px; box-shadow: 1px 1px 5px #0003; } .gesture-plane { position: absolute; inset: 0; z-index: 5; touch-action: none; } .reset { position: absolute; bottom: 5px; left: 5px; box-shadow: 1px 1px 3px #0004; z-index: 7; } @media print { :host { display: contents; } .gesture-plane, .reset { display: none; } ::slotted(svg) { position: fixed; inset: 0; width: 100%; height: 100%; border: none; background-color: white; border-radius: 0; box-shadow: none; color: black; transform: none !important; } }
-
-
-
@@ -0,0 +1,127 @@// Copyright 2026 Shota FUJI // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. // // SPDX-License-Identifier: MPL-2.0 import css from "./x-preview.css"; const SCALE_MIN = 0.1; const SCALE_MAX = 5.0; export class XPreview extends HTMLElement { #slot = document.createElement("slot"); #gesturePlane = document.createElement("div"); #size = null; #resizeObserver = new ResizeObserver((entries) => { for (const entry of entries) { this.#size = entry.contentRect; return; } }); #dx = 0; #dy = 0; #scale = 1.0; #isRenderScheduled = false; constructor() { super(); const shadow = this.attachShadow({ mode: "open", }); const style = document.createElement("style"); style.textContent = css; shadow.appendChild(style); shadow.appendChild(this.#slot); this.#gesturePlane.classList.add("gesture-plane"); shadow.appendChild(this.#gesturePlane); const reset = document.createElement("button"); reset.textContent = "Reset Viewport"; reset.classList.add("reset"); reset.classList.add("hidden"); reset.addEventListener("click", (event) => { this.#dx = 0; this.#dy = 0; this.#scale = 1.0; this.#render(); }); shadow.appendChild(reset); } connectedCallback() { this.#gesturePlane.addEventListener("wheel", this.#onWheel); this.#render(); this.#resizeObserver.observe(this.#gesturePlane); } disconnectedCallback() { this.#gesturePlane.removeEventListener("wheel", this.#onWheel); this.#resizeObserver.disconnect(); } #onWheel = (event) => { event.preventDefault(); if (event.ctrlKey || event.metaKey) { let { deltaY } = event; // Workaround shitty design. switch (event.deltaMode) { // DOM_DELTA_LINE case 1: deltaY *= 15; break; // DOM_DELTA_PAGE case 2: deltaY *= 100; break; } const previous = this.#scale; this.#scale = Math.max(SCALE_MIN, Math.min(SCALE_MAX, this.#scale * (1 - deltaY / 100))); const ox = event.offsetX - this.#size.width * 0.5; const oy = event.offsetY - this.#size.height * 0.5; this.#dx += ox / this.#scale - ox / previous; this.#dy += oy / this.#scale - oy / previous; } else { this.#dx -= event.deltaX / this.#scale; this.#dy -= event.deltaY / this.#scale; } this.#render(); }; #render() { if (this.#isRenderScheduled) { return; } this.#isRenderScheduled = true; requestAnimationFrame(() => { if (!this.isConnected) { return; } for (const element of this.#slot.assignedElements()) { element.style.transform = `scale(${this.#scale}) translate(${this.#dx}px, ${this.#dy}px)`; } this.#isRenderScheduled = false; }); } }
-
-
-
@@ -9,11 +9,11 @@ * SPDX-License-Identifier: MPL-2.0*/ :host { background-color: tomato; } .text { font-weight: bold; display: block; padding: 0.5em 1em; border: 1px solid oklch(0% 0 0deg / 0.2); color: white; background-color: Canvas; box-shadow: 0 0 3px oklch(0% 0 0deg / 0.2); overflow-y: auto; }
-
-
-
@@ -6,9 +6,9 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/.// // SPDX-License-Identifier: MPL-2.0 import css from "./x-text.css"; import css from "./x-panel.css"; export class XText extends HTMLElement { export class XPanel extends HTMLElement { constructor() { super();
-
@@ -20,11 +20,7 @@ const style = document.createElement("style");style.textContent = css; shadow.appendChild(style); const span = document.createElement("span"); span.classList.add("text"); shadow.appendChild(span); const slot = document.createElement("slot"); span.appendChild(slot); shadow.appendChild(slot); } }
-
-
-
@@ -21,40 +21,19 @@ Elm.Main.init();}); </script> <style> .print { position: absolute; inset: 0; margin: auto; height: 90dvh; border: 1px solid #0002; border-radius: 2px; box-shadow: 1px 1px 5px #0003; *, *::before, *::after { box-sizing: border-box; } @media print { @page { size: A4; margin: 0; } :root, body { margin: 0; padding: 0; } body > :not(.print) { display: none !important; } .print { position: fixed; inset: 0; width: 100%; height: 100%; background-color: white; color: black; @page { size: A4; margin: 0; } } </style>
-