- Words light up as you type (@1, times, cuts, quotes, labels), with @ autocomplete and a ? cheat sheet - Timeline bar shows every picture a line names; drag a block to reorder, drag its edge to set its time, click an edge for a cut, click a block to jump to its line - Sound and Music boxes (new optional inputs, appended) fill H3's overall_soundscape and non_diegetic_music - Pictures and video as chips with their jobs; Length, Camera and Look stay plain rows so they can still be wired - Help page and changelog updated; tests for the colors, autocomplete, timeline edits and the new inputs
87 lines
4.7 KiB
JavaScript
87 lines
4.7 KiB
JavaScript
// Colours in H3 Prompt Writer's script box: the words the node reads as
|
|
// instructions stand out while you type. @1 is a picture, 3s: is a time, a
|
|
// blank line is a cut, sound: and music: lines go to their own part, and
|
|
// words in quotes are spoken.
|
|
//
|
|
// The real textarea stays on top with see-through text, so typing, undo,
|
|
// copy and the caret are the browser's own. A copy of the text sits under it
|
|
// with the colours. Only colours and backgrounds change, never the font or
|
|
// the spacing, so both lay out the same lines.
|
|
import { scriptMarkup } from "./script_colours_math.mjs";
|
|
|
|
const CSS_ID = "ausboss-script-colours-css";
|
|
|
|
function ensureCss() {
|
|
if (document.getElementById(CSS_ID)) return;
|
|
const style = document.createElement("style");
|
|
style.id = CSS_ID;
|
|
style.textContent = `
|
|
.ausboss-colours-host{position:relative}
|
|
.ausboss-colours-host>:not(.ausboss-colours):not(textarea){display:none !important}
|
|
.ausboss-colours{position:absolute;inset:0;box-sizing:border-box;margin:0;overflow:hidden;white-space:pre-wrap;overflow-wrap:break-word;word-break:normal;pointer-events:none;color:#d8ecea;border:1px solid transparent;border-radius:6px;background:#0b0f10}
|
|
.ausboss-colours-host>textarea.ausboss-colours-on{position:relative;background:transparent !important;color:transparent !important;caret-color:#d8ecea;-webkit-text-fill-color:transparent}
|
|
.ausboss-colours-host>textarea.ausboss-colours-on::placeholder{color:#4f6462;-webkit-text-fill-color:#4f6462}
|
|
.ausboss-colours-host>textarea.ausboss-colours-on::selection{background:rgba(0,180,170,.35)}
|
|
.ausboss-colours .at{border-radius:3px;background:rgba(0,180,170,.3);box-shadow:0 0 0 1px rgba(0,180,170,.45);color:#a5f3ec}
|
|
.ausboss-colours .at.unknown{background:rgba(245,181,61,.18);box-shadow:0 0 0 1px rgba(245,181,61,.5);color:#f5c76b}
|
|
.ausboss-colours .time{color:#f5b53d}
|
|
.ausboss-colours .key{color:#6f8886}
|
|
.ausboss-colours .say{color:#f0dcae}
|
|
.ausboss-colours .cut{display:inline-block;width:100%;background:linear-gradient(transparent calc(50% - 1px),#2f4a48 calc(50% - 1px),#2f4a48 50%,transparent 50%);position:relative}
|
|
.ausboss-colours .cut::after{content:"cut";position:absolute;right:0;top:0;padding-left:4px;background:#0b0f10;color:#56706d;font-size:9px;letter-spacing:.1em;text-transform:uppercase}
|
|
`;
|
|
document.head.append(style);
|
|
}
|
|
|
|
// The box's own text settings, copied so the coloured copy wraps the same.
|
|
const COPIED = ["fontFamily", "fontSize", "fontWeight", "fontStyle", "lineHeight", "letterSpacing", "wordSpacing", "tabSize", "textIndent", "paddingTop", "paddingRight", "paddingBottom", "paddingLeft", "borderTopWidth", "borderRightWidth", "borderBottomWidth", "borderLeftWidth"];
|
|
|
|
export function attachScriptColours(node) {
|
|
const none = { sync: () => {}, dispose: () => {} };
|
|
const area = node.__ausbossCard?.root?.querySelector?.("textarea.ausboss-card-area");
|
|
if (!area?.parentElement) return none;
|
|
ensureCss();
|
|
const host = area.parentElement;
|
|
const layer = document.createElement("div");
|
|
layer.className = "ausboss-colours";
|
|
layer.setAttribute("aria-hidden", "true");
|
|
host.classList.add("ausboss-colours-host");
|
|
host.insertBefore(layer, area);
|
|
area.classList.add("ausboss-colours-on");
|
|
// Another pack that colours every text box puts its own copy beside the
|
|
// box; the rule above hides it, so the text never shows twice.
|
|
area.dataset.ausbossColoured = "true";
|
|
|
|
let wiredNumbers = new Set([1, 2, 3, 4]);
|
|
const draw = () => {
|
|
const style = getComputedStyle(area);
|
|
for (const key of COPIED) layer.style[key] = style[key];
|
|
// A scroll bar takes width from the text; the copy gives up the same.
|
|
const bar = area.offsetWidth - area.clientWidth - (parseFloat(style.borderLeftWidth) || 0) - (parseFloat(style.borderRightWidth) || 0);
|
|
layer.style.paddingRight = `${(parseFloat(style.paddingRight) || 0) + Math.max(0, bar)}px`;
|
|
layer.innerHTML = scriptMarkup(area.value, (number) => wiredNumbers.has(number));
|
|
layer.scrollTop = area.scrollTop;
|
|
};
|
|
const onScroll = () => { layer.scrollTop = area.scrollTop; };
|
|
area.addEventListener("input", draw);
|
|
area.addEventListener("scroll", onScroll);
|
|
const observer = typeof ResizeObserver === "function" ? new ResizeObserver(draw) : null;
|
|
observer?.observe(area);
|
|
queueMicrotask(draw);
|
|
return {
|
|
area,
|
|
layer,
|
|
// values: the node's widget values; numbers: the pictures plugged in.
|
|
sync(values, numbers) {
|
|
if (numbers) wiredNumbers = new Set(numbers);
|
|
if (document.activeElement !== area && typeof values?.script === "string" && area.value !== values.script) area.value = values.script;
|
|
draw();
|
|
},
|
|
dispose() {
|
|
observer?.disconnect();
|
|
area.removeEventListener("input", draw);
|
|
area.removeEventListener("scroll", onScroll);
|
|
},
|
|
};
|
|
}
|