@web-atoms/web-controls 2.1.202 → 2.1.205
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/basic/DataGrid.d.ts.map +1 -1
- package/dist/basic/DataGrid.js +1 -0
- package/dist/basic/DataGrid.js.map +1 -1
- package/dist/basic/Editable.d.ts +0 -1
- package/dist/basic/Editable.d.ts.map +1 -1
- package/dist/basic/Editable.js +5 -7
- package/dist/basic/Editable.js.map +1 -1
- package/dist/html-editor/AtomHtmlEditor.d.ts +1 -0
- package/dist/html-editor/AtomHtmlEditor.d.ts.map +1 -1
- package/dist/html-editor/AtomHtmlEditor.js +26 -1
- package/dist/html-editor/AtomHtmlEditor.js.map +1 -1
- package/dist/html-editor/commands/AttachFile.d.ts +6 -0
- package/dist/html-editor/commands/AttachFile.d.ts.map +1 -0
- package/dist/html-editor/commands/AttachFile.js +78 -0
- package/dist/html-editor/commands/AttachFile.js.map +1 -0
- package/dist/html-editor/commands/CommandButton.js +1 -1
- package/dist/html-editor/commands/CommandButton.js.map +1 -1
- package/dist/mobile-app/MobileApp.d.ts +41 -0
- package/dist/mobile-app/MobileApp.d.ts.map +1 -0
- package/dist/mobile-app/MobileApp.js +322 -0
- package/dist/mobile-app/MobileApp.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/basic/DataGrid.tsx +2 -1
- package/src/basic/Editable.tsx +12 -20
- package/src/html-editor/AtomHtmlEditor.tsx +35 -0
- package/src/html-editor/commands/AttachFile.tsx +63 -0
- package/src/html-editor/commands/CommandButton.tsx +1 -1
- package/src/mobile-app/MobileApp.tsx +348 -0
package/src/basic/Editable.tsx
CHANGED
|
@@ -5,7 +5,7 @@ import IElement from "./IElement";
|
|
|
5
5
|
type Getter = (item) => any;
|
|
6
6
|
|
|
7
7
|
export interface IPropertyInfo {
|
|
8
|
-
key?: string
|
|
8
|
+
key?: string;
|
|
9
9
|
getter(item): any;
|
|
10
10
|
setter(item, value): void;
|
|
11
11
|
}
|
|
@@ -20,15 +20,15 @@ export function getPropertyInfo(propertyPath: string[] | string | IPropertyInfo)
|
|
|
20
20
|
item[propertyPath] = value;
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
-
}
|
|
23
|
+
};
|
|
24
24
|
}
|
|
25
25
|
if (!Array.isArray(propertyPath)) {
|
|
26
26
|
return propertyPath;
|
|
27
27
|
}
|
|
28
28
|
const last = propertyPath.pop();
|
|
29
|
-
let getterIntermediate
|
|
29
|
+
let getterIntermediate;
|
|
30
30
|
if (propertyPath.length > 0) {
|
|
31
|
-
getterIntermediate =
|
|
31
|
+
getterIntermediate = (item) => {
|
|
32
32
|
if (!item) {
|
|
33
33
|
return item;
|
|
34
34
|
}
|
|
@@ -39,19 +39,19 @@ export function getPropertyInfo(propertyPath: string[] | string | IPropertyInfo)
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
return item;
|
|
42
|
-
}
|
|
42
|
+
};
|
|
43
43
|
}
|
|
44
|
-
|
|
44
|
+
const setter = (item, value) => {
|
|
45
45
|
if (item) {
|
|
46
46
|
if (getterIntermediate) {
|
|
47
|
-
item = getterIntermediate(item)
|
|
47
|
+
item = getterIntermediate(item);
|
|
48
48
|
}
|
|
49
49
|
if (item) {
|
|
50
50
|
item[last] = value;
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
-
}
|
|
54
|
-
|
|
53
|
+
};
|
|
54
|
+
const getter = (item) => {
|
|
55
55
|
if (!item) {
|
|
56
56
|
return item;
|
|
57
57
|
}
|
|
@@ -64,7 +64,7 @@ export function getPropertyInfo(propertyPath: string[] | string | IPropertyInfo)
|
|
|
64
64
|
key: propertyPath.join(","),
|
|
65
65
|
getter,
|
|
66
66
|
setter
|
|
67
|
-
}
|
|
67
|
+
};
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
const handlers = {};
|
|
@@ -72,7 +72,7 @@ const handlers = {};
|
|
|
72
72
|
function registerEvent(property: IPropertyInfo, changeEvents: string[], editorValuePath: (editor) => any) {
|
|
73
73
|
property.key ??= property.setter + ":" + property.getter;
|
|
74
74
|
const name = `${property.key}:${changeEvents.join(",")}`;
|
|
75
|
-
if(!handlers[name]) {
|
|
75
|
+
if (!handlers[name]) {
|
|
76
76
|
handlers[name] = changeEvents;
|
|
77
77
|
for (const iterator of changeEvents) {
|
|
78
78
|
document.body.addEventListener(iterator, (e) => {
|
|
@@ -84,6 +84,7 @@ function registerEvent(property: IPropertyInfo, changeEvents: string[], editorVa
|
|
|
84
84
|
const [ _, repeater, item] = ri;
|
|
85
85
|
const oldValue = property.getter(item);
|
|
86
86
|
const value = editorValuePath(e.target);
|
|
87
|
+
// tslint:disable-next-line: triple-equals
|
|
87
88
|
if (oldValue != value) {
|
|
88
89
|
property.setter(item, value);
|
|
89
90
|
repeater.refreshItem(item);
|
|
@@ -94,19 +95,10 @@ function registerEvent(property: IPropertyInfo, changeEvents: string[], editorVa
|
|
|
94
95
|
}
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
|
|
98
98
|
export interface IEditable extends IElement {
|
|
99
99
|
propertyPath: string[] | string | IPropertyInfo;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
export default function Editable(
|
|
103
|
-
{
|
|
104
|
-
propertyPath
|
|
105
|
-
}: IEditable
|
|
106
|
-
) {
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
|
|
110
102
|
export function EditableInput(
|
|
111
103
|
{
|
|
112
104
|
propertyPath,
|
|
@@ -6,9 +6,11 @@ import XNode from "@web-atoms/core/dist/core/XNode";
|
|
|
6
6
|
import StyleRule from "@web-atoms/core/dist/style/StyleRule";
|
|
7
7
|
import { AtomControl } from "@web-atoms/core/dist/web/controls/AtomControl";
|
|
8
8
|
import CSS from "@web-atoms/core/dist/web/styles/CSS";
|
|
9
|
+
import AtomRepeater from "../basic/AtomRepeater";
|
|
9
10
|
import AddImage, { showImageDialog } from "./commands/AddImage";
|
|
10
11
|
import AddLink from "./commands/AddLink";
|
|
11
12
|
import Align from "./commands/Align";
|
|
13
|
+
import AttachFile from "./commands/AttachFile";
|
|
12
14
|
import Bold from "./commands/Bold";
|
|
13
15
|
import ChangeColor from "./commands/ChangeColor";
|
|
14
16
|
import ChangeFont from "./commands/ChangeFont";
|
|
@@ -39,6 +41,21 @@ const css = CSS(StyleRule()
|
|
|
39
41
|
.child(StyleRule("iframe")
|
|
40
42
|
.flexStretch()
|
|
41
43
|
)
|
|
44
|
+
.child(StyleRule(".files")
|
|
45
|
+
.child(StyleRule(".file")
|
|
46
|
+
.flexLayout({ inline: true })
|
|
47
|
+
.borderColor(Colors.lightGray.withAlphaPercent(0.5))
|
|
48
|
+
.borderWidth(1)
|
|
49
|
+
.borderStyle("solid")
|
|
50
|
+
.borderRadius(15)
|
|
51
|
+
.paddingLeft(10)
|
|
52
|
+
.paddingRight(10)
|
|
53
|
+
.child(StyleRule("label")
|
|
54
|
+
.maxWidth(100)
|
|
55
|
+
.textEllipsis()
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
)
|
|
42
59
|
.nested(StyleRule(".toolbar")
|
|
43
60
|
.display("flex")
|
|
44
61
|
.child(StyleRule(".command")
|
|
@@ -148,6 +165,9 @@ export default class AtomHtmlEditor extends AtomControl {
|
|
|
148
165
|
@BindableProperty
|
|
149
166
|
public version: number;
|
|
150
167
|
|
|
168
|
+
@BindableProperty
|
|
169
|
+
public files: File[];
|
|
170
|
+
|
|
151
171
|
public editor: HTMLDivElement;
|
|
152
172
|
|
|
153
173
|
public eventDocumentCreated: (e: CustomEvent<HTMLDivElement>) => void;
|
|
@@ -200,6 +220,7 @@ export default class AtomHtmlEditor extends AtomControl {
|
|
|
200
220
|
|
|
201
221
|
protected preCreate() {
|
|
202
222
|
this.version = 1;
|
|
223
|
+
this.files = [];
|
|
203
224
|
this.runAfterInit(() => {
|
|
204
225
|
this.setup();
|
|
205
226
|
});
|
|
@@ -323,6 +344,7 @@ export default class AtomHtmlEditor extends AtomControl {
|
|
|
323
344
|
<IndentMore/>
|
|
324
345
|
<Separator/>
|
|
325
346
|
<AddImage/>
|
|
347
|
+
<AttachFile/>
|
|
326
348
|
<Separator/>
|
|
327
349
|
<AddLink/>
|
|
328
350
|
<Unlink/>
|
|
@@ -333,6 +355,19 @@ export default class AtomHtmlEditor extends AtomControl {
|
|
|
333
355
|
}
|
|
334
356
|
super.render(<div {... node.attributes}>
|
|
335
357
|
{ ... node.children as any[]}
|
|
358
|
+
<AtomRepeater
|
|
359
|
+
class="files"
|
|
360
|
+
event-item-delete={(ce) => this.files.remove(ce.detail)}
|
|
361
|
+
items={Bind.oneWay(() => this.files)}
|
|
362
|
+
itemRenderer={(file: File) => <div class="file">
|
|
363
|
+
<i class="ri-attachment-2"/>
|
|
364
|
+
<label text={file.name}/>
|
|
365
|
+
<i
|
|
366
|
+
data-click-event="item-delete"
|
|
367
|
+
class="ri-close-circle-fill"
|
|
368
|
+
/>
|
|
369
|
+
</div>}
|
|
370
|
+
/>
|
|
336
371
|
<iframe class="editor-frame"/>
|
|
337
372
|
</div>);
|
|
338
373
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import AtomHtmlEditor from "../AtomHtmlEditor";
|
|
2
|
+
import CommandButton from "./CommandButton";
|
|
3
|
+
import HtmlCommands from "./HtmlCommands";
|
|
4
|
+
|
|
5
|
+
function promptForFiles(
|
|
6
|
+
accept = "*",
|
|
7
|
+
multiple = true
|
|
8
|
+
) {
|
|
9
|
+
const file = document.createElement("input");
|
|
10
|
+
file.type = "file";
|
|
11
|
+
file.multiple = multiple;
|
|
12
|
+
file.accept = accept;
|
|
13
|
+
file.style.position = "absolute";
|
|
14
|
+
file.style.left = "-1000px";
|
|
15
|
+
file.style.top = "-1000px";
|
|
16
|
+
document.body.append(file);
|
|
17
|
+
const previous = file;
|
|
18
|
+
return new Promise<File[]>((resolve, reject) => {
|
|
19
|
+
file.addEventListener("change", () => {
|
|
20
|
+
if (file.files.length) {
|
|
21
|
+
const files = Array.from(file.files);
|
|
22
|
+
resolve(files);
|
|
23
|
+
} else {
|
|
24
|
+
reject("cancelled");
|
|
25
|
+
}
|
|
26
|
+
if (previous) {
|
|
27
|
+
previous.remove();
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
file.dispatchEvent(new MouseEvent("click"));
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default function AttachFile({
|
|
35
|
+
eventInsertHtml = (source: AtomHtmlEditor, e: Event) => {
|
|
36
|
+
const ce = new CustomEvent("attachFile",
|
|
37
|
+
{
|
|
38
|
+
detail: {
|
|
39
|
+
source,
|
|
40
|
+
files: source.files
|
|
41
|
+
},
|
|
42
|
+
cancelable: true
|
|
43
|
+
});
|
|
44
|
+
source.element.dispatchEvent(ce);
|
|
45
|
+
if (ce.defaultPrevented) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
source.app.runAsync(async () => {
|
|
49
|
+
const result = await promptForFiles();
|
|
50
|
+
source.files ??= [];
|
|
51
|
+
source.files.addAll(result);
|
|
52
|
+
})
|
|
53
|
+
},
|
|
54
|
+
insertCommand = HtmlCommands.insertImage
|
|
55
|
+
}) {
|
|
56
|
+
return CommandButton({
|
|
57
|
+
icon: "ri-attachment-2",
|
|
58
|
+
insertCommand,
|
|
59
|
+
disabled: false,
|
|
60
|
+
title: "Insert Image",
|
|
61
|
+
eventInsertHtml
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { AtomLoader } from "@web-atoms/core/dist/core/AtomLoader";
|
|
2
|
+
import { AtomUri } from "@web-atoms/core/dist/core/AtomUri";
|
|
3
|
+
import Bind from "@web-atoms/core/dist/core/Bind";
|
|
4
|
+
import { BindableProperty } from "@web-atoms/core/dist/core/BindableProperty";
|
|
5
|
+
import Colors from "@web-atoms/core/dist/core/Colors";
|
|
6
|
+
import { CancelToken, IClassOf, IDisposable } from "@web-atoms/core/dist/core/types";
|
|
7
|
+
import XNode from "@web-atoms/core/dist/core/XNode";
|
|
8
|
+
import { NavigationService } from "@web-atoms/core/dist/services/NavigationService";
|
|
9
|
+
import StyleRule from "@web-atoms/core/dist/style/StyleRule";
|
|
10
|
+
import { AtomWindowViewModel } from "@web-atoms/core/dist/view-model/AtomWindowViewModel";
|
|
11
|
+
import { AtomControl } from "@web-atoms/core/dist/web/controls/AtomControl";
|
|
12
|
+
import PopupService, { ConfirmPopup, IPopup, PopupControl } from "@web-atoms/core/dist/web/services/PopupService";
|
|
13
|
+
import CSS from "@web-atoms/core/dist/web/styles/CSS";
|
|
14
|
+
|
|
15
|
+
CSS(StyleRule()
|
|
16
|
+
.absolutePosition({ left: 0, top: 0, bottom: 0, right: 0})
|
|
17
|
+
.overflow("hidden")
|
|
18
|
+
, "div[data-page-app=page-app]");
|
|
19
|
+
|
|
20
|
+
CSS(StyleRule()
|
|
21
|
+
.absolutePosition({ left: 0, top: 0, bottom: 0, right: 0})
|
|
22
|
+
.overflow("hidden")
|
|
23
|
+
.transition("transform 0.3s ease-out")
|
|
24
|
+
.display("grid")
|
|
25
|
+
.gridTemplateRows("25px 1fr auto")
|
|
26
|
+
.gridTemplateColumns("25px 1fr auto")
|
|
27
|
+
.child(StyleRule("[data-icon-button=icon-button]")
|
|
28
|
+
.padding(5)
|
|
29
|
+
)
|
|
30
|
+
.child(StyleRule("[data-page-element=header]")
|
|
31
|
+
.gridRowStart("1")
|
|
32
|
+
.gridColumnStart("1")
|
|
33
|
+
.gridColumnEnd("span 3")
|
|
34
|
+
.backgroundColor(Colors.silver)
|
|
35
|
+
)
|
|
36
|
+
.child(StyleRule("[data-page-element=icon]")
|
|
37
|
+
.gridRowStart("1")
|
|
38
|
+
.gridColumnStart("1")
|
|
39
|
+
)
|
|
40
|
+
.child(StyleRule("[data-page-element=title]")
|
|
41
|
+
.gridRowStart("1")
|
|
42
|
+
.gridColumnStart("2")
|
|
43
|
+
)
|
|
44
|
+
.child(StyleRule("[data-page-element=action]")
|
|
45
|
+
.gridRowStart("1")
|
|
46
|
+
.gridColumnStart("3")
|
|
47
|
+
)
|
|
48
|
+
.child(StyleRule("[data-page-element=content]")
|
|
49
|
+
.gridRowStart("2")
|
|
50
|
+
.gridColumnStart("1")
|
|
51
|
+
.gridColumnEnd("span 3")
|
|
52
|
+
)
|
|
53
|
+
.child(StyleRule("[data-page-element=footer]")
|
|
54
|
+
.gridRowStart("3")
|
|
55
|
+
.gridColumnStart("1")
|
|
56
|
+
.gridColumnEnd("span 3")
|
|
57
|
+
)
|
|
58
|
+
.transform("translate(100%,0)" as any)
|
|
59
|
+
.and(StyleRule("[data-page-state=ready]")
|
|
60
|
+
.transform("translate(0,0)" as any)
|
|
61
|
+
)
|
|
62
|
+
.and(StyleRule("[data-page-state=hidden]")
|
|
63
|
+
.transform("translate(-100%,0)" as any)
|
|
64
|
+
)
|
|
65
|
+
, "div[data-base-page=base-page]");
|
|
66
|
+
|
|
67
|
+
export class BasePage extends AtomControl {
|
|
68
|
+
|
|
69
|
+
public close: (result) => void;
|
|
70
|
+
|
|
71
|
+
public cancel: (error?) => void;
|
|
72
|
+
|
|
73
|
+
@BindableProperty
|
|
74
|
+
public closeWarning: string;
|
|
75
|
+
|
|
76
|
+
@BindableProperty
|
|
77
|
+
public title?: string;
|
|
78
|
+
|
|
79
|
+
public titleRenderer: () => XNode;
|
|
80
|
+
|
|
81
|
+
public iconRenderer: () => XNode;
|
|
82
|
+
|
|
83
|
+
public actionRenderer: () => XNode;
|
|
84
|
+
|
|
85
|
+
public footerRenderer: () => XNode;
|
|
86
|
+
|
|
87
|
+
public headerBackgroundRenderer: () => XNode;
|
|
88
|
+
|
|
89
|
+
public iconClass: any;
|
|
90
|
+
|
|
91
|
+
public async requestCancel() {
|
|
92
|
+
if (this.closeWarning) {
|
|
93
|
+
if (!await ConfirmPopup.showModal<boolean>({
|
|
94
|
+
parameters: {
|
|
95
|
+
message : this.closeWarning
|
|
96
|
+
}
|
|
97
|
+
})) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
this.cancel();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
protected preCreate(): void {
|
|
105
|
+
this.element.dataset.basePage = "base-page";
|
|
106
|
+
this.iconClass = "";
|
|
107
|
+
this.runAfterInit(() => {
|
|
108
|
+
if (!this.element) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const anyAutofocus = this.element.querySelector(`*[autofocus]`);
|
|
112
|
+
if (!anyAutofocus) {
|
|
113
|
+
const windowContent = this.element.querySelector(`[data-page-element="content"]`);
|
|
114
|
+
if (windowContent) {
|
|
115
|
+
const firstInput = windowContent.querySelector("input,button,a,textarea") as HTMLInputElement;
|
|
116
|
+
if (firstInput) {
|
|
117
|
+
firstInput.focus();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
setTimeout((p) => {
|
|
126
|
+
p.dataset.pageState = "ready";
|
|
127
|
+
}, 10, this.element);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
protected render(node: XNode, e?: any, creator?: any): void {
|
|
131
|
+
this.render = super.render;
|
|
132
|
+
const titleContent = this.titleRenderer?.() ?? <span
|
|
133
|
+
class="title-text" text={Bind.oneWay(() => this.title)}/>;
|
|
134
|
+
const icon = this.iconRenderer?.() ?? <i
|
|
135
|
+
data-icon-button="icon-button"
|
|
136
|
+
class={Bind.oneWay(() => this.iconClass)}
|
|
137
|
+
eventClick={(e1) => this.dispatchIconClickEvent(e1)}/>;
|
|
138
|
+
const action = this.actionRenderer?.() ?? undefined;
|
|
139
|
+
const footer = this.footerRenderer?.() ?? undefined;
|
|
140
|
+
const header = this.headerBackgroundRenderer?.() ?? <div/>;
|
|
141
|
+
const a = node.attributes ??= {};
|
|
142
|
+
a["data-page-element"] = "content";
|
|
143
|
+
if (header) {
|
|
144
|
+
header.attributes ??= {};
|
|
145
|
+
header.attributes["data-page-element"] = "header";
|
|
146
|
+
}
|
|
147
|
+
if (icon) {
|
|
148
|
+
icon.attributes ??= {};
|
|
149
|
+
icon.attributes["data-page-element"] = "icon";
|
|
150
|
+
}
|
|
151
|
+
if (titleContent) {
|
|
152
|
+
titleContent.attributes ??= {};
|
|
153
|
+
titleContent.attributes["data-page-element"] = "title";
|
|
154
|
+
}
|
|
155
|
+
if (action) {
|
|
156
|
+
action.attributes ??= {};
|
|
157
|
+
action.attributes["data-page-element"] = "action";
|
|
158
|
+
}
|
|
159
|
+
if (footer) {
|
|
160
|
+
footer.attributes ??= {};
|
|
161
|
+
footer.attributes["data-page-element"] = "footer";
|
|
162
|
+
}
|
|
163
|
+
super.render(<div viewModelTitle={Bind.oneWay(() => this.viewModel.title)}>
|
|
164
|
+
{ header }
|
|
165
|
+
{ icon }
|
|
166
|
+
{ titleContent }
|
|
167
|
+
{ action }
|
|
168
|
+
{ node }
|
|
169
|
+
{ footer }
|
|
170
|
+
</div>);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
protected dispatchIconClickEvent(e: Event) {
|
|
174
|
+
const ce = new CustomEvent("iconClick", { bubbles: true });
|
|
175
|
+
e.target.dispatchEvent(ce);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export class ContentPage extends BasePage {
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export class TabbedPage extends BasePage {
|
|
183
|
+
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export class Drawer extends AtomControl {
|
|
187
|
+
protected preCreate(): void {
|
|
188
|
+
this.bindEvent(this.element, "click", (e) => this.closeDrawer());
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
protected closeDrawer() {
|
|
192
|
+
const ce = new CustomEvent("closeDrawer", { bubbles: true });
|
|
193
|
+
this.element.dispatchEvent(ce);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export default class MobileApp extends AtomControl {
|
|
198
|
+
|
|
199
|
+
public static drawer = XNode.prepare("drawer", true, true);
|
|
200
|
+
|
|
201
|
+
public drawer: () => XNode;
|
|
202
|
+
|
|
203
|
+
public drawerCancelToken: CancelToken;
|
|
204
|
+
|
|
205
|
+
public pages: BasePage[];
|
|
206
|
+
|
|
207
|
+
public selectedPage: BasePage;
|
|
208
|
+
|
|
209
|
+
public icon: any;
|
|
210
|
+
|
|
211
|
+
public async back() {
|
|
212
|
+
if (this.pages.length === 0) {
|
|
213
|
+
|
|
214
|
+
const drawer = this.drawer;
|
|
215
|
+
if (drawer) {
|
|
216
|
+
const drawerNode = this.drawer();
|
|
217
|
+
const da = drawerNode.attributes ??= {};
|
|
218
|
+
da["event-click"] = (de: Event) => {
|
|
219
|
+
de.target.dispatchEvent(new CustomEvent("closeDrawer", { bubbles: true }));
|
|
220
|
+
};
|
|
221
|
+
class DrawerPopup extends PopupControl {
|
|
222
|
+
protected create(): void {
|
|
223
|
+
this.render(<div event-close-drawer={() => cancelToken.cancel()}>
|
|
224
|
+
{ drawerNode }
|
|
225
|
+
</div>);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const cancelToken = new CancelToken();
|
|
229
|
+
this.drawerCancelToken = cancelToken;
|
|
230
|
+
DrawerPopup.showControl(this.icon, { cancelToken });
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
this.selectedPage.cancel("cancelled");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
protected preCreate(): void {
|
|
240
|
+
this.drawer = null;
|
|
241
|
+
this.element.dataset.pageApp = "page-app";
|
|
242
|
+
this.pages = [];
|
|
243
|
+
this.selectedPage = null;
|
|
244
|
+
this.bindEvent(this.element, "iconClick", (e) => { this.icon = e.target; return this.back(); });
|
|
245
|
+
this.bindEvent(this.element, "closeDrawer", (e) => {
|
|
246
|
+
this.drawerCancelToken?.cancel();
|
|
247
|
+
this.drawerCancelToken = undefined;
|
|
248
|
+
});
|
|
249
|
+
const navigationService = this.app.resolve(NavigationService);
|
|
250
|
+
navigationService.registerNavigationHook(
|
|
251
|
+
(uri, { target, clearHistory }) => {
|
|
252
|
+
if (target === "app") {
|
|
253
|
+
return this.loadPageForReturn(uri, clearHistory);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
protected async loadPageForReturn(url: AtomUri, clearHistory: boolean): Promise<any> {
|
|
260
|
+
const p = await this.loadPage(url, clearHistory);
|
|
261
|
+
try {
|
|
262
|
+
return await (p as any).returnPromise;
|
|
263
|
+
} catch (ex) {
|
|
264
|
+
// this will prevent warning in chrome for unhandled exception
|
|
265
|
+
if ((ex.message ? ex.message : ex) === "cancelled") {
|
|
266
|
+
// tslint:disable-next-line: no-console
|
|
267
|
+
console.warn(ex);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
throw ex;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
protected async loadPage(url: AtomUri, clearHistory: boolean) {
|
|
275
|
+
const { view: page, disposables } =
|
|
276
|
+
await AtomLoader.loadView<BasePage>(url, this.app, false, () => new AtomWindowViewModel(this.app));
|
|
277
|
+
page.title = "Title";
|
|
278
|
+
if (url.query && url.query.title) {
|
|
279
|
+
page.title = url.query.title.toString();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
page.bind(page.element, "title", [["viewModel", "title"]]);
|
|
283
|
+
|
|
284
|
+
const selectedPage = this.selectedPage;
|
|
285
|
+
if (selectedPage) {
|
|
286
|
+
selectedPage.element.dataset.pageState = "hidden";
|
|
287
|
+
this.pages.add(selectedPage);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const hasPages = !clearHistory && this.pages.length;
|
|
291
|
+
|
|
292
|
+
if (clearHistory) {
|
|
293
|
+
for (const iterator of this.pages) {
|
|
294
|
+
const e = iterator.element;
|
|
295
|
+
iterator.dispose();
|
|
296
|
+
e.remove();
|
|
297
|
+
}
|
|
298
|
+
this.pages.length = 0;
|
|
299
|
+
this.selectedPage = null;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (!hasPages) {
|
|
303
|
+
page.element.dataset.pageState = "ready";
|
|
304
|
+
page.iconClass = "fas fa-bars";
|
|
305
|
+
} else {
|
|
306
|
+
page.iconClass = "fas fa-arrow-left";
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
this.element.appendChild(page.element);
|
|
310
|
+
this.selectedPage = page;
|
|
311
|
+
|
|
312
|
+
const vm = page.viewModel as AtomWindowViewModel;
|
|
313
|
+
const element = page.element;
|
|
314
|
+
return new Promise((resolve, reject) => {
|
|
315
|
+
const cancel: any = (error?) => {
|
|
316
|
+
// page.dispose();
|
|
317
|
+
// element.remove();
|
|
318
|
+
element.dataset.pageState = "";
|
|
319
|
+
const last = this.pages.pop();
|
|
320
|
+
last.element.dataset.pageState = "ready";
|
|
321
|
+
setTimeout(() => {
|
|
322
|
+
page.dispose();
|
|
323
|
+
element.remove();
|
|
324
|
+
reject(error ?? "cancelled");
|
|
325
|
+
this.selectedPage = last;
|
|
326
|
+
}, 300);
|
|
327
|
+
};
|
|
328
|
+
const close: any = (r) => {
|
|
329
|
+
// page.dispose();
|
|
330
|
+
// element.remove();
|
|
331
|
+
delete element.dataset.pageState;
|
|
332
|
+
const last = this.pages.pop();
|
|
333
|
+
last.element.dataset.pageState = "ready";
|
|
334
|
+
setTimeout(() => {
|
|
335
|
+
page.dispose();
|
|
336
|
+
element.remove();
|
|
337
|
+
resolve(r);
|
|
338
|
+
this.selectedPage = last;
|
|
339
|
+
}, 300);
|
|
340
|
+
};
|
|
341
|
+
vm.cancel = cancel;
|
|
342
|
+
page.cancel = cancel;
|
|
343
|
+
vm.close = close;
|
|
344
|
+
page.close = close;
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
}
|
|
348
|
+
}
|