@etsoo/materialui
Version:
TypeScript Material-UI Implementation
359 lines (358 loc) • 10.9 kB
JavaScript
import { BridgeUtils, CoreApp } from "@etsoo/appscript";
import { NotificationMessageType } from "@etsoo/notificationbase";
import { WindowStorage } from "@etsoo/shared";
import React from "react";
import { NotifierMU } from "../NotifierMU";
import { ProgressCount } from "../ProgressCount";
import { Labels } from "./Labels";
import { CultureState, UserActionType, useRequiredContext, UserState } from "@etsoo/react";
/**
* React application context
*/
export const ReactAppContext = React.createContext(null);
/**
* Get React application context hook
* @returns React application
*/
export function useAppContext() {
return React.useContext(ReactAppContext);
}
/**
* Get React application context hook
* @returns React application
*/
export function useRequiredAppContext() {
return useRequiredContext(ReactAppContext);
}
/**
* React application
*/
export class ReactApp extends CoreApp {
static _notifierProvider;
/**
* Get notifier provider
*/
static get notifierProvider() {
return this._notifierProvider;
}
static createNotifier(debug) {
// Notifier
ReactApp._notifierProvider = NotifierMU.setup(undefined, debug);
return NotifierMU.instance;
}
/**
* Culture state
*/
cultureState;
/**
* User state
*/
userState = new UserState();
/**
* Is screen size down 'sm'
*/
smDown;
/**
* Is screen size up 'md'
*/
mdUp;
/**
* Navigate function
*/
navigateFunction;
/**
* User state dispatch
*/
userStateDispatch;
/**
* Constructor
* @param settings Settings
* @param name Application name
* @param debug Debug mode
*/
constructor(settings, name, debug = false) {
super(settings, null, ReactApp.createNotifier(debug), new WindowStorage(), name, debug);
if (BridgeUtils.host) {
BridgeUtils.host.onUpdate((app, version) => {
this.notifier.message(NotificationMessageType.Success, this.get("updateTip") + `(${[app, version].join(", ")})`, this.get("updateReady"));
});
}
this.cultureState = new CultureState(this.settings.currentCulture);
}
/**
* Override alert action result
* @param result Action result
* @param callback Callback
* @param forceToLocal Force to local labels
*/
alertResult(result, callback, forceToLocal) {
const message = typeof result === "string"
? result
: this.formatResult(result, forceToLocal);
if (message.endsWith(")")) {
const startPos = message.lastIndexOf("(");
if (startPos > 0) {
const main = message.substring(0, startPos).trim();
const tip = message.substring(startPos);
const titleNode = React.createElement(React.Fragment, null, main, React.createElement("br"), React.createElement("span", { style: { fontSize: "9px" } }, tip));
this.notifier.alert(titleNode, callback);
return;
}
}
super.alertResult(message, callback);
}
/**
* Change culture
* @param culture New culture definition
*/
async changeCulture(culture) {
// Super call to update cultrue
const resources = await super.changeCulture(culture);
// Update component labels
Labels.setLabels(resources, {
notificationMU: {
alertTitle: "warning",
alertOK: "ok",
confirmTitle: "confirm",
confirmYes: "ok",
confirmNo: "cancel",
promptTitle: "prompt",
promptCancel: "cancel",
promptOK: "ok"
}
});
// Document title
// Default is servier name's label or appName label
const title = this.get(this.name) ?? this.get("appName") ?? this.name;
const host = BridgeUtils.host;
if (host) {
// Notify host
host.changeCulture(culture.name);
host.setTitle(title);
}
else {
document.title = title;
}
return resources;
}
/**
* Change culture extended
* @param dispatch Dispatch method
* @param culture New culture definition
*/
changeCultureEx(dispatch, culture) {
// Same?
if (culture.name === this.culture)
return;
// Super call
this.changeCulture(culture).then(() => {
// Dispatch action
dispatch(culture);
});
}
/**
* Get date format props
* @returns Props
*/
getDateFormatProps() {
return { culture: this.culture, timeZone: this.getTimeZone() };
}
/**
* Get money format props
* @param currency Currency, if undefined, default currency applied
* @returns Props
*/
getMoneyFormatProps(currency) {
return { culture: this.culture, currency: currency ?? this.currency };
}
/**
* Fresh countdown UI
* @param callback Callback
*/
freshCountdownUI(callback) {
// Labels
const labels = this.getLabels("cancel", "tokenExpiry");
// Progress
const progress = React.createElement(ProgressCount, {
seconds: 30,
valueUnit: "s",
onComplete: () => {
// Stop the progress
return false;
}
});
// Popup
this.notifier.alert(labels.tokenExpiry, async () => {
if (callback)
await callback();
else
await this.tryLogin();
}, undefined, {
okLabel: labels.cancel,
primaryButtonProps: { fullWidth: true, autoFocus: false },
inputs: progress
});
}
/**
* Try login
* @param data Try login parameters
* @returns Result
*/
async tryLogin(data) {
// Destruct
const { onFailure = (type) => {
console.log(`Try login failed: ${type}.`);
if (globalThis.navigator.onLine &&
!type.includes('"title":"Failed to fetch"')) {
this.clearCacheToken();
}
this.toLoginPage(rest);
}, onSuccess, ...rest } = data ?? {};
// Check status
const result = await super.tryLogin(data);
if (!result) {
onFailure("ReactAppSuperTryLoginFailed");
return false;
}
// Refresh token
await this.refreshToken({
showLoading: data?.showLoading
}, (result) => {
if (result === true) {
onSuccess?.();
}
else if (result === false) {
onFailure("ReactAppRefreshTokenFailed");
}
else if (result != null && !this.tryLoginIgnoreResult(result)) {
onFailure("ReactAppRefreshTokenFailed: " + JSON.stringify(result));
}
else {
// Ignore other results
onFailure("ReactAppRefreshTokenIgnoredFailure: " + JSON.stringify(result));
return true;
}
});
return true;
}
/**
* Check if the action result should be ignored during try login
* @param result Action result
* @returns Result
*/
tryLoginIgnoreResult(result) {
// Ignore no token warning
if (result.type === "noData" && result.field === "token")
return true;
else
return false;
}
/**
* Navigate to Url or delta
* @param url Url or delta
* @param options Options
*/
navigate(to, options) {
if (this.navigateFunction == null)
super.navigate(to, options);
else if (typeof to === "number")
this.navigateFunction(to);
else
this.navigateFunction(to, options);
}
/**
* Show input dialog
* @param props Props
*/
showInputDialog({ title, message, callback, ...rest }) {
return this.notifier.prompt(message, callback, title, rest);
}
stateDetector(props) {
// Destruct
const { targetFields, update } = props;
// Context
const { state } = React.useContext(this.userState.context);
// Ready
React.useEffect(() => {
// Match fields
const changedFields = state.lastChangedFields;
let matchedFields;
if (targetFields == null || changedFields == null) {
matchedFields = changedFields;
}
else {
matchedFields = [];
targetFields.forEach((targetField) => {
if (changedFields.includes(targetField))
matchedFields?.push(targetField);
});
}
// Callback
update(state.authorized, matchedFields);
}, [state]);
// return
return React.createElement(React.Fragment);
}
/**
* User login extended
* @param user New user
* @param refreshToken Refresh token
* @param dispatch User state dispatch
*/
userLogin(user, refreshToken, dispatch) {
// Super call, set token
super.userLogin(user, refreshToken);
// Dispatch action
if (dispatch !== false) {
this.doLoginDispatch(user);
}
}
/**
* User login callback
* @param user New user
*/
onUserLogin(user) {
return Promise.resolve();
}
/**
* User login dispatch
* @param user New user
*/
doLoginDispatch(user) {
this.onUserLogin(user).then(() => {
if (this.userStateDispatch != null)
this.userStateDispatch({
type: UserActionType.Login,
user
});
});
}
/**
* User logout
* @param clearToken Clear refresh token or not
* @param noTrigger No trigger for state change
*/
userLogout(clearToken = true, noTrigger = false) {
// Super call
super.userLogout(clearToken);
// Dispatch action
if (!noTrigger && this.userStateDispatch != null)
this.userStateDispatch({
type: UserActionType.Logout
});
}
/**
* User unauthorized
*/
userUnauthorized() {
// Super call
super.userUnauthorized();
if (this.userStateDispatch != null) {
// There is delay during state update
// Not a good idea to try login multiple times with API calls
this.userStateDispatch({
type: UserActionType.Unauthorized
});
}
}
}