@jskit-ai/shell-web 0.1.4
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/package.descriptor.mjs +165 -0
- package/package.json +23 -0
- package/src/client/components/ShellErrorHost.vue +208 -0
- package/src/client/components/ShellLayout.vue +191 -0
- package/src/client/components/ShellOutlet.vue +95 -0
- package/src/client/components/useShellLayout.js +93 -0
- package/src/client/error/index.js +2 -0
- package/src/client/error/inject.js +142 -0
- package/src/client/error/normalize.js +75 -0
- package/src/client/error/policy.js +50 -0
- package/src/client/error/presenters.js +89 -0
- package/src/client/error/runtime.js +418 -0
- package/src/client/error/store.js +176 -0
- package/src/client/error/tokens.js +14 -0
- package/src/client/index.js +17 -0
- package/src/client/navigation/linkResolver.js +117 -0
- package/src/client/placement/debug.js +52 -0
- package/src/client/placement/index.js +26 -0
- package/src/client/placement/inject.js +104 -0
- package/src/client/placement/pathname.js +14 -0
- package/src/client/placement/registry.js +41 -0
- package/src/client/placement/runtime.js +435 -0
- package/src/client/placement/surfaceContext.js +290 -0
- package/src/client/placement/tokens.js +29 -0
- package/src/client/placement/validators.js +210 -0
- package/src/client/providers/ShellWebClientProvider.js +352 -0
- package/templates/src/App.vue +11 -0
- package/templates/src/components/ShellLayout.vue +247 -0
- package/templates/src/error.js +13 -0
- package/templates/src/pages/console/index.vue +24 -0
- package/templates/src/pages/console.vue +20 -0
- package/templates/src/pages/home/index.vue +54 -0
- package/templates/src/pages/home.vue +20 -0
- package/templates/src/placement.js +12 -0
- package/test/errorRuntime.test.js +191 -0
- package/test/errorStore.test.js +26 -0
- package/test/linkResolver.test.js +112 -0
- package/test/placementRegistry.test.js +45 -0
- package/test/placementRuntime.test.js +374 -0
- package/test/provider.test.js +163 -0
- package/test/surfaceContext.test.js +184 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { computed, ref } from "vue";
|
|
2
|
+
import { normalizeObject } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
|
+
|
|
4
|
+
const DEFAULT_ACTION_FALLBACK = Object.freeze({
|
|
5
|
+
label: "",
|
|
6
|
+
to: "",
|
|
7
|
+
variant: "text",
|
|
8
|
+
color: "secondary"
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const DEFAULT_MENU_FALLBACK = Object.freeze({
|
|
12
|
+
label: "",
|
|
13
|
+
to: "/",
|
|
14
|
+
icon: "$menu"
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
function normalizeAction(action, fallback) {
|
|
18
|
+
const source = normalizeObject(action);
|
|
19
|
+
const fallbackSource = normalizeObject(fallback);
|
|
20
|
+
const label = String(source.label || fallbackSource.label || "").trim();
|
|
21
|
+
if (!label) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
label,
|
|
27
|
+
to: String(source.to || fallbackSource.to || "").trim(),
|
|
28
|
+
variant: String(source.variant || fallbackSource.variant || "text").trim(),
|
|
29
|
+
color: String(source.color || fallbackSource.color || "secondary").trim()
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function normalizeMenuItem(item, fallback) {
|
|
34
|
+
const source = normalizeObject(item);
|
|
35
|
+
const fallbackSource = normalizeObject(fallback);
|
|
36
|
+
const label = String(source.label || fallbackSource.label || "").trim();
|
|
37
|
+
if (!label) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
label,
|
|
43
|
+
to: String(source.to || fallbackSource.to || "").trim() || "/",
|
|
44
|
+
icon: String(source.icon || fallbackSource.icon || "$menu").trim() || "$menu"
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizeActionList(actions) {
|
|
49
|
+
const source = Array.isArray(actions) ? actions : [];
|
|
50
|
+
return source
|
|
51
|
+
.map((item) => normalizeAction(item, DEFAULT_ACTION_FALLBACK))
|
|
52
|
+
.filter(Boolean);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function normalizeMenuList(items) {
|
|
56
|
+
const source = Array.isArray(items) ? items : [];
|
|
57
|
+
return source
|
|
58
|
+
.map((item) => normalizeMenuItem(item, DEFAULT_MENU_FALLBACK))
|
|
59
|
+
.filter(Boolean);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function useShellLayout({ topLeftActions, topRightActions, menuItems } = {}) {
|
|
63
|
+
const drawerOpen = ref(true);
|
|
64
|
+
|
|
65
|
+
const resolvedTopLeftActions = computed(() => {
|
|
66
|
+
const source = topLeftActions?.value;
|
|
67
|
+
return normalizeActionList(source);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const resolvedTopRightActions = computed(() => {
|
|
71
|
+
const source = topRightActions?.value;
|
|
72
|
+
return normalizeActionList(source);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const resolvedMenuItems = computed(() => {
|
|
76
|
+
const source = menuItems?.value;
|
|
77
|
+
return normalizeMenuList(source);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
function toggleDrawer() {
|
|
81
|
+
drawerOpen.value = !drawerOpen.value;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
drawerOpen,
|
|
86
|
+
resolvedTopLeftActions,
|
|
87
|
+
resolvedTopRightActions,
|
|
88
|
+
resolvedMenuItems,
|
|
89
|
+
toggleDrawer
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export { useShellLayout };
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {
|
|
2
|
+
inject,
|
|
3
|
+
onBeforeUnmount,
|
|
4
|
+
onMounted,
|
|
5
|
+
shallowRef
|
|
6
|
+
} from "vue";
|
|
7
|
+
import {
|
|
8
|
+
SHELL_WEB_ERROR_RUNTIME_INJECTION_KEY,
|
|
9
|
+
SHELL_WEB_ERROR_PRESENTATION_STORE_INJECTION_KEY
|
|
10
|
+
} from "./tokens.js";
|
|
11
|
+
|
|
12
|
+
const EMPTY_PRESENTATION_STATE = Object.freeze({
|
|
13
|
+
revision: 0,
|
|
14
|
+
channels: Object.freeze({
|
|
15
|
+
snackbar: Object.freeze([]),
|
|
16
|
+
banner: Object.freeze([]),
|
|
17
|
+
dialog: Object.freeze([])
|
|
18
|
+
})
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const EMPTY_ERROR_RUNTIME = Object.freeze({
|
|
22
|
+
report() {
|
|
23
|
+
return Object.freeze({
|
|
24
|
+
skipped: true,
|
|
25
|
+
reason: "unavailable"
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
dismiss() {
|
|
29
|
+
return 0;
|
|
30
|
+
},
|
|
31
|
+
configure() {
|
|
32
|
+
return Object.freeze({
|
|
33
|
+
presenterIds: Object.freeze([]),
|
|
34
|
+
appDefaultPresenterId: "",
|
|
35
|
+
moduleDefaultPresenterId: "",
|
|
36
|
+
resolvedDefaultPresenterId: ""
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
registerPresenter() {
|
|
40
|
+
throw new Error("Shell web error runtime is not available.");
|
|
41
|
+
},
|
|
42
|
+
registerPresenters() {
|
|
43
|
+
throw new Error("Shell web error runtime is not available.");
|
|
44
|
+
},
|
|
45
|
+
setPolicy() {
|
|
46
|
+
throw new Error("Shell web error runtime is not available.");
|
|
47
|
+
},
|
|
48
|
+
setAppDefaultPresenterId() {
|
|
49
|
+
throw new Error("Shell web error runtime is not available.");
|
|
50
|
+
},
|
|
51
|
+
assertBootReady() {
|
|
52
|
+
throw new Error("Shell web error runtime is not available.");
|
|
53
|
+
},
|
|
54
|
+
getSnapshot() {
|
|
55
|
+
return Object.freeze({
|
|
56
|
+
presenterIds: Object.freeze([]),
|
|
57
|
+
appDefaultPresenterId: "",
|
|
58
|
+
moduleDefaultPresenterId: "",
|
|
59
|
+
resolvedDefaultPresenterId: ""
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
subscribe() {
|
|
63
|
+
return () => {};
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const EMPTY_PRESENTATION_STORE = Object.freeze({
|
|
68
|
+
getState() {
|
|
69
|
+
return EMPTY_PRESENTATION_STATE;
|
|
70
|
+
},
|
|
71
|
+
subscribe() {
|
|
72
|
+
return () => {};
|
|
73
|
+
},
|
|
74
|
+
present() {
|
|
75
|
+
throw new Error("Shell web error presentation store is not available.");
|
|
76
|
+
},
|
|
77
|
+
dismiss() {
|
|
78
|
+
return 0;
|
|
79
|
+
},
|
|
80
|
+
clear() {
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
function useShellWebErrorRuntime({ required = false } = {}) {
|
|
86
|
+
const runtime = inject(SHELL_WEB_ERROR_RUNTIME_INJECTION_KEY, null);
|
|
87
|
+
if (runtime && typeof runtime.report === "function") {
|
|
88
|
+
return runtime;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (required) {
|
|
92
|
+
throw new Error("Shell web error runtime is not available in Vue injection context.");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return EMPTY_ERROR_RUNTIME;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function useShellWebErrorPresentationStore({ required = false } = {}) {
|
|
99
|
+
const store = inject(SHELL_WEB_ERROR_PRESENTATION_STORE_INJECTION_KEY, null);
|
|
100
|
+
if (store && typeof store.getState === "function" && typeof store.subscribe === "function") {
|
|
101
|
+
return store;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (required) {
|
|
105
|
+
throw new Error("Shell web error presentation store is not available in Vue injection context.");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return EMPTY_PRESENTATION_STORE;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function useShellWebErrorPresentationState({ required = false } = {}) {
|
|
112
|
+
const store = useShellWebErrorPresentationStore({ required });
|
|
113
|
+
const state = shallowRef(store.getState());
|
|
114
|
+
let unsubscribe = null;
|
|
115
|
+
|
|
116
|
+
onMounted(() => {
|
|
117
|
+
unsubscribe = store.subscribe((nextState) => {
|
|
118
|
+
state.value = nextState;
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
onBeforeUnmount(() => {
|
|
123
|
+
if (typeof unsubscribe === "function") {
|
|
124
|
+
unsubscribe();
|
|
125
|
+
unsubscribe = null;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return Object.freeze({
|
|
130
|
+
state,
|
|
131
|
+
store
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export {
|
|
136
|
+
EMPTY_ERROR_RUNTIME,
|
|
137
|
+
EMPTY_PRESENTATION_STORE,
|
|
138
|
+
EMPTY_PRESENTATION_STATE,
|
|
139
|
+
useShellWebErrorRuntime,
|
|
140
|
+
useShellWebErrorPresentationStore,
|
|
141
|
+
useShellWebErrorPresentationState
|
|
142
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
|
+
|
|
3
|
+
const ERROR_CHANNELS = Object.freeze(["snackbar", "banner", "dialog", "silent"]);
|
|
4
|
+
const ERROR_SEVERITIES = Object.freeze(["info", "success", "warning", "error", "critical"]);
|
|
5
|
+
|
|
6
|
+
function normalizeText(value, fallback = "") {
|
|
7
|
+
const normalized = String(value || "").trim();
|
|
8
|
+
if (normalized) {
|
|
9
|
+
return normalized;
|
|
10
|
+
}
|
|
11
|
+
return String(fallback || "").trim();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function normalizeChannel(value, fallback = "") {
|
|
15
|
+
const normalized = normalizeText(value).toLowerCase();
|
|
16
|
+
if (ERROR_CHANNELS.includes(normalized)) {
|
|
17
|
+
return normalized;
|
|
18
|
+
}
|
|
19
|
+
return normalizeText(fallback).toLowerCase();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function normalizeSeverity(value, fallback = "error") {
|
|
23
|
+
const normalized = normalizeText(value).toLowerCase();
|
|
24
|
+
if (ERROR_SEVERITIES.includes(normalized)) {
|
|
25
|
+
return normalized;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const normalizedFallback = normalizeText(fallback).toLowerCase();
|
|
29
|
+
if (ERROR_SEVERITIES.includes(normalizedFallback)) {
|
|
30
|
+
return normalizedFallback;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return "error";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function normalizeNonNegativeInteger(value, fallback = 0) {
|
|
37
|
+
const numericValue = Number(value);
|
|
38
|
+
if (!Number.isFinite(numericValue)) {
|
|
39
|
+
return Math.max(0, Number(fallback || 0));
|
|
40
|
+
}
|
|
41
|
+
if (numericValue < 0) {
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
return Math.trunc(numericValue);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function normalizeAction(value) {
|
|
48
|
+
const source = isRecord(value) ? value : null;
|
|
49
|
+
if (!source) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const label = normalizeText(source.label);
|
|
54
|
+
const handler = typeof source.handler === "function" ? source.handler : null;
|
|
55
|
+
if (!label || !handler) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return Object.freeze({
|
|
60
|
+
label,
|
|
61
|
+
handler,
|
|
62
|
+
dismissOnRun: source.dismissOnRun !== false
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export {
|
|
67
|
+
ERROR_CHANNELS,
|
|
68
|
+
ERROR_SEVERITIES,
|
|
69
|
+
isRecord,
|
|
70
|
+
normalizeText,
|
|
71
|
+
normalizeChannel,
|
|
72
|
+
normalizeSeverity,
|
|
73
|
+
normalizeNonNegativeInteger,
|
|
74
|
+
normalizeAction
|
|
75
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeAction,
|
|
3
|
+
normalizeChannel,
|
|
4
|
+
normalizeSeverity,
|
|
5
|
+
normalizeText
|
|
6
|
+
} from "./normalize.js";
|
|
7
|
+
|
|
8
|
+
function createDefaultErrorPolicy({
|
|
9
|
+
defaultChannel = "snackbar",
|
|
10
|
+
unauthorizedChannel = "banner",
|
|
11
|
+
serverErrorChannel = "dialog",
|
|
12
|
+
defaultSeverity = "error"
|
|
13
|
+
} = {}) {
|
|
14
|
+
const normalizedDefaultChannel = normalizeChannel(defaultChannel, "snackbar") || "snackbar";
|
|
15
|
+
const normalizedUnauthorizedChannel = normalizeChannel(unauthorizedChannel, "banner") || "banner";
|
|
16
|
+
const normalizedServerErrorChannel = normalizeChannel(serverErrorChannel, "dialog") || "dialog";
|
|
17
|
+
const normalizedDefaultSeverity = normalizeSeverity(defaultSeverity, "error");
|
|
18
|
+
|
|
19
|
+
return function defaultErrorPolicy(event = {}) {
|
|
20
|
+
const status = Number(event.status || 0);
|
|
21
|
+
const explicitChannel = normalizeChannel(event.channel);
|
|
22
|
+
|
|
23
|
+
let channel = explicitChannel;
|
|
24
|
+
if (!channel) {
|
|
25
|
+
if (event.blocking === true || status >= 500) {
|
|
26
|
+
channel = normalizedServerErrorChannel;
|
|
27
|
+
} else if (status === 401 || status === 403) {
|
|
28
|
+
channel = normalizedUnauthorizedChannel;
|
|
29
|
+
} else {
|
|
30
|
+
channel = normalizedDefaultChannel;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const message = normalizeText(event.userMessage || event.message, "Request failed.");
|
|
35
|
+
|
|
36
|
+
return Object.freeze({
|
|
37
|
+
channel,
|
|
38
|
+
message,
|
|
39
|
+
severity: normalizeSeverity(event.severity, normalizedDefaultSeverity),
|
|
40
|
+
presenterId: normalizeText(event.presenterId),
|
|
41
|
+
action: normalizeAction(event.action),
|
|
42
|
+
persist: channel !== "snackbar",
|
|
43
|
+
dedupeKey: normalizeText(event.dedupeKey)
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export {
|
|
49
|
+
createDefaultErrorPolicy
|
|
50
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { normalizeText } from "./normalize.js";
|
|
2
|
+
|
|
3
|
+
const MATERIAL_SNACKBAR_PRESENTER_ID = "material.snackbar";
|
|
4
|
+
const MATERIAL_BANNER_PRESENTER_ID = "material.banner";
|
|
5
|
+
const MATERIAL_DIALOG_PRESENTER_ID = "material.dialog";
|
|
6
|
+
const MODULE_DEFAULT_PRESENTER_ID = MATERIAL_SNACKBAR_PRESENTER_ID;
|
|
7
|
+
|
|
8
|
+
function createStoreBackedPresenter({
|
|
9
|
+
id,
|
|
10
|
+
channel,
|
|
11
|
+
store,
|
|
12
|
+
defaultPersist = false
|
|
13
|
+
} = {}) {
|
|
14
|
+
const normalizedId = normalizeText(id);
|
|
15
|
+
const normalizedChannel = normalizeText(channel).toLowerCase();
|
|
16
|
+
if (!normalizedId) {
|
|
17
|
+
throw new Error("createStoreBackedPresenter requires id.");
|
|
18
|
+
}
|
|
19
|
+
if (!normalizedChannel) {
|
|
20
|
+
throw new Error(`createStoreBackedPresenter("${normalizedId}") requires channel.`);
|
|
21
|
+
}
|
|
22
|
+
if (!store || typeof store.present !== "function" || typeof store.dismiss !== "function") {
|
|
23
|
+
throw new Error(`createStoreBackedPresenter("${normalizedId}") requires a presentation store.`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return Object.freeze({
|
|
27
|
+
id: normalizedId,
|
|
28
|
+
supports(requestedChannel = "") {
|
|
29
|
+
return String(requestedChannel || "").trim().toLowerCase() === normalizedChannel;
|
|
30
|
+
},
|
|
31
|
+
present(payload = {}) {
|
|
32
|
+
return store.present(normalizedChannel, {
|
|
33
|
+
...payload,
|
|
34
|
+
persist: typeof payload.persist === "boolean" ? payload.persist : defaultPersist,
|
|
35
|
+
presenterId: normalizedId
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
dismiss(presentationId = "") {
|
|
39
|
+
return store.dismiss(normalizedChannel, String(presentationId || ""));
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function createMaterialSnackbarPresenter({ store } = {}) {
|
|
45
|
+
return createStoreBackedPresenter({
|
|
46
|
+
id: MATERIAL_SNACKBAR_PRESENTER_ID,
|
|
47
|
+
channel: "snackbar",
|
|
48
|
+
store,
|
|
49
|
+
defaultPersist: false
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function createMaterialBannerPresenter({ store } = {}) {
|
|
54
|
+
return createStoreBackedPresenter({
|
|
55
|
+
id: MATERIAL_BANNER_PRESENTER_ID,
|
|
56
|
+
channel: "banner",
|
|
57
|
+
store,
|
|
58
|
+
defaultPersist: true
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function createMaterialDialogPresenter({ store } = {}) {
|
|
63
|
+
return createStoreBackedPresenter({
|
|
64
|
+
id: MATERIAL_DIALOG_PRESENTER_ID,
|
|
65
|
+
channel: "dialog",
|
|
66
|
+
store,
|
|
67
|
+
defaultPersist: true
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function createDefaultMaterialErrorPresenters({ store } = {}) {
|
|
72
|
+
return Object.freeze([
|
|
73
|
+
createMaterialSnackbarPresenter({ store }),
|
|
74
|
+
createMaterialBannerPresenter({ store }),
|
|
75
|
+
createMaterialDialogPresenter({ store })
|
|
76
|
+
]);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export {
|
|
80
|
+
MATERIAL_SNACKBAR_PRESENTER_ID,
|
|
81
|
+
MATERIAL_BANNER_PRESENTER_ID,
|
|
82
|
+
MATERIAL_DIALOG_PRESENTER_ID,
|
|
83
|
+
MODULE_DEFAULT_PRESENTER_ID,
|
|
84
|
+
createStoreBackedPresenter,
|
|
85
|
+
createMaterialSnackbarPresenter,
|
|
86
|
+
createMaterialBannerPresenter,
|
|
87
|
+
createMaterialDialogPresenter,
|
|
88
|
+
createDefaultMaterialErrorPresenters
|
|
89
|
+
};
|