@tolgee/web 4.9.3-rc.d287ae9.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 +143 -0
  10. package/dist/tolgee-context-ui.cjs.min.js.map +1 -0
  11. package/dist/tolgee-context-ui.esm.min.mjs +143 -0
  12. package/dist/tolgee-context-ui.esm.min.mjs.map +1 -0
  13. package/dist/tolgee-context-ui.umd.min.js +143 -0
  14. package/dist/tolgee-context-ui.umd.min.js.map +1 -0
  15. package/dist/tolgee-in-context-tools.cjs.min.js +143 -0
  16. package/dist/tolgee-in-context-tools.cjs.min.js.map +1 -0
  17. package/dist/tolgee-in-context-tools.esm.min.mjs +143 -0
  18. package/dist/tolgee-in-context-tools.esm.min.mjs.map +1 -0
  19. package/dist/tolgee-in-context-tools.umd.min.js +143 -0
  20. package/dist/tolgee-in-context-tools.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 +26921 -0
  52. package/dist/tolgee-web.cjs.js.map +1 -0
  53. package/dist/tolgee-web.cjs.min.js +143 -0
  54. package/dist/tolgee-web.cjs.min.js.map +1 -0
  55. package/dist/tolgee-web.esm.min.mjs +143 -0
  56. package/dist/tolgee-web.esm.min.mjs.map +1 -0
  57. package/dist/tolgee-web.esm.mjs +26899 -0
  58. package/dist/tolgee-web.esm.mjs.map +1 -0
  59. package/dist/tolgee-web.umd.js +26927 -0
  60. package/dist/tolgee-web.umd.js.map +1 -0
  61. package/dist/tolgee-web.umd.min.js +143 -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/constants.d.ts +3 -0
  66. package/lib/BrowserExtensionPlugin/loadInContextLib.d.ts +1 -0
  67. package/lib/ContextUi.d.ts +2 -0
  68. package/lib/DevBackend.d.ts +2 -0
  69. package/lib/DevTools.d.ts +3 -0
  70. package/lib/InContextTools.d.ts +2 -0
  71. package/lib/InvisibleObserver.d.ts +2 -0
  72. package/lib/LanguageDetector.d.ts +3 -0
  73. package/lib/LanguageStorage.d.ts +3 -0
  74. package/lib/TextObserver.d.ts +2 -0
  75. package/lib/WebTolgee.d.ts +3 -0
  76. package/lib/index.d.ts +4 -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 +11 -0
  81. package/lib/observers/general/ElementStore.d.ts +10 -0
  82. package/lib/observers/general/GeneralObserver.d.ts +12 -0
  83. package/lib/observers/general/MouseEventHandler.d.ts +13 -0
  84. package/lib/observers/general/NodeHandler.d.ts +6 -0
  85. package/lib/observers/general/helpers.d.ts +7 -0
  86. package/lib/observers/invisible/InvisibleWrapper.d.ts +2 -0
  87. package/lib/observers/invisible/ValueMemory.d.ts +5 -0
  88. package/lib/observers/invisible/encoderPolyfill.d.ts +8 -0
  89. package/lib/observers/invisible/secret.d.ts +6 -0
  90. package/lib/observers/text/TextWrapper.d.ts +8 -0
  91. package/lib/observers/text/helpers.d.ts +3 -0
  92. package/lib/tools/decodeApiKey.d.ts +1 -0
  93. package/lib/tools/extension.d.ts +28 -0
  94. package/lib/typedIndex.d.ts +11 -0
  95. package/lib/types.d.ts +28 -0
  96. package/lib/ui/KeyContextMenu/KeyContextMenu.d.ts +19 -0
  97. package/lib/ui/KeyDialog/KeyDialog.d.ts +23 -0
  98. package/lib/ui/KeyDialog/KeyForm.d.ts +2 -0
  99. package/lib/ui/KeyDialog/LanguageSelect.d.ts +2 -0
  100. package/lib/ui/KeyDialog/NewWindow.d.ts +2 -0
  101. package/lib/ui/KeyDialog/NsSelect.d.ts +9 -0
  102. package/lib/ui/KeyDialog/ScreenshotGallery/ExtensionPrompt.d.ts +6 -0
  103. package/lib/ui/KeyDialog/ScreenshotGallery/ScreenshotDetail.d.ts +8 -0
  104. package/lib/ui/KeyDialog/ScreenshotGallery/ScreenshotDropzone.d.ts +6 -0
  105. package/lib/ui/KeyDialog/ScreenshotGallery/ScreenshotGallery.d.ts +2 -0
  106. package/lib/ui/KeyDialog/ScreenshotGallery/ScreenshotThumbnail.d.ts +8 -0
  107. package/lib/ui/KeyDialog/ScreenshotGallery/utils.d.ts +3 -0
  108. package/lib/ui/KeyDialog/TranslationDialog.d.ts +2 -0
  109. package/lib/ui/KeyDialog/TranslationDialogContextProvider.d.ts +128 -0
  110. package/lib/ui/KeyDialog/TranslationDialogWrapper.d.ts +2 -0
  111. package/lib/ui/KeyDialog/TranslationFields.d.ts +2 -0
  112. package/lib/ui/KeyDialog/languageHelpers.d.ts +12 -0
  113. package/lib/ui/KeyDialog/tools.d.ts +3 -0
  114. package/lib/ui/ThemeProvider.d.ts +2 -0
  115. package/lib/ui/client/QueryProvider.d.ts +12 -0
  116. package/lib/ui/client/apiSchema.generated.d.ts +3283 -0
  117. package/lib/ui/client/client.d.ts +5 -0
  118. package/lib/ui/client/types.d.ts +58 -0
  119. package/lib/ui/client/useQueryApi.d.ts +84 -0
  120. package/lib/ui/common/BodyEnd.d.ts +13 -0
  121. package/lib/ui/common/FieldTitle.d.ts +2 -0
  122. package/lib/ui/common/LoadingButton.d.ts +7 -0
  123. package/lib/ui/constants.d.ts +5 -0
  124. package/lib/ui/index.d.ts +13 -0
  125. package/lib/ui/tools/createProvider.d.ts +5 -0
  126. package/lib/ui/tools/isLanguagePermitted.d.ts +1 -0
  127. package/lib/ui/tools/sleep.d.ts +1 -0
  128. package/package.json +91 -0
  129. package/src/BackendFetch.ts +64 -0
  130. package/src/BrowserExtensionPlugin/BrowserExtensionPlugin.ts +98 -0
  131. package/src/BrowserExtensionPlugin/constants.ts +3 -0
  132. package/src/BrowserExtensionPlugin/loadInContextLib.ts +32 -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/InContextTools.ts +20 -0
  137. package/src/InvisibleObserver.ts +16 -0
  138. package/src/LanguageDetector.test.ts +19 -0
  139. package/src/LanguageDetector.ts +32 -0
  140. package/src/LanguageStorage.ts +23 -0
  141. package/src/TextObserver.ts +45 -0
  142. package/src/WebTolgee.ts +8 -0
  143. package/src/__test__/browser.extension.test.ts +69 -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 +4 -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 +198 -0
  155. package/src/observers/general/NodeHandler.ts +39 -0
  156. package/src/observers/general/helpers.ts +65 -0
  157. package/src/observers/invisible/InvisibleWrapper.test.ts +17 -0
  158. package/src/observers/invisible/InvisibleWrapper.ts +96 -0
  159. package/src/observers/invisible/ValueMemory.test.ts +25 -0
  160. package/src/observers/invisible/ValueMemory.ts +20 -0
  161. package/src/observers/invisible/encoderPolyfill.ts +96 -0
  162. package/src/observers/invisible/secret.test.ts +61 -0
  163. package/src/observers/invisible/secret.ts +68 -0
  164. package/src/observers/text/TextWrapper.ts +258 -0
  165. package/src/observers/text/helpers.ts +56 -0
  166. package/src/tools/decodeApiKey.test.ts +14 -0
  167. package/src/tools/decodeApiKey.ts +74 -0
  168. package/src/tools/extension.test.ts +159 -0
  169. package/src/tools/extension.ts +117 -0
  170. package/src/typedIndex.ts +13 -0
  171. package/src/types.ts +33 -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 +88 -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 +9 -0
@@ -0,0 +1,117 @@
1
+ type Props = {
2
+ message: string;
3
+ recievingMessage: string[];
4
+ data?: any;
5
+ attempts?: number;
6
+ };
7
+
8
+ export function listen<T = any>(type: string[], callback: (data?: T) => any) {
9
+ const handler = (e: MessageEvent) => {
10
+ if (type.includes(e.data?.type)) {
11
+ callback(e.data?.data);
12
+ }
13
+ };
14
+ window.addEventListener('message', handler, false);
15
+ return {
16
+ unsubscribe: () => {
17
+ window.removeEventListener('message', handler);
18
+ },
19
+ };
20
+ }
21
+
22
+ export function sendAndRecieve<T>({
23
+ message,
24
+ recievingMessage,
25
+ data,
26
+ attempts = 1,
27
+ }: Props) {
28
+ let cancelled = false;
29
+ const makeAttempt = () =>
30
+ new Promise<T>((resolve, reject) => {
31
+ const listener = listen(recievingMessage, handler);
32
+ window.postMessage({ type: message, data }, window.origin);
33
+ const timer = setTimeout(timeout, 300);
34
+
35
+ function handler(data: any) {
36
+ clearTimeout(timer);
37
+ removeEventListener();
38
+ resolve(data);
39
+ }
40
+ function removeEventListener() {
41
+ listener.unsubscribe();
42
+ }
43
+ function timeout() {
44
+ removeEventListener();
45
+ reject();
46
+ }
47
+ });
48
+
49
+ return {
50
+ cancel: () => (cancelled = true),
51
+ promise: new Promise<T>(async (resolve, reject) => {
52
+ for (let i = 0; i < attempts; i++) {
53
+ if (cancelled) {
54
+ return;
55
+ }
56
+ try {
57
+ const result = await makeAttempt();
58
+ resolve(result);
59
+ return;
60
+ } catch (e) {
61
+ continue;
62
+ }
63
+ }
64
+ if (!cancelled) {
65
+ reject(`Didn't recieve ${recievingMessage.join(' or ')} in time.`);
66
+ }
67
+ }),
68
+ };
69
+ }
70
+
71
+ export function takeScreenshot(): Promise<string> {
72
+ return sendAndRecieve({
73
+ message: 'TOLGEE_TAKE_SCREENSHOT',
74
+ recievingMessage: ['TOLGEE_SCREENSHOT_TAKEN'],
75
+ }).promise as Promise<string>;
76
+ }
77
+
78
+ export async function detectExtension(): Promise<boolean> {
79
+ try {
80
+ await sendAndRecieve({
81
+ message: 'TOLGEE_PING',
82
+ recievingMessage: ['TOLGEE_PONG'],
83
+ attempts: 2,
84
+ }).promise;
85
+ return true;
86
+ } catch {
87
+ return false;
88
+ }
89
+ }
90
+
91
+ export type LibConfig = {
92
+ uiPresent: boolean;
93
+ uiVersion?: string;
94
+ mode: 'production' | 'development';
95
+ config: {
96
+ apiUrl: string;
97
+ apiKey: string;
98
+ };
99
+ };
100
+
101
+ export function Handshaker() {
102
+ let cancelLast: undefined | (() => void) = undefined;
103
+ async function update(data: LibConfig): Promise<boolean> {
104
+ cancelLast?.();
105
+ const { cancel, promise } = sendAndRecieve<boolean>({
106
+ message: 'TOLGEE_READY',
107
+ recievingMessage: ['TOLGEE_PLUGIN_READY', 'TOLGEE_PLUGIN_UPDATED'],
108
+ data,
109
+ attempts: 4,
110
+ });
111
+ cancelLast = cancel;
112
+ return promise;
113
+ }
114
+ return {
115
+ update,
116
+ };
117
+ }
@@ -0,0 +1,13 @@
1
+ export { InvisibleObserver } from './InvisibleObserver';
2
+ export { TextObserver } from './TextObserver';
3
+ export { DevBackend } from './DevBackend';
4
+ export { getProjectIdFromApiKey } from './tools/decodeApiKey';
5
+ export { BrowserExtensionPlugin } from './BrowserExtensionPlugin/BrowserExtensionPlugin';
6
+ export { LanguageStorage } from './LanguageStorage';
7
+ export { LanguageDetector } from './LanguageDetector';
8
+ export { BackendFetch } from './BackendFetch';
9
+
10
+ export * from './types';
11
+ export * from '@tolgee/core';
12
+ // Tolgee with injected browser extension plugin
13
+ export { Tolgee, TolgeeCore } from './WebTolgee';
package/src/types.ts ADDED
@@ -0,0 +1,33 @@
1
+ export type NodeLock = {
2
+ locked?: boolean;
3
+ };
4
+
5
+ export type TolgeeElement = Element &
6
+ ElementCSSInlineStyle & {
7
+ _tolgee?: boolean;
8
+ };
9
+
10
+ import type { BackendGetRecordProps, TreeTranslationsData } from '@tolgee/core';
11
+
12
+ export type BackendOptions = Omit<RequestInit, 'headers'> & {
13
+ /**
14
+ * Path prefix (default: '/i18n')
15
+ */
16
+ prefix: string;
17
+ /**
18
+ * Generate own path based on namespace, language and prefix
19
+ */
20
+ getPath: GetPath;
21
+ /**
22
+ * Custom headers
23
+ */
24
+ headers: Record<string, string>;
25
+ /**
26
+ * Extract data from fetch response (default: (r) => r.json())
27
+ */
28
+ getData: (r: Response) => Promise<TreeTranslationsData | undefined>;
29
+ };
30
+
31
+ export type GetPath = (
32
+ options: BackendGetRecordProps & { prefix: string }
33
+ ) => string;
@@ -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
+ };