@tolgee/web 4.10.0-rc.578d1d0.0

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 (206) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +31 -0
  3. package/dist/tolgee-backend-fetch.cjs.min.js +2 -0
  4. package/dist/tolgee-backend-fetch.cjs.min.js.map +1 -0
  5. package/dist/tolgee-backend-fetch.esm.min.mjs +2 -0
  6. package/dist/tolgee-backend-fetch.esm.min.mjs.map +1 -0
  7. package/dist/tolgee-backend-fetch.umd.min.js +2 -0
  8. package/dist/tolgee-backend-fetch.umd.min.js.map +1 -0
  9. package/dist/tolgee-context-ui.cjs.min.js +133 -0
  10. package/dist/tolgee-context-ui.cjs.min.js.map +1 -0
  11. package/dist/tolgee-context-ui.esm.min.mjs +133 -0
  12. package/dist/tolgee-context-ui.esm.min.mjs.map +1 -0
  13. package/dist/tolgee-context-ui.umd.min.js +133 -0
  14. package/dist/tolgee-context-ui.umd.min.js.map +1 -0
  15. package/dist/tolgee-in-context-production.cjs.min.js +133 -0
  16. package/dist/tolgee-in-context-production.cjs.min.js.map +1 -0
  17. package/dist/tolgee-in-context-production.esm.min.mjs +133 -0
  18. package/dist/tolgee-in-context-production.esm.min.mjs.map +1 -0
  19. package/dist/tolgee-in-context-production.umd.min.js +133 -0
  20. package/dist/tolgee-in-context-production.umd.min.js.map +1 -0
  21. package/dist/tolgee-invisible-observer.cjs.min.js +2 -0
  22. package/dist/tolgee-invisible-observer.cjs.min.js.map +1 -0
  23. package/dist/tolgee-invisible-observer.esm.min.mjs +2 -0
  24. package/dist/tolgee-invisible-observer.esm.min.mjs.map +1 -0
  25. package/dist/tolgee-invisible-observer.umd.min.js +2 -0
  26. package/dist/tolgee-invisible-observer.umd.min.js.map +1 -0
  27. package/dist/tolgee-language-detector.cjs.min.js +2 -0
  28. package/dist/tolgee-language-detector.cjs.min.js.map +1 -0
  29. package/dist/tolgee-language-detector.esm.min.mjs +2 -0
  30. package/dist/tolgee-language-detector.esm.min.mjs.map +1 -0
  31. package/dist/tolgee-language-detector.umd.min.js +2 -0
  32. package/dist/tolgee-language-detector.umd.min.js.map +1 -0
  33. package/dist/tolgee-language-storage.cjs.min.js +2 -0
  34. package/dist/tolgee-language-storage.cjs.min.js.map +1 -0
  35. package/dist/tolgee-language-storage.esm.min.mjs +2 -0
  36. package/dist/tolgee-language-storage.esm.min.mjs.map +1 -0
  37. package/dist/tolgee-language-storage.umd.min.js +2 -0
  38. package/dist/tolgee-language-storage.umd.min.js.map +1 -0
  39. package/dist/tolgee-text-observer.cjs.min.js +2 -0
  40. package/dist/tolgee-text-observer.cjs.min.js.map +1 -0
  41. package/dist/tolgee-text-observer.esm.min.mjs +2 -0
  42. package/dist/tolgee-text-observer.esm.min.mjs.map +1 -0
  43. package/dist/tolgee-text-observer.umd.min.js +2 -0
  44. package/dist/tolgee-text-observer.umd.min.js.map +1 -0
  45. package/dist/tolgee-web-tolgee.cjs.min.js +2 -0
  46. package/dist/tolgee-web-tolgee.cjs.min.js.map +1 -0
  47. package/dist/tolgee-web-tolgee.esm.min.mjs +2 -0
  48. package/dist/tolgee-web-tolgee.esm.min.mjs.map +1 -0
  49. package/dist/tolgee-web-tolgee.umd.min.js +2 -0
  50. package/dist/tolgee-web-tolgee.umd.min.js.map +1 -0
  51. package/dist/tolgee-web.cjs.js +26835 -0
  52. package/dist/tolgee-web.cjs.js.map +1 -0
  53. package/dist/tolgee-web.cjs.min.js +133 -0
  54. package/dist/tolgee-web.cjs.min.js.map +1 -0
  55. package/dist/tolgee-web.esm.min.mjs +133 -0
  56. package/dist/tolgee-web.esm.min.mjs.map +1 -0
  57. package/dist/tolgee-web.esm.mjs +26812 -0
  58. package/dist/tolgee-web.esm.mjs.map +1 -0
  59. package/dist/tolgee-web.umd.js +26841 -0
  60. package/dist/tolgee-web.umd.js.map +1 -0
  61. package/dist/tolgee-web.umd.min.js +133 -0
  62. package/dist/tolgee-web.umd.min.js.map +1 -0
  63. package/lib/BackendFetch.d.ts +4 -0
  64. package/lib/BrowserExtensionPlugin/BrowserExtensionPlugin.d.ts +8 -0
  65. package/lib/BrowserExtensionPlugin/injectUiLib.d.ts +1 -0
  66. package/lib/ContextUi.d.ts +2 -0
  67. package/lib/DevBackend.d.ts +2 -0
  68. package/lib/DevTools.d.ts +3 -0
  69. package/lib/InContextProduction.d.ts +2 -0
  70. package/lib/InContextTools.d.ts +8 -0
  71. package/lib/InvisibleObserver.d.ts +3 -0
  72. package/lib/LanguageDetector.d.ts +3 -0
  73. package/lib/LanguageStorage.d.ts +3 -0
  74. package/lib/TextObserver.d.ts +3 -0
  75. package/lib/WebTolgee.d.ts +3 -0
  76. package/lib/index.d.ts +5 -0
  77. package/lib/observers/general/DomHelper.d.ts +4 -0
  78. package/lib/observers/general/ElementHighlighter.d.ts +10 -0
  79. package/lib/observers/general/ElementMeta.d.ts +3 -0
  80. package/lib/observers/general/ElementRegistry.d.ts +12 -0
  81. package/lib/observers/general/ElementStore.d.ts +10 -0
  82. package/lib/observers/general/GeneralObserver.d.ts +13 -0
  83. package/lib/observers/general/MouseEventHandler.d.ts +12 -0
  84. package/lib/observers/general/NodeHandler.d.ts +7 -0
  85. package/lib/observers/general/helpers.d.ts +7 -0
  86. package/lib/observers/general/initOptions.d.ts +2 -0
  87. package/lib/observers/invisible/InvisibleWrapper.d.ts +2 -0
  88. package/lib/observers/invisible/ValueMemory.d.ts +5 -0
  89. package/lib/observers/invisible/encoderPolyfill.d.ts +8 -0
  90. package/lib/observers/invisible/secret.d.ts +6 -0
  91. package/lib/observers/text/TextWrapper.d.ts +8 -0
  92. package/lib/observers/text/helpers.d.ts +3 -0
  93. package/lib/tools/decodeApiKey.d.ts +1 -0
  94. package/lib/tools/plugin.d.ts +16 -0
  95. package/lib/typedIndex.d.ts +11 -0
  96. package/lib/types.d.ts +45 -0
  97. package/lib/ui/KeyContextMenu/KeyContextMenu.d.ts +19 -0
  98. package/lib/ui/KeyDialog/KeyDialog.d.ts +23 -0
  99. package/lib/ui/KeyDialog/KeyForm.d.ts +1 -0
  100. package/lib/ui/KeyDialog/LanguageSelect.d.ts +2 -0
  101. package/lib/ui/KeyDialog/NewWindow.d.ts +2 -0
  102. package/lib/ui/KeyDialog/NsSelect.d.ts +9 -0
  103. package/lib/ui/KeyDialog/ScreenshotGallery/ExtensionPrompt.d.ts +6 -0
  104. package/lib/ui/KeyDialog/ScreenshotGallery/ScreenshotDetail.d.ts +8 -0
  105. package/lib/ui/KeyDialog/ScreenshotGallery/ScreenshotDropzone.d.ts +6 -0
  106. package/lib/ui/KeyDialog/ScreenshotGallery/ScreenshotGallery.d.ts +2 -0
  107. package/lib/ui/KeyDialog/ScreenshotGallery/ScreenshotThumbnail.d.ts +8 -0
  108. package/lib/ui/KeyDialog/ScreenshotGallery/utils.d.ts +3 -0
  109. package/lib/ui/KeyDialog/TranslationDialog.d.ts +1 -0
  110. package/lib/ui/KeyDialog/TranslationDialogContextProvider.d.ts +128 -0
  111. package/lib/ui/KeyDialog/TranslationDialogWrapper.d.ts +2 -0
  112. package/lib/ui/KeyDialog/TranslationFields.d.ts +2 -0
  113. package/lib/ui/KeyDialog/languageHelpers.d.ts +12 -0
  114. package/lib/ui/KeyDialog/tools.d.ts +3 -0
  115. package/lib/ui/ThemeProvider.d.ts +2 -0
  116. package/lib/ui/client/QueryProvider.d.ts +12 -0
  117. package/lib/ui/client/apiSchema.generated.d.ts +3283 -0
  118. package/lib/ui/client/client.d.ts +5 -0
  119. package/lib/ui/client/types.d.ts +58 -0
  120. package/lib/ui/client/useQueryApi.d.ts +84 -0
  121. package/lib/ui/common/BodyEnd.d.ts +13 -0
  122. package/lib/ui/common/FieldTitle.d.ts +2 -0
  123. package/lib/ui/common/LoadingButton.d.ts +7 -0
  124. package/lib/ui/constants.d.ts +5 -0
  125. package/lib/ui/index.d.ts +13 -0
  126. package/lib/ui/tools/createProvider.d.ts +5 -0
  127. package/lib/ui/tools/isLanguagePermitted.d.ts +1 -0
  128. package/lib/ui/tools/sleep.d.ts +1 -0
  129. package/package.json +89 -0
  130. package/src/BackendFetch.ts +64 -0
  131. package/src/BrowserExtensionPlugin/BrowserExtensionPlugin.ts +91 -0
  132. package/src/BrowserExtensionPlugin/injectUiLib.ts +22 -0
  133. package/src/ContextUi.ts +7 -0
  134. package/src/DevBackend.ts +30 -0
  135. package/src/DevTools.ts +9 -0
  136. package/src/InContextProduction.ts +15 -0
  137. package/src/InContextTools.ts +21 -0
  138. package/src/InvisibleObserver.ts +21 -0
  139. package/src/LanguageDetector.test.ts +19 -0
  140. package/src/LanguageDetector.ts +32 -0
  141. package/src/LanguageStorage.ts +23 -0
  142. package/src/TextObserver.ts +50 -0
  143. package/src/WebTolgee.ts +8 -0
  144. package/src/__test__/observer.test.ts +13 -0
  145. package/src/__test__/testObserver.ts +106 -0
  146. package/src/__test__/testRetranslate.ts +47 -0
  147. package/src/index.ts +5 -0
  148. package/src/observers/general/DomHelper.ts +46 -0
  149. package/src/observers/general/ElementHighlighter.ts +72 -0
  150. package/src/observers/general/ElementMeta.ts +17 -0
  151. package/src/observers/general/ElementRegistry.ts +159 -0
  152. package/src/observers/general/ElementStore.ts +34 -0
  153. package/src/observers/general/GeneralObserver.ts +133 -0
  154. package/src/observers/general/MouseEventHandler.ts +200 -0
  155. package/src/observers/general/NodeHandler.ts +40 -0
  156. package/src/observers/general/helpers.ts +65 -0
  157. package/src/observers/general/initOptions.ts +23 -0
  158. package/src/observers/invisible/InvisibleWrapper.test.ts +17 -0
  159. package/src/observers/invisible/InvisibleWrapper.ts +96 -0
  160. package/src/observers/invisible/ValueMemory.test.ts +25 -0
  161. package/src/observers/invisible/ValueMemory.ts +20 -0
  162. package/src/observers/invisible/encoderPolyfill.ts +96 -0
  163. package/src/observers/invisible/secret.test.ts +61 -0
  164. package/src/observers/invisible/secret.ts +68 -0
  165. package/src/observers/text/TextWrapper.ts +258 -0
  166. package/src/observers/text/helpers.ts +56 -0
  167. package/src/tools/decodeApiKey.test.ts +14 -0
  168. package/src/tools/decodeApiKey.ts +74 -0
  169. package/src/tools/plugin.ts +105 -0
  170. package/src/typedIndex.ts +13 -0
  171. package/src/types.ts +52 -0
  172. package/src/ui/KeyContextMenu/KeyContextMenu.tsx +106 -0
  173. package/src/ui/KeyDialog/KeyDialog.tsx +67 -0
  174. package/src/ui/KeyDialog/KeyForm.tsx +208 -0
  175. package/src/ui/KeyDialog/LanguageSelect.tsx +78 -0
  176. package/src/ui/KeyDialog/NewWindow.tsx +106 -0
  177. package/src/ui/KeyDialog/NsSelect.tsx +67 -0
  178. package/src/ui/KeyDialog/ScreenshotGallery/ExtensionPrompt.tsx +97 -0
  179. package/src/ui/KeyDialog/ScreenshotGallery/ScreenshotDetail.tsx +33 -0
  180. package/src/ui/KeyDialog/ScreenshotGallery/ScreenshotDropzone.tsx +138 -0
  181. package/src/ui/KeyDialog/ScreenshotGallery/ScreenshotGallery.tsx +240 -0
  182. package/src/ui/KeyDialog/ScreenshotGallery/ScreenshotThumbnail.tsx +113 -0
  183. package/src/ui/KeyDialog/ScreenshotGallery/utils.ts +17 -0
  184. package/src/ui/KeyDialog/TranslationDialog.tsx +14 -0
  185. package/src/ui/KeyDialog/TranslationDialogContextProvider.tsx +464 -0
  186. package/src/ui/KeyDialog/TranslationDialogWrapper.tsx +44 -0
  187. package/src/ui/KeyDialog/TranslationFields.tsx +113 -0
  188. package/src/ui/KeyDialog/languageHelpers.ts +18 -0
  189. package/src/ui/KeyDialog/tools.ts +30 -0
  190. package/src/ui/ThemeProvider.tsx +71 -0
  191. package/src/ui/__test__/keyContextMenu.test.ts +56 -0
  192. package/src/ui/client/QueryProvider.tsx +38 -0
  193. package/src/ui/client/apiSchema.generated.ts +3281 -0
  194. package/src/ui/client/client.ts +155 -0
  195. package/src/ui/client/types.ts +113 -0
  196. package/src/ui/client/useQueryApi.ts +121 -0
  197. package/src/ui/common/BodyEnd.tsx +44 -0
  198. package/src/ui/common/FieldTitle.tsx +9 -0
  199. package/src/ui/common/LoadingButton.tsx +45 -0
  200. package/src/ui/constants.ts +12 -0
  201. package/src/ui/index.ts +86 -0
  202. package/src/ui/screenshots/ScreenshotPreview.tsx +18 -0
  203. package/src/ui/tools/createProvider.tsx +54 -0
  204. package/src/ui/tools/isLanguagePermitted.ts +14 -0
  205. package/src/ui/tools/sleep.ts +2 -0
  206. package/types/index.d.ts +12 -0
@@ -0,0 +1,106 @@
1
+ import React from 'react';
2
+ import Menu from '@mui/material/Menu';
3
+ import MenuItem from '@mui/material/MenuItem';
4
+ import { styled } from '@mui/material/styles';
5
+
6
+ import { DEVTOOLS_ID, DEVTOOLS_Z_INDEX } from '../constants';
7
+ import { ThemeProvider } from '../ThemeProvider';
8
+
9
+ const ScMenuItem = styled(MenuItem)`
10
+ display: flex;
11
+ flex-direction: column;
12
+ height: 50px;
13
+ justify-content: center;
14
+ align-items: flex-start;
15
+ `;
16
+
17
+ const ScTranslation = styled('div')`
18
+ display: flex;
19
+ padding: 3px;
20
+ `;
21
+
22
+ const ScKey = styled('div')`
23
+ display: flex;
24
+ margin-top: -5px;
25
+ padding: 3px;
26
+ font-weight: bold;
27
+ font-size: 12px;
28
+ font-family: Monospace, 'Courier New', Courier;
29
+ `;
30
+
31
+ export interface KeyContextMenuParams {
32
+ openEvent: MouseEvent;
33
+ keys: Map<string, string | undefined>;
34
+ onSelect: (key: string | undefined) => void;
35
+ }
36
+
37
+ export type KeyContextMenuState = Partial<KeyContextMenuParams> & {
38
+ opened: boolean;
39
+ };
40
+
41
+ export class KeyContextMenu extends React.Component {
42
+ state: KeyContextMenuState & { opened: boolean } = {
43
+ opened: false,
44
+ };
45
+
46
+ async show(params: KeyContextMenuParams) {
47
+ this.setState({ ...params, opened: true });
48
+ }
49
+
50
+ keyDown = (e: KeyboardEvent) => {
51
+ if (e.key === 'Escape') {
52
+ this.setState((s) => ({ ...s, opened: false }));
53
+ this.state.onSelect && this.state.onSelect(undefined);
54
+ }
55
+ };
56
+
57
+ componentDidMount() {
58
+ document.addEventListener('keydown', this.keyDown);
59
+ }
60
+
61
+ componentWillUnmount() {
62
+ document.removeEventListener('keydown', this.keyDown);
63
+ }
64
+
65
+ render() {
66
+ return (
67
+ <ThemeProvider>
68
+ {this.state.opened && (
69
+ <Menu
70
+ disablePortal
71
+ disableEnforceFocus
72
+ anchorEl={this.state.openEvent?.target as Element}
73
+ anchorOrigin={{
74
+ vertical: 'bottom',
75
+ horizontal: 'center',
76
+ }}
77
+ open
78
+ onClose={() => {
79
+ this.setState({ opened: false });
80
+ this.state.onSelect?.(undefined);
81
+ }}
82
+ container={document.getElementById(DEVTOOLS_ID)}
83
+ style={{ zIndex: DEVTOOLS_Z_INDEX }}
84
+ >
85
+ {Array.from(this.state.keys || []).map(
86
+ ([key, translation], index) => (
87
+ <ScMenuItem
88
+ onClick={() => {
89
+ this.state.onSelect?.(key);
90
+ setTimeout(() => {
91
+ this.setState({ opened: false });
92
+ });
93
+ }}
94
+ key={index}
95
+ >
96
+ <ScTranslation>{translation}</ScTranslation>
97
+ <ScKey>{key}</ScKey>
98
+ </ScMenuItem>
99
+ )
100
+ )}
101
+ </Menu>
102
+ )}
103
+ </ThemeProvider>
104
+ );
105
+ }
106
+ }
@@ -0,0 +1,67 @@
1
+ import * as React from 'react';
2
+ import { BodyEnd } from '../common/BodyEnd';
3
+ import { DialogProvider } from './TranslationDialogContextProvider';
4
+ import { TranslationDialog } from './TranslationDialog';
5
+ import type { FallbackNSTranslation, UiProps } from '@tolgee/core';
6
+ import { QueryProvider } from '../../ui/client/QueryProvider';
7
+
8
+ export type ComponentDependencies = UiProps;
9
+
10
+ export type Props = UiProps;
11
+
12
+ type State = {
13
+ key: null | string;
14
+ defaultValue: undefined | string;
15
+ dialogOpened: boolean;
16
+ ns: FallbackNSTranslation;
17
+ };
18
+
19
+ export class KeyDialog extends React.Component<Props, State> {
20
+ state = {
21
+ key: null,
22
+ defaultValue: undefined,
23
+ dialogOpened: false,
24
+ ns: undefined,
25
+ };
26
+
27
+ constructor(props: Props) {
28
+ super(props);
29
+ }
30
+
31
+ public translationEdit(
32
+ key: string,
33
+ defaultValue: string | undefined,
34
+ ns: FallbackNSTranslation
35
+ ) {
36
+ this.setState({
37
+ ...this.state,
38
+ dialogOpened: true,
39
+ defaultValue: defaultValue,
40
+ key,
41
+ ns,
42
+ });
43
+ }
44
+
45
+ public render = () => (
46
+ <BodyEnd>
47
+ <QueryProvider apiUrl={this.props.apiUrl} apiKey={this.props.apiKey}>
48
+ {this.state.dialogOpened && (
49
+ <DialogProvider
50
+ uiProps={this.props}
51
+ defaultValue={this.state.defaultValue || ''}
52
+ open={this.state.dialogOpened}
53
+ keyName={this.state.key!}
54
+ ns={this.state.ns}
55
+ onClose={this.onClose}
56
+ >
57
+ <TranslationDialog />
58
+ </DialogProvider>
59
+ )}
60
+ </QueryProvider>
61
+ </BodyEnd>
62
+ );
63
+
64
+ private onClose = () => {
65
+ this.setState({ ...this.state, dialogOpened: false });
66
+ };
67
+ }
@@ -0,0 +1,208 @@
1
+ import React from 'react';
2
+ import { RESTRICTED_ASCENDANT_ATTRIBUTE } from '@tolgee/core';
3
+
4
+ import IconButton from '@mui/material/IconButton';
5
+ import Button from '@mui/material/Button';
6
+ import { styled } from '@mui/material/styles';
7
+ import OpenInNew from '@mui/icons-material/OpenInNew';
8
+
9
+ import { TranslationFields } from './TranslationFields';
10
+ import { LanguageSelect } from './LanguageSelect';
11
+ import { LoadingButton } from '../common/LoadingButton';
12
+ import { ScreenshotGallery } from './ScreenshotGallery/ScreenshotGallery';
13
+ import { ScFieldTitle } from '../common/FieldTitle';
14
+ import {
15
+ useDialogContext,
16
+ useDialogDispatch,
17
+ } from './TranslationDialogContextProvider';
18
+ import { isAuthorizedTo } from './ScreenshotGallery/utils';
19
+ import { NsSelect } from './NsSelect';
20
+
21
+ const ScContainer = styled('div')`
22
+ font-family: Rubik, Roboto, Arial;
23
+ padding: 20px;
24
+ box-sizing: border-box;
25
+ max-width: 100%;
26
+ width: 700px;
27
+ display: flex;
28
+ flex-direction: column;
29
+ `;
30
+
31
+ const ScHeading = styled('div')`
32
+ display: flex;
33
+ align-items: center;
34
+ gap: 5px;
35
+ `;
36
+
37
+ const ScHeadingTitle = styled('div')`
38
+ display: flex;
39
+ margin: 0px;
40
+ font-size: 19px;
41
+ `;
42
+
43
+ const ScHeadingRight = styled('div')`
44
+ display: flex;
45
+ justify-content: flex-end;
46
+ align-items: center;
47
+ flex-grow: 1;
48
+ `;
49
+
50
+ const ScKey = styled('p')`
51
+ margin: 0px;
52
+ `;
53
+
54
+ const ScKeyHint = styled('span')`
55
+ color: grey;
56
+ `;
57
+
58
+ const ScFieldsWrapper = styled('div')`
59
+ margin-top: 20px;
60
+ `;
61
+
62
+ const ScGalleryWrapper = styled('div')`
63
+ margin-top: 10px;
64
+ `;
65
+
66
+ const ScControls = styled('div')`
67
+ display: flex;
68
+ justify-content: flex-end;
69
+ margin-top: 20px;
70
+ min-height: 36px;
71
+ `;
72
+
73
+ const ScRestriction = styled('div')`
74
+ margin-top: 8px;
75
+ color: ${({ theme }) => theme.palette.text.secondary};
76
+ `;
77
+
78
+ const ScError = styled('div')`
79
+ color: red;
80
+ `;
81
+
82
+ export const KeyForm = () => {
83
+ const dispatch = useDialogDispatch();
84
+
85
+ const linkToPlatform = useDialogContext((c) => c.linkToPlatform);
86
+ const useBrowserWindow = useDialogContext((c) => c.useBrowserWindow);
87
+ const input = useDialogContext((c) => c.input);
88
+ const translations = useDialogContext((c) => c.translations);
89
+ const formDisabled = useDialogContext((c) => c.formDisabled);
90
+ const loading = useDialogContext((c) => c.loading);
91
+ const error = useDialogContext((c) => c.error);
92
+ const saving = useDialogContext((c) => c.saving);
93
+ const success = useDialogContext((c) => c.success);
94
+ const keyExists = useDialogContext((c) => c.keyExists);
95
+ const scopes = useDialogContext((c) => c.scopes);
96
+ const ns = useDialogContext((c) => c.ns);
97
+ const selectedNs = useDialogContext((c) => c.selectedNs);
98
+
99
+ const screenshotsView = isAuthorizedTo('screenshots.view', scopes);
100
+
101
+ const setUseBrowserWindow = (value: boolean) => {
102
+ dispatch({ type: 'SET_USE_BROWSER_WINDOW', payload: value });
103
+ };
104
+
105
+ const onClose = () => {
106
+ dispatch({ type: 'ON_CLOSE' });
107
+ };
108
+
109
+ const onSave = () => {
110
+ dispatch({ type: 'ON_SAVE' });
111
+ };
112
+
113
+ const onNamespaceChange = (ns: string) => {
114
+ dispatch({ type: 'SELECTED_NS_CHANGE', payload: { ns } });
115
+ };
116
+
117
+ const ready = !loading && !error;
118
+
119
+ return (
120
+ <ScContainer {...{ [RESTRICTED_ASCENDANT_ATTRIBUTE]: 'true' }}>
121
+ <ScHeading>
122
+ <a
123
+ href={linkToPlatform}
124
+ target="_blank"
125
+ rel="noreferrer noopener"
126
+ id="_tolgee_platform_link"
127
+ >
128
+ <svg
129
+ viewBox="0 0 200 200"
130
+ xmlns="http://www.w3.org/2000/svg"
131
+ opacity="0.99"
132
+ fill="#822B55"
133
+ style={{
134
+ fillRule: 'evenodd',
135
+ clipRule: 'evenodd',
136
+ strokeLinejoin: 'round',
137
+ strokeMiterlimit: 2,
138
+ height: 23,
139
+ }}
140
+ >
141
+ <path d="M97.16,7.27a16.94,16.94,0,0,0-1.9,24.47,16.36,16.36,0,0,0,5,3.83,3.23,3.23,0,0,1-2.9,5.77,23.14,23.14,0,0,1-11.41-13C73.83,31.1,63.46,37.09,52.82,46.51c-27.44,24.3-34.35,61.74-16.38,85.26-4.57,5.79-8,12.22-8.9,18.69a20.88,20.88,0,0,0,5.62,18c9.18,9.61,21.42,7.13,31.26,5.14,6.58-1.34,12.8-2.6,16.5-.23,3.22,2.07,3.47,3.87,3.61,4.45,2.1,9.32-5.79,13.89-7.67,16.27a1.48,1.48,0,0,0,1.13,2.4c3.48,0,9-1.18,12.34-4.08s7.16-7.9,5.89-16.32c-.08-.5-.18-1-.32-1.58-.86-3.35-3.1-7.57-8.61-11.09-7.72-4.95-17-3.07-25.22-1.41-9.76,2-16,2.85-20.37-1.71a9.13,9.13,0,0,1-2.46-8.19c.54-3.77,2.65-7.89,5.62-11.86,21.71,16.89,56.87,13.47,82.67-9.39a75.34,75.34,0,0,0,20.81-28.09A23.14,23.14,0,0,1,134.8,89a3.23,3.23,0,0,1,6.08-2.19,16.37,16.37,0,0,0,3.2,5.39,16.85,16.85,0,1,0,11.48-28,3.23,3.23,0,0,1-.51-6.44,23.41,23.41,0,0,1,12.88,2.69c2.6-14.08,3.34-31.41-2.06-37.51-4.08-4.61-20.62-8-35.18-7.76A23.48,23.48,0,0,1,130.8,25a3.23,3.23,0,0,1-6.33-1.28A16.94,16.94,0,0,0,97.16,7.27Zm63.25,21a5.29,5.29,0,0,1-.57,6.19c-1.29,1.14-2.72-.51-4.1-2.06s-3.1-3.42-1.81-4.56A5.74,5.74,0,0,1,160.41,28.27Z"></path>
142
+ </svg>
143
+ </a>
144
+
145
+ <ScHeadingTitle>Quick translation</ScHeadingTitle>
146
+
147
+ {!useBrowserWindow && (
148
+ <IconButton
149
+ title="Open in new window"
150
+ onClick={() => setUseBrowserWindow(true)}
151
+ color="inherit"
152
+ size="small"
153
+ >
154
+ <OpenInNew fontSize="small" />
155
+ </IconButton>
156
+ )}
157
+ <ScHeadingRight>{!loading && <LanguageSelect />}</ScHeadingRight>
158
+ </ScHeading>
159
+
160
+ <ScFieldTitle>Key</ScFieldTitle>
161
+ <ScKey>
162
+ {input}
163
+ <ScKeyHint>
164
+ {!keyExists && ready && " (key doesn't exist yet)"}
165
+ </ScKeyHint>
166
+ </ScKey>
167
+
168
+ <NsSelect options={ns} value={selectedNs} onChange={onNamespaceChange} />
169
+
170
+ <ScFieldsWrapper>
171
+ <TranslationFields />
172
+ </ScFieldsWrapper>
173
+
174
+ {screenshotsView && ready && (
175
+ <ScGalleryWrapper>
176
+ <ScreenshotGallery />
177
+ </ScGalleryWrapper>
178
+ )}
179
+
180
+ {formDisabled && ready && (
181
+ <ScRestriction>{`Modification is restricted due to missing ${
182
+ translations?.keyId !== undefined ? 'translations.edit' : 'keys.edit'
183
+ } scope in current api key settings.`}</ScRestriction>
184
+ )}
185
+
186
+ {error && <ScError>{error}</ScError>}
187
+ <ScControls>
188
+ <Button onClick={onClose} color="secondary">
189
+ {useBrowserWindow ? 'Close' : 'Cancel'}
190
+ </Button>
191
+ <LoadingButton
192
+ loading={saving}
193
+ disabled={saving || formDisabled}
194
+ onClick={onSave}
195
+ color="primary"
196
+ variant="contained"
197
+ style={{ marginLeft: '10px' }}
198
+ >
199
+ {success
200
+ ? 'Saved! ✓'
201
+ : translations?.keyId === undefined
202
+ ? 'Create'
203
+ : 'Update'}
204
+ </LoadingButton>
205
+ </ScControls>
206
+ </ScContainer>
207
+ );
208
+ };
@@ -0,0 +1,78 @@
1
+ import React from 'react';
2
+
3
+ import Select, { SelectChangeEvent } from '@mui/material/Select';
4
+ import MenuItem from '@mui/material/MenuItem';
5
+ import Checkbox from '@mui/material/Checkbox';
6
+ import ListItemText from '@mui/material/ListItemText';
7
+ import OutlinedInput from '@mui/material/OutlinedInput';
8
+ import FormControl from '@mui/material/FormControl';
9
+ import { styled } from '@mui/material/styles';
10
+
11
+ import { DEVTOOLS_Z_INDEX } from '../constants';
12
+ import {
13
+ useDialogContext,
14
+ useDialogDispatch,
15
+ } from './TranslationDialogContextProvider';
16
+
17
+ const ScFormControl = styled(FormControl)`
18
+ min-width: 200px;
19
+ `;
20
+
21
+ export const LanguageSelect: React.FC = () => {
22
+ const dispatch = useDialogDispatch();
23
+ const availableLanguages = useDialogContext((c) => c.availableLanguages);
24
+ const selectedLanguages = useDialogContext((c) => c.selectedLanguages);
25
+
26
+ const options = availableLanguages
27
+ ? [...availableLanguages].map((lang) => ({
28
+ label: lang.name,
29
+ value: lang.tag,
30
+ }))
31
+ : [];
32
+
33
+ const selected = options.filter((o) => selectedLanguages.includes(o.value));
34
+ const onChange = (e: SelectChangeEvent<string[]>) => {
35
+ const value = e.target.value;
36
+ const languages = typeof value === 'string' ? value.split(',') : value;
37
+ dispatch({
38
+ type: 'ON_SELECTED_LANGUAGES_CHANGE',
39
+ payload: { languages },
40
+ });
41
+ };
42
+
43
+ return (
44
+ <>
45
+ {availableLanguages && (
46
+ <ScFormControl
47
+ variant="outlined"
48
+ size="small"
49
+ style={{ maxWidth: 250 }}
50
+ >
51
+ <Select
52
+ multiple
53
+ value={selected.map((o) => o.value)}
54
+ onChange={(value) => onChange(value)}
55
+ input={<OutlinedInput />}
56
+ renderValue={(selected) => selected.join(', ')}
57
+ MenuProps={{
58
+ style: { zIndex: DEVTOOLS_Z_INDEX },
59
+ disablePortal: true,
60
+ }}
61
+ >
62
+ {options.map((option) => (
63
+ <MenuItem key={option.value} value={option.value} dense>
64
+ <Checkbox
65
+ size="small"
66
+ checked={Boolean(
67
+ selected.find((o) => o.value === option.value)
68
+ )}
69
+ />
70
+ <ListItemText>{option.label}</ListItemText>
71
+ </MenuItem>
72
+ ))}
73
+ </Select>
74
+ </ScFormControl>
75
+ )}
76
+ </>
77
+ );
78
+ };
@@ -0,0 +1,106 @@
1
+ import React, { FC, useEffect, useRef, useState } from 'react';
2
+ import { createPortal } from 'react-dom';
3
+ import createCache from '@emotion/cache';
4
+ import { CacheProvider } from '@emotion/react';
5
+ import {
6
+ useDialogContext,
7
+ useDialogDispatch,
8
+ } from './TranslationDialogContextProvider';
9
+
10
+ export const NewWindow: FC = (props) => {
11
+ const newWindow = useRef<Window>(null);
12
+ const [popup, setPopup] = useState<Window | null>(null);
13
+ const dispatch = useDialogDispatch();
14
+ const container = useDialogContext((c) => c.container);
15
+
16
+ const setContainer = (el: Element | undefined) => {
17
+ dispatch({ type: 'SET_CONTAINER', payload: el });
18
+ };
19
+ const onClose = () => {
20
+ dispatch({ type: 'ON_CLOSE' });
21
+ };
22
+
23
+ useEffect(() => {
24
+ // Create container element on client-side
25
+ const div = document.createElement('div');
26
+ div.style.width = '100vw';
27
+ div.style.height = '100vh';
28
+ div.style.position = 'relative';
29
+ setContainer(div);
30
+ }, []);
31
+
32
+ useEffect(() => {
33
+ // When container is ready
34
+ if (container) {
35
+ // Create window
36
+ const win = window.open('', '', 'width=600,height=600,left=200,top=200');
37
+
38
+ if (!win) {
39
+ return;
40
+ }
41
+
42
+ win.document.title = 'Tolgee - Translate Text';
43
+ if (!win.document) {
44
+ alert('Please allow popups to open new window.');
45
+ }
46
+ // @ts-ignore
47
+ newWindow.current = win;
48
+ // Append container
49
+ win.document.body.style.margin = '0px';
50
+ win.document.body.appendChild(container);
51
+
52
+ const onExit = () => {
53
+ setContainer(undefined);
54
+ win.close();
55
+ onClose();
56
+ };
57
+
58
+ win.onbeforeunload = () => {
59
+ setContainer(undefined);
60
+ onClose();
61
+ };
62
+
63
+ const onKeyDown = (e: KeyboardEvent) => {
64
+ if (e.key === 'Escape') {
65
+ dispatch({ type: 'ON_CLOSE' });
66
+ }
67
+ };
68
+
69
+ const onBeforeUnload = () => {
70
+ onExit();
71
+ };
72
+
73
+ win.document.addEventListener('keydown', onKeyDown, true);
74
+ window.addEventListener('beforeunload', onBeforeUnload, true);
75
+ setPopup(win);
76
+
77
+ return () => {
78
+ win.document.removeEventListener('keydown', onKeyDown, true);
79
+ window.removeEventListener('beforeunload', onBeforeUnload, true);
80
+ setContainer(undefined);
81
+ newWindow.current?.close();
82
+ setPopup(null);
83
+ };
84
+ }
85
+ }, [container]);
86
+
87
+ useEffect(() => {
88
+ popup?.focus();
89
+ });
90
+
91
+ const styleCache = React.useMemo(() => {
92
+ // styles insertion point in popup head
93
+ const head = popup?.document.head;
94
+ return createCache({
95
+ key: 'external',
96
+ container: head,
97
+ });
98
+ }, [popup]);
99
+
100
+ return container
101
+ ? createPortal(
102
+ <CacheProvider value={styleCache}>{props.children}</CacheProvider>,
103
+ container
104
+ )
105
+ : null;
106
+ };
@@ -0,0 +1,67 @@
1
+ import React from 'react';
2
+ import { FallbackNSTranslation } from '@tolgee/core';
3
+
4
+ import FormControl from '@mui/material/FormControl';
5
+ import MenuItem from '@mui/material/MenuItem';
6
+ import OutlinedInput from '@mui/material/OutlinedInput';
7
+ import Select from '@mui/material/Select';
8
+ import ListItemText from '@mui/material/ListItemText';
9
+ import { DEVTOOLS_Z_INDEX } from '../constants';
10
+ import { ScFieldTitle } from '../../ui/common/FieldTitle';
11
+ import { getFallbackArray } from '@tolgee/core';
12
+
13
+ const getNsName = (ns: string) => {
14
+ if (!ns) {
15
+ return 'No namespace';
16
+ }
17
+ return ns;
18
+ };
19
+
20
+ type Props = {
21
+ options: FallbackNSTranslation;
22
+ value: string;
23
+ onChange: (value: string) => void;
24
+ };
25
+
26
+ export const NsSelect: React.FC<Props> = ({ onChange, options, value }) => {
27
+ const namespaces = getFallbackArray(options);
28
+ const namespaceOne = namespaces.length === 1;
29
+ const namespaceEmpty = namespaceOne && namespaces[0] === '';
30
+
31
+ return (
32
+ <>
33
+ {!namespaceEmpty && (
34
+ <>
35
+ <ScFieldTitle>Namespace</ScFieldTitle>
36
+ {namespaceOne ? (
37
+ getNsName(namespaces[0])
38
+ ) : (
39
+ <FormControl
40
+ variant="outlined"
41
+ size="small"
42
+ style={{ maxWidth: 250 }}
43
+ >
44
+ <Select
45
+ displayEmpty
46
+ value={value}
47
+ onChange={(e) => onChange(e.target.value)}
48
+ input={<OutlinedInput />}
49
+ renderValue={(value) => getNsName(value)}
50
+ MenuProps={{
51
+ style: { zIndex: DEVTOOLS_Z_INDEX },
52
+ disablePortal: true,
53
+ }}
54
+ >
55
+ {namespaces.map((ns) => (
56
+ <MenuItem key={ns} value={ns} dense>
57
+ <ListItemText>{getNsName(ns)}</ListItemText>
58
+ </MenuItem>
59
+ ))}
60
+ </Select>
61
+ </FormControl>
62
+ )}
63
+ </>
64
+ )}
65
+ </>
66
+ );
67
+ };