@xh/hoist 71.0.0-SNAPSHOT.1733198772915 → 71.0.0-SNAPSHOT.1733266596001
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/CHANGELOG.md +8 -2
- package/appcontainer/AppContainerModel.ts +2 -1
- package/build/types/cmp/chart/impl/copyToClipboard.d.ts +1 -2
- package/build/types/cmp/viewmanager/SaveAsDialogModel.d.ts +23 -0
- package/build/types/cmp/viewmanager/View.d.ts +28 -0
- package/build/types/cmp/viewmanager/ViewInfo.d.ts +30 -0
- package/build/types/cmp/viewmanager/ViewManagerModel.d.ts +185 -0
- package/build/types/cmp/viewmanager/index.d.ts +4 -0
- package/build/types/core/XH.d.ts +2 -1
- package/build/types/core/persist/PersistOptions.d.ts +3 -1
- package/build/types/core/persist/index.d.ts +6 -5
- package/build/types/core/persist/{CustomProvider.d.ts → provider/CustomProvider.d.ts} +1 -1
- package/build/types/core/persist/{DashViewProvider.d.ts → provider/DashViewProvider.d.ts} +2 -2
- package/build/types/core/persist/{LocalStorageProvider.d.ts → provider/LocalStorageProvider.d.ts} +1 -1
- package/build/types/core/persist/{PrefProvider.d.ts → provider/PrefProvider.d.ts} +1 -2
- package/build/types/core/persist/provider/SessionStorageProvider.d.ts +10 -0
- package/build/types/core/persist/{viewmanager → provider}/ViewManagerProvider.d.ts +3 -3
- package/build/types/desktop/cmp/viewmanager/ViewManager.d.ts +9 -10
- package/build/types/desktop/cmp/viewmanager/ViewMenu.d.ts +5 -0
- package/build/types/desktop/cmp/viewmanager/dialog/EditForm.d.ts +5 -0
- package/build/types/desktop/cmp/viewmanager/dialog/EditFormModel.d.ts +18 -0
- package/build/types/desktop/cmp/viewmanager/dialog/ManageDialog.d.ts +5 -0
- package/build/types/desktop/cmp/viewmanager/dialog/ManageDialogModel.d.ts +38 -0
- package/build/types/desktop/cmp/viewmanager/dialog/SaveAsDialog.d.ts +5 -0
- package/build/types/svc/JsonBlobService.d.ts +1 -1
- package/build/types/svc/index.d.ts +2 -1
- package/build/types/svc/storage/BaseStorageService.d.ts +21 -0
- package/build/types/svc/storage/LocalStorageService.d.ts +12 -0
- package/build/types/svc/storage/SessionStorageService.d.ts +12 -0
- package/cmp/chart/impl/copyToClipboard.ts +1 -2
- package/cmp/viewmanager/SaveAsDialogModel.ts +97 -0
- package/cmp/viewmanager/View.ts +56 -0
- package/cmp/viewmanager/ViewInfo.ts +58 -0
- package/cmp/viewmanager/ViewManagerModel.ts +710 -0
- package/cmp/viewmanager/index.ts +4 -0
- package/core/XH.ts +2 -0
- package/core/persist/PersistOptions.ts +4 -1
- package/core/persist/PersistenceProvider.ts +5 -0
- package/core/persist/index.ts +6 -5
- package/core/persist/{CustomProvider.ts → provider/CustomProvider.ts} +1 -1
- package/core/persist/{DashViewProvider.ts → provider/DashViewProvider.ts} +1 -1
- package/core/persist/{LocalStorageProvider.ts → provider/LocalStorageProvider.ts} +1 -1
- package/core/persist/{PrefProvider.ts → provider/PrefProvider.ts} +2 -2
- package/core/persist/provider/SessionStorageProvider.ts +35 -0
- package/core/persist/{viewmanager → provider}/ViewManagerProvider.ts +5 -9
- package/desktop/cmp/viewmanager/ViewManager.ts +47 -229
- package/desktop/cmp/viewmanager/ViewMenu.ts +191 -0
- package/desktop/cmp/viewmanager/dialog/EditForm.ts +126 -0
- package/desktop/cmp/viewmanager/dialog/EditFormModel.ts +125 -0
- package/desktop/cmp/viewmanager/dialog/ManageDialog.ts +98 -0
- package/desktop/cmp/viewmanager/dialog/ManageDialogModel.ts +279 -0
- package/desktop/cmp/viewmanager/{impl/SaveDialog.ts → dialog/SaveAsDialog.ts} +20 -12
- package/package.json +1 -1
- package/svc/JsonBlobService.ts +1 -1
- package/svc/index.ts +2 -1
- package/svc/{LocalStorageService.ts → storage/BaseStorageService.ts} +13 -23
- package/svc/storage/LocalStorageService.ts +23 -0
- package/svc/storage/SessionStorageService.ts +23 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/build/types/core/persist/viewmanager/Types.d.ts +0 -48
- package/build/types/core/persist/viewmanager/ViewManagerModel.d.ts +0 -145
- package/build/types/core/persist/viewmanager/impl/BuildViewTree.d.ts +0 -8
- package/build/types/core/persist/viewmanager/impl/ManageDialogModel.d.ts +0 -30
- package/build/types/core/persist/viewmanager/impl/SaveDialogModel.d.ts +0 -23
- package/build/types/core/persist/viewmanager/index.d.ts +0 -2
- package/build/types/desktop/cmp/viewmanager/impl/ManageDialog.d.ts +0 -6
- package/build/types/desktop/cmp/viewmanager/impl/SaveDialog.d.ts +0 -2
- package/build/types/svc/LocalStorageService.d.ts +0 -24
- package/core/persist/viewmanager/Types.ts +0 -53
- package/core/persist/viewmanager/ViewManagerModel.ts +0 -481
- package/core/persist/viewmanager/impl/BuildViewTree.ts +0 -68
- package/core/persist/viewmanager/impl/ManageDialogModel.ts +0 -276
- package/core/persist/viewmanager/impl/SaveDialogModel.ts +0 -112
- package/core/persist/viewmanager/index.ts +0 -2
- package/desktop/cmp/viewmanager/impl/ManageDialog.ts +0 -197
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file belongs to Hoist, an application development toolkit
|
|
3
|
+
* developed by Extremely Heavy Industries (www.xh.io | info@xh.io)
|
|
4
|
+
*
|
|
5
|
+
* Copyright © 2024 Extremely Heavy Industries Inc.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {grid, GridAutosizeMode, GridModel} from '@xh/hoist/cmp/grid';
|
|
9
|
+
import {fragment, p, strong} from '@xh/hoist/cmp/layout';
|
|
10
|
+
import {TabContainerModel} from '@xh/hoist/cmp/tab';
|
|
11
|
+
import {HoistModel, LoadSpec, lookup, managed, TaskObserver, XH} from '@xh/hoist/core';
|
|
12
|
+
import {FilterTestFn} from '@xh/hoist/data';
|
|
13
|
+
import {computed} from 'mobx';
|
|
14
|
+
import {ReactNode} from 'react';
|
|
15
|
+
import {EditFormModel} from './EditFormModel';
|
|
16
|
+
import {Icon} from '@xh/hoist/icon';
|
|
17
|
+
import {bindable, makeObservable} from '@xh/hoist/mobx';
|
|
18
|
+
import {pluralize} from '@xh/hoist/utils/js';
|
|
19
|
+
import {ViewInfo, ViewManagerModel} from '@xh/hoist/cmp/viewmanager';
|
|
20
|
+
import {find, some, startCase} from 'lodash';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Backing model for ManageDialog
|
|
24
|
+
*/
|
|
25
|
+
export class ManageDialogModel extends HoistModel {
|
|
26
|
+
@lookup(() => ViewManagerModel)
|
|
27
|
+
viewManagerModel: ViewManagerModel;
|
|
28
|
+
|
|
29
|
+
@managed privateGridModel: GridModel;
|
|
30
|
+
@managed globalGridModel: GridModel;
|
|
31
|
+
@managed editFormModel: EditFormModel;
|
|
32
|
+
@managed tabContainerModel: TabContainerModel;
|
|
33
|
+
|
|
34
|
+
@bindable filter: FilterTestFn;
|
|
35
|
+
|
|
36
|
+
readonly updateTask = TaskObserver.trackLast();
|
|
37
|
+
|
|
38
|
+
get loadTask(): TaskObserver {
|
|
39
|
+
return this.viewManagerModel.loadModel;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get gridModel(): GridModel {
|
|
43
|
+
return this.tabContainerModel.activeTabId == 'global'
|
|
44
|
+
? this.globalGridModel
|
|
45
|
+
: this.privateGridModel;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@computed
|
|
49
|
+
get selectedView(): ViewInfo {
|
|
50
|
+
return this.gridModel.selectedRecord?.data.info;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@computed
|
|
54
|
+
get selectedViews(): ViewInfo[] {
|
|
55
|
+
return this.gridModel.selectedRecords.map(it => it.data.info) as ViewInfo[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get canDelete(): boolean {
|
|
59
|
+
const {viewManagerModel, manageGlobal, selectedViews} = this,
|
|
60
|
+
{views, enableDefault} = viewManagerModel;
|
|
61
|
+
|
|
62
|
+
// Can't delete global views without role.
|
|
63
|
+
if (!manageGlobal && selectedViews.some(v => v.isGlobal)) return false;
|
|
64
|
+
|
|
65
|
+
// Can't delete all the views, unless default mode is enabled.
|
|
66
|
+
return enableDefault || views.length - selectedViews.length > 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get manageGlobal(): boolean {
|
|
70
|
+
return this.viewManagerModel.manageGlobal;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
get typeDisplayName(): string {
|
|
74
|
+
return this.viewManagerModel.typeDisplayName;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
get globalDisplayName(): string {
|
|
78
|
+
return this.viewManagerModel.globalDisplayName;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
get enableFavorites(): boolean {
|
|
82
|
+
return this.viewManagerModel.enableFavorites;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
constructor() {
|
|
86
|
+
super();
|
|
87
|
+
makeObservable(this);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
close() {
|
|
91
|
+
this.viewManagerModel.closeManageDialog();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
override onLinked() {
|
|
95
|
+
super.onLinked();
|
|
96
|
+
|
|
97
|
+
this.privateGridModel = this.createGridModel('personal');
|
|
98
|
+
this.globalGridModel = this.createGridModel(this.globalDisplayName);
|
|
99
|
+
this.tabContainerModel = this.createTabContainerModel();
|
|
100
|
+
this.editFormModel = new EditFormModel(this);
|
|
101
|
+
|
|
102
|
+
const {privateGridModel, globalGridModel, editFormModel} = this;
|
|
103
|
+
this.addReaction(
|
|
104
|
+
{
|
|
105
|
+
track: () => this.selectedView,
|
|
106
|
+
run: r => editFormModel.setView(r)
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
track: () => this.filter,
|
|
110
|
+
run: f => {
|
|
111
|
+
privateGridModel.store.setFilter(f);
|
|
112
|
+
globalGridModel.store.setFilter(f);
|
|
113
|
+
},
|
|
114
|
+
fireImmediately: true
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
track: () => privateGridModel.selectedRecords,
|
|
118
|
+
run: recs => {
|
|
119
|
+
if (recs.length) globalGridModel.clearSelection();
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
track: () => globalGridModel.selectedRecords,
|
|
124
|
+
run: recs => {
|
|
125
|
+
if (recs.length) privateGridModel.clearSelection();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
override async doLoadAsync(loadSpec: LoadSpec) {
|
|
132
|
+
const {view, globalViews, privateViews} = this.viewManagerModel;
|
|
133
|
+
this.globalGridModel.loadData(globalViews);
|
|
134
|
+
this.privateGridModel.loadData(privateViews);
|
|
135
|
+
if (!loadSpec.isRefresh && !view.isDefault) {
|
|
136
|
+
await this.selectViewAsync(view.info);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async deleteAsync(views: ViewInfo[]) {
|
|
141
|
+
return this.doDeleteAsync(views).linkTo(this.updateTask).catchDefault();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async updateAsync(view: ViewInfo, name: string, description: string, isGlobal: boolean) {
|
|
145
|
+
return this.doUpdateAsync(view, name, description, isGlobal)
|
|
146
|
+
.linkTo(this.updateTask)
|
|
147
|
+
.catchDefault();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
//------------------------
|
|
151
|
+
// Implementation
|
|
152
|
+
//------------------------
|
|
153
|
+
private async doUpdateAsync(
|
|
154
|
+
view: ViewInfo,
|
|
155
|
+
name: string,
|
|
156
|
+
description: string,
|
|
157
|
+
isGlobal: boolean
|
|
158
|
+
) {
|
|
159
|
+
const {viewManagerModel} = this;
|
|
160
|
+
|
|
161
|
+
await viewManagerModel.updateViewAsync(view, name, description, isGlobal);
|
|
162
|
+
await viewManagerModel.refreshAsync();
|
|
163
|
+
await this.refreshAsync();
|
|
164
|
+
|
|
165
|
+
// reselect the updated copy of this view -- it may have moved.
|
|
166
|
+
await this.selectViewAsync(find(viewManagerModel.views, {token: view.token}));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private async doDeleteAsync(views: ViewInfo[]) {
|
|
170
|
+
const {viewManagerModel, typeDisplayName} = this,
|
|
171
|
+
{enableDefault} = viewManagerModel,
|
|
172
|
+
count = views.length;
|
|
173
|
+
|
|
174
|
+
if (!count) return;
|
|
175
|
+
|
|
176
|
+
if (viewManagerModel.views.length === count && !enableDefault) {
|
|
177
|
+
throw XH.exception({
|
|
178
|
+
message: `You cannot delete all ${pluralize(typeDisplayName)}.`,
|
|
179
|
+
isRoutine: true
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const confirmStr = count > 1 ? pluralize(typeDisplayName, count, true) : views[0].typedName;
|
|
184
|
+
const msgs: ReactNode[] = [`Are you sure you want to delete ${confirmStr}?`];
|
|
185
|
+
if (some(views, 'isGlobal')) {
|
|
186
|
+
count > 1
|
|
187
|
+
? msgs.push(strong('These global views will no longer be available to ALL users.'))
|
|
188
|
+
: msgs.push(strong('This global view will no longer be available to ALL users.'));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const confirmed = await XH.confirm({
|
|
192
|
+
message: fragment(msgs.map(m => p(m))),
|
|
193
|
+
confirmProps: {
|
|
194
|
+
text: `Yes, delete ${pluralize(typeDisplayName, count)}`,
|
|
195
|
+
outlined: true,
|
|
196
|
+
autoFocus: false,
|
|
197
|
+
intent: 'danger'
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
if (!confirmed) return;
|
|
201
|
+
|
|
202
|
+
for (const view of views) {
|
|
203
|
+
await viewManagerModel.deleteViewAsync(view);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
await viewManagerModel.refreshAsync();
|
|
207
|
+
await this.refreshAsync();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async selectViewAsync(view: ViewInfo) {
|
|
211
|
+
this.tabContainerModel.activateTab(view.isGlobal ? 'global' : 'private');
|
|
212
|
+
await this.gridModel.selectAsync(view.token);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private createGridModel(name: string): GridModel {
|
|
216
|
+
return new GridModel({
|
|
217
|
+
emptyText: `No ${name} ${pluralize(this.typeDisplayName)} found...`,
|
|
218
|
+
sortBy: 'name',
|
|
219
|
+
hideHeaders: true,
|
|
220
|
+
showGroupRowCounts: false,
|
|
221
|
+
selModel: 'multiple',
|
|
222
|
+
contextMenu: null,
|
|
223
|
+
sizingMode: 'standard',
|
|
224
|
+
store: {
|
|
225
|
+
idSpec: 'token',
|
|
226
|
+
processRawData: v => ({name: v.name, isFavorite: v.isFavorite, info: v}),
|
|
227
|
+
fields: [
|
|
228
|
+
{name: 'name', type: 'string'},
|
|
229
|
+
{name: 'isFavorite', type: 'bool'},
|
|
230
|
+
{name: 'info', type: 'auto'}
|
|
231
|
+
]
|
|
232
|
+
},
|
|
233
|
+
autosizeOptions: {mode: GridAutosizeMode.DISABLED},
|
|
234
|
+
columns: [
|
|
235
|
+
{field: 'name', flex: true},
|
|
236
|
+
{
|
|
237
|
+
colId: 'isFavorite',
|
|
238
|
+
field: 'info',
|
|
239
|
+
omit: !this.enableFavorites,
|
|
240
|
+
width: 40,
|
|
241
|
+
align: 'center',
|
|
242
|
+
headerName: Icon.favorite(),
|
|
243
|
+
highlightOnChange: true,
|
|
244
|
+
renderer: v => {
|
|
245
|
+
const {isFavorite} = v;
|
|
246
|
+
return Icon.favorite({
|
|
247
|
+
prefix: isFavorite ? 'fas' : 'fal',
|
|
248
|
+
className: isFavorite ? 'xh-yellow' : 'xh-text-color-muted'
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
],
|
|
253
|
+
onCellClicked: ({colDef, data: record, api}) => {
|
|
254
|
+
if (colDef.colId === 'isFavorite') {
|
|
255
|
+
this.viewManagerModel.toggleFavorite(record.id);
|
|
256
|
+
api.redrawRows();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private createTabContainerModel(): TabContainerModel {
|
|
263
|
+
const pluralType = startCase(pluralize(this.typeDisplayName));
|
|
264
|
+
return new TabContainerModel({
|
|
265
|
+
tabs: [
|
|
266
|
+
{
|
|
267
|
+
id: 'private',
|
|
268
|
+
title: `My ${pluralType}`,
|
|
269
|
+
content: grid({model: this.privateGridModel})
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
id: 'global',
|
|
273
|
+
title: `${startCase(this.globalDisplayName)} ${pluralType}`,
|
|
274
|
+
content: grid({model: this.globalGridModel})
|
|
275
|
+
}
|
|
276
|
+
]
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -1,26 +1,35 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file belongs to Hoist, an application development toolkit
|
|
3
|
+
* developed by Extremely Heavy Industries (www.xh.io | info@xh.io)
|
|
4
|
+
*
|
|
5
|
+
* Copyright © 2024 Extremely Heavy Industries Inc.
|
|
6
|
+
*/
|
|
7
|
+
|
|
1
8
|
import {form} from '@xh/hoist/cmp/form';
|
|
2
9
|
import {filler, vframe} from '@xh/hoist/cmp/layout';
|
|
3
10
|
import {hoistCmp, uses} from '@xh/hoist/core';
|
|
4
|
-
import {
|
|
11
|
+
import {SaveAsDialogModel} from '@xh/hoist/cmp/viewmanager/';
|
|
5
12
|
import {button} from '@xh/hoist/desktop/cmp/button';
|
|
6
13
|
import {formField} from '@xh/hoist/desktop/cmp/form';
|
|
7
14
|
import {textArea, textInput} from '@xh/hoist/desktop/cmp/input';
|
|
8
15
|
import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
9
16
|
import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
|
|
10
|
-
import {Icon} from '@xh/hoist/icon';
|
|
11
17
|
import {dialog} from '@xh/hoist/kit/blueprint';
|
|
18
|
+
import {startCase} from 'lodash';
|
|
12
19
|
|
|
13
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Default Save As dialog used by ViewManager.
|
|
22
|
+
*/
|
|
23
|
+
export const saveAsDialog = hoistCmp.factory<SaveAsDialogModel>({
|
|
14
24
|
displayName: 'SaveDialog',
|
|
15
25
|
className: 'xh-view-manager__save-dialog',
|
|
16
|
-
model: uses(
|
|
26
|
+
model: uses(SaveAsDialogModel),
|
|
17
27
|
|
|
18
28
|
render({model, className}) {
|
|
19
29
|
if (!model.isOpen) return null;
|
|
20
30
|
|
|
21
31
|
return dialog({
|
|
22
32
|
title: `Save as...`,
|
|
23
|
-
icon: Icon.copy(),
|
|
24
33
|
className,
|
|
25
34
|
isOpen: true,
|
|
26
35
|
style: {width: 500},
|
|
@@ -31,7 +40,7 @@ export const saveDialog = hoistCmp.factory<SaveDialogModel>({
|
|
|
31
40
|
}
|
|
32
41
|
});
|
|
33
42
|
|
|
34
|
-
const formPanel = hoistCmp.factory<
|
|
43
|
+
const formPanel = hoistCmp.factory<SaveAsDialogModel>({
|
|
35
44
|
render({model}) {
|
|
36
45
|
return panel({
|
|
37
46
|
item: form({
|
|
@@ -62,14 +71,14 @@ const formPanel = hoistCmp.factory<SaveDialogModel>({
|
|
|
62
71
|
})
|
|
63
72
|
}),
|
|
64
73
|
bbar: bbar(),
|
|
65
|
-
mask: model.saveTask
|
|
74
|
+
mask: model.parent.saveTask
|
|
66
75
|
});
|
|
67
76
|
}
|
|
68
77
|
});
|
|
69
78
|
|
|
70
|
-
const bbar = hoistCmp.factory<
|
|
79
|
+
const bbar = hoistCmp.factory<SaveAsDialogModel>({
|
|
71
80
|
render({model}) {
|
|
72
|
-
const {
|
|
81
|
+
const {typeDisplayName} = model;
|
|
73
82
|
return toolbar(
|
|
74
83
|
filler(),
|
|
75
84
|
button({
|
|
@@ -77,11 +86,10 @@ const bbar = hoistCmp.factory<SaveDialogModel>({
|
|
|
77
86
|
onClick: () => model.cancel()
|
|
78
87
|
}),
|
|
79
88
|
button({
|
|
80
|
-
text: `Save as new ${
|
|
81
|
-
icon: Icon.copy(),
|
|
89
|
+
text: `Save as new ${startCase(typeDisplayName)}`,
|
|
82
90
|
outlined: true,
|
|
83
91
|
intent: 'success',
|
|
84
|
-
disabled: !formModel.isValid,
|
|
92
|
+
disabled: !model.formModel.isValid,
|
|
85
93
|
onClick: () => model.saveAsAsync()
|
|
86
94
|
})
|
|
87
95
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xh/hoist",
|
|
3
|
-
"version": "71.0.0-SNAPSHOT.
|
|
3
|
+
"version": "71.0.0-SNAPSHOT.1733266596001",
|
|
4
4
|
"description": "Hoist add-on for building and deploying React Applications.",
|
|
5
5
|
"repository": "github:xh/hoist-react",
|
|
6
6
|
"homepage": "https://xh.io",
|
package/svc/JsonBlobService.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {pickBy} from 'lodash';
|
|
|
9
9
|
|
|
10
10
|
export interface JsonBlob {
|
|
11
11
|
/** Either null for private blobs or special token "*" for globally shared blobs. */
|
|
12
|
-
acl:
|
|
12
|
+
acl: '*';
|
|
13
13
|
/** True if this blob has been archived (soft-deleted). */
|
|
14
14
|
archived: boolean;
|
|
15
15
|
/** Timestamp indicating when this blob was archived, or special value `0` if not archived. */
|
package/svc/index.ts
CHANGED
|
@@ -16,7 +16,8 @@ export * from './IdentityService';
|
|
|
16
16
|
export * from './IdleService';
|
|
17
17
|
export * from './InspectorService';
|
|
18
18
|
export * from './JsonBlobService';
|
|
19
|
-
export * from './LocalStorageService';
|
|
20
19
|
export * from './PrefService';
|
|
21
20
|
export * from './TrackService';
|
|
22
21
|
export * from './WebSocketService';
|
|
22
|
+
export * from './storage/LocalStorageService';
|
|
23
|
+
export * from './storage/SessionStorageService';
|
|
@@ -6,26 +6,22 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import {HoistService, XH} from '@xh/hoist/core';
|
|
8
8
|
import {throwIf} from '@xh/hoist/utils/js';
|
|
9
|
-
import
|
|
9
|
+
import {StoreType} from 'store2';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Service to provide simple key/value access to browser local storage, appropriately
|
|
13
|
-
* by application code and username.
|
|
12
|
+
* Service to provide simple key/value access to browser local/session storage, appropriately
|
|
13
|
+
* namespaced by application code and username.
|
|
14
14
|
*
|
|
15
|
-
* In the unexpected case that
|
|
15
|
+
* In the unexpected case that the core apis are not available, will provide a transient in-memory
|
|
16
16
|
* storage to support its operations and API.
|
|
17
|
-
*
|
|
18
|
-
* Relied upon by Hoist features such as local preference values and grid state.
|
|
19
17
|
*/
|
|
20
|
-
export class
|
|
21
|
-
static instance: LocalStorageService;
|
|
22
|
-
|
|
18
|
+
export abstract class BaseStorageService extends HoistService {
|
|
23
19
|
constructor() {
|
|
24
20
|
super();
|
|
25
21
|
if (this.isFake) {
|
|
26
22
|
XH.handleException(
|
|
27
23
|
XH.exception(
|
|
28
|
-
'
|
|
24
|
+
'Requested Storage is not supported in this browser. Transient in-memory storage ' +
|
|
29
25
|
'will be used as a fallback. All data stored will be lost when page is closed.'
|
|
30
26
|
),
|
|
31
27
|
{showAlert: false}
|
|
@@ -34,7 +30,7 @@ export class LocalStorageService extends HoistService {
|
|
|
34
30
|
}
|
|
35
31
|
|
|
36
32
|
get(key: string, defaultValue?: any): any {
|
|
37
|
-
const storage = this.
|
|
33
|
+
const storage = this.storeInstance,
|
|
38
34
|
val = storage.get(key, defaultValue);
|
|
39
35
|
|
|
40
36
|
throwIf(val === undefined, `Key '${key}' not found`);
|
|
@@ -42,7 +38,7 @@ export class LocalStorageService extends HoistService {
|
|
|
42
38
|
}
|
|
43
39
|
|
|
44
40
|
set(key: string, value: any) {
|
|
45
|
-
this.
|
|
41
|
+
this.storeInstance.set(key, value, true);
|
|
46
42
|
}
|
|
47
43
|
|
|
48
44
|
apply(key: string, newProps: object) {
|
|
@@ -53,7 +49,7 @@ export class LocalStorageService extends HoistService {
|
|
|
53
49
|
}
|
|
54
50
|
|
|
55
51
|
remove(key: string) {
|
|
56
|
-
this.
|
|
52
|
+
this.storeInstance.remove(key);
|
|
57
53
|
}
|
|
58
54
|
|
|
59
55
|
removeIf(predicateFn: (s: string) => boolean) {
|
|
@@ -63,25 +59,19 @@ export class LocalStorageService extends HoistService {
|
|
|
63
59
|
}
|
|
64
60
|
|
|
65
61
|
clear() {
|
|
66
|
-
this.
|
|
62
|
+
this.storeInstance().clear();
|
|
67
63
|
}
|
|
68
64
|
|
|
69
65
|
keys(): string[] {
|
|
70
|
-
return this.
|
|
66
|
+
return this.storeInstance.keys();
|
|
71
67
|
}
|
|
72
68
|
|
|
73
69
|
get isFake(): boolean {
|
|
74
|
-
return
|
|
70
|
+
return this.storeInstance.isFake();
|
|
75
71
|
}
|
|
76
72
|
|
|
77
73
|
//------------------
|
|
78
74
|
// Implementation
|
|
79
75
|
//------------------
|
|
80
|
-
|
|
81
|
-
return store.namespace(this.getNamespace());
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
private getNamespace() {
|
|
85
|
-
return `${XH.appCode}.${XH.getUsername()}`;
|
|
86
|
-
}
|
|
76
|
+
protected abstract get storeInstance(): StoreType;
|
|
87
77
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file belongs to Hoist, an application development toolkit
|
|
3
|
+
* developed by Extremely Heavy Industries (www.xh.io | info@xh.io)
|
|
4
|
+
*
|
|
5
|
+
* Copyright © 2024 Extremely Heavy Industries Inc.
|
|
6
|
+
*/
|
|
7
|
+
import {XH} from '@xh/hoist/core';
|
|
8
|
+
import {BaseStorageService} from '@xh/hoist/svc/storage/BaseStorageService';
|
|
9
|
+
import store, {StoreType} from 'store2';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Service to provide simple key/value access to browser local storage, appropriately namespaced
|
|
13
|
+
* by application code and username.
|
|
14
|
+
*
|
|
15
|
+
* Relied upon by Hoist persistence mechanisms when using key 'localStorageKey`
|
|
16
|
+
*/
|
|
17
|
+
export class LocalStorageService extends BaseStorageService {
|
|
18
|
+
static instance: LocalStorageService;
|
|
19
|
+
|
|
20
|
+
protected get storeInstance(): StoreType {
|
|
21
|
+
return store.local.namespace(`${XH.appCode}.${XH.getUsername()}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file belongs to Hoist, an application development toolkit
|
|
3
|
+
* developed by Extremely Heavy Industries (www.xh.io | info@xh.io)
|
|
4
|
+
*
|
|
5
|
+
* Copyright © 2024 Extremely Heavy Industries Inc.
|
|
6
|
+
*/
|
|
7
|
+
import {XH} from '@xh/hoist/core';
|
|
8
|
+
import {BaseStorageService} from '@xh/hoist/svc/storage/BaseStorageService';
|
|
9
|
+
import store, {StoreType} from 'store2';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Service to provide simple key/value access to browser session storage, appropriately namespaced
|
|
13
|
+
* by application code and username.
|
|
14
|
+
*
|
|
15
|
+
* Relied upon by Hoist persistence mechanisms when using key 'sessionStorageKey`
|
|
16
|
+
*/
|
|
17
|
+
export class SessionStorageService extends BaseStorageService {
|
|
18
|
+
static instance: SessionStorageService;
|
|
19
|
+
|
|
20
|
+
protected get storeInstance(): StoreType {
|
|
21
|
+
return store.session.namespace(`${XH.appCode}.${XH.getUsername()}`);
|
|
22
|
+
}
|
|
23
|
+
}
|