mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-12 04:09:12 +00:00
* 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>
158 lines
3.8 KiB
JavaScript
158 lines
3.8 KiB
JavaScript
import { deepEach, deepGet, deepSet } from './util/deep.js';
|
||
|
||
export default class Permalink extends URLSearchParams {
|
||
/** Params changed since last URL I/O */
|
||
changed = false;
|
||
|
||
constructor(params) {
|
||
super(location.search);
|
||
this.params = params;
|
||
}
|
||
|
||
toJSON() {
|
||
return Object.fromEntries(this.entries());
|
||
}
|
||
|
||
/**
|
||
* Set multiple values from an object. Nested values will be joined with a hyphen.
|
||
* @param {object} values - The object containing the values to set.
|
||
* @param {object} defaults - The object containing the default values.
|
||
*
|
||
*/
|
||
setAll(values, defaults) {
|
||
deepEach(values, (value, key, parent, path) => {
|
||
let fullPath = [...path, key];
|
||
let param = fullPath.join('-');
|
||
let defaultValue = deepGet(defaults, fullPath);
|
||
|
||
if (typeof value === 'object') {
|
||
// We'll handle this when we descend into it
|
||
return;
|
||
}
|
||
|
||
if (!value || value === defaultValue) {
|
||
// Remove the param from the URL
|
||
this.delete(param);
|
||
return;
|
||
}
|
||
|
||
this.set(param, value);
|
||
});
|
||
}
|
||
|
||
getAll(...args) {
|
||
if (args.length > 0) {
|
||
return super.getAll(...args);
|
||
}
|
||
|
||
// Get all values as a nested object
|
||
// Assumes that hyphens always mean nesting
|
||
let obj = {};
|
||
|
||
for (let [key, value] of this.entries()) {
|
||
let path = key.split('-');
|
||
deepSet(obj, path, value);
|
||
}
|
||
|
||
return obj;
|
||
}
|
||
|
||
delete(key, value) {
|
||
let hadValue = this.has(key);
|
||
super.delete(key, value);
|
||
|
||
if (hadValue) {
|
||
this.changed = true;
|
||
}
|
||
}
|
||
|
||
set(key, value, defaultValue) {
|
||
if (equals(value, defaultValue) || equals(value, '')) {
|
||
value = null;
|
||
}
|
||
|
||
value ??= null; // undefined -> null
|
||
|
||
let oldValue = Array.isArray(value) ? this.getAll(key) : this.get(key);
|
||
let changed = !equals(value, oldValue);
|
||
|
||
if (!changed) {
|
||
// Nothing to do here
|
||
return;
|
||
}
|
||
|
||
if (Array.isArray(value)) {
|
||
super.delete(key);
|
||
value = value.slice();
|
||
|
||
for (let v of value) {
|
||
if (v || v === 0) {
|
||
if (typeof v === 'object') {
|
||
super.append(key, JSON.stringify(v));
|
||
} else {
|
||
super.append(key, v);
|
||
}
|
||
}
|
||
}
|
||
} else if (value === null) {
|
||
super.delete(key);
|
||
} else {
|
||
super.set(key, value);
|
||
}
|
||
|
||
this.sort();
|
||
this.changed ||= changed;
|
||
}
|
||
|
||
/**
|
||
* Update page URL if it has changed since last time
|
||
*/
|
||
updateLocation() {
|
||
if (this.changed) {
|
||
// If there’s already a search, replace it.
|
||
// We don’t want to clog the user’s history while they iterate
|
||
let search = this.toString();
|
||
let historyAction = location.search && search ? 'replaceState' : 'pushState';
|
||
history[historyAction](null, '', `?${search}`);
|
||
this.changed = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
function equals(value, oldValue) {
|
||
if (Array.isArray(value) || Array.isArray(oldValue)) {
|
||
value = toArray(value);
|
||
oldValue = toArray(oldValue);
|
||
|
||
if (value.length !== oldValue.length) {
|
||
return false;
|
||
}
|
||
|
||
return value.every((v, i) => equals(v, oldValue[i]));
|
||
}
|
||
|
||
// (value ?? oldValue ?? true) returns true if they're both empty (null or undefined)
|
||
[value, oldValue] = [value, oldValue].map(v => (!v && v !== false && v !== 0 ? null : v));
|
||
return value === oldValue || String(value) === String(oldValue);
|
||
}
|
||
|
||
/**
|
||
* Convert a value to an array. `undefined` and `null` values are converted to an empty array.
|
||
* @param {*} value - The value to convert.
|
||
* @returns {any[]} The converted array.
|
||
*/
|
||
function toArray(value) {
|
||
value ??= [];
|
||
|
||
if (Array.isArray(value)) {
|
||
return value;
|
||
}
|
||
|
||
// Don't convert "foo" into ["f", "o", "o"]
|
||
if (typeof value !== 'string' && typeof value[Symbol.iterator] === 'function') {
|
||
return Array.from(value);
|
||
}
|
||
|
||
return [value];
|
||
}
|