@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')}`
|
|
@@ -4115,7 +4115,8 @@ const SCREENS = runtimeSetting({
|
|
|
4115
4115
|
"props": {
|
|
4116
4116
|
"text": "$service:spacesDeleteAccount.message",
|
|
4117
4117
|
"style": "caption",
|
|
4118
|
-
"color": "textTertiary"
|
|
4118
|
+
"color": "textTertiary",
|
|
4119
|
+
"collapseWhenEmpty": true
|
|
4119
4120
|
}
|
|
4120
4121
|
}
|
|
4121
4122
|
},
|
|
@@ -4410,6 +4411,9 @@ const SCREENS = runtimeSetting({
|
|
|
4410
4411
|
}
|
|
4411
4412
|
]
|
|
4412
4413
|
}
|
|
4414
|
+
},
|
|
4415
|
+
"modifiers": {
|
|
4416
|
+
"hiddenPath": "$service:spacesWhatsNew.noItems"
|
|
4413
4417
|
}
|
|
4414
4418
|
},
|
|
4415
4419
|
{
|
|
@@ -4791,6 +4795,24 @@ const SCREENS = runtimeSetting({
|
|
|
4791
4795
|
"props": {
|
|
4792
4796
|
"spacing": 0,
|
|
4793
4797
|
"children": [
|
|
4798
|
+
{
|
|
4799
|
+
"id": "himi_shop.list.collection.loading",
|
|
4800
|
+
"body": {
|
|
4801
|
+
"package": "himalaya",
|
|
4802
|
+
"type": "Skeleton",
|
|
4803
|
+
"props": {
|
|
4804
|
+
"height": 72,
|
|
4805
|
+
"cornerRadius": 12,
|
|
4806
|
+
"count": 3
|
|
4807
|
+
}
|
|
4808
|
+
},
|
|
4809
|
+
"modifiers": {
|
|
4810
|
+
"hiddenPath": "$service:himiStoreShop.collection.noLoading",
|
|
4811
|
+
"padding": {
|
|
4812
|
+
"bottom": 12
|
|
4813
|
+
}
|
|
4814
|
+
}
|
|
4815
|
+
},
|
|
4794
4816
|
{
|
|
4795
4817
|
"id": "",
|
|
4796
4818
|
"body": {
|
|
@@ -5052,6 +5074,52 @@ const SCREENS = runtimeSetting({
|
|
|
5052
5074
|
}
|
|
5053
5075
|
}
|
|
5054
5076
|
},
|
|
5077
|
+
{
|
|
5078
|
+
"id": "himi_shop.list.collection.empty-source",
|
|
5079
|
+
"body": {
|
|
5080
|
+
"package": "himalaya",
|
|
5081
|
+
"type": "VStack",
|
|
5082
|
+
"props": {
|
|
5083
|
+
"spacing": 8,
|
|
5084
|
+
"alignment": "center",
|
|
5085
|
+
"children": [
|
|
5086
|
+
{
|
|
5087
|
+
"id": "",
|
|
5088
|
+
"body": {
|
|
5089
|
+
"package": "himalaya",
|
|
5090
|
+
"type": "Icon",
|
|
5091
|
+
"props": {
|
|
5092
|
+
"name": "inbox",
|
|
5093
|
+
"size": 28,
|
|
5094
|
+
"tint": "textTertiary"
|
|
5095
|
+
}
|
|
5096
|
+
},
|
|
5097
|
+
"modifiers": {
|
|
5098
|
+
"accessibilityRole": "decorative"
|
|
5099
|
+
}
|
|
5100
|
+
},
|
|
5101
|
+
{
|
|
5102
|
+
"id": "",
|
|
5103
|
+
"body": {
|
|
5104
|
+
"package": "himalaya",
|
|
5105
|
+
"type": "Text",
|
|
5106
|
+
"props": {
|
|
5107
|
+
"text": "$service:himiStoreShop.collection.emptySourceText",
|
|
5108
|
+
"style": "body",
|
|
5109
|
+
"color": "textSecondary"
|
|
5110
|
+
}
|
|
5111
|
+
}
|
|
5112
|
+
}
|
|
5113
|
+
]
|
|
5114
|
+
}
|
|
5115
|
+
},
|
|
5116
|
+
"modifiers": {
|
|
5117
|
+
"hiddenPath": "$service:himiStoreShop.collection.noEmptySource",
|
|
5118
|
+
"padding": {
|
|
5119
|
+
"bottom": 12
|
|
5120
|
+
}
|
|
5121
|
+
}
|
|
5122
|
+
},
|
|
5055
5123
|
{
|
|
5056
5124
|
"id": "",
|
|
5057
5125
|
"body": {
|
|
@@ -7463,6 +7531,9 @@ const SCREENS = runtimeSetting({
|
|
|
7463
7531
|
"actionId": "tier5.himiStoreSaved.load"
|
|
7464
7532
|
}
|
|
7465
7533
|
}
|
|
7534
|
+
},
|
|
7535
|
+
"modifiers": {
|
|
7536
|
+
"hiddenPath": "$service:himiStoreSaved.noRows"
|
|
7466
7537
|
}
|
|
7467
7538
|
},
|
|
7468
7539
|
{
|
|
@@ -7714,6 +7785,9 @@ const SCREENS = runtimeSetting({
|
|
|
7714
7785
|
}
|
|
7715
7786
|
]
|
|
7716
7787
|
}
|
|
7788
|
+
},
|
|
7789
|
+
"modifiers": {
|
|
7790
|
+
"hiddenPath": "$service:spacesCollections.noCollections"
|
|
7717
7791
|
}
|
|
7718
7792
|
},
|
|
7719
7793
|
{
|
|
@@ -8760,6 +8834,24 @@ const SCREENS = runtimeSetting({
|
|
|
8760
8834
|
"props": {
|
|
8761
8835
|
"spacing": 0,
|
|
8762
8836
|
"children": [
|
|
8837
|
+
{
|
|
8838
|
+
"id": "himi_orders.list.collection.loading",
|
|
8839
|
+
"body": {
|
|
8840
|
+
"package": "himalaya",
|
|
8841
|
+
"type": "Skeleton",
|
|
8842
|
+
"props": {
|
|
8843
|
+
"height": 72,
|
|
8844
|
+
"cornerRadius": 12,
|
|
8845
|
+
"count": 3
|
|
8846
|
+
}
|
|
8847
|
+
},
|
|
8848
|
+
"modifiers": {
|
|
8849
|
+
"hiddenPath": "$service:himiBrandedOrders.collection.noLoading",
|
|
8850
|
+
"padding": {
|
|
8851
|
+
"bottom": 12
|
|
8852
|
+
}
|
|
8853
|
+
}
|
|
8854
|
+
},
|
|
8763
8855
|
{
|
|
8764
8856
|
"id": "himi_orders.list.collection.search",
|
|
8765
8857
|
"body": {
|
|
@@ -9042,6 +9134,52 @@ const SCREENS = runtimeSetting({
|
|
|
9042
9134
|
}
|
|
9043
9135
|
}
|
|
9044
9136
|
},
|
|
9137
|
+
{
|
|
9138
|
+
"id": "himi_orders.list.collection.empty-source",
|
|
9139
|
+
"body": {
|
|
9140
|
+
"package": "himalaya",
|
|
9141
|
+
"type": "VStack",
|
|
9142
|
+
"props": {
|
|
9143
|
+
"spacing": 8,
|
|
9144
|
+
"alignment": "center",
|
|
9145
|
+
"children": [
|
|
9146
|
+
{
|
|
9147
|
+
"id": "",
|
|
9148
|
+
"body": {
|
|
9149
|
+
"package": "himalaya",
|
|
9150
|
+
"type": "Icon",
|
|
9151
|
+
"props": {
|
|
9152
|
+
"name": "inbox",
|
|
9153
|
+
"size": 28,
|
|
9154
|
+
"tint": "textTertiary"
|
|
9155
|
+
}
|
|
9156
|
+
},
|
|
9157
|
+
"modifiers": {
|
|
9158
|
+
"accessibilityRole": "decorative"
|
|
9159
|
+
}
|
|
9160
|
+
},
|
|
9161
|
+
{
|
|
9162
|
+
"id": "",
|
|
9163
|
+
"body": {
|
|
9164
|
+
"package": "himalaya",
|
|
9165
|
+
"type": "Text",
|
|
9166
|
+
"props": {
|
|
9167
|
+
"text": "$service:himiBrandedOrders.collection.emptySourceText",
|
|
9168
|
+
"style": "body",
|
|
9169
|
+
"color": "textSecondary"
|
|
9170
|
+
}
|
|
9171
|
+
}
|
|
9172
|
+
}
|
|
9173
|
+
]
|
|
9174
|
+
}
|
|
9175
|
+
},
|
|
9176
|
+
"modifiers": {
|
|
9177
|
+
"hiddenPath": "$service:himiBrandedOrders.collection.noEmptySource",
|
|
9178
|
+
"padding": {
|
|
9179
|
+
"bottom": 12
|
|
9180
|
+
}
|
|
9181
|
+
}
|
|
9182
|
+
},
|
|
9045
9183
|
{
|
|
9046
9184
|
"id": "",
|
|
9047
9185
|
"body": {
|
|
@@ -11511,6 +11649,24 @@ const SCREENS = runtimeSetting({
|
|
|
11511
11649
|
"props": {
|
|
11512
11650
|
"spacing": 0,
|
|
11513
11651
|
"children": [
|
|
11652
|
+
{
|
|
11653
|
+
"id": "himi_blog.list.collection.loading",
|
|
11654
|
+
"body": {
|
|
11655
|
+
"package": "himalaya",
|
|
11656
|
+
"type": "Skeleton",
|
|
11657
|
+
"props": {
|
|
11658
|
+
"height": 72,
|
|
11659
|
+
"cornerRadius": 12,
|
|
11660
|
+
"count": 3
|
|
11661
|
+
}
|
|
11662
|
+
},
|
|
11663
|
+
"modifiers": {
|
|
11664
|
+
"hiddenPath": "$service:himiBlogFeed.collection.noLoading",
|
|
11665
|
+
"padding": {
|
|
11666
|
+
"bottom": 12
|
|
11667
|
+
}
|
|
11668
|
+
}
|
|
11669
|
+
},
|
|
11514
11670
|
{
|
|
11515
11671
|
"id": "",
|
|
11516
11672
|
"body": {
|
|
@@ -11729,6 +11885,52 @@ const SCREENS = runtimeSetting({
|
|
|
11729
11885
|
}
|
|
11730
11886
|
}
|
|
11731
11887
|
},
|
|
11888
|
+
{
|
|
11889
|
+
"id": "himi_blog.list.collection.empty-source",
|
|
11890
|
+
"body": {
|
|
11891
|
+
"package": "himalaya",
|
|
11892
|
+
"type": "VStack",
|
|
11893
|
+
"props": {
|
|
11894
|
+
"spacing": 8,
|
|
11895
|
+
"alignment": "center",
|
|
11896
|
+
"children": [
|
|
11897
|
+
{
|
|
11898
|
+
"id": "",
|
|
11899
|
+
"body": {
|
|
11900
|
+
"package": "himalaya",
|
|
11901
|
+
"type": "Icon",
|
|
11902
|
+
"props": {
|
|
11903
|
+
"name": "inbox",
|
|
11904
|
+
"size": 28,
|
|
11905
|
+
"tint": "textTertiary"
|
|
11906
|
+
}
|
|
11907
|
+
},
|
|
11908
|
+
"modifiers": {
|
|
11909
|
+
"accessibilityRole": "decorative"
|
|
11910
|
+
}
|
|
11911
|
+
},
|
|
11912
|
+
{
|
|
11913
|
+
"id": "",
|
|
11914
|
+
"body": {
|
|
11915
|
+
"package": "himalaya",
|
|
11916
|
+
"type": "Text",
|
|
11917
|
+
"props": {
|
|
11918
|
+
"text": "$service:himiBlogFeed.collection.emptySourceText",
|
|
11919
|
+
"style": "body",
|
|
11920
|
+
"color": "textSecondary"
|
|
11921
|
+
}
|
|
11922
|
+
}
|
|
11923
|
+
}
|
|
11924
|
+
]
|
|
11925
|
+
}
|
|
11926
|
+
},
|
|
11927
|
+
"modifiers": {
|
|
11928
|
+
"hiddenPath": "$service:himiBlogFeed.collection.noEmptySource",
|
|
11929
|
+
"padding": {
|
|
11930
|
+
"bottom": 12
|
|
11931
|
+
}
|
|
11932
|
+
}
|
|
11933
|
+
},
|
|
11732
11934
|
{
|
|
11733
11935
|
"id": "",
|
|
11734
11936
|
"body": {
|
|
@@ -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
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineService } from "@himalaya/state";
|
|
2
|
+
import { withEmptyFlags } from "../_shared/empty-flags.js";
|
|
2
3
|
import { readSavedProducts, removeSavedProduct, type SavedProduct } from "../_shared/himi-store-saved.js";
|
|
3
4
|
import { stashCopy, copyParam } from "../_shared/himi-copy.js";
|
|
4
5
|
|
|
@@ -25,7 +26,7 @@ function failed(s: S): S {
|
|
|
25
26
|
const svc = defineService<S, {
|
|
26
27
|
load: (s: S, arg?: unknown) => S;
|
|
27
28
|
remove: (s: S, arg?: unknown) => S;
|
|
28
|
-
}>("himiStoreSaved", {
|
|
29
|
+
}>("himiStoreSaved", withEmptyFlags({
|
|
29
30
|
state: shape({ status: "loading", rows: [], error: "" }),
|
|
30
31
|
actions: {
|
|
31
32
|
load: (s, arg) => {
|
|
@@ -41,5 +42,5 @@ const svc = defineService<S, {
|
|
|
41
42
|
} catch { return failed(s); }
|
|
42
43
|
},
|
|
43
44
|
},
|
|
44
|
-
});
|
|
45
|
+
}, { rows: "noRows" }));
|
|
45
46
|
export { svc };
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Prefer Catalog V3 Categories API (`queryStoreCategories`); V1 `queryCollections`
|
|
3
3
|
// returns HTTP 428 on modern CATALOG_V3 sites (docs/wix-authentication.md).
|
|
4
4
|
import { defineService } from "@himalaya/state";
|
|
5
|
+
import { withEmptyFlags } from "../_shared/empty-flags.js";
|
|
5
6
|
import {
|
|
6
7
|
categoryCount,
|
|
7
8
|
queryCollections,
|
|
@@ -71,7 +72,7 @@ async function loadCollections(): Promise<Loaded> {
|
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
const svc = defineService<S, { load: (s: S) => Promise<S> }>("spacesCollections", {
|
|
75
|
+
const svc = defineService<S, { load: (s: S) => Promise<S> }>("spacesCollections", withEmptyFlags({
|
|
75
76
|
state: { status: "loading", collections: [], emptyText: "", error: "" },
|
|
76
77
|
actions: {
|
|
77
78
|
load: async () => {
|
|
@@ -93,6 +94,6 @@ const svc = defineService<S, { load: (s: S) => Promise<S> }>("spacesCollections"
|
|
|
93
94
|
};
|
|
94
95
|
},
|
|
95
96
|
},
|
|
96
|
-
});
|
|
97
|
+
}, { collections: "noCollections" }));
|
|
97
98
|
svc.load();
|
|
98
99
|
export { svc };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Tier-5 bundle: Wix Spaces clone — What's New (ON-DEVICE, account-level).
|
|
2
2
|
import { defineService } from "@himalaya/state";
|
|
3
|
+
import { withEmptyFlags } from "../_shared/empty-flags.js";
|
|
3
4
|
import { listAnnouncements, type WixAnnouncement } from "@himalaya/wix-new-releases";
|
|
4
5
|
|
|
5
6
|
interface Row { id: string; name: string; subtitle: string }
|
|
@@ -10,7 +11,7 @@ function norm(a: WixAnnouncement, i: number): Row {
|
|
|
10
11
|
return { id: aa.id || `a${i}`, name: aa.title || "Announcement", subtitle: aa.description || aa.bodyText || "" };
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
const svc = defineService<S, { load: (s: S) => Promise<S> }>("spacesWhatsNew", {
|
|
14
|
+
const svc = defineService<S, { load: (s: S) => Promise<S> }>("spacesWhatsNew", withEmptyFlags({
|
|
14
15
|
state: { status: "loading", items: [], emptyText: "" },
|
|
15
16
|
actions: {
|
|
16
17
|
load: async (s) => {
|
|
@@ -21,6 +22,6 @@ const svc = defineService<S, { load: (s: S) => Promise<S> }>("spacesWhatsNew", {
|
|
|
21
22
|
} catch { return { ...s, status: "ready", items: [], emptyText: "Nothing new right now" }; }
|
|
22
23
|
},
|
|
23
24
|
},
|
|
24
|
-
});
|
|
25
|
+
}, { items: "noItems" }));
|
|
25
26
|
svc.load();
|
|
26
27
|
export { svc };
|