Files
webawesome/docs/assets/scripts/my.js
Lea Verou bdd25571a8 Themer first slice (#857)
* Basic scaffolding

* Generate theme & palette data.js that other JS can import

* Make it possible to include page-card without links

* WIP

* Add `appearance` to details, closes #569

Except `accent` as that's a) far less useful and b) trickier due to the icon color

* Fix broken link

* WIP

* WIP

* Icons icon

* Unify styles for interactive cards

* Prevent focusing inside theme icons

* Fixes

* Action page cards

* Panel scrollables

* scrollable

* Scroll shadows

* Add renaming UI

* UI

* Move styling of heading icons to `ui.css`

* Support permalinks & CRUD

* Make clickable cards more accessible

* Style cards a little better

* Default to styles panel if theme is selected

* Update theme-icons.css

* Custom themes should be saved under Custom

* Get theme code

* Bigger title

* Fixes

* Use theme Vue app for remixing too

* Fix preview jank and make preview script more flexible

* Make radio groups scrollable

* Add affordance to button cards

* Sticky

* `<color-select>`

* Fix theme remixing

* Improve previewing logic

* Fix preview

* Move `domChange()` to separate module

`theme-picker.js` includes side-effects, which may not always be desirable everywhere we may want to import `domChange()`

* Update preview.js

* Panel animation

* Hide Save button if no changes and not saved

* Do not show blank code when no selection has been made

* Use theme slug in filename

* Remove unused component

* Better UI for editing title (and any other text)

* Tweak UI of renaming

* Better indicate default selection

* Fix preview reverting bug

* Fill out app preview with more examples

* Remove `zoom` from theme showcase (yields unexpected/painful results Safari), improve display in wider viewports

* Pending delete

* Make styles panel cards scrollable

* Fix some of the Safari issues

* Update search.css

* Update panel.css

* Select preview UI

* Fix typo

* Frame colors setting as color contrast

* Show dark mode in color mappings

* Brand color

* Swatch styling

* Fix caret icon

* Move Starting theme to the same level as other controls

* Rename typography to Fonts

* Fix bug: Swatch select should show swatches from the selected palette

* Move capitalize to shared utils

* Add utils for handling nested objects

* Icons panel

* Update code.js

* Move utils around

* Add fit and finish to sidebar panels

* Theme card: Move icons to separate data structure

* Move data to dedicated folder since we now have a lot more of it

* Add default icon families and variants to themes

* Data

* Add `deepEntries()`

* Add Duotone

* Spruce up icons preview

* Use theme's icon family in showcase

* Font cards

* Font cards

* Add `max-inline-size` to preview container

* Remove alternate preview options

* Remove theme subtitle

* Support FA kit codes

* Remove Pro badges from theme cards

* Use panagram preview for Fonts

* Consistent heading and label capitalization

* Classes for different icons-card types

* Update data.js.njk

* Variable style on icon family cards

* Fix Sharp Duotone

* Clean up FA kit code hint

* Hide non-functional Icon Library field

* Fix theme icon heights

* icon variant -> style in theme metadata

* Fix bug with icons defaults not being shown

* More convenient theme defaults

* Fix bug with non updating URL

* Fix bug

* Fix multiplying badges

* Custom docs pages

* Add Duotone icons to Mellow theme

* Fix 404

* Remove "Create" from sidebar

* Fix bug

* Move vue components to `/assets/`, move their CSS with them

* Safari/FF compatibility

* Make panels scrollable again

* Fix extra spacing

---------

Co-authored-by: lindsaym-fa <dev@lindsaym.design>
2025-05-09 17:04:06 -04:00

173 lines
3.5 KiB
JavaScript

const my = (globalThis.my = new EventTarget());
export default my;
class PersistedArray extends Array {
constructor(key) {
super();
this.key = key;
if (this.key) {
this.fromLocalStorage();
}
// Items were updated in another tab
addEventListener('storage', event => {
if (event.key === this.key || !event.key) {
this.fromLocalStorage();
}
});
}
/**
* Update data from local storage
*/
fromLocalStorage() {
// First, empty the array
this.splice(0, this.length);
// Then, fill it with the data from local storage
let saved = localStorage[this.key] ? JSON.parse(localStorage[this.key]) : null;
if (saved) {
this.push(...saved);
}
}
/**
* Write data to local storage
*/
toLocalStorage() {
if (this.length > 0) {
localStorage[this.key] = JSON.stringify(this);
} else {
delete localStorage[this.key];
}
}
}
class SavedEntities extends EventTarget {
constructor({ key, type, url }) {
super();
this.key = key;
this.type = type;
this.url = url ?? type + 's';
this.saved = new PersistedArray(key);
let all = this;
this.entityPrototype = {
type: this.type,
baseUrl: this.baseUrl,
get url() {
return all.getURL(this);
},
get parentUrl() {
return all.getParentURL(this);
},
delete() {
all.delete(this);
},
};
}
getUid() {
if (this.saved.length === 0) {
return 1;
}
let uids = new Set(this.saved.map(p => p.uid));
// Find first available number
for (let i = 1; i <= this.saved.length + 1; i++) {
if (!uids.has(i)) {
return i;
}
}
}
get baseUrl() {
return `/docs/${this.url}/`;
}
getURL(entity) {
return this.getParentURL(entity) + entity.search;
}
getParentURL(entity) {
return this.baseUrl + entity.id + '/';
}
getObject(entity) {
let ret = Object.create(this.entityPrototype, Object.getOwnPropertyDescriptors(entity));
// debugger;
return ret;
}
/**
* Save an entity, either by updating its existing entry or creating a new one
* @param {object} entity
*/
save(entity) {
if (!entity.uid) {
// First time saving
entity.uid = this.getUid();
}
let savedPalettes = this.saved;
let existingIndex = entity.uid ? this.saved.findIndex(p => p.uid === entity.uid) : -1;
let newIndex = existingIndex > -1 ? existingIndex : savedPalettes.length;
this.saved.splice(newIndex, 1, entity);
this.saved.toLocalStorage();
this.dispatchEvent(new CustomEvent('save', { detail: this.getObject(entity) }));
return entity;
}
delete(entity) {
let count = this.saved.length;
if (count === 0 || !entity?.uid) {
// No stored entities or this entity has not been saved
return;
}
// TODO improve UX of this
if (!confirm(`Are you sure you want to delete ${this.type}${entity.title}”?`)) {
return;
}
for (let index; (index = this.saved.findIndex(p => p.uid === entity.uid)) > -1; ) {
this.saved.splice(index, 1);
}
if (this.saved.length === count) {
// Nothing was removed
return;
}
this.saved.toLocalStorage();
this.dispatchEvent(new CustomEvent('delete', { detail: this.getObject(entity) }));
}
dispatchEvent(event) {
super.dispatchEvent(event);
my.dispatchEvent(event);
}
}
my.palettes = new SavedEntities({
key: 'savedPalettes',
type: 'palette',
});
my.themes = new SavedEntities({
key: 'savedThemes',
type: 'theme',
});