@wix/himalaya-cli 0.1032.0 → 0.1034.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/cli.mjs +0 -2
- package/guides/getting-started.md +6 -2
- package/guides/wix-site-to-himi-app.md +9 -1
- package/package.json +2 -2
- package/templates/beauty-salon/dev/index.ts +398 -1
- package/templates/beauty-salon/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/beauty-salon/tier5-src/_shared/collection-service.ts +36 -5
- package/templates/beauty-salon/tier5-src/_shared/empty-flags.ts +33 -0
- package/templates/beauty-salon/tier5-src/himi-store-saved/index.ts +3 -2
- package/templates/beauty-salon/tier5-src/wix-spaces-collections/index.ts +3 -2
- package/templates/beauty-salon/tier5-src/wix-spaces-my-subscriptions/index.ts +11 -2
- package/templates/beauty-salon/tier5-src/wix-spaces-whats-new/index.ts +3 -2
- package/templates/beauty-salon-owner/dev/index.ts +1216 -0
- package/templates/beauty-salon-owner/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/beauty-salon-owner/tier5-src/_shared/collection-service.ts +36 -5
- package/templates/branded-lite/dev/index.ts +2239 -343
- package/templates/branded-lite/tier5-src/_shared/branded-load-state.ts +106 -0
- package/templates/branded-lite/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/branded-lite/tier5-src/_shared/collection-service.ts +36 -5
- package/templates/branded-lite/tier5-src/_shared/empty-flags.ts +33 -0
- package/templates/branded-lite/tier5-src/himi-branded-lite-badges/index.ts +20 -16
- package/templates/branded-lite/tier5-src/himi-branded-lite-coupons/index.ts +20 -20
- package/templates/branded-lite/tier5-src/himi-branded-lite-donations/index.ts +20 -16
- package/templates/branded-lite/tier5-src/himi-branded-lite-forms/index.ts +23 -31
- package/templates/branded-lite/tier5-src/himi-branded-lite-galleries/index.ts +19 -14
- package/templates/branded-lite/tier5-src/himi-branded-lite-giftcards/index.ts +20 -16
- package/templates/branded-lite/tier5-src/himi-branded-lite-inbox/index.ts +20 -15
- package/templates/branded-lite/tier5-src/himi-branded-lite-invoices/index.ts +20 -16
- package/templates/branded-lite/tier5-src/himi-branded-lite-loyalty/index.ts +20 -17
- package/templates/branded-lite/tier5-src/himi-branded-lite-notifications/index.ts +20 -18
- package/templates/branded-lite/tier5-src/himi-branded-lite-notifsettings/index.ts +23 -19
- package/templates/branded-lite/tier5-src/himi-branded-lite-podcasts/index.ts +1 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-reservations/index.ts +24 -20
- package/templates/branded-lite/tier5-src/himi-branded-lite-reviews/index.ts +20 -17
- package/templates/branded-lite/tier5-src/himi-branded-lite-videos/index.ts +19 -15
- package/templates/branded-lite/tier5-src/himi-store-saved/index.ts +3 -2
- package/templates/branded-lite/tier5-src/wix-spaces-collections/index.ts +3 -2
- package/templates/branded-lite/tier5-src/wix-spaces-forum/index.ts +3 -2
- package/templates/branded-lite/tier5-src/wix-spaces-members/index.ts +19 -6
- package/templates/branded-lite/tier5-src/wix-spaces-my-subscriptions/index.ts +11 -2
- package/templates/branded-lite/tier5-src/wix-spaces-new-message/index.ts +3 -2
- package/templates/branded-lite/tier5-src/wix-spaces-whats-new/index.ts +3 -2
- package/templates/community-club/dev/index.ts +283 -2
- package/templates/community-club/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/community-club/tier5-src/_shared/collection-service.ts +36 -5
- package/templates/community-club/tier5-src/_shared/empty-flags.ts +33 -0
- package/templates/community-club/tier5-src/wix-spaces-whats-new/index.ts +3 -2
- package/templates/community-club-owner/dev/index.ts +1216 -0
- package/templates/community-club-owner/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/community-club-owner/tier5-src/_shared/collection-service.ts +36 -5
- package/templates/events-meetups/dev/index.ts +197 -1
- package/templates/events-meetups/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/events-meetups/tier5-src/_shared/collection-service.ts +36 -5
- package/templates/events-meetups/tier5-src/_shared/empty-flags.ts +33 -0
- package/templates/events-meetups/tier5-src/wix-spaces-whats-new/index.ts +3 -2
- package/templates/events-meetups-owner/dev/index.ts +1088 -0
- package/templates/events-meetups-owner/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/events-meetups-owner/tier5-src/_shared/collection-service.ts +36 -5
- package/templates/gym-fitness/dev/index.ts +462 -1
- package/templates/gym-fitness/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/gym-fitness/tier5-src/_shared/collection-service.ts +36 -5
- package/templates/gym-fitness/tier5-src/_shared/empty-flags.ts +33 -0
- package/templates/gym-fitness/tier5-src/himi-store-saved/index.ts +3 -2
- package/templates/gym-fitness/tier5-src/wix-spaces-collections/index.ts +3 -2
- package/templates/gym-fitness/tier5-src/wix-spaces-my-subscriptions/index.ts +11 -2
- package/templates/gym-fitness/tier5-src/wix-spaces-whats-new/index.ts +3 -2
- package/templates/gym-fitness-owner/dev/index.ts +1280 -0
- package/templates/gym-fitness-owner/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/gym-fitness-owner/tier5-src/_shared/collection-service.ts +36 -5
- package/templates/nonprofit-cause/dev/index.ts +264 -1
- package/templates/nonprofit-cause/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/nonprofit-cause/tier5-src/_shared/collection-service.ts +36 -5
- package/templates/nonprofit-cause/tier5-src/_shared/empty-flags.ts +33 -0
- package/templates/nonprofit-cause/tier5-src/wix-spaces-my-subscriptions/index.ts +11 -2
- package/templates/nonprofit-cause/tier5-src/wix-spaces-whats-new/index.ts +3 -2
- package/templates/nonprofit-cause-owner/dev/index.ts +1152 -0
- package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-service.ts +36 -5
- package/templates/owner-lite/dev/index.ts +1280 -0
- package/templates/owner-lite/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/owner-lite/tier5-src/_shared/collection-service.ts +36 -5
- package/templates/podcast-creator/dev/index.ts +286 -23
- package/templates/podcast-creator/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/podcast-creator/tier5-src/_shared/collection-service.ts +36 -5
- package/templates/podcast-creator/tier5-src/_shared/empty-flags.ts +33 -0
- package/templates/podcast-creator/tier5-src/himi-branded-lite-podcasts/index.ts +1 -1
- package/templates/podcast-creator/tier5-src/wix-spaces-my-subscriptions/index.ts +11 -2
- package/templates/podcast-creator/tier5-src/wix-spaces-whats-new/index.ts +3 -2
- package/templates/podcast-creator-owner/dev/index.ts +1216 -0
- package/templates/podcast-creator-owner/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/podcast-creator-owner/tier5-src/_shared/collection-service.ts +36 -5
- package/templates/portfolio-creative/dev/index.ts +353 -23
- package/templates/portfolio-creative/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/portfolio-creative/tier5-src/_shared/collection-service.ts +36 -5
- package/templates/portfolio-creative/tier5-src/_shared/empty-flags.ts +33 -0
- package/templates/portfolio-creative/tier5-src/himi-store-saved/index.ts +3 -2
- package/templates/portfolio-creative/tier5-src/wix-spaces-collections/index.ts +3 -2
- package/templates/portfolio-creative/tier5-src/wix-spaces-whats-new/index.ts +3 -2
- package/templates/portfolio-creative-owner/dev/index.ts +1216 -0
- package/templates/portfolio-creative-owner/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/portfolio-creative-owner/tier5-src/_shared/collection-service.ts +36 -5
- package/templates/restaurant/dev/index.ts +197 -1
- package/templates/restaurant/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/restaurant/tier5-src/_shared/collection-service.ts +36 -5
- package/templates/restaurant/tier5-src/_shared/empty-flags.ts +33 -0
- package/templates/restaurant/tier5-src/wix-spaces-whats-new/index.ts +3 -2
- package/templates/restaurant-owner/dev/index.ts +1280 -0
- package/templates/restaurant-owner/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/restaurant-owner/tier5-src/_shared/collection-service.ts +36 -5
- package/templates/retail-store/dev/index.ts +203 -1
- package/templates/retail-store/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/retail-store/tier5-src/_shared/collection-service.ts +36 -5
- package/templates/retail-store/tier5-src/_shared/empty-flags.ts +33 -0
- package/templates/retail-store/tier5-src/himi-store-saved/index.ts +3 -2
- package/templates/retail-store/tier5-src/wix-spaces-collections/index.ts +3 -2
- package/templates/retail-store/tier5-src/wix-spaces-whats-new/index.ts +3 -2
- package/templates/retail-store-owner/dev/index.ts +1152 -0
- package/templates/retail-store-owner/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/retail-store-owner/tier5-src/_shared/collection-service.ts +36 -5
- package/templates/yoga-studio/dev/index.ts +392 -1
- package/templates/yoga-studio/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/yoga-studio/tier5-src/_shared/collection-service.ts +36 -5
- package/templates/yoga-studio/tier5-src/_shared/empty-flags.ts +33 -0
- package/templates/yoga-studio/tier5-src/wix-spaces-my-subscriptions/index.ts +11 -2
- package/templates/yoga-studio/tier5-src/wix-spaces-whats-new/index.ts +3 -2
- package/templates/yoga-studio-owner/dev/index.ts +1152 -0
- package/templates/yoga-studio-owner/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/yoga-studio-owner/tier5-src/_shared/collection-service.ts +36 -5
|
@@ -30,6 +30,9 @@ export interface CollectionConfig {
|
|
|
30
30
|
* `screenId` opens that screen with the row id; without it the verb reuses the
|
|
31
31
|
* row's own tap action (the detail screen that owns the verb). */
|
|
32
32
|
primaryVerb?: { label: string; screenId?: string };
|
|
33
|
+
/** A state field the DOMAIN narrows `rows` by (e.g. a category picker). While it is set,
|
|
34
|
+
* zero rows is a filtered result, not an empty source. */
|
|
35
|
+
narrowedBy?: string;
|
|
33
36
|
}
|
|
34
37
|
/** At or below this many records, with no query/filter narrowing them and no further
|
|
35
38
|
* page, the list/grid toggle, Refine, chips, search and count are noise. */
|
|
@@ -41,7 +44,7 @@ export const COLLECTIONS: Record<string, CollectionConfig> = {
|
|
|
41
44
|
defaultFilter: 'value:Upcoming', searchPlaceholder: 'Exact booking ID', filterValues: ['Upcoming', 'Past', 'Cancelled'],
|
|
42
45
|
sortLabels: { default: 'Soonest', az: 'Oldest', za: 'Latest' } },
|
|
43
46
|
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' } },
|
|
44
|
-
himiBlogFeed: { rows: 'posts', title: 'title', subtitle: 'excerpt', image: 'image', filter: 'category', filterValuesFrom: 'categories', view: 'grid' },
|
|
47
|
+
himiBlogFeed: { rows: 'posts', title: 'title', subtitle: 'excerpt', image: 'image', filter: 'category', filterValuesFrom: 'categories', view: 'grid', narrowedBy: 'selectedCategoryId' },
|
|
45
48
|
himiOwnerLiteProducts: { rows: 'products', title: 'name', subtitle: 'price', image: 'image', view: 'grid', backendQuery: true },
|
|
46
49
|
himiOwnerModules: { instanceInput: 'module', rows: 'rows', title: 'title', subtitle: 'subtitle', image: 'image', filter: 'statusLabel', view: 'list', paged: true, reviewAction: 'prepareRecord', reviewSelectedAction: 'prepareSelected' },
|
|
47
50
|
himiOwnerLiteServices: { rows: 'services', title: 'name', subtitle: 'subtitle', view: 'list', backendQuery: true, searchPlaceholder: 'Service name starts with…' },
|
|
@@ -122,14 +122,38 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
122
122
|
const errored = s.status === 'error' || s.isErrored === true || s.errored === true || backendState?.status === 'error';
|
|
123
123
|
const domainError = typeof s.error === 'string' ? s.error.trim() : '';
|
|
124
124
|
const queryError = typeof backendState?.error === 'string' ? backendState.error.trim() : '';
|
|
125
|
-
|
|
125
|
+
// Some domains carry their failure sentence in `emptyText` (portfolio, podcasts);
|
|
126
|
+
// the screen folds that node into this block, so keep the domain's own words.
|
|
127
|
+
const domainEmptyError = errored && typeof s.emptyText === 'string' ? s.emptyText.trim() : '';
|
|
128
|
+
const errorMessage = queryError || domainError || domainEmptyError || copyParam('collection.loadError', 'This collection could not load. Try again.');
|
|
126
129
|
const canRetry = !!backend || typeof def.actions?.refresh === 'function' || typeof def.actions?.load === 'function' || typeof def.actions?.collectionRetry === 'function';
|
|
130
|
+
// Wix refused the visitor: retrying cannot help, signing in can.
|
|
131
|
+
const signInRequired = errored && s.signInRequired === true;
|
|
132
|
+
// "the source has nothing" and "your filter matched nothing" are
|
|
133
|
+
// different states. Only the second may offer "Clear filters", and the list chrome
|
|
134
|
+
// (view, refine, chips, counter, search) means nothing over a source with no rows.
|
|
135
|
+
// Loaded-row collections hold the whole source in `rows`, so zero rows IS an empty
|
|
136
|
+
// source. A backend query returns only matches, so zero rows is source-empty only
|
|
137
|
+
// when nothing narrowed it.
|
|
138
|
+
// Anything the customer (or the domain) narrowed the result by. While narrowed, zero rows
|
|
139
|
+
// is "your filter matched nothing" and the controls that got them here must stay.
|
|
140
|
+
// The DOMAIN's own filter (config.narrowedBy, e.g. blog category) narrowed the rows: the
|
|
141
|
+
// domain owns that empty state and its clear action. The shared "Clear filters" block
|
|
142
|
+
// cannot reset a domain filter, so it never shows for one.
|
|
143
|
+
const domainNarrowed = !!config.narrowedBy && !!s[config.narrowedBy];
|
|
127
144
|
// A default view is what the screen opens on; anything else is the member's own
|
|
128
145
|
// narrowing, which keeps the chrome (and the Clear that undoes it) on screen.
|
|
129
146
|
// "All" and the collection's own default (e.g. Upcoming) are both un-narrowed: Clear
|
|
130
147
|
// 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
|
|
148
|
+
const narrowed = !!prefs.query.trim() || (prefs.filter !== 'all' && prefs.filter !== initialCollection(config).filter) || domainNarrowed;
|
|
149
|
+
const sourceEmpty = !loading && !errored && rows.length === 0 &&
|
|
150
|
+
(!(backend && config.backendQuery) || !narrowed) && !domainNarrowed;
|
|
151
|
+
const emptySourceText = (typeof s.emptyText === 'string' && s.emptyText.trim()) || copyParam('collection.emptySource', 'Nothing here yet');
|
|
152
|
+
// Chrome is noise over a small un-narrowed list (fewer than COLLECTION_CHROME_MIN_ITEMS
|
|
153
|
+
// records, no further page), over an empty source, and before the FIRST load or an
|
|
154
|
+
// un-narrowed failure. A re-query while typing keeps it (and the search field's focus).
|
|
155
|
+
const hideChrome = (!narrowed && !s.nextCursor && projected.loaded < COLLECTION_CHROME_MIN_ITEMS)
|
|
156
|
+
|| sourceEmpty || ((loading || errored) && rows.length === 0 && !narrowed);
|
|
133
157
|
return { ...s, collection: { ...prefs, ...projected,
|
|
134
158
|
hideChrome, noActiveCriteria: !narrowed,
|
|
135
159
|
viewName, viewMessage, saveMessage, noSavedViews: prefs.savedViews.length === 0,
|
|
@@ -138,8 +162,15 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
138
162
|
noPageError: s.pageError !== true,
|
|
139
163
|
noOffline: s.fromCache !== true,
|
|
140
164
|
noError: !errored,
|
|
141
|
-
noRetry: !canRetry,
|
|
165
|
+
noRetry: !canRetry || signInRequired,
|
|
166
|
+
noSignIn: !signInRequired,
|
|
167
|
+
errorTitle: signInRequired ? copyParam('collection.signInTitle', 'Sign in to see this') : copyParam('collection.loadErrorTitle', 'This collection could not load'),
|
|
142
168
|
errorMessage,
|
|
169
|
+
noLoading: !loading,
|
|
170
|
+
// De-dup against a domain that paints its OWN empty block (screen-kit emptyBlock is
|
|
171
|
+
// gated on the domain's top-level `noEmpty`): only one empty state ever shows.
|
|
172
|
+
noEmptySource: !sourceEmpty || s.noEmpty === false,
|
|
173
|
+
emptySourceText,
|
|
143
174
|
hideList: prefs.view !== 'list', hideGrid: prefs.view !== 'grid',
|
|
144
175
|
hideComfortable: prefs.density !== 'comfortable', hideCompact: prefs.density !== 'compact',
|
|
145
176
|
selectingLabel: prefs.selecting ? copyParam('collection.done', 'Done') : copyParam('collection.select', 'Select'),
|
|
@@ -147,7 +178,7 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
147
178
|
cannotSaveSelected: !prefs.selectedIds.length || prefs.selectedIds.some(id => !rows.some(row => collectionId(row) === id)),
|
|
148
179
|
filtersPresented, previewPresented: !!previewId, preview: projected.items.filter(row => row.collectionId === previewId),
|
|
149
180
|
persistenceLabel: persisted.available ? copyParam('collection.onDevice', 'Saved on this device') : copyParam('collection.sessionOnly', 'Choices last for this session'),
|
|
150
|
-
noEmpty: loading || errored || projected.items.length > 0,
|
|
181
|
+
noEmpty: loading || errored || sourceEmpty || domainNarrowed || projected.items.length > 0,
|
|
151
182
|
summary: backend && config.backendQuery
|
|
152
183
|
? (s.fromCache ? copyParam('collection.cached', 'Saved offline · may be outdated')
|
|
153
184
|
: typeof s.total === 'number' ? `${projected.items.length} / ${s.total} ${copyParam('collection.results', 'results')}`
|
|
@@ -4801,7 +4801,8 @@ const SCREENS = runtimeSetting({
|
|
|
4801
4801
|
"props": {
|
|
4802
4802
|
"text": "$service:spacesDeleteAccount.message",
|
|
4803
4803
|
"style": "caption",
|
|
4804
|
-
"color": "textTertiary"
|
|
4804
|
+
"color": "textTertiary",
|
|
4805
|
+
"collapseWhenEmpty": true
|
|
4805
4806
|
}
|
|
4806
4807
|
}
|
|
4807
4808
|
},
|
|
@@ -5096,6 +5097,9 @@ const SCREENS = runtimeSetting({
|
|
|
5096
5097
|
}
|
|
5097
5098
|
]
|
|
5098
5099
|
}
|
|
5100
|
+
},
|
|
5101
|
+
"modifiers": {
|
|
5102
|
+
"hiddenPath": "$service:spacesWhatsNew.noItems"
|
|
5099
5103
|
}
|
|
5100
5104
|
},
|
|
5101
5105
|
{
|
|
@@ -5321,6 +5325,24 @@ const SCREENS = runtimeSetting({
|
|
|
5321
5325
|
"props": {
|
|
5322
5326
|
"spacing": 0,
|
|
5323
5327
|
"children": [
|
|
5328
|
+
{
|
|
5329
|
+
"id": "himi_plans.rows.collection.loading",
|
|
5330
|
+
"body": {
|
|
5331
|
+
"package": "himalaya",
|
|
5332
|
+
"type": "Skeleton",
|
|
5333
|
+
"props": {
|
|
5334
|
+
"height": 72,
|
|
5335
|
+
"cornerRadius": 12,
|
|
5336
|
+
"count": 3
|
|
5337
|
+
}
|
|
5338
|
+
},
|
|
5339
|
+
"modifiers": {
|
|
5340
|
+
"hiddenPath": "$service:himiBrandedPlans.collection.noLoading",
|
|
5341
|
+
"padding": {
|
|
5342
|
+
"bottom": 12
|
|
5343
|
+
}
|
|
5344
|
+
}
|
|
5345
|
+
},
|
|
5324
5346
|
{
|
|
5325
5347
|
"id": "himi_plans.rows.collection.search",
|
|
5326
5348
|
"body": {
|
|
@@ -5560,6 +5582,52 @@ const SCREENS = runtimeSetting({
|
|
|
5560
5582
|
}
|
|
5561
5583
|
}
|
|
5562
5584
|
},
|
|
5585
|
+
{
|
|
5586
|
+
"id": "himi_plans.rows.collection.empty-source",
|
|
5587
|
+
"body": {
|
|
5588
|
+
"package": "himalaya",
|
|
5589
|
+
"type": "VStack",
|
|
5590
|
+
"props": {
|
|
5591
|
+
"spacing": 8,
|
|
5592
|
+
"alignment": "center",
|
|
5593
|
+
"children": [
|
|
5594
|
+
{
|
|
5595
|
+
"id": "",
|
|
5596
|
+
"body": {
|
|
5597
|
+
"package": "himalaya",
|
|
5598
|
+
"type": "Icon",
|
|
5599
|
+
"props": {
|
|
5600
|
+
"name": "inbox",
|
|
5601
|
+
"size": 28,
|
|
5602
|
+
"tint": "textTertiary"
|
|
5603
|
+
}
|
|
5604
|
+
},
|
|
5605
|
+
"modifiers": {
|
|
5606
|
+
"accessibilityRole": "decorative"
|
|
5607
|
+
}
|
|
5608
|
+
},
|
|
5609
|
+
{
|
|
5610
|
+
"id": "",
|
|
5611
|
+
"body": {
|
|
5612
|
+
"package": "himalaya",
|
|
5613
|
+
"type": "Text",
|
|
5614
|
+
"props": {
|
|
5615
|
+
"text": "$service:himiBrandedPlans.collection.emptySourceText",
|
|
5616
|
+
"style": "body",
|
|
5617
|
+
"color": "textSecondary"
|
|
5618
|
+
}
|
|
5619
|
+
}
|
|
5620
|
+
}
|
|
5621
|
+
]
|
|
5622
|
+
}
|
|
5623
|
+
},
|
|
5624
|
+
"modifiers": {
|
|
5625
|
+
"hiddenPath": "$service:himiBrandedPlans.collection.noEmptySource",
|
|
5626
|
+
"padding": {
|
|
5627
|
+
"bottom": 12
|
|
5628
|
+
}
|
|
5629
|
+
}
|
|
5630
|
+
},
|
|
5563
5631
|
{
|
|
5564
5632
|
"id": "",
|
|
5565
5633
|
"body": {
|
|
@@ -7915,6 +7983,9 @@ const SCREENS = runtimeSetting({
|
|
|
7915
7983
|
}
|
|
7916
7984
|
]
|
|
7917
7985
|
}
|
|
7986
|
+
},
|
|
7987
|
+
"modifiers": {
|
|
7988
|
+
"hiddenPath": "$service:spacesMySubscriptions.noOrders"
|
|
7918
7989
|
}
|
|
7919
7990
|
},
|
|
7920
7991
|
{
|
|
@@ -9448,6 +9519,24 @@ const SCREENS = runtimeSetting({
|
|
|
9448
9519
|
"props": {
|
|
9449
9520
|
"spacing": 0,
|
|
9450
9521
|
"children": [
|
|
9522
|
+
{
|
|
9523
|
+
"id": "himi_events.list.collection.loading",
|
|
9524
|
+
"body": {
|
|
9525
|
+
"package": "himalaya",
|
|
9526
|
+
"type": "Skeleton",
|
|
9527
|
+
"props": {
|
|
9528
|
+
"height": 72,
|
|
9529
|
+
"cornerRadius": 12,
|
|
9530
|
+
"count": 3
|
|
9531
|
+
}
|
|
9532
|
+
},
|
|
9533
|
+
"modifiers": {
|
|
9534
|
+
"hiddenPath": "$service:himiEvents.collection.noLoading",
|
|
9535
|
+
"padding": {
|
|
9536
|
+
"bottom": 12
|
|
9537
|
+
}
|
|
9538
|
+
}
|
|
9539
|
+
},
|
|
9451
9540
|
{
|
|
9452
9541
|
"id": "",
|
|
9453
9542
|
"body": {
|
|
@@ -9709,6 +9798,52 @@ const SCREENS = runtimeSetting({
|
|
|
9709
9798
|
}
|
|
9710
9799
|
}
|
|
9711
9800
|
},
|
|
9801
|
+
{
|
|
9802
|
+
"id": "himi_events.list.collection.empty-source",
|
|
9803
|
+
"body": {
|
|
9804
|
+
"package": "himalaya",
|
|
9805
|
+
"type": "VStack",
|
|
9806
|
+
"props": {
|
|
9807
|
+
"spacing": 8,
|
|
9808
|
+
"alignment": "center",
|
|
9809
|
+
"children": [
|
|
9810
|
+
{
|
|
9811
|
+
"id": "",
|
|
9812
|
+
"body": {
|
|
9813
|
+
"package": "himalaya",
|
|
9814
|
+
"type": "Icon",
|
|
9815
|
+
"props": {
|
|
9816
|
+
"name": "inbox",
|
|
9817
|
+
"size": 28,
|
|
9818
|
+
"tint": "textTertiary"
|
|
9819
|
+
}
|
|
9820
|
+
},
|
|
9821
|
+
"modifiers": {
|
|
9822
|
+
"accessibilityRole": "decorative"
|
|
9823
|
+
}
|
|
9824
|
+
},
|
|
9825
|
+
{
|
|
9826
|
+
"id": "",
|
|
9827
|
+
"body": {
|
|
9828
|
+
"package": "himalaya",
|
|
9829
|
+
"type": "Text",
|
|
9830
|
+
"props": {
|
|
9831
|
+
"text": "$service:himiEvents.collection.emptySourceText",
|
|
9832
|
+
"style": "body",
|
|
9833
|
+
"color": "textSecondary"
|
|
9834
|
+
}
|
|
9835
|
+
}
|
|
9836
|
+
}
|
|
9837
|
+
]
|
|
9838
|
+
}
|
|
9839
|
+
},
|
|
9840
|
+
"modifiers": {
|
|
9841
|
+
"hiddenPath": "$service:himiEvents.collection.noEmptySource",
|
|
9842
|
+
"padding": {
|
|
9843
|
+
"bottom": 12
|
|
9844
|
+
}
|
|
9845
|
+
}
|
|
9846
|
+
},
|
|
9712
9847
|
{
|
|
9713
9848
|
"id": "",
|
|
9714
9849
|
"body": {
|
|
@@ -12752,6 +12887,24 @@ const SCREENS = runtimeSetting({
|
|
|
12752
12887
|
"props": {
|
|
12753
12888
|
"spacing": 0,
|
|
12754
12889
|
"children": [
|
|
12890
|
+
{
|
|
12891
|
+
"id": "himi_blog.list.collection.loading",
|
|
12892
|
+
"body": {
|
|
12893
|
+
"package": "himalaya",
|
|
12894
|
+
"type": "Skeleton",
|
|
12895
|
+
"props": {
|
|
12896
|
+
"height": 72,
|
|
12897
|
+
"cornerRadius": 12,
|
|
12898
|
+
"count": 3
|
|
12899
|
+
}
|
|
12900
|
+
},
|
|
12901
|
+
"modifiers": {
|
|
12902
|
+
"hiddenPath": "$service:himiBlogFeed.collection.noLoading",
|
|
12903
|
+
"padding": {
|
|
12904
|
+
"bottom": 12
|
|
12905
|
+
}
|
|
12906
|
+
}
|
|
12907
|
+
},
|
|
12755
12908
|
{
|
|
12756
12909
|
"id": "",
|
|
12757
12910
|
"body": {
|
|
@@ -12970,6 +13123,52 @@ const SCREENS = runtimeSetting({
|
|
|
12970
13123
|
}
|
|
12971
13124
|
}
|
|
12972
13125
|
},
|
|
13126
|
+
{
|
|
13127
|
+
"id": "himi_blog.list.collection.empty-source",
|
|
13128
|
+
"body": {
|
|
13129
|
+
"package": "himalaya",
|
|
13130
|
+
"type": "VStack",
|
|
13131
|
+
"props": {
|
|
13132
|
+
"spacing": 8,
|
|
13133
|
+
"alignment": "center",
|
|
13134
|
+
"children": [
|
|
13135
|
+
{
|
|
13136
|
+
"id": "",
|
|
13137
|
+
"body": {
|
|
13138
|
+
"package": "himalaya",
|
|
13139
|
+
"type": "Icon",
|
|
13140
|
+
"props": {
|
|
13141
|
+
"name": "inbox",
|
|
13142
|
+
"size": 28,
|
|
13143
|
+
"tint": "textTertiary"
|
|
13144
|
+
}
|
|
13145
|
+
},
|
|
13146
|
+
"modifiers": {
|
|
13147
|
+
"accessibilityRole": "decorative"
|
|
13148
|
+
}
|
|
13149
|
+
},
|
|
13150
|
+
{
|
|
13151
|
+
"id": "",
|
|
13152
|
+
"body": {
|
|
13153
|
+
"package": "himalaya",
|
|
13154
|
+
"type": "Text",
|
|
13155
|
+
"props": {
|
|
13156
|
+
"text": "$service:himiBlogFeed.collection.emptySourceText",
|
|
13157
|
+
"style": "body",
|
|
13158
|
+
"color": "textSecondary"
|
|
13159
|
+
}
|
|
13160
|
+
}
|
|
13161
|
+
}
|
|
13162
|
+
]
|
|
13163
|
+
}
|
|
13164
|
+
},
|
|
13165
|
+
"modifiers": {
|
|
13166
|
+
"hiddenPath": "$service:himiBlogFeed.collection.noEmptySource",
|
|
13167
|
+
"padding": {
|
|
13168
|
+
"bottom": 12
|
|
13169
|
+
}
|
|
13170
|
+
}
|
|
13171
|
+
},
|
|
12973
13172
|
{
|
|
12974
13173
|
"id": "",
|
|
12975
13174
|
"body": {
|
|
@@ -15624,6 +15823,24 @@ const SCREENS = runtimeSetting({
|
|
|
15624
15823
|
"props": {
|
|
15625
15824
|
"spacing": 0,
|
|
15626
15825
|
"children": [
|
|
15826
|
+
{
|
|
15827
|
+
"id": "himi_podcasts.rows.collection.loading",
|
|
15828
|
+
"body": {
|
|
15829
|
+
"package": "himalaya",
|
|
15830
|
+
"type": "Skeleton",
|
|
15831
|
+
"props": {
|
|
15832
|
+
"height": 72,
|
|
15833
|
+
"cornerRadius": 12,
|
|
15834
|
+
"count": 3
|
|
15835
|
+
}
|
|
15836
|
+
},
|
|
15837
|
+
"modifiers": {
|
|
15838
|
+
"hiddenPath": "$service:himiBrandedPodcasts.collection.noLoading",
|
|
15839
|
+
"padding": {
|
|
15840
|
+
"bottom": 12
|
|
15841
|
+
}
|
|
15842
|
+
}
|
|
15843
|
+
},
|
|
15627
15844
|
{
|
|
15628
15845
|
"id": "himi_podcasts.rows.collection.search",
|
|
15629
15846
|
"body": {
|
|
@@ -15877,7 +16094,7 @@ const SCREENS = runtimeSetting({
|
|
|
15877
16094
|
"package": "himalaya",
|
|
15878
16095
|
"type": "Text",
|
|
15879
16096
|
"props": {
|
|
15880
|
-
"text": "
|
|
16097
|
+
"text": "$service:himiBrandedPodcasts.collection.errorTitle",
|
|
15881
16098
|
"style": "bodyEmphasis"
|
|
15882
16099
|
}
|
|
15883
16100
|
}
|
|
@@ -15911,6 +16128,27 @@ const SCREENS = runtimeSetting({
|
|
|
15911
16128
|
"modifiers": {
|
|
15912
16129
|
"hiddenPath": "$service:himiBrandedPodcasts.collection.noRetry"
|
|
15913
16130
|
}
|
|
16131
|
+
},
|
|
16132
|
+
{
|
|
16133
|
+
"id": "himi_podcasts.rows.collection.sign_in",
|
|
16134
|
+
"body": {
|
|
16135
|
+
"package": "himalaya",
|
|
16136
|
+
"type": "Button",
|
|
16137
|
+
"props": {
|
|
16138
|
+
"title": "Sign in",
|
|
16139
|
+
"style": "primary",
|
|
16140
|
+
"leadingIcon": "person",
|
|
16141
|
+
"action": {
|
|
16142
|
+
"actionId": "himi.open",
|
|
16143
|
+
"params": {
|
|
16144
|
+
"screenId": "himi_account"
|
|
16145
|
+
}
|
|
16146
|
+
}
|
|
16147
|
+
}
|
|
16148
|
+
},
|
|
16149
|
+
"modifiers": {
|
|
16150
|
+
"hiddenPath": "$service:himiBrandedPodcasts.collection.noSignIn"
|
|
16151
|
+
}
|
|
15914
16152
|
}
|
|
15915
16153
|
]
|
|
15916
16154
|
}
|
|
@@ -15922,6 +16160,52 @@ const SCREENS = runtimeSetting({
|
|
|
15922
16160
|
}
|
|
15923
16161
|
}
|
|
15924
16162
|
},
|
|
16163
|
+
{
|
|
16164
|
+
"id": "himi_podcasts.rows.collection.empty-source",
|
|
16165
|
+
"body": {
|
|
16166
|
+
"package": "himalaya",
|
|
16167
|
+
"type": "VStack",
|
|
16168
|
+
"props": {
|
|
16169
|
+
"spacing": 8,
|
|
16170
|
+
"alignment": "center",
|
|
16171
|
+
"children": [
|
|
16172
|
+
{
|
|
16173
|
+
"id": "",
|
|
16174
|
+
"body": {
|
|
16175
|
+
"package": "himalaya",
|
|
16176
|
+
"type": "Icon",
|
|
16177
|
+
"props": {
|
|
16178
|
+
"name": "inbox",
|
|
16179
|
+
"size": 28,
|
|
16180
|
+
"tint": "textTertiary"
|
|
16181
|
+
}
|
|
16182
|
+
},
|
|
16183
|
+
"modifiers": {
|
|
16184
|
+
"accessibilityRole": "decorative"
|
|
16185
|
+
}
|
|
16186
|
+
},
|
|
16187
|
+
{
|
|
16188
|
+
"id": "",
|
|
16189
|
+
"body": {
|
|
16190
|
+
"package": "himalaya",
|
|
16191
|
+
"type": "Text",
|
|
16192
|
+
"props": {
|
|
16193
|
+
"text": "$service:himiBrandedPodcasts.collection.emptySourceText",
|
|
16194
|
+
"style": "body",
|
|
16195
|
+
"color": "textSecondary"
|
|
16196
|
+
}
|
|
16197
|
+
}
|
|
16198
|
+
}
|
|
16199
|
+
]
|
|
16200
|
+
}
|
|
16201
|
+
},
|
|
16202
|
+
"modifiers": {
|
|
16203
|
+
"hiddenPath": "$service:himiBrandedPodcasts.collection.noEmptySource",
|
|
16204
|
+
"padding": {
|
|
16205
|
+
"bottom": 12
|
|
16206
|
+
}
|
|
16207
|
+
}
|
|
16208
|
+
},
|
|
15925
16209
|
{
|
|
15926
16210
|
"id": "",
|
|
15927
16211
|
"body": {
|
|
@@ -17950,27 +18234,6 @@ const SCREENS = runtimeSetting({
|
|
|
17950
18234
|
]
|
|
17951
18235
|
}
|
|
17952
18236
|
}
|
|
17953
|
-
},
|
|
17954
|
-
{
|
|
17955
|
-
"id": "",
|
|
17956
|
-
"body": {
|
|
17957
|
-
"package": "himalaya",
|
|
17958
|
-
"type": "Text",
|
|
17959
|
-
"props": {
|
|
17960
|
-
"text": "$service:himiBrandedPodcasts.emptyText",
|
|
17961
|
-
"style": "body",
|
|
17962
|
-
"color": "textSecondary",
|
|
17963
|
-
"collapseWhenEmpty": true
|
|
17964
|
-
}
|
|
17965
|
-
},
|
|
17966
|
-
"modifiers": {
|
|
17967
|
-
"padding": {
|
|
17968
|
-
"leading": 8,
|
|
17969
|
-
"trailing": 8,
|
|
17970
|
-
"top": 8,
|
|
17971
|
-
"bottom": 0
|
|
17972
|
-
}
|
|
17973
|
-
}
|
|
17974
18237
|
}
|
|
17975
18238
|
]
|
|
17976
18239
|
}
|
|
@@ -30,6 +30,9 @@ export interface CollectionConfig {
|
|
|
30
30
|
* `screenId` opens that screen with the row id; without it the verb reuses the
|
|
31
31
|
* row's own tap action (the detail screen that owns the verb). */
|
|
32
32
|
primaryVerb?: { label: string; screenId?: string };
|
|
33
|
+
/** A state field the DOMAIN narrows `rows` by (e.g. a category picker). While it is set,
|
|
34
|
+
* zero rows is a filtered result, not an empty source. */
|
|
35
|
+
narrowedBy?: string;
|
|
33
36
|
}
|
|
34
37
|
/** At or below this many records, with no query/filter narrowing them and no further
|
|
35
38
|
* page, the list/grid toggle, Refine, chips, search and count are noise. */
|
|
@@ -41,7 +44,7 @@ export const COLLECTIONS: Record<string, CollectionConfig> = {
|
|
|
41
44
|
defaultFilter: 'value:Upcoming', searchPlaceholder: 'Exact booking ID', filterValues: ['Upcoming', 'Past', 'Cancelled'],
|
|
42
45
|
sortLabels: { default: 'Soonest', az: 'Oldest', za: 'Latest' } },
|
|
43
46
|
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' } },
|
|
44
|
-
himiBlogFeed: { rows: 'posts', title: 'title', subtitle: 'excerpt', image: 'image', filter: 'category', filterValuesFrom: 'categories', view: 'grid' },
|
|
47
|
+
himiBlogFeed: { rows: 'posts', title: 'title', subtitle: 'excerpt', image: 'image', filter: 'category', filterValuesFrom: 'categories', view: 'grid', narrowedBy: 'selectedCategoryId' },
|
|
45
48
|
himiOwnerLiteProducts: { rows: 'products', title: 'name', subtitle: 'price', image: 'image', view: 'grid', backendQuery: true },
|
|
46
49
|
himiOwnerModules: { instanceInput: 'module', rows: 'rows', title: 'title', subtitle: 'subtitle', image: 'image', filter: 'statusLabel', view: 'list', paged: true, reviewAction: 'prepareRecord', reviewSelectedAction: 'prepareSelected' },
|
|
47
50
|
himiOwnerLiteServices: { rows: 'services', title: 'name', subtitle: 'subtitle', view: 'list', backendQuery: true, searchPlaceholder: 'Service name starts with…' },
|
|
@@ -122,14 +122,38 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
122
122
|
const errored = s.status === 'error' || s.isErrored === true || s.errored === true || backendState?.status === 'error';
|
|
123
123
|
const domainError = typeof s.error === 'string' ? s.error.trim() : '';
|
|
124
124
|
const queryError = typeof backendState?.error === 'string' ? backendState.error.trim() : '';
|
|
125
|
-
|
|
125
|
+
// Some domains carry their failure sentence in `emptyText` (portfolio, podcasts);
|
|
126
|
+
// the screen folds that node into this block, so keep the domain's own words.
|
|
127
|
+
const domainEmptyError = errored && typeof s.emptyText === 'string' ? s.emptyText.trim() : '';
|
|
128
|
+
const errorMessage = queryError || domainError || domainEmptyError || copyParam('collection.loadError', 'This collection could not load. Try again.');
|
|
126
129
|
const canRetry = !!backend || typeof def.actions?.refresh === 'function' || typeof def.actions?.load === 'function' || typeof def.actions?.collectionRetry === 'function';
|
|
130
|
+
// Wix refused the visitor: retrying cannot help, signing in can.
|
|
131
|
+
const signInRequired = errored && s.signInRequired === true;
|
|
132
|
+
// "the source has nothing" and "your filter matched nothing" are
|
|
133
|
+
// different states. Only the second may offer "Clear filters", and the list chrome
|
|
134
|
+
// (view, refine, chips, counter, search) means nothing over a source with no rows.
|
|
135
|
+
// Loaded-row collections hold the whole source in `rows`, so zero rows IS an empty
|
|
136
|
+
// source. A backend query returns only matches, so zero rows is source-empty only
|
|
137
|
+
// when nothing narrowed it.
|
|
138
|
+
// Anything the customer (or the domain) narrowed the result by. While narrowed, zero rows
|
|
139
|
+
// is "your filter matched nothing" and the controls that got them here must stay.
|
|
140
|
+
// The DOMAIN's own filter (config.narrowedBy, e.g. blog category) narrowed the rows: the
|
|
141
|
+
// domain owns that empty state and its clear action. The shared "Clear filters" block
|
|
142
|
+
// cannot reset a domain filter, so it never shows for one.
|
|
143
|
+
const domainNarrowed = !!config.narrowedBy && !!s[config.narrowedBy];
|
|
127
144
|
// A default view is what the screen opens on; anything else is the member's own
|
|
128
145
|
// narrowing, which keeps the chrome (and the Clear that undoes it) on screen.
|
|
129
146
|
// "All" and the collection's own default (e.g. Upcoming) are both un-narrowed: Clear
|
|
130
147
|
// 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
|
|
148
|
+
const narrowed = !!prefs.query.trim() || (prefs.filter !== 'all' && prefs.filter !== initialCollection(config).filter) || domainNarrowed;
|
|
149
|
+
const sourceEmpty = !loading && !errored && rows.length === 0 &&
|
|
150
|
+
(!(backend && config.backendQuery) || !narrowed) && !domainNarrowed;
|
|
151
|
+
const emptySourceText = (typeof s.emptyText === 'string' && s.emptyText.trim()) || copyParam('collection.emptySource', 'Nothing here yet');
|
|
152
|
+
// Chrome is noise over a small un-narrowed list (fewer than COLLECTION_CHROME_MIN_ITEMS
|
|
153
|
+
// records, no further page), over an empty source, and before the FIRST load or an
|
|
154
|
+
// un-narrowed failure. A re-query while typing keeps it (and the search field's focus).
|
|
155
|
+
const hideChrome = (!narrowed && !s.nextCursor && projected.loaded < COLLECTION_CHROME_MIN_ITEMS)
|
|
156
|
+
|| sourceEmpty || ((loading || errored) && rows.length === 0 && !narrowed);
|
|
133
157
|
return { ...s, collection: { ...prefs, ...projected,
|
|
134
158
|
hideChrome, noActiveCriteria: !narrowed,
|
|
135
159
|
viewName, viewMessage, saveMessage, noSavedViews: prefs.savedViews.length === 0,
|
|
@@ -138,8 +162,15 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
138
162
|
noPageError: s.pageError !== true,
|
|
139
163
|
noOffline: s.fromCache !== true,
|
|
140
164
|
noError: !errored,
|
|
141
|
-
noRetry: !canRetry,
|
|
165
|
+
noRetry: !canRetry || signInRequired,
|
|
166
|
+
noSignIn: !signInRequired,
|
|
167
|
+
errorTitle: signInRequired ? copyParam('collection.signInTitle', 'Sign in to see this') : copyParam('collection.loadErrorTitle', 'This collection could not load'),
|
|
142
168
|
errorMessage,
|
|
169
|
+
noLoading: !loading,
|
|
170
|
+
// De-dup against a domain that paints its OWN empty block (screen-kit emptyBlock is
|
|
171
|
+
// gated on the domain's top-level `noEmpty`): only one empty state ever shows.
|
|
172
|
+
noEmptySource: !sourceEmpty || s.noEmpty === false,
|
|
173
|
+
emptySourceText,
|
|
143
174
|
hideList: prefs.view !== 'list', hideGrid: prefs.view !== 'grid',
|
|
144
175
|
hideComfortable: prefs.density !== 'comfortable', hideCompact: prefs.density !== 'compact',
|
|
145
176
|
selectingLabel: prefs.selecting ? copyParam('collection.done', 'Done') : copyParam('collection.select', 'Select'),
|
|
@@ -147,7 +178,7 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
147
178
|
cannotSaveSelected: !prefs.selectedIds.length || prefs.selectedIds.some(id => !rows.some(row => collectionId(row) === id)),
|
|
148
179
|
filtersPresented, previewPresented: !!previewId, preview: projected.items.filter(row => row.collectionId === previewId),
|
|
149
180
|
persistenceLabel: persisted.available ? copyParam('collection.onDevice', 'Saved on this device') : copyParam('collection.sessionOnly', 'Choices last for this session'),
|
|
150
|
-
noEmpty: loading || errored || projected.items.length > 0,
|
|
181
|
+
noEmpty: loading || errored || sourceEmpty || domainNarrowed || projected.items.length > 0,
|
|
151
182
|
summary: backend && config.backendQuery
|
|
152
183
|
? (s.fromCache ? copyParam('collection.cached', 'Saved offline · may be outdated')
|
|
153
184
|
: typeof s.total === 'number' ? `${projected.items.length} / ${s.total} ${copyParam('collection.results', 'results')}`
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Empty-collection flags for descriptors.
|
|
2
|
+
//
|
|
3
|
+
// A bound Grid/LazyList with zero rows still renders a zero-height box, and a stack
|
|
4
|
+
// allocates its `spacing` between CHILDREN, not between painted children — so every
|
|
5
|
+
// empty list left a full gap of blank on the screen (the measure pass's `dead-gap`, on
|
|
6
|
+
// iOS and Android as much as on web). `hiddenWhen` hides on a TRUE scalar only, and an
|
|
7
|
+
// empty array is not one, so a descriptor cannot gate a list on its own rows.
|
|
8
|
+
//
|
|
9
|
+
// `withEmptyFlags(def, { rows: "noRows" })` derives `noRows = rows is empty` into the
|
|
10
|
+
// initial state and after EVERY action (sync or async), so a builder can write
|
|
11
|
+
// `hiddenWhen(grid(...), "$service:svc.noRows")` without each reducer remembering to.
|
|
12
|
+
type State = Record<string, unknown>;
|
|
13
|
+
type Action = (s: State, ...args: unknown[]) => unknown;
|
|
14
|
+
|
|
15
|
+
export function withEmptyFlags<D extends { state: object; actions?: object }>(def: D, flags: Record<string, string>): D {
|
|
16
|
+
const derive = <T>(s: T): T => {
|
|
17
|
+
if (!s || typeof s !== "object") return s;
|
|
18
|
+
const out = { ...(s as State) };
|
|
19
|
+
for (const [rows, flag] of Object.entries(flags)) {
|
|
20
|
+
const v = out[rows];
|
|
21
|
+
out[flag] = !Array.isArray(v) || v.length === 0;
|
|
22
|
+
}
|
|
23
|
+
return out as T;
|
|
24
|
+
};
|
|
25
|
+
const actions: Record<string, Action> = {};
|
|
26
|
+
for (const [name, action] of Object.entries((def.actions ?? {}) as Record<string, Action>)) {
|
|
27
|
+
actions[name] = (s, ...args) => {
|
|
28
|
+
const r = action(s, ...args);
|
|
29
|
+
return r && typeof (r as Promise<unknown>).then === "function" ? (r as Promise<unknown>).then(derive) : derive(r);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return { ...def, state: derive(def.state), actions } as D;
|
|
33
|
+
}
|
|
@@ -154,7 +154,7 @@ const svc = defineService<S, {
|
|
|
154
154
|
load: async (s, arg) => {
|
|
155
155
|
const configured = typeof (arg as { feedUrl?: unknown } | undefined)?.feedUrl === "string" ? String((arg as { feedUrl: string }).feedUrl).trim() : "";
|
|
156
156
|
if (!configured) {
|
|
157
|
-
return derive({ ...s, status: "ready", episodes: [], emptyText: "
|
|
157
|
+
return derive({ ...s, status: "ready", episodes: [], emptyText: "No episodes yet — check back soon.", isMock: false, notMock: true, mockBanner: "" });
|
|
158
158
|
}
|
|
159
159
|
try {
|
|
160
160
|
const api = host().wixApi;
|