@wix/himalaya-cli 0.1026.0 → 0.1027.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/builder.d.ts +9 -0
- package/dist/cli.mjs +1052 -653
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/app-config.md +6 -0
- package/guides/headless-loop.md +3 -1
- package/package.json +2 -2
- package/templates/beauty-salon/dev/index.ts +331 -199
- package/templates/beauty-salon/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/beauty-salon/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/beauty-salon-owner/dev/index.ts +857 -458
- package/templates/beauty-salon-owner/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/beauty-salon-owner/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/branded-lite/dev/index.ts +1539 -623
- package/templates/branded-lite/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/branded-lite/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/community-club/dev/index.ts +237 -142
- package/templates/community-club/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/community-club/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/community-club-owner/dev/index.ts +857 -458
- package/templates/community-club-owner/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/community-club-owner/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/events-meetups/dev/index.ts +202 -129
- package/templates/events-meetups/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/events-meetups/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/events-meetups-owner/dev/index.ts +815 -458
- package/templates/events-meetups-owner/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/events-meetups-owner/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/gym-fitness/dev/index.ts +461 -212
- package/templates/gym-fitness/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/gym-fitness/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/gym-fitness-owner/dev/index.ts +758 -338
- package/templates/gym-fitness-owner/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/gym-fitness-owner/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/himi-chat/dev/index.ts +26 -1
- package/templates/nonprofit-cause/dev/index.ts +246 -153
- package/templates/nonprofit-cause/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/nonprofit-cause/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/nonprofit-cause-owner/dev/index.ts +716 -338
- package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/owner-lite/dev/index.ts +758 -338
- package/templates/owner-lite/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/owner-lite/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/podcast-creator/dev/index.ts +255 -164
- package/templates/podcast-creator/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/podcast-creator/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/podcast-creator-owner/dev/index.ts +857 -458
- package/templates/podcast-creator-owner/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/podcast-creator-owner/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/portfolio-creative/dev/index.ts +287 -175
- package/templates/portfolio-creative/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/portfolio-creative/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/portfolio-creative-owner/dev/index.ts +857 -458
- package/templates/portfolio-creative-owner/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/portfolio-creative-owner/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/restaurant/dev/index.ts +200 -126
- package/templates/restaurant/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/restaurant/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/restaurant-owner/dev/index.ts +758 -338
- package/templates/restaurant-owner/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/restaurant-owner/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/retail-store/dev/index.ts +113 -138
- package/templates/retail-store/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/retail-store/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/retail-store-owner/dev/index.ts +716 -338
- package/templates/retail-store-owner/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/retail-store-owner/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/surface-showcase/dev/index.ts +27 -0
- package/templates/yoga-studio/dev/index.ts +427 -198
- package/templates/yoga-studio/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/yoga-studio/tier5-src/_shared/collection-service.ts +28 -3
- package/templates/yoga-studio-owner/dev/index.ts +716 -338
- package/templates/yoga-studio-owner/tier5-src/_shared/collection-contract.ts +18 -8
- package/templates/yoga-studio-owner/tier5-src/_shared/collection-service.ts +28 -3
|
@@ -8,6 +8,9 @@ export interface CollectionConfig {
|
|
|
8
8
|
image?: string;
|
|
9
9
|
filter?: string;
|
|
10
10
|
filterValues?: string[];
|
|
11
|
+
/** A state key holding `{ label }` rows (e.g. a worker's full category list) whose
|
|
12
|
+
* labels are filter choices even when no loaded row carries them yet. */
|
|
13
|
+
filterValuesFrom?: string;
|
|
11
14
|
searchPlaceholder?: string;
|
|
12
15
|
sortLabels?: { default: string; az: string; za: string };
|
|
13
16
|
metadata?: string[];
|
|
@@ -20,15 +23,22 @@ export interface CollectionConfig {
|
|
|
20
23
|
reviewSelectedAction?: string;
|
|
21
24
|
/** Optional comfortable grid media height for immersive visual catalogs. */
|
|
22
25
|
gridImageHeight?: number;
|
|
26
|
+
/** The card's primary verb, shown as a visible button beside the overflow.
|
|
27
|
+
* `screenId` opens that screen with the row id; without it the verb reuses the
|
|
28
|
+
* row's own tap action (the detail screen that owns the verb). */
|
|
29
|
+
primaryVerb?: { label: string; screenId?: string };
|
|
23
30
|
}
|
|
31
|
+
/** At or below this many records, with no query/filter narrowing them and no further
|
|
32
|
+
* page, the list/grid toggle, Refine, chips, search and count are noise. */
|
|
33
|
+
export const COLLECTION_CHROME_MIN_ITEMS = 4;
|
|
24
34
|
export const COLLECTIONS: Record<string, CollectionConfig> = {
|
|
25
35
|
himiStoreShop: { rows: 'results', title: 'name', subtitle: 'subtitle', image: 'image', view: 'grid', backendQuery: true },
|
|
26
|
-
himiBookingServices: { rows: 'results', title: 'name', subtitle: 'subtitle', image: 'image', filter: 'category', view: 'grid', backendQuery: true, searchPlaceholder: 'Service name starts with…' },
|
|
36
|
+
himiBookingServices: { rows: 'results', title: 'name', subtitle: 'subtitle', image: 'image', filter: 'category', view: 'grid', backendQuery: true, searchPlaceholder: 'Service name starts with…', primaryVerb: { label: 'Book', screenId: 'himi_time' } },
|
|
27
37
|
himiBookingBookings: { rows: 'rows', title: 'title', subtitle: 'subtitle', metadata: ['status'], view: 'list', backendQuery: true,
|
|
28
38
|
defaultFilter: 'value:Upcoming', searchPlaceholder: 'Exact booking ID', filterValues: ['Upcoming', 'Past', 'Cancelled'],
|
|
29
39
|
sortLabels: { default: 'Soonest', az: 'Oldest', za: 'Latest' } },
|
|
30
|
-
himiEvents: { rows: 'results', title: 'title', subtitle: 'dateLine', image: 'image', view: 'grid', backendQuery: true, searchPlaceholder: 'Exact event title', filterValues: ['Upcoming', 'Past'], sortLabels: { default: 'Date', az: 'Title A–Z', za: 'Title Z–A' } },
|
|
31
|
-
himiBlogFeed: { rows: 'posts', title: 'title', subtitle: 'excerpt', image: 'image', view: 'grid' },
|
|
40
|
+
himiEvents: { rows: 'results', title: 'title', subtitle: 'dateLine', image: 'image', view: 'grid', backendQuery: true, searchPlaceholder: 'Exact event title', filterValues: ['Upcoming', 'Past'], sortLabels: { default: 'Date', az: 'Title A–Z', za: 'Title Z–A' }, primaryVerb: { label: 'Register' } },
|
|
41
|
+
himiBlogFeed: { rows: 'posts', title: 'title', subtitle: 'excerpt', image: 'image', filter: 'category', filterValuesFrom: 'categories', view: 'grid' },
|
|
32
42
|
himiOwnerLiteProducts: { rows: 'products', title: 'name', subtitle: 'price', image: 'image', view: 'grid', backendQuery: true },
|
|
33
43
|
himiOwnerModules: { instanceInput: 'module', rows: 'rows', title: 'title', subtitle: 'subtitle', image: 'image', filter: 'statusLabel', view: 'list', paged: true, reviewAction: 'prepareRecord', reviewSelectedAction: 'prepareSelected' },
|
|
34
44
|
himiOwnerLiteServices: { rows: 'services', title: 'name', subtitle: 'subtitle', view: 'list', backendQuery: true, searchPlaceholder: 'Service name starts with…' },
|
|
@@ -45,14 +55,14 @@ export const COLLECTIONS: Record<string, CollectionConfig> = {
|
|
|
45
55
|
sortLabels: { default: 'Newest', az: 'Oldest', za: 'Order # ↓' } },
|
|
46
56
|
himiBrandedPlans: { rows: 'rows', title: 'name', subtitle: 'description', metadata: ['priceLine', 'periodLine', 'trialBadge', 'perk1', 'perk2', 'perk3'], view: 'grid' },
|
|
47
57
|
himiBrandedPrograms: { rows: 'rows', title: 'name', subtitle: 'description', image: 'image', view: 'grid' },
|
|
48
|
-
himiBrandedCoupons: { rows: 'coupons', title: 'name', subtitle: 'subtitle', view: 'list' },
|
|
58
|
+
himiBrandedCoupons: { rows: 'coupons', title: 'name', subtitle: 'subtitle', view: 'list', primaryVerb: { label: 'Get code' } },
|
|
49
59
|
himiBrandedNotifications: { rows: 'items', title: 'name', subtitle: 'subtitle', image: 'photo', metadata: ['unreadDot'], view: 'list' },
|
|
50
|
-
himiBrandedLoyalty: { rows: 'rewards', title: 'name', subtitle: 'description', view: 'grid' },
|
|
60
|
+
himiBrandedLoyalty: { rows: 'rewards', title: 'name', subtitle: 'description', view: 'grid', primaryVerb: { label: 'Redeem' } },
|
|
51
61
|
himiBrandedReviews: { rows: 'reviews', title: 'title', subtitle: 'body', metadata: ['author', 'stars'], view: 'list' },
|
|
52
62
|
himiBrandedPortfolio: { rows: 'projects', title: 'title', subtitle: 'subtitle', image: 'image', view: 'grid', gridImageHeight: 180 },
|
|
53
63
|
himiBrandedInvoices: { rows: 'invoices', title: 'title', subtitle: 'subtitle', view: 'list' },
|
|
54
64
|
himiBrandedBadges: { rows: 'badges', title: 'name', subtitle: 'description', view: 'grid' },
|
|
55
|
-
himiBrandedDonations: { rows: 'campaigns', title: 'name', subtitle: 'description', view: 'grid' },
|
|
65
|
+
himiBrandedDonations: { rows: 'campaigns', title: 'name', subtitle: 'description', view: 'grid', primaryVerb: { label: 'Donate' } },
|
|
56
66
|
himiBrandedGiftCards: { rows: 'cards', title: 'name', subtitle: 'description', view: 'grid' },
|
|
57
67
|
himiBrandedVideos: { rows: 'channels', title: 'title', subtitle: 'description', view: 'grid' },
|
|
58
68
|
himiBrandedPodcasts: { rows: 'episodes', title: 'title', subtitle: 'subtitle', image: 'image', metadata: ['progressLabel'], view: 'list' },
|
|
@@ -114,7 +124,7 @@ export function collectionTitle(row: CollectionRow, config: CollectionConfig): s
|
|
|
114
124
|
return scalar(row[config.title]) || scalar(row.title) || scalar(row.name) || scalar(row.display) || scalar(row.label);
|
|
115
125
|
}
|
|
116
126
|
/** Does not mutate domain rows, paging, or domain-derived state. */
|
|
117
|
-
export function projectCollection(source: CollectionRow[], config: CollectionConfig, prefs: CollectionPreferences, backendQuery = false) {
|
|
127
|
+
export function projectCollection(source: CollectionRow[], config: CollectionConfig, prefs: CollectionPreferences, backendQuery = false, knownFilters: string[] = []) {
|
|
118
128
|
const query = prefs.query.trim().toLocaleLowerCase();
|
|
119
129
|
const unique = new Set<string>();
|
|
120
130
|
// Menu sections describe their following items; they are not actionable
|
|
@@ -130,7 +140,7 @@ export function projectCollection(source: CollectionRow[], config: CollectionCon
|
|
|
130
140
|
if (unique.has(id)) return false;
|
|
131
141
|
unique.add(id); return true;
|
|
132
142
|
});
|
|
133
|
-
const filters = config.filterValues ?? [...new Set(rows.map(row => config.filter ? scalar(row[config.filter]) : '').filter(Boolean))].sort();
|
|
143
|
+
const filters = config.filterValues ?? [...new Set([...knownFilters, ...rows.map(row => config.filter ? scalar(row[config.filter]) : '')].filter(Boolean))].sort();
|
|
134
144
|
const visible = rows.filter(row => {
|
|
135
145
|
if (backendQuery) return true;
|
|
136
146
|
if (prefs.filter === 'saved' && !prefs.savedIds.includes(collectionId(row))) return false;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineService as register, type ServiceDefinition } from '@himalaya/state';
|
|
2
|
-
import { COLLECTIONS, normalizeCollection, projectCollection, collectionId, type CollectionRow } from './collection-contract.js';
|
|
2
|
+
import { COLLECTIONS, COLLECTION_CHROME_MIN_ITEMS, initialCollection, normalizeCollection, projectCollection, collectionId, type CollectionRow } from './collection-contract.js';
|
|
3
3
|
import { localPreferences, type PreferenceRuntime } from './local-preferences.js';
|
|
4
4
|
import { copyParam, stashCopy } from './himi-copy.js';
|
|
5
5
|
import { createCollectionQuery, type CollectionQuerySource, type CollectionQueryState } from './collection-query.js';
|
|
@@ -54,6 +54,14 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
54
54
|
/** Navigation can open a collection in a useful, reviewable context. Keep
|
|
55
55
|
* this input additive and constrained to the same criteria the collection UI
|
|
56
56
|
* can select; arbitrary route values must never become backend filters. */
|
|
57
|
+
/** Labels of the worker-listed filter choices (`filterValuesFrom`), minus the "All" row. */
|
|
58
|
+
const listedFilters = (state: State): string[] => config.filterValuesFrom && Array.isArray(state[config.filterValuesFrom])
|
|
59
|
+
? (state[config.filterValuesFrom] as unknown[]).flatMap(row => {
|
|
60
|
+
const r = row && typeof row === 'object' ? row as Record<string, unknown> : {};
|
|
61
|
+
return typeof r.label === 'string' && r.id !== '' ? [r.label] : [];
|
|
62
|
+
})
|
|
63
|
+
: [];
|
|
64
|
+
let pendingFilter = '';
|
|
57
65
|
const applyNavigationCriteria = (arg: unknown, state: State): boolean => {
|
|
58
66
|
const input = object(arg);
|
|
59
67
|
let changed = false;
|
|
@@ -70,9 +78,14 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
70
78
|
const configured = config.filterValues ?? [];
|
|
71
79
|
const rows = Array.isArray(state[config.rows]) ? state[config.rows] as CollectionRow[] : [];
|
|
72
80
|
const derived = config.filter ? rows.map(row => String(row[config.filter!] ?? '')).filter(Boolean) : [];
|
|
81
|
+
const known = listedFilters(state);
|
|
73
82
|
const allowed = filter === 'all' || filter === 'saved' ||
|
|
74
|
-
(filter.startsWith('value:') && [...configured, ...derived].includes(filter.slice(6)));
|
|
83
|
+
(filter.startsWith('value:') && [...configured, ...derived, ...known].includes(filter.slice(6)));
|
|
75
84
|
if (allowed) { prefs.filter = filter; changed = true; }
|
|
85
|
+
// A worker-listed choice (e.g. a blog category) may not have loaded yet at boot:
|
|
86
|
+
// hold it and settle it against the list once the list arrives, instead of
|
|
87
|
+
// discarding a valid deep link before load().
|
|
88
|
+
else if (filter.startsWith('value:') && config.filterValuesFrom && known.length === 0) pendingFilter = filter;
|
|
76
89
|
}
|
|
77
90
|
if (changed) {
|
|
78
91
|
prefs.selectedIds = [];
|
|
@@ -90,7 +103,12 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
90
103
|
const derive = (s: State): State => {
|
|
91
104
|
syncSaved();
|
|
92
105
|
const rows = Array.isArray(s[config.rows]) ? (s[config.rows] as CollectionRow[]).filter(x => x && typeof x === 'object') : [];
|
|
93
|
-
const
|
|
106
|
+
const known = listedFilters(s);
|
|
107
|
+
if (pendingFilter && known.length) {
|
|
108
|
+
if (known.includes(pendingFilter.slice(6))) { prefs.filter = pendingFilter; persisted.write(prefs); }
|
|
109
|
+
pendingFilter = '';
|
|
110
|
+
}
|
|
111
|
+
const projected = projectCollection(rows, config, prefs, !!backend && config.backendQuery === true, known);
|
|
94
112
|
projected.items.forEach(row => { row.collectionSelectLabel = row.collectionSelected ? copyParam('collection.selected', 'Selected') : copyParam('collection.select', 'Select'); });
|
|
95
113
|
if (previewId && !rows.some(row => collectionId(row) === previewId)) previewId = '';
|
|
96
114
|
const filterChoices = [{ id: 'all', label: copyParam('collection.all', 'All') }, { id: 'saved', label: copyParam('collection.saved', 'Saved') },
|
|
@@ -106,7 +124,14 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
106
124
|
const queryError = typeof backendState?.error === 'string' ? backendState.error.trim() : '';
|
|
107
125
|
const errorMessage = queryError || domainError || copyParam('collection.loadError', 'This collection could not load. Try again.');
|
|
108
126
|
const canRetry = !!backend || typeof def.actions?.refresh === 'function' || typeof def.actions?.load === 'function' || typeof def.actions?.collectionRetry === 'function';
|
|
127
|
+
// A default view is what the screen opens on; anything else is the member's own
|
|
128
|
+
// narrowing, which keeps the chrome (and the Clear that undoes it) on screen.
|
|
129
|
+
// "All" and the collection's own default (e.g. Upcoming) are both un-narrowed: Clear
|
|
130
|
+
// resets to "All", so showing it on either would offer a reset that changes nothing.
|
|
131
|
+
const narrowed = !!prefs.query.trim() || (prefs.filter !== 'all' && prefs.filter !== initialCollection(config).filter);
|
|
132
|
+
const hideChrome = !narrowed && !s.nextCursor && projected.loaded < COLLECTION_CHROME_MIN_ITEMS;
|
|
109
133
|
return { ...s, collection: { ...prefs, ...projected,
|
|
134
|
+
hideChrome, noActiveCriteria: !narrowed,
|
|
110
135
|
viewName, viewMessage, saveMessage, noSavedViews: prefs.savedViews.length === 0,
|
|
111
136
|
cursor: s.pageError === true || s.loadingMore === true ? '' : s.nextCursor,
|
|
112
137
|
noMore: !s.nextCursor || s.pageError === true || s.loadingMore === true,
|