128 lines
3.5 KiB
TypeScript
128 lines
3.5 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: bg, 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="${bg.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 | null): Iterable<string> {
|
|
let seen = new Set<string>;
|
|
let done = 0;
|
|
if (maxLength === undefined) maxLength = 100;
|
|
|
|
for (let i = this.items.length - 1; i >= 0; i--) {
|
|
if (maxLength !== null && done > maxLength) break;
|
|
const name = this.items[i]!;
|
|
if (!name || seen.has(name)) continue;
|
|
seen.add(name); done++;
|
|
yield name;
|
|
}
|
|
}
|
|
|
|
*iterItems(maxLength?: number | null): 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)) return;
|
|
if (!x.every(i => typeof i === 'string')) return;
|
|
return new History(x);
|
|
}
|
|
|
|
toJSON() { return this.items; }
|
|
|
|
save(persist = true) {
|
|
const storage = persist ? localStorage : sessionStorage;
|
|
storage.setItem('history', JSON.stringify(this));
|
|
}
|
|
|
|
// if the json was invalid, return it
|
|
// if no history exists just start a new one
|
|
static load(): History | string {
|
|
const json =
|
|
sessionStorage.getItem('history') ??
|
|
localStorage.getItem('history');
|
|
if (json != null) {
|
|
let h = History.validate(JSON.parse(json));
|
|
if (h) { h.prune(); return h; }
|
|
else return json;
|
|
} else {
|
|
return new History;
|
|
}
|
|
}
|
|
|
|
// if the json is invalid, discard it
|
|
static loadOrClear(): History {
|
|
const h = History.load();
|
|
return h instanceof History ? h : new History;
|
|
}
|
|
|
|
addSave(name: string, persist = true): void {
|
|
this.add(name);
|
|
this.save(persist);
|
|
}
|
|
|
|
prune(maxLength?: number | null) {
|
|
let keep = [];
|
|
for (let name of this.iterNames(maxLength)) keep.push(name);
|
|
this.items = keep.reverse();
|
|
}
|
|
|
|
pruneSave(maxLength?: number | null, persist = true) {
|
|
this.prune(maxLength);
|
|
this.save(persist);
|
|
}
|
|
}
|