@etsoo/materialui 1.0.1

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.
Files changed (250) hide show
  1. package/.eslintignore +3 -0
  2. package/.eslintrc.json +38 -0
  3. package/.gitattributes +2 -0
  4. package/.github/workflows/main.yml +48 -0
  5. package/.prettierignore +5 -0
  6. package/.prettierrc +6 -0
  7. package/LICENSE +21 -0
  8. package/README.md +16 -0
  9. package/__tests__/ComboBox.tsx +30 -0
  10. package/__tests__/MUGlobalTests.tsx +58 -0
  11. package/__tests__/NotifierMUTests.tsx +217 -0
  12. package/__tests__/SelectEx.tsx +26 -0
  13. package/__tests__/tsconfig.json +19 -0
  14. package/babel.config.json +11 -0
  15. package/lib/AuditDisplay.d.ts +33 -0
  16. package/lib/AuditDisplay.js +52 -0
  17. package/lib/AutocompleteExtendedProps.d.ts +64 -0
  18. package/lib/AutocompleteExtendedProps.js +1 -0
  19. package/lib/BackButton.d.ts +13 -0
  20. package/lib/BackButton.js +33 -0
  21. package/lib/BridgeCloseButton.d.ts +23 -0
  22. package/lib/BridgeCloseButton.js +32 -0
  23. package/lib/ButtonLink.d.ts +17 -0
  24. package/lib/ButtonLink.js +19 -0
  25. package/lib/ComboBox.d.ts +38 -0
  26. package/lib/ComboBox.js +108 -0
  27. package/lib/CountdownButton.d.ts +23 -0
  28. package/lib/CountdownButton.js +81 -0
  29. package/lib/CustomFabProps.d.ts +27 -0
  30. package/lib/CustomFabProps.js +1 -0
  31. package/lib/DataGridEx.d.ts +94 -0
  32. package/lib/DataGridEx.js +329 -0
  33. package/lib/DataGridRenderers.d.ts +22 -0
  34. package/lib/DataGridRenderers.js +99 -0
  35. package/lib/DialogButton.d.ts +54 -0
  36. package/lib/DialogButton.js +45 -0
  37. package/lib/DnDList.d.ts +87 -0
  38. package/lib/DnDList.js +153 -0
  39. package/lib/DraggablePaperComponent.d.ts +8 -0
  40. package/lib/DraggablePaperComponent.js +12 -0
  41. package/lib/EmailInput.d.ts +11 -0
  42. package/lib/EmailInput.js +15 -0
  43. package/lib/FabBox.d.ts +21 -0
  44. package/lib/FabBox.js +31 -0
  45. package/lib/FlexBox.d.ts +14 -0
  46. package/lib/FlexBox.js +18 -0
  47. package/lib/GridDataFormat.d.ts +10 -0
  48. package/lib/GridDataFormat.js +43 -0
  49. package/lib/IconButtonLink.d.ts +17 -0
  50. package/lib/IconButtonLink.js +16 -0
  51. package/lib/InputField.d.ts +21 -0
  52. package/lib/InputField.js +39 -0
  53. package/lib/ItemList.d.ts +56 -0
  54. package/lib/ItemList.js +69 -0
  55. package/lib/ListItemRightIcon.d.ts +4 -0
  56. package/lib/ListItemRightIcon.js +8 -0
  57. package/lib/ListMoreDisplay.d.ts +35 -0
  58. package/lib/ListMoreDisplay.js +99 -0
  59. package/lib/LoadingButton.d.ts +16 -0
  60. package/lib/LoadingButton.js +41 -0
  61. package/lib/MUGlobal.d.ts +102 -0
  62. package/lib/MUGlobal.js +184 -0
  63. package/lib/MaskInput.d.ts +34 -0
  64. package/lib/MaskInput.js +43 -0
  65. package/lib/MobileListItemRenderer.d.ts +17 -0
  66. package/lib/MobileListItemRenderer.js +35 -0
  67. package/lib/MoreFab.d.ts +45 -0
  68. package/lib/MoreFab.js +95 -0
  69. package/lib/NotifierMU.d.ts +47 -0
  70. package/lib/NotifierMU.js +387 -0
  71. package/lib/NotifierPromptProps.d.ts +22 -0
  72. package/lib/NotifierPromptProps.js +1 -0
  73. package/lib/OptionGroup.d.ts +58 -0
  74. package/lib/OptionGroup.js +81 -0
  75. package/lib/PList.d.ts +15 -0
  76. package/lib/PList.js +12 -0
  77. package/lib/ProgressCount.d.ts +44 -0
  78. package/lib/ProgressCount.js +79 -0
  79. package/lib/PullToRefreshUI.d.ts +9 -0
  80. package/lib/PullToRefreshUI.js +18 -0
  81. package/lib/RLink.d.ts +14 -0
  82. package/lib/RLink.js +37 -0
  83. package/lib/ResponsibleContainer.d.ts +87 -0
  84. package/lib/ResponsibleContainer.js +156 -0
  85. package/lib/ScrollTopFab.d.ts +7 -0
  86. package/lib/ScrollTopFab.js +25 -0
  87. package/lib/ScrollerListEx.d.ts +81 -0
  88. package/lib/ScrollerListEx.js +167 -0
  89. package/lib/SearchBar.d.ts +29 -0
  90. package/lib/SearchBar.js +260 -0
  91. package/lib/SearchField.d.ts +21 -0
  92. package/lib/SearchField.js +39 -0
  93. package/lib/SearchOptionGroup.d.ts +9 -0
  94. package/lib/SearchOptionGroup.js +14 -0
  95. package/lib/SelectBool.d.ts +13 -0
  96. package/lib/SelectBool.js +22 -0
  97. package/lib/SelectEx.d.ts +50 -0
  98. package/lib/SelectEx.js +156 -0
  99. package/lib/ShowDataComparison.d.ts +20 -0
  100. package/lib/ShowDataComparison.js +58 -0
  101. package/lib/Switch.d.ts +29 -0
  102. package/lib/Switch.js +34 -0
  103. package/lib/SwitchAnt.d.ts +25 -0
  104. package/lib/SwitchAnt.js +40 -0
  105. package/lib/TabBox.d.ts +54 -0
  106. package/lib/TabBox.js +31 -0
  107. package/lib/TableEx.d.ts +65 -0
  108. package/lib/TableEx.js +270 -0
  109. package/lib/TextFieldEx.d.ts +101 -0
  110. package/lib/TextFieldEx.js +126 -0
  111. package/lib/Tiplist.d.ts +18 -0
  112. package/lib/Tiplist.js +157 -0
  113. package/lib/TooltipClick.d.ts +15 -0
  114. package/lib/TooltipClick.js +40 -0
  115. package/lib/UserAvatar.d.ts +24 -0
  116. package/lib/UserAvatar.js +25 -0
  117. package/lib/UserAvatarEditor.d.ts +53 -0
  118. package/lib/UserAvatarEditor.js +129 -0
  119. package/lib/app/CommonApp.d.ts +38 -0
  120. package/lib/app/CommonApp.js +149 -0
  121. package/lib/app/IServiceAppSettings.d.ts +11 -0
  122. package/lib/app/IServiceAppSettings.js +1 -0
  123. package/lib/app/IServicePage.d.ts +6 -0
  124. package/lib/app/IServicePage.js +1 -0
  125. package/lib/app/IServiceUser.d.ts +14 -0
  126. package/lib/app/IServiceUser.js +1 -0
  127. package/lib/app/ISmartERPUser.d.ts +14 -0
  128. package/lib/app/ISmartERPUser.js +1 -0
  129. package/lib/app/Labels.d.ts +65 -0
  130. package/lib/app/Labels.js +62 -0
  131. package/lib/app/ReactApp.d.ts +195 -0
  132. package/lib/app/ReactApp.js +296 -0
  133. package/lib/app/ServiceApp.d.ts +78 -0
  134. package/lib/app/ServiceApp.js +244 -0
  135. package/lib/index.d.ts +74 -0
  136. package/lib/index.js +74 -0
  137. package/lib/pages/CommonPage.d.ts +11 -0
  138. package/lib/pages/CommonPage.js +60 -0
  139. package/lib/pages/CommonPageProps.d.ts +59 -0
  140. package/lib/pages/CommonPageProps.js +1 -0
  141. package/lib/pages/DataGridPage.d.ts +9 -0
  142. package/lib/pages/DataGridPage.js +79 -0
  143. package/lib/pages/DataGridPageProps.d.ts +17 -0
  144. package/lib/pages/DataGridPageProps.js +1 -0
  145. package/lib/pages/EditPage.d.ts +33 -0
  146. package/lib/pages/EditPage.js +29 -0
  147. package/lib/pages/FixedListPage.d.ts +15 -0
  148. package/lib/pages/FixedListPage.js +70 -0
  149. package/lib/pages/ListPage.d.ts +9 -0
  150. package/lib/pages/ListPage.js +50 -0
  151. package/lib/pages/ListPageProps.d.ts +7 -0
  152. package/lib/pages/ListPageProps.js +1 -0
  153. package/lib/pages/ResponsivePage.d.ts +9 -0
  154. package/lib/pages/ResponsivePage.js +45 -0
  155. package/lib/pages/ResponsivePageProps.d.ts +39 -0
  156. package/lib/pages/ResponsivePageProps.js +1 -0
  157. package/lib/pages/SearchPageProps.d.ts +30 -0
  158. package/lib/pages/SearchPageProps.js +1 -0
  159. package/lib/pages/TablePage.d.ts +9 -0
  160. package/lib/pages/TablePage.js +69 -0
  161. package/lib/pages/TablePageProps.d.ts +7 -0
  162. package/lib/pages/TablePageProps.js +1 -0
  163. package/lib/pages/ViewPage.d.ts +66 -0
  164. package/lib/pages/ViewPage.js +105 -0
  165. package/lib/texts/DateText.d.ts +34 -0
  166. package/lib/texts/DateText.js +25 -0
  167. package/lib/texts/MoneyText.d.ts +21 -0
  168. package/lib/texts/MoneyText.js +14 -0
  169. package/lib/texts/NumberText.d.ts +25 -0
  170. package/lib/texts/NumberText.js +14 -0
  171. package/package.json +97 -0
  172. package/src/AuditDisplay.tsx +114 -0
  173. package/src/AutocompleteExtendedProps.ts +83 -0
  174. package/src/BackButton.tsx +55 -0
  175. package/src/BridgeCloseButton.tsx +69 -0
  176. package/src/ButtonLink.tsx +32 -0
  177. package/src/ComboBox.tsx +251 -0
  178. package/src/CountdownButton.tsx +119 -0
  179. package/src/CustomFabProps.ts +32 -0
  180. package/src/DataGridEx.tsx +713 -0
  181. package/src/DataGridRenderers.tsx +140 -0
  182. package/src/DialogButton.tsx +163 -0
  183. package/src/DnDList.tsx +344 -0
  184. package/src/DraggablePaperComponent.tsx +19 -0
  185. package/src/EmailInput.tsx +24 -0
  186. package/src/FabBox.tsx +51 -0
  187. package/src/FlexBox.tsx +20 -0
  188. package/src/GridDataFormat.tsx +77 -0
  189. package/src/IconButtonLink.tsx +29 -0
  190. package/src/InputField.tsx +82 -0
  191. package/src/ItemList.tsx +204 -0
  192. package/src/ListItemRightIcon.tsx +9 -0
  193. package/src/ListMoreDisplay.tsx +205 -0
  194. package/src/LoadingButton.tsx +75 -0
  195. package/src/MUGlobal.ts +220 -0
  196. package/src/MaskInput.tsx +107 -0
  197. package/src/MobileListItemRenderer.tsx +79 -0
  198. package/src/MoreFab.tsx +211 -0
  199. package/src/NotifierMU.tsx +654 -0
  200. package/src/NotifierPromptProps.ts +24 -0
  201. package/src/OptionGroup.tsx +223 -0
  202. package/src/PList.tsx +27 -0
  203. package/src/ProgressCount.tsx +166 -0
  204. package/src/PullToRefreshUI.tsx +21 -0
  205. package/src/RLink.tsx +64 -0
  206. package/src/ResponsibleContainer.tsx +394 -0
  207. package/src/ScrollTopFab.tsx +34 -0
  208. package/src/ScrollerListEx.tsx +387 -0
  209. package/src/SearchBar.tsx +396 -0
  210. package/src/SearchField.tsx +82 -0
  211. package/src/SearchOptionGroup.tsx +31 -0
  212. package/src/SelectBool.tsx +33 -0
  213. package/src/SelectEx.tsx +290 -0
  214. package/src/ShowDataComparison.tsx +106 -0
  215. package/src/Switch.tsx +94 -0
  216. package/src/SwitchAnt.tsx +95 -0
  217. package/src/TabBox.tsx +118 -0
  218. package/src/TableEx.tsx +558 -0
  219. package/src/TextFieldEx.tsx +249 -0
  220. package/src/Tiplist.tsx +303 -0
  221. package/src/TooltipClick.tsx +84 -0
  222. package/src/UserAvatar.tsx +64 -0
  223. package/src/UserAvatarEditor.tsx +287 -0
  224. package/src/app/CommonApp.ts +223 -0
  225. package/src/app/IServiceAppSettings.ts +13 -0
  226. package/src/app/IServicePage.ts +6 -0
  227. package/src/app/IServiceUser.ts +17 -0
  228. package/src/app/ISmartERPUser.ts +16 -0
  229. package/src/app/Labels.ts +77 -0
  230. package/src/app/ReactApp.ts +504 -0
  231. package/src/app/ServiceApp.ts +352 -0
  232. package/src/index.ts +77 -0
  233. package/src/pages/CommonPage.tsx +128 -0
  234. package/src/pages/CommonPageProps.ts +70 -0
  235. package/src/pages/DataGridPage.tsx +140 -0
  236. package/src/pages/DataGridPageProps.ts +24 -0
  237. package/src/pages/EditPage.tsx +114 -0
  238. package/src/pages/FixedListPage.tsx +141 -0
  239. package/src/pages/ListPage.tsx +90 -0
  240. package/src/pages/ListPageProps.ts +12 -0
  241. package/src/pages/ResponsivePage.tsx +68 -0
  242. package/src/pages/ResponsivePageProps.ts +57 -0
  243. package/src/pages/SearchPageProps.ts +39 -0
  244. package/src/pages/TablePage.tsx +126 -0
  245. package/src/pages/TablePageProps.ts +12 -0
  246. package/src/pages/ViewPage.tsx +282 -0
  247. package/src/texts/DateText.tsx +74 -0
  248. package/src/texts/MoneyText.tsx +49 -0
  249. package/src/texts/NumberText.tsx +40 -0
  250. package/tsconfig.json +19 -0
package/lib/Tiplist.js ADDED
@@ -0,0 +1,157 @@
1
+ import { ReactUtils, useDelayedExecutor } from '@etsoo/react';
2
+ import { DataTypes } from '@etsoo/shared';
3
+ import { Autocomplete } from '@mui/material';
4
+ import React from 'react';
5
+ import { InputField } from './InputField';
6
+ import { SearchField } from './SearchField';
7
+ /**
8
+ * Tiplist
9
+ * @param props Props
10
+ * @returns Component
11
+ */
12
+ export function Tiplist(props) {
13
+ // Destruct
14
+ const { search = false, idField = 'id', idValue, inputAutoComplete = 'off', inputError, inputHelperText, inputMargin, inputOnChange, inputRequired, inputVariant, label, loadData, defaultValue, value, name, readOnly, onChange, openOnFocus = true, sx = { minWidth: '180px' }, ...rest } = props;
15
+ // Value input ref
16
+ const inputRef = React.createRef();
17
+ // Local value
18
+ let localValue = value !== null && value !== void 0 ? value : defaultValue;
19
+ // One time calculation for input's default value (uncontrolled)
20
+ const localIdValue = idValue !== null && idValue !== void 0 ? idValue : DataTypes.getValue(localValue, idField);
21
+ // Changable states
22
+ const [states, stateUpdate] = React.useReducer((currentState, newState) => {
23
+ return { ...currentState, ...newState };
24
+ }, {
25
+ // Loading unknown
26
+ open: false,
27
+ options: [],
28
+ value: null
29
+ });
30
+ // Input value
31
+ const inputValue = React.useMemo(() => states.value && states.value[idField], [states.value]);
32
+ React.useEffect(() => {
33
+ if (localValue != null)
34
+ stateUpdate({ value: localValue });
35
+ }, [localValue]);
36
+ // State
37
+ const [state] = React.useState({});
38
+ const isMounted = React.useRef(true);
39
+ // Add readOnly
40
+ const addReadOnly = (params) => {
41
+ if (readOnly != null) {
42
+ Object.assign(params, { readOnly });
43
+ }
44
+ // https://stackoverflow.com/questions/15738259/disabling-chrome-autofill
45
+ // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html
46
+ Object.assign(params.inputProps, { autoComplete: inputAutoComplete });
47
+ return params;
48
+ };
49
+ // Change handler
50
+ const changeHandle = (event) => {
51
+ // Stop processing with auto trigger event
52
+ if (event.nativeEvent.cancelable && !event.nativeEvent.composed) {
53
+ stateUpdate({ options: [] });
54
+ return;
55
+ }
56
+ // Stop bubble
57
+ event.stopPropagation();
58
+ // Call with delay
59
+ delayed.call(undefined, event.currentTarget.value);
60
+ };
61
+ // Directly load data
62
+ const loadDataDirect = (keyword, id) => {
63
+ // Reset options
64
+ // setOptions([]);
65
+ if (id == null) {
66
+ // Reset real value
67
+ const input = inputRef.current;
68
+ if (input && input.value !== '') {
69
+ // Different value, trigger change event
70
+ ReactUtils.triggerChange(input, '', false);
71
+ }
72
+ if (states.options.length > 0) {
73
+ // Reset options
74
+ stateUpdate({ options: [] });
75
+ }
76
+ }
77
+ // Loading indicator
78
+ if (!states.loading)
79
+ stateUpdate({ loading: true });
80
+ // Load list
81
+ loadData(keyword, id).then((options) => {
82
+ if (!isMounted.current)
83
+ return;
84
+ // Indicates loading completed
85
+ stateUpdate({
86
+ loading: false,
87
+ ...(options != null && { options })
88
+ });
89
+ });
90
+ };
91
+ const delayed = useDelayedExecutor(loadDataDirect, 480);
92
+ const setInputValue = (value) => {
93
+ var _a;
94
+ stateUpdate({ value });
95
+ // Input value
96
+ const input = inputRef.current;
97
+ if (input) {
98
+ // Update value
99
+ const newValue = (_a = DataTypes.getStringValue(value, idField)) !== null && _a !== void 0 ? _a : '';
100
+ if (newValue !== input.value) {
101
+ // Different value, trigger change event
102
+ ReactUtils.triggerChange(input, newValue, false);
103
+ }
104
+ }
105
+ };
106
+ if (localIdValue != null && localIdValue !== '') {
107
+ if (state.idLoaded) {
108
+ // Set default
109
+ if (!state.idSet && states.options.length == 1) {
110
+ stateUpdate({ value: states.options[0] });
111
+ state.idSet = true;
112
+ }
113
+ }
114
+ else {
115
+ // Load id data
116
+ loadDataDirect(undefined, localIdValue);
117
+ state.idLoaded = true;
118
+ }
119
+ }
120
+ React.useEffect(() => {
121
+ return () => {
122
+ isMounted.current = false;
123
+ delayed.clear();
124
+ };
125
+ }, []);
126
+ // Layout
127
+ return (React.createElement("div", null,
128
+ React.createElement("input", { ref: inputRef, "data-reset": "true", type: "text", style: { display: 'none' }, name: name, value: `${inputValue !== null && inputValue !== void 0 ? inputValue : ''}`, readOnly: true, onChange: inputOnChange }),
129
+ React.createElement(Autocomplete, { filterOptions: (options, _state) => options, value: states.value, options: states.options, onChange: (event, value, reason, details) => {
130
+ // Set value
131
+ setInputValue(value);
132
+ // Custom
133
+ if (onChange != null)
134
+ onChange(event, value, reason, details);
135
+ // For clear case
136
+ if (reason === 'clear') {
137
+ stateUpdate({ options: [] });
138
+ loadDataDirect();
139
+ }
140
+ }, open: states.open, openOnFocus: openOnFocus, onOpen: () => {
141
+ // Should load
142
+ const loading = states.loading
143
+ ? true
144
+ : states.options.length === 0;
145
+ stateUpdate({ open: true, loading });
146
+ // If not loading
147
+ if (loading)
148
+ loadDataDirect(undefined, states.value == null
149
+ ? undefined
150
+ : states.value[idField]);
151
+ }, onClose: () => {
152
+ stateUpdate({
153
+ open: false,
154
+ ...(!states.value && { options: [] })
155
+ });
156
+ }, loading: states.loading, sx: sx, renderInput: (params) => search ? (React.createElement(SearchField, { onChange: changeHandle, ...params, readOnly: readOnly, label: label, name: name + 'Input', margin: inputMargin, variant: inputVariant, required: inputRequired, autoComplete: inputAutoComplete, error: inputError, helperText: inputHelperText })) : (React.createElement(InputField, { onChange: changeHandle, ...addReadOnly(params), label: label, name: name + 'Input', margin: inputMargin, variant: inputVariant, required: inputRequired, autoComplete: inputAutoComplete, error: inputError, helperText: inputHelperText })), isOptionEqualToValue: (option, value) => option[idField] === value[idField], ...rest })));
157
+ }
@@ -0,0 +1,15 @@
1
+ import { TooltipProps } from '@mui/material';
2
+ import React from 'react';
3
+ /**
4
+ * Tooltip with click visibility props
5
+ */
6
+ export interface TooltipClickProps extends Omit<TooltipProps, 'children' | 'open' | 'disableFocusListener' | 'disableTouchListener'> {
7
+ children: (openTooltip: (newTitle?: string) => void) => React.ReactElement<any, any>;
8
+ disableHoverListener?: boolean;
9
+ }
10
+ /**
11
+ * Tooltip with click visibility
12
+ * @param props Props
13
+ * @returns Component
14
+ */
15
+ export declare function TooltipClick(props: TooltipClickProps): JSX.Element;
@@ -0,0 +1,40 @@
1
+ import { useDelayedExecutor } from '@etsoo/react';
2
+ import { ClickAwayListener, Tooltip } from '@mui/material';
3
+ import React from 'react';
4
+ /**
5
+ * Tooltip with click visibility
6
+ * @param props Props
7
+ * @returns Component
8
+ */
9
+ export function TooltipClick(props) {
10
+ // Destruct
11
+ // leaveDelay set to 5 seconds to hide the tooltip automatically
12
+ const { children, disableHoverListener = true, leaveDelay = 5000, onClose, title, ...rest } = props;
13
+ // State
14
+ const [localTitle, setTitle] = React.useState(title);
15
+ const [open, setOpen] = React.useState(false);
16
+ const delayed = leaveDelay > 0
17
+ ? useDelayedExecutor(() => setOpen(false), leaveDelay)
18
+ : undefined;
19
+ // Callback for open the tooltip
20
+ const openTooltip = (newTitle) => {
21
+ setOpen(true);
22
+ if (newTitle)
23
+ setTitle(newTitle);
24
+ delayed === null || delayed === void 0 ? void 0 : delayed.call();
25
+ };
26
+ React.useEffect(() => {
27
+ return () => {
28
+ delayed === null || delayed === void 0 ? void 0 : delayed.clear();
29
+ };
30
+ }, []);
31
+ // Layout
32
+ return (React.createElement(ClickAwayListener, { onClickAway: () => setOpen(false) },
33
+ React.createElement(Tooltip, { PopperProps: {
34
+ disablePortal: true
35
+ }, onClose: (event) => {
36
+ setOpen(false);
37
+ if (onClose)
38
+ onClose(event);
39
+ }, title: localTitle, open: open, disableFocusListener: true, disableTouchListener: true, disableHoverListener: disableHoverListener, onMouseOver: disableHoverListener ? undefined : () => setOpen(true), ...rest }, children(openTooltip))));
40
+ }
@@ -0,0 +1,24 @@
1
+ /// <reference types="react" />
2
+ /**
3
+ * User avatar props
4
+ */
5
+ export interface UserAvatarProps {
6
+ /**
7
+ * Photo src
8
+ */
9
+ src?: string;
10
+ /**
11
+ * Format title
12
+ */
13
+ formatTitle?: (title?: string) => string;
14
+ /**
15
+ * Title of the user
16
+ */
17
+ title?: string;
18
+ }
19
+ /**
20
+ * User avatar
21
+ * @param props Props
22
+ * @returns Component
23
+ */
24
+ export declare function UserAvatar(props: UserAvatarProps): JSX.Element;
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import { Avatar } from '@mui/material';
3
+ import { BusinessUtils } from '@etsoo/appscript';
4
+ import { globalApp } from './app/ReactApp';
5
+ /**
6
+ * User avatar
7
+ * @param props Props
8
+ * @returns Component
9
+ */
10
+ export function UserAvatar(props) {
11
+ // Destruct
12
+ const { src, title, formatTitle = (title) => {
13
+ return BusinessUtils.formatAvatarTitle(title, 3, typeof globalApp === 'undefined'
14
+ ? 'ME'
15
+ : globalApp.get('me'));
16
+ } } = props;
17
+ // Format
18
+ const fTitle = formatTitle(title);
19
+ const count = fTitle.length;
20
+ return (React.createElement(Avatar, { title: title, src: src, sx: {
21
+ width: 48,
22
+ height: 32,
23
+ fontSize: count <= 2 ? '15px' : '12px'
24
+ } }, fTitle));
25
+ }
@@ -0,0 +1,53 @@
1
+ /// <reference types="react" />
2
+ /**
3
+ * User avatar editor to Blob helper
4
+ */
5
+ export interface UserAvatarEditorToBlob {
6
+ (canvas: HTMLCanvasElement, mimeType?: string, quality?: number): Promise<Blob>;
7
+ }
8
+ /**
9
+ * User avatar editor on done handler
10
+ */
11
+ export interface UserAvatarEditorOnDoneHandler {
12
+ (canvas: HTMLCanvasElement, toBlob: UserAvatarEditorToBlob): void;
13
+ }
14
+ /**
15
+ * User avatar editor props
16
+ */
17
+ export interface UserAvatarEditorProps {
18
+ /**
19
+ * Cropping border size
20
+ */
21
+ border?: number;
22
+ /**
23
+ * Image source
24
+ */
25
+ image?: string | File;
26
+ /**
27
+ * Max width to save
28
+ */
29
+ maxWidth?: number;
30
+ /**
31
+ * On done handler
32
+ */
33
+ onDone: UserAvatarEditorOnDoneHandler;
34
+ /**
35
+ * Return scaled result?
36
+ */
37
+ scaledResult?: boolean;
38
+ /**
39
+ * Width of the editor
40
+ */
41
+ width?: number;
42
+ /**
43
+ * Height of the editor
44
+ */
45
+ height?: number;
46
+ }
47
+ /**
48
+ * User avatar editor
49
+ * https://github.com/mosch/react-avatar-editor
50
+ * @param props Props
51
+ * @returns Component
52
+ */
53
+ export declare function UserAvatarEditor(props: UserAvatarEditorProps): JSX.Element;
@@ -0,0 +1,129 @@
1
+ import { Button, ButtonGroup, Slider, Stack } from '@mui/material';
2
+ import React from 'react';
3
+ import AvatarEditor from 'react-avatar-editor';
4
+ import RotateLeftIcon from '@mui/icons-material/RotateLeft';
5
+ import RotateRightIcon from '@mui/icons-material/RotateRight';
6
+ import ClearAllIcon from '@mui/icons-material/ClearAll';
7
+ import ComputerIcon from '@mui/icons-material/Computer';
8
+ import DoneIcon from '@mui/icons-material/Done';
9
+ import pica from 'pica';
10
+ import { Labels } from './app/Labels';
11
+ const defaultState = {
12
+ scale: 1,
13
+ rotate: 0
14
+ };
15
+ /**
16
+ * User avatar editor
17
+ * https://github.com/mosch/react-avatar-editor
18
+ * @param props Props
19
+ * @returns Component
20
+ */
21
+ export function UserAvatarEditor(props) {
22
+ // Destruct
23
+ const { border = 30, image, maxWidth, onDone, scaledResult = false, width = 200, height = 200 } = props;
24
+ // Container width
25
+ const containerWidth = width + 2 * border + 44 + 4;
26
+ // Calculated max width
27
+ const maxWidthCalculated = maxWidth == null || maxWidth < 200 ? 3 * width : maxWidth;
28
+ // Labels
29
+ const labels = Labels.UserAvatarEditor;
30
+ // Ref
31
+ const ref = React.createRef();
32
+ // Button ref
33
+ const buttonRef = React.createRef();
34
+ // Preview image state
35
+ const [previewImage, setPreviewImage] = React.useState(image);
36
+ // Is ready state
37
+ const [ready, setReady] = React.useState(false);
38
+ // Editor states
39
+ const [editorState, setEditorState] = React.useState(defaultState);
40
+ // Handle zoom
41
+ const handleZoom = (_event, value, _activeThumb) => {
42
+ const scale = typeof value === 'number' ? value : value[0];
43
+ const newState = { ...editorState, scale };
44
+ setEditorState(newState);
45
+ };
46
+ // Handle image load
47
+ const handleLoad = () => {
48
+ setReady(true);
49
+ };
50
+ // Handle file change
51
+ const handleFileChange = (event) => {
52
+ var _a;
53
+ const files = event.target.files;
54
+ if (files == null || files.length == 0)
55
+ return;
56
+ // Reset all settings
57
+ handleReset();
58
+ // Set new preview image
59
+ setPreviewImage(files[0]);
60
+ // Set ready state
61
+ setReady(false);
62
+ // Make the submit button visible
63
+ (_a = buttonRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView(false);
64
+ };
65
+ // Handle reset
66
+ const handleReset = () => {
67
+ setEditorState({ ...defaultState });
68
+ };
69
+ // Handle rotate
70
+ const handleRotate = (r) => {
71
+ let rotate = editorState.rotate + r;
72
+ if (rotate >= 360 || rotate <= -360)
73
+ rotate = 0;
74
+ const newState = { ...editorState, rotate };
75
+ setEditorState(newState);
76
+ };
77
+ // Handle done
78
+ const handleDone = () => {
79
+ var _a, _b;
80
+ // Data
81
+ var data = scaledResult
82
+ ? (_a = ref.current) === null || _a === void 0 ? void 0 : _a.getImageScaledToCanvas()
83
+ : (_b = ref.current) === null || _b === void 0 ? void 0 : _b.getImage();
84
+ if (data == null)
85
+ return;
86
+ // pica
87
+ const picaInstance = pica();
88
+ // toBlob helper
89
+ // Convenience method, similar to canvas.toBlob(), but with promise interface & polyfill for old browsers.
90
+ const toBlob = (canvas, mimeType = 'image/jpeg', quality = 1) => {
91
+ return picaInstance.toBlob(canvas, mimeType, quality);
92
+ };
93
+ if (data.width > maxWidthCalculated) {
94
+ // Target height
95
+ const heightCalculated = (height * maxWidthCalculated) / width;
96
+ // Target
97
+ const to = document.createElement('canvas');
98
+ to.width = maxWidthCalculated;
99
+ to.height = heightCalculated;
100
+ // Large photo, resize it
101
+ // https://github.com/nodeca/pica
102
+ picaInstance
103
+ .resize(data, to, {
104
+ unsharpAmount: 160,
105
+ unsharpRadius: 0.6,
106
+ unsharpThreshold: 1
107
+ })
108
+ .then((result) => onDone(result, toBlob));
109
+ }
110
+ else {
111
+ onDone(data, toBlob);
112
+ }
113
+ };
114
+ return (React.createElement(Stack, { direction: "column", spacing: 0.5, width: containerWidth },
115
+ React.createElement(Button, { variant: "outlined", size: "medium", component: "label", startIcon: React.createElement(ComputerIcon, null), fullWidth: true },
116
+ labels.upload,
117
+ React.createElement("input", { id: "fileInput", type: "file", accept: "image/png, image/jpeg", multiple: false, hidden: true, onChange: handleFileChange })),
118
+ React.createElement(Stack, { direction: "row", spacing: 0.5 },
119
+ React.createElement(AvatarEditor, { ref: ref, border: border, width: width, height: height, onLoadSuccess: handleLoad, image: previewImage !== null && previewImage !== void 0 ? previewImage : '', scale: editorState.scale, rotate: editorState.rotate }),
120
+ React.createElement(ButtonGroup, { size: "small", orientation: "vertical", disabled: !ready },
121
+ React.createElement(Button, { onClick: () => handleRotate(90), title: labels.rotateRight },
122
+ React.createElement(RotateRightIcon, null)),
123
+ React.createElement(Button, { onClick: () => handleRotate(-90), title: labels.rotateLeft },
124
+ React.createElement(RotateLeftIcon, null)),
125
+ React.createElement(Button, { onClick: handleReset, title: labels.reset },
126
+ React.createElement(ClearAllIcon, null)))),
127
+ React.createElement(Slider, { title: labels.zoom, disabled: !ready, min: 1, max: 5, step: 0.01, value: editorState.scale, onChange: handleZoom }),
128
+ React.createElement(Button, { ref: buttonRef, variant: "contained", startIcon: React.createElement(DoneIcon, null), disabled: !ready, onClick: handleDone }, labels.done)));
129
+ }
@@ -0,0 +1,38 @@
1
+ import { IAppSettings, IUser, RefreshTokenProps } from '@etsoo/appscript';
2
+ import { IPageData, RefreshTokenRQ } from '@etsoo/react';
3
+ import { ReactApp } from './ReactApp';
4
+ /**
5
+ * Common independent application
6
+ * 通用独立程序
7
+ */
8
+ export declare abstract class CommonApp<U extends IUser = IUser, P extends IPageData = IPageData, S extends IAppSettings = IAppSettings> extends ReactApp<S, U, P> {
9
+ /**
10
+ * Override persistedFields
11
+ */
12
+ protected get persistedFields(): string[];
13
+ /**
14
+ * Init call update fields in local storage
15
+ * @returns Fields
16
+ */
17
+ protected initCallEncryptedUpdateFields(): string[];
18
+ /**
19
+ * Do user login
20
+ * @param data User data
21
+ * @param refreshToken Refresh token
22
+ * @param keep Keep login
23
+ * @returns Success data
24
+ */
25
+ protected doUserLogin(data: U, refreshToken: string, keep: boolean): string | undefined;
26
+ /**
27
+ * Refresh token
28
+ * @param props Props
29
+ */
30
+ refreshToken<D extends object = RefreshTokenRQ>(props?: RefreshTokenProps<D>): Promise<boolean>;
31
+ /**
32
+ * Try login
33
+ * @param data Additional data
34
+ * @param showLoading Show loading bar or not
35
+ * @returns Result
36
+ */
37
+ tryLogin<D extends object = RefreshTokenRQ>(data?: D, showLoading?: boolean): Promise<boolean>;
38
+ }
@@ -0,0 +1,149 @@
1
+ import { CoreConstants } from '@etsoo/react';
2
+ import { ReactApp } from './ReactApp';
3
+ /**
4
+ * Common independent application
5
+ * 通用独立程序
6
+ */
7
+ export class CommonApp extends ReactApp {
8
+ /**
9
+ * Override persistedFields
10
+ */
11
+ get persistedFields() {
12
+ return [...super.persistedFields, CoreConstants.FieldUserIdSaved];
13
+ }
14
+ /**
15
+ * Init call update fields in local storage
16
+ * @returns Fields
17
+ */
18
+ initCallEncryptedUpdateFields() {
19
+ const fields = super.initCallEncryptedUpdateFields();
20
+ fields.push(CoreConstants.FieldUserIdSaved);
21
+ return fields;
22
+ }
23
+ /**
24
+ * Do user login
25
+ * @param data User data
26
+ * @param refreshToken Refresh token
27
+ * @param keep Keep login
28
+ * @returns Success data
29
+ */
30
+ doUserLogin(data, refreshToken, keep) {
31
+ this.userLogin(data, refreshToken, keep);
32
+ return undefined;
33
+ }
34
+ /**
35
+ * Refresh token
36
+ * @param props Props
37
+ */
38
+ async refreshToken(props) {
39
+ // Destruct
40
+ const { callback, data, relogin = false, showLoading = false } = props !== null && props !== void 0 ? props : {};
41
+ // Token
42
+ const token = this.getCacheToken();
43
+ if (token == null || token === '') {
44
+ if (callback)
45
+ callback(false);
46
+ return false;
47
+ }
48
+ // Reqest data
49
+ const rq = {
50
+ deviceId: this.deviceId,
51
+ timezone: this.getTimeZone(),
52
+ ...data
53
+ };
54
+ // Payload
55
+ const payload = {
56
+ // No loading bar needed to avoid screen flicks
57
+ showLoading,
58
+ config: { headers: { [CoreConstants.TokenHeaderRefresh]: token } },
59
+ onError: (error) => {
60
+ if (callback)
61
+ callback(error);
62
+ // Prevent further processing
63
+ return false;
64
+ }
65
+ };
66
+ // Success callback
67
+ const success = (result, failCallback) => {
68
+ // Token
69
+ const refreshToken = this.getResponseToken(payload.response);
70
+ if (refreshToken == null || result.data == null) {
71
+ if (failCallback)
72
+ failCallback(this.get('noData'));
73
+ return false;
74
+ }
75
+ // Keep
76
+ const keep = this.storage.getData(CoreConstants.FieldLoginKeep, false);
77
+ // User login
78
+ var successData = this.doUserLogin(result.data, refreshToken, keep);
79
+ // Callback
80
+ if (failCallback)
81
+ failCallback(true, successData);
82
+ return true;
83
+ };
84
+ // Call API
85
+ const result = await this.api.put('Auth/RefreshToken', rq, payload);
86
+ if (result == null)
87
+ return false;
88
+ if (!result.ok) {
89
+ if (result.type === 'TokenExpired' && relogin) {
90
+ // Try login
91
+ // Dialog to receive password
92
+ var labels = this.getLabels('reloginTip', 'login');
93
+ this.notifier.prompt(labels.reloginTip, async (pwd) => {
94
+ if (pwd == null) {
95
+ this.toLoginPage();
96
+ return;
97
+ }
98
+ // Set password for the action
99
+ rq.pwd = this.encrypt(this.hash(pwd));
100
+ // Submit again
101
+ const result = await this.api.put('Auth/RefreshToken', rq, payload);
102
+ if (result == null)
103
+ return;
104
+ if (result.ok) {
105
+ success(result, (loginResult) => {
106
+ if (loginResult === true) {
107
+ if (callback)
108
+ callback(true);
109
+ return;
110
+ }
111
+ const message = this.formatRefreshTokenResult(loginResult);
112
+ if (message)
113
+ this.notifier.alert(message);
114
+ });
115
+ return;
116
+ }
117
+ // Popup message
118
+ this.alertResult(result);
119
+ return false;
120
+ }, labels.login, { type: 'password' });
121
+ // Fake truth to avoid reloading
122
+ return true;
123
+ }
124
+ if (callback)
125
+ callback(result);
126
+ return false;
127
+ }
128
+ return success(result, callback);
129
+ }
130
+ /**
131
+ * Try login
132
+ * @param data Additional data
133
+ * @param showLoading Show loading bar or not
134
+ * @returns Result
135
+ */
136
+ async tryLogin(data, showLoading) {
137
+ // Reset user state
138
+ const result = await super.tryLogin(data);
139
+ if (!result)
140
+ return false;
141
+ // Refresh token
142
+ return await this.refreshToken({
143
+ callback: (result) => this.doRefreshTokenResult(result),
144
+ data,
145
+ showLoading,
146
+ relogin: true
147
+ });
148
+ }
149
+ }
@@ -0,0 +1,11 @@
1
+ import { IAppSettings } from '@etsoo/appscript';
2
+ import { DataTypes } from '@etsoo/shared';
3
+ /**
4
+ * Service app settings interface
5
+ */
6
+ export interface IServiceAppSettings<S extends DataTypes.IdType = number> extends IAppSettings {
7
+ /**
8
+ * Service id
9
+ */
10
+ readonly serviceId: S;
11
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import { IPageData } from '@etsoo/react';
2
+ /**
3
+ * Service page data interface
4
+ */
5
+ export interface IServicePageData extends IPageData {
6
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import { IActionResult, IUser } from '@etsoo/appscript';
2
+ /**
3
+ * Service user interface
4
+ */
5
+ export interface IServiceUser extends IUser {
6
+ /**
7
+ * Service device id
8
+ */
9
+ serviceDeviceId: string;
10
+ }
11
+ /**
12
+ * Service user login result
13
+ */
14
+ export declare type ServiceLoginResult<U extends IServiceUser = IServiceUser> = IActionResult<U>;
@@ -0,0 +1 @@
1
+ export {};