|
|
|
|
@ -6,7 +6,6 @@ |
|
|
|
|
import * as MonoDisplay from "./index"; |
|
|
|
|
import { MonoDisplayFile } from "./file"; |
|
|
|
|
import { cycleTheme } from "./themes"; |
|
|
|
|
import { ElementType } from "./types"; |
|
|
|
|
|
|
|
|
|
// Expose on globalThis (= window in browser, globalThis elsewhere)
|
|
|
|
|
(globalThis as typeof globalThis & { MonoDisplay: typeof MonoDisplay }).MonoDisplay = MonoDisplay; |
|
|
|
|
@ -20,8 +19,8 @@ let isDirty = false; |
|
|
|
|
|
|
|
|
|
// --- Element type definitions ------------------------------------------------
|
|
|
|
|
type ElFieldMeta = { key: string; label: string; type: string; default: any; full?: boolean }; |
|
|
|
|
type ElFlagMeta = { key: string; label: string }; |
|
|
|
|
const EL_FIELDS: Partial<Record<keyof typeof ElementType, ElFieldMeta[]>> = { |
|
|
|
|
type ElFlagMeta = { key: string; label: string, default?: boolean; }; |
|
|
|
|
const EL_FIELDS: Partial<Record<MonoDisplay.ElementTypeName, ElFieldMeta[]>> = { |
|
|
|
|
Image2D: [ |
|
|
|
|
{ key: 'xOffset', label: 'X offset', type: 'number', default: 0 }, { key: 'yOffset', label: 'Y offset', type: 'number', default: 0 }, |
|
|
|
|
{ key: 'width', label: 'Width', type: 'number', default: W }, { key: 'height', label: 'Height', type: 'number', default: H }, |
|
|
|
|
@ -59,11 +58,11 @@ const EL_FIELDS: Partial<Record<keyof typeof ElementType, ElFieldMeta[]>> = { |
|
|
|
|
{ key: 'fontIndex', label: 'Font', type: 'font', default: 'Font 0' }, |
|
|
|
|
], |
|
|
|
|
}; |
|
|
|
|
const EL_FLAGS: Partial<Record<keyof typeof ElementType, ElFlagMeta[]>> = { |
|
|
|
|
const EL_FLAGS: Partial<Record<MonoDisplay.ElementTypeName, ElFlagMeta[]>> = { |
|
|
|
|
HScrollText: [{ key: 'endless', label: 'Endless' }, { key: 'invertDirection', label: 'Invert direction' }], |
|
|
|
|
CurrentTime: [{ key: 'clock12h', label: '12h mode' }, { key: 'showHours', label: 'Show hours' }, { key: 'showSeconds', label: 'Show seconds' }], |
|
|
|
|
CurrentTime: [{ key: 'clock12h', label: '12h mode' , default: false}, { key: 'showHours', label: 'Show hours', default:true}, { key: 'showSeconds', label: 'Show seconds' , default:true}], |
|
|
|
|
}; |
|
|
|
|
const EL_TYPES: ElementType[] = Object.keys(EL_FIELDS).map((x: string) => MonoDisplay.StringToElementType[x as MonoDisplay.ElementTypeName]); |
|
|
|
|
const EL_TYPES: MonoDisplay.ElementType[] = Object.keys(EL_FIELDS).map((x: string) => MonoDisplay.StringToElementType[x as MonoDisplay.ElementTypeName]); |
|
|
|
|
|
|
|
|
|
const DEFAULT_FONTS = [ |
|
|
|
|
"Font 1", |
|
|
|
|
@ -129,20 +128,28 @@ function newSec() { |
|
|
|
|
flags: {}, |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
function newEl(type: MonoDisplay.ElementType) { |
|
|
|
|
const fields = {}; |
|
|
|
|
(EL_FIELDS[type] || []).forEach(f => fields[f.k] = f.d); |
|
|
|
|
const flags = {}; |
|
|
|
|
(EL_FLAGS[type] || []).forEach(f => flags[f.k] = false); |
|
|
|
|
return { type, fields, flags }; |
|
|
|
|
function createNewElement(type: MonoDisplay.ElementType) : MonoDisplay.MonoFormatElement { |
|
|
|
|
const fields:Record<string,any> = {}; |
|
|
|
|
(EL_FIELDS[MonoDisplay.ElementTypeToString[type]] as any[] || []) |
|
|
|
|
.forEach((f: ElFieldMeta) => fields[f.key] = f.default); |
|
|
|
|
const flags:Record<string,any> = {}; |
|
|
|
|
(EL_FLAGS[MonoDisplay.ElementTypeToString[type]] as any[] || []) |
|
|
|
|
.forEach((f: ElFlagMeta) => flags[f.key] = f.default || false); |
|
|
|
|
return { |
|
|
|
|
type, |
|
|
|
|
...fields, |
|
|
|
|
flags, |
|
|
|
|
} as MonoDisplay.MonoFormatElement; |
|
|
|
|
} |
|
|
|
|
function getSec(sectionIndex: number | null) { |
|
|
|
|
function getSectionByIndex(sectionIndex: number | null): MonoDisplay.MonoFormatSection | undefined { |
|
|
|
|
return file.sections.find((s,index) => index === sectionIndex) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function getEl(sectionIndex: number, elementIndex: number) { |
|
|
|
|
const section = getSec(sectionIndex); |
|
|
|
|
return section && "elements" in section && section.elements.find((e, index) => index == elementIndex) |
|
|
|
|
function getElementByIndex(sectionIndex: number, elementIndex: number): MonoDisplay.MonoFormatElement | undefined { |
|
|
|
|
const section = getSectionByIndex(sectionIndex); |
|
|
|
|
if(section && "elements" in section) |
|
|
|
|
return section.elements.find((e, index) => index == elementIndex) |
|
|
|
|
return undefined; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// --- Mutations ----------------------------------------------------------------
|
|
|
|
|
@ -159,27 +166,43 @@ function removeSection(sectionIndex:number) { |
|
|
|
|
} |
|
|
|
|
markDirty(); renderHTML(); triggerPreview(); |
|
|
|
|
} |
|
|
|
|
function toggleSection(id) { |
|
|
|
|
const s = getSec(id); if (!s) return; |
|
|
|
|
s.open = !s.open; activeSecIndex = id; renderHTML(); updateMeta(); |
|
|
|
|
function toggleSection(sectionIndex: number): void { |
|
|
|
|
const s = getSectionByIndex(sectionIndex); |
|
|
|
|
if (!s) return; |
|
|
|
|
activeSecIndex = sectionIndex; |
|
|
|
|
renderHTML(); |
|
|
|
|
updateMeta(); |
|
|
|
|
} |
|
|
|
|
function setSectionFlag(flag, val) { |
|
|
|
|
if (!activeSecIndex) return; |
|
|
|
|
const s = getSec(activeSecIndex); if (!s) return; |
|
|
|
|
s.flags[flag] = val; markDirty(); triggerPreview(); |
|
|
|
|
const s = getSectionByIndex(activeSecIndex); |
|
|
|
|
if (!s) return; |
|
|
|
|
s.flags[flag] = val; |
|
|
|
|
markDirty(); |
|
|
|
|
triggerPreview(); |
|
|
|
|
} |
|
|
|
|
function addElement(sectionIndex: number) { |
|
|
|
|
const s = getSec(secId); if (!s) return; |
|
|
|
|
const el = newEl(EL_TYPES[0]); s.elements.push(el); |
|
|
|
|
activeSecIndex = secId; activeElIndex = { secId, elId: el.id }; |
|
|
|
|
markDirty(); renderHTML(); triggerPreview(); |
|
|
|
|
const s = getSectionByIndex(sectionIndex); |
|
|
|
|
if (!s) return; |
|
|
|
|
if (!("elements" in s)) return; |
|
|
|
|
s.elements.push(createNewElement(MonoDisplay.ElementType.HScrollText)); |
|
|
|
|
activeElIndex = s.elements.length; |
|
|
|
|
activeSecIndex = sectionIndex; |
|
|
|
|
markDirty(); |
|
|
|
|
renderHTML(); |
|
|
|
|
triggerPreview(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function removeElement(sectionIndex:number, elementIndex:number) { |
|
|
|
|
const s = getSec(sectionIndex); if (!s) return; |
|
|
|
|
s.elements = s.elements.filter(e => e.id !== elId); |
|
|
|
|
if (activeElIndex && activeElIndex.secId === secId && activeElIndex.elId === elId) activeElIndex = null; |
|
|
|
|
markDirty(); renderHTML(); triggerPreview(); |
|
|
|
|
const s = getSectionByIndex(sectionIndex); |
|
|
|
|
if (!s) return; |
|
|
|
|
if (!("elements" in s)) return; |
|
|
|
|
s.elements = s.elements.filter((e,i) => i != elementIndex); |
|
|
|
|
if (activeElIndex) |
|
|
|
|
activeElIndex = null; |
|
|
|
|
markDirty(); |
|
|
|
|
renderHTML(); |
|
|
|
|
triggerPreview(); |
|
|
|
|
} |
|
|
|
|
function selectElement(sectionIndex:number, elementIndex:number) { |
|
|
|
|
activeSecIndex = sectionIndex; |
|
|
|
|
@ -189,25 +212,33 @@ function selectElement(sectionIndex:number, elementIndex:number) { |
|
|
|
|
null : elementIndex; |
|
|
|
|
renderHTML(); |
|
|
|
|
} |
|
|
|
|
function changeElType(sectionIndex:number, elementIndex:number, typeString: string) { |
|
|
|
|
const el = getEl(sectionIndex, elementIndex); if (!el) return; |
|
|
|
|
const type = MonoDisplay.StringToElementType[typeString as MonoDisplay.ElementTypeName]; |
|
|
|
|
const fresh = newEl(type); |
|
|
|
|
el.type = type; el.fields = fresh.fields; el.flags = fresh.flags; |
|
|
|
|
function changeElType(sectionIndex:number, elementIndex:number, typeString: MonoDisplay.ElementTypeName) { |
|
|
|
|
const el = getElementByIndex(sectionIndex, elementIndex); if (!el) return; |
|
|
|
|
const fresh = createNewElement(MonoDisplay.StringToElementType[typeString]); |
|
|
|
|
setElementByElement(el, fresh); |
|
|
|
|
markDirty(); renderHTML(); triggerPreview(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function setElementByElement(el: MonoDisplay.MonoFormatElement, source:MonoDisplay.MonoFormatElement ) { |
|
|
|
|
el.type = source.type; |
|
|
|
|
for (const [key, value] of Object.entries(source)) { |
|
|
|
|
(el as any)[key as any] = value; |
|
|
|
|
} |
|
|
|
|
return el; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function setElField(sectionIndex:number, elementIndex:number, key:string, val:any) { |
|
|
|
|
const el = getEl(sectionIndex, elementIndex); if (!el) return; |
|
|
|
|
const el = getElementByIndex(sectionIndex, elementIndex); if (!el) return; |
|
|
|
|
el[key] = val; |
|
|
|
|
markDirty(); triggerPreview(); |
|
|
|
|
} |
|
|
|
|
function setElFlag(sectionIndex:number, elementIndex:number, key:string, val:any) { |
|
|
|
|
const el = getEl(sectionIndex, elementIndex); if (!el) return; |
|
|
|
|
const el = getElementByIndex(sectionIndex, elementIndex); if (!el) return; |
|
|
|
|
el.flags[key] = val; |
|
|
|
|
markDirty(); triggerPreview(); |
|
|
|
|
} |
|
|
|
|
function setSectionType(sectionIndex: number, sectionType: string) { |
|
|
|
|
const sec = getSec(sectionIndex); |
|
|
|
|
const sec = getSectionByIndex(sectionIndex); |
|
|
|
|
if (sec) { |
|
|
|
|
sec.sectionType = MonoDisplay.StringToSectionType[sectionType as MonoDisplay.SectionTypeName]; |
|
|
|
|
if (sec.sectionType == MonoDisplay.SectionType.CustomFont) { |
|
|
|
|
@ -404,22 +435,31 @@ function renderHTMLSection(sectionIndex: number) { |
|
|
|
|
case MonoDisplay.SectionType.ElementsTimespan: |
|
|
|
|
return ` |
|
|
|
|
<div class="sec-card${isActive ? ' active' : ''}${isOpen ? ' open' : ''}" id="${sectionId}"> |
|
|
|
|
<div class="sec-hdr${isActive ? ' active' : ''}" onclick="toggleSection('${sectionId}')"> |
|
|
|
|
<div class="sec-hdr${isActive ? ' active' : ''}" onclick="toggleSection(${sectionIndex})"> |
|
|
|
|
<span class="sec-arrow">▶</span> |
|
|
|
|
<span class="sec-label">${MonoDisplay.SectionTypeToString[section.sectionType]}</span> |
|
|
|
|
<span class="sec-badge">${section.elements.length} el</span> |
|
|
|
|
<button class="x-btn" title="Remove section" onclick="event.stopPropagation();removeSection(${sectionIndex})">X</button> |
|
|
|
|
</div> |
|
|
|
|
${section.sectionType == MonoDisplay.SectionType.ElementsTimespan ? ` |
|
|
|
|
<div class="el-list">${ |
|
|
|
|
Object.entries({ "Start Time": "startTimestamp", "Stop Time":"endTimestamp"}).map(([label, key]) => { |
|
|
|
|
return `<div class="field id="timespan">
|
|
|
|
|
<label>${label}</label> |
|
|
|
|
<input type="datetime-local" value="${(section as any)[key]}" onchange="setSectionField(${sectionIndex}, '${key}',this.value)" /> |
|
|
|
|
</div>`;
|
|
|
|
|
}).join("") |
|
|
|
|
}</div>`: ``}
|
|
|
|
|
${isOpen ? ` |
|
|
|
|
<div class="el-list"> |
|
|
|
|
${section.elements.map((el: MonoDisplay.MonoFormatElement, index:number) => renderHTMLElement(sectionIndex, index, el)).join('')} |
|
|
|
|
<button class="add-el" onclick="addElement('${sectionId}')">+ add element</button> |
|
|
|
|
<button class="add-el" onclick="addElement(${sectionIndex})">+ add element</button> |
|
|
|
|
</div>`: ''}
|
|
|
|
|
</div>`;
|
|
|
|
|
case MonoDisplay.SectionType.CustomFont: { |
|
|
|
|
return ` |
|
|
|
|
<div class="sec-card${isActive ? ' active' : ''}${isOpen ? ' open' : ''}" id="${sectionId}"> |
|
|
|
|
<div class="sec-hdr${isActive ? ' active' : ''}" onclick="toggleSection('${sectionId}')"> |
|
|
|
|
<div class="sec-hdr${isActive ? ' active' : ''}" onclick="toggleSection(${sectionIndex})"> |
|
|
|
|
<span class="sec-arrow">▶</span> |
|
|
|
|
<span class="sec-label">Custom Font</span> |
|
|
|
|
<span class="sec-badge">1 el</span> |
|
|
|
|
@ -528,18 +568,18 @@ ${isActive ? activeRender : ``} |
|
|
|
|
|
|
|
|
|
function elSummary(el: MonoDisplay.MonoFormatElement) { |
|
|
|
|
switch (el.type) { |
|
|
|
|
case ElementType.ClippedText: |
|
|
|
|
case ElementType.HScrollText: |
|
|
|
|
case MonoDisplay.ElementType.ClippedText: |
|
|
|
|
case MonoDisplay.ElementType.HScrollText: |
|
|
|
|
// case ElementType.VScrollText:
|
|
|
|
|
return esc(el.text.slice(0, 24) + (el.text.length > 24 ? '...' : '')); |
|
|
|
|
case ElementType.CurrentTime: |
|
|
|
|
case ElementType.Image2D: |
|
|
|
|
case ElementType.HorizontalScroll: |
|
|
|
|
case ElementType.VerticalScroll: |
|
|
|
|
case MonoDisplay.ElementType.CurrentTime: |
|
|
|
|
case MonoDisplay.ElementType.Image2D: |
|
|
|
|
case MonoDisplay.ElementType.HorizontalScroll: |
|
|
|
|
case MonoDisplay.ElementType.VerticalScroll: |
|
|
|
|
return `${el.xOffset ?? 0}, ${el.yOffset ?? 0}`; |
|
|
|
|
|
|
|
|
|
case ElementType.Animation: |
|
|
|
|
case ElementType.Line: |
|
|
|
|
case MonoDisplay.ElementType.Animation: |
|
|
|
|
case MonoDisplay.ElementType.Line: |
|
|
|
|
return `${el.type.toString()}`; |
|
|
|
|
default: |
|
|
|
|
return "?"; |
|
|
|
|
@ -548,7 +588,7 @@ function elSummary(el: MonoDisplay.MonoFormatElement) { |
|
|
|
|
function esc(s) { return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"') } |
|
|
|
|
|
|
|
|
|
function updateMeta() { |
|
|
|
|
const section = getSec(activeSecIndex); |
|
|
|
|
const section = getSectionByIndex(activeSecIndex); |
|
|
|
|
const sectionSelection = document.getElementById('meta-name'); |
|
|
|
|
if (sectionSelection) { |
|
|
|
|
if (section) { |
|
|
|
|
|