yummy.cricket/rainbow-quox/script/history.ts
2024-12-28 22:27:09 +01:00

114 lines
3.1 KiB
TypeScript

import { Colors as Oklchs, Rgbs } from './color.js';
import * as Color from './color.js';
export class HistoryItem {
name: string;
oklch: Oklchs;
rgb: Rgbs;
constructor(name: string, oklch: Oklchs, rgb: Rgbs) {
this.oklch = oklch;
this.rgb = rgb;
this.name = name;
}
asHtml(): HTMLButtonElement {
const { lines, outer, belly1: belly, fins1: fins } = this.rgb;
const content = `
<svg class=history-colors width=30 height=25 viewBox="-10 -10 140 120">
<rect x=-10 y=-10 width=140 height=120 fill="${lines.css()}" />
<path fill="${fins.css()}" d="M 60,0 h -57.73 v 100 z">
<title>fin colour: ${fins.css()}</title>
</path>
<path fill="${belly.css()}" d="M 70,0 h 40 l -57.73,100 h -40 z">
<title>belly colour: ${belly.css()}</title>
</path>
<path fill="${outer.css()}" d="M 120,0 v 100 h -57.73 z">
<title>outer body colour: ${outer.css()}</title>
</path>
<desc>
sample of the palette for ${this.name}.
fin colour: ${fins.css()}.
belly colour: ${belly.css()}.
outer body colour: ${outer.css()}.
</desc>
</svg>
<span class=history-name>${this.name}</span>
`;
let button = document.createElement('button');
button.className = 'history-item';
button.dataset.name = this.name;
button.innerHTML = content;
return button;
}
}
export class History {
items: string[];
constructor(items: string[] = []) { this.items = items; }
add(name: string): void { this.items.push(name); }
*iterNames(maxLength: number = 100): Iterable<string> {
let seen = new Set<string>;
let done = 0;
for (let i = this.items.length - 1; i >= 0; i--) {
if (maxLength >= 0 && done > maxLength) break;
const name = this.items[i]!;
if (!name || seen.has(name)) continue;
yield name;
seen.add(name);
done++;
}
}
// pass a negative number to iterate over all
*iterItems(maxLength?: number): Iterable<HistoryItem> {
for (const name of this.iterNames(maxLength)) {
const oklch = Color.colors(new Color.Rand(name), Color.KNOWN[name]);
const rgbs = Color.toRgbs(oklch);
yield new HistoryItem(name, oklch, rgbs);
}
}
static validate(x: unknown): History | undefined {
if (Array.isArray(x) && x.every(i => typeof i == 'string'))
return new History(x);
}
toJSON(): unknown { return this.items; }
save(persist = true): void {
const storage = persist ? localStorage : sessionStorage;
storage.setItem('history', JSON.stringify(this));
}
// if no history exists, or it's invalid, just start a new one
static load(): History {
const json =
sessionStorage.getItem('history') ??
localStorage.getItem('history');
if (json === null) return new History;
return History.validate(JSON.parse(json)) ?? new History;
}
addSave(name: string, persist = true): void {
this.add(name);
this.save(persist);
}
prune(maxLength?: number): void {
let keep = [];
for (let name of this.iterNames(maxLength)) keep.push(name);
this.items = keep.reverse();
}
}