@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,113 @@
1
+ import React, { useState } from 'react';
2
+ import Tooltip from '@mui/material/Tooltip';
3
+ import IconButton from '@mui/material/IconButton';
4
+ import { styled } from '@mui/material/styles';
5
+ import Clear from '@mui/icons-material/Clear';
6
+ import clsx from 'clsx';
7
+ import { ScreenshotInterface } from '../TranslationDialogContextProvider';
8
+ import { DEVTOOLS_Z_INDEX } from '../../constants';
9
+
10
+ const Screenshot = styled('img')`
11
+ width: 100%;
12
+ height: 100%;
13
+ object-fit: cover;
14
+ z-index: 1;
15
+ transition: transform 0.1s, filter 0.5s;
16
+ &:hover {
17
+ transform: scale(1.1);
18
+ }
19
+ `;
20
+
21
+ const ScreenshotBox = styled('div')`
22
+ border: 1px solid ${({ theme }) => theme.palette.grey[300]};
23
+ position: relative;
24
+ box-sizing: border-box;
25
+ width: 98px;
26
+ height: 98px;
27
+ align-items: center;
28
+ justify-content: center;
29
+ display: flex;
30
+ margin: 1px;
31
+ cursor: pointer;
32
+ overflow: visible;
33
+ `;
34
+
35
+ const ScreenshotOverflowWrapper = styled('div')`
36
+ overflow: hidden;
37
+ width: 100%;
38
+ height: 100%;
39
+ `;
40
+
41
+ const DeleteIconButton = styled(IconButton)`
42
+ position: absolute;
43
+ z-index: 2;
44
+ font-size: 20px;
45
+ right: -8px;
46
+ top: -8px;
47
+ padding: 2px;
48
+ background-color: rgba(62, 62, 62, 0.9);
49
+ color: rgba(255, 255, 255, 0.8);
50
+ visibility: hidden;
51
+ opacity: 0;
52
+ transition: visibility 0.1s linear, opacity 0.1s linear;
53
+ &:hover {
54
+ background-color: rgba(62, 62, 62, 1);
55
+ color: rgba(255, 255, 255, 0.9);
56
+ }
57
+ &.hover {
58
+ opacity: 1;
59
+ visibility: visible;
60
+ }
61
+ `;
62
+
63
+ const DeleteIcon = styled(Clear)`
64
+ font-size: 20px;
65
+ `;
66
+
67
+ export type Props = {
68
+ onClick: () => void;
69
+ onDelete?: (id: number) => void;
70
+ data: ScreenshotInterface;
71
+ };
72
+
73
+ export const ScreenshotThumbnail: React.FC<Props> = (props) => {
74
+ const [hover, setHover] = useState(false);
75
+
76
+ const onMouseOver = () => {
77
+ setHover(true);
78
+ };
79
+
80
+ const onMouseOut = () => {
81
+ setHover(false);
82
+ };
83
+
84
+ const onDeleteClick = () => {
85
+ props.onDelete?.(props.data.id);
86
+ };
87
+
88
+ return (
89
+ <ScreenshotBox onMouseOver={onMouseOver} onMouseOut={onMouseOut}>
90
+ {props.onDelete && (
91
+ <Tooltip
92
+ title="Delete"
93
+ PopperProps={{
94
+ disablePortal: true,
95
+ style: { zIndex: DEVTOOLS_Z_INDEX },
96
+ }}
97
+ >
98
+ <DeleteIconButton onClick={onDeleteClick} className={clsx({ hover })}>
99
+ <DeleteIcon />
100
+ </DeleteIconButton>
101
+ </Tooltip>
102
+ )}
103
+
104
+ <ScreenshotOverflowWrapper key={props.data.id} onClick={props.onClick}>
105
+ <Screenshot
106
+ onMouseDown={(e) => e.preventDefault()}
107
+ src={props.data.fileUrl}
108
+ aria-label="Screenshot"
109
+ />
110
+ </ScreenshotOverflowWrapper>
111
+ </ScreenshotBox>
112
+ );
113
+ };
@@ -0,0 +1,17 @@
1
+ export const MAX_FILE_COUNT = 20;
2
+
3
+ export const dataTransferItemsToArray = (
4
+ items: DataTransferItemList
5
+ ): File[] => {
6
+ const result = [] as any[];
7
+ for (let i = 0; i < items.length; i++) {
8
+ if (items[i].kind === 'file') {
9
+ result.push(items[i].getAsFile());
10
+ }
11
+ }
12
+ return result;
13
+ };
14
+
15
+ export function isAuthorizedTo(scope: string, scopes: string[] | undefined) {
16
+ return Boolean(scopes?.includes(scope));
17
+ }
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ import { TranslationDialogWrapper } from './TranslationDialogWrapper';
3
+ import { KeyForm } from './KeyForm';
4
+ import { ThemeProvider } from '../ThemeProvider';
5
+
6
+ export const TranslationDialog = () => {
7
+ return (
8
+ <ThemeProvider>
9
+ <TranslationDialogWrapper>
10
+ <KeyForm />
11
+ </TranslationDialogWrapper>
12
+ </ThemeProvider>
13
+ );
14
+ };
@@ -0,0 +1,464 @@
1
+ import { useEffect, useMemo, useState } from 'react';
2
+
3
+ import { sleep } from '../tools/sleep';
4
+ import { createProvider } from '../tools/createProvider';
5
+ import { isLanguagePermitted } from '../tools/isLanguagePermitted';
6
+ import { putBaseLangFirst, putBaseLangFirstTags } from './languageHelpers';
7
+ import { FallbackNSTranslation, getFallbackArray, UiProps } from '@tolgee/core';
8
+ import { useApiMutation, useApiQuery } from '../../ui/client/useQueryApi';
9
+ import { isAuthorizedTo } from './ScreenshotGallery/utils';
10
+ import { getInitialLanguages, setPreferredLanguages } from './tools';
11
+ import { detectExtension, takeScreenshot } from '../../tools/extension';
12
+
13
+ export interface ScreenshotInterface {
14
+ id: number;
15
+ filename: string;
16
+ fileUrl: string;
17
+ createdAt?: string;
18
+ // is it screenshot or only uploaded image
19
+ justUploaded: boolean;
20
+ }
21
+
22
+ type FormTranslations = {
23
+ [key: string]: string;
24
+ };
25
+
26
+ type DialogProps = {
27
+ keyName: string;
28
+ defaultValue: string;
29
+ open: boolean;
30
+ onClose: () => void;
31
+ uiProps: UiProps;
32
+ ns: FallbackNSTranslation;
33
+ };
34
+
35
+ type Actions =
36
+ | { type: 'ON_INPUT_CHANGE'; payload: { key: string; value: string } }
37
+ | {
38
+ type: 'ON_SELECTED_LANGUAGES_CHANGE';
39
+ payload: { languages: string[] };
40
+ }
41
+ | { type: 'HANDLE_UPLOAD_IMAGES'; payload: { files: File[] } }
42
+ | { type: 'HANDLE_TAKE_SCREENSHOT' }
43
+ | { type: 'HANDLE_REMOVE_SCREENSHOT'; payload: { id: number } }
44
+ | { type: 'ON_SAVE' }
45
+ | { type: 'ON_CLOSE' }
46
+ | { type: 'SET_USE_BROWSER_WINDOW'; payload: boolean }
47
+ | { type: 'SET_CONTAINER'; payload: Element | undefined }
48
+ | { type: 'OPEN_SCREENSHOT_DETAIL'; payload: ScreenshotInterface }
49
+ | { type: 'CLOSE_SCREENSHOT_DETAIL' }
50
+ | { type: 'ON_ESCAPE' }
51
+ | { type: 'SELECTED_NS_CHANGE'; payload: { ns: string } };
52
+
53
+ export const [DialogProvider, useDialogDispatch, useDialogContext] =
54
+ createProvider((props: DialogProps) => {
55
+ const [namespaces, setNamespaces] = useState<undefined | string[]>();
56
+ const [success, setSuccess] = useState<boolean>(false);
57
+ const [takingScreenshot, setTakingScreenshot] = useState(false);
58
+ const [translationsForm, setTranslationsForm] = useState<FormTranslations>(
59
+ {}
60
+ );
61
+ const [pluginAvailable, setPluginAvailable] = useState<boolean | undefined>(
62
+ undefined
63
+ );
64
+ const [translationsFormTouched, setTranslationsFormTouched] =
65
+ useState(false);
66
+ const [selectedNs, setSelectedNs] = useState<string>(
67
+ getFallbackArray(props.ns)[0]
68
+ );
69
+
70
+ const scopesLoadable = useApiQuery({
71
+ url: '/v2/api-keys/current',
72
+ method: 'get',
73
+ });
74
+
75
+ useEffect(() => {
76
+ detectExtension().then((available) => setPluginAvailable(available));
77
+ }, []);
78
+
79
+ const languagesLoadable = useApiQuery({
80
+ url: '/v2/projects/languages',
81
+ method: 'get',
82
+ options: {
83
+ onSuccess(data) {
84
+ setSelectedLanguages(
85
+ getInitialLanguages(
86
+ data._embedded?.languages?.map((l) => l.tag) || []
87
+ )
88
+ );
89
+ },
90
+ },
91
+ });
92
+
93
+ const availableLanguages = useMemo(() => {
94
+ return putBaseLangFirst(languagesLoadable.data?._embedded?.languages);
95
+ }, [languagesLoadable.data]);
96
+
97
+ const [selectedLanguages, setSelectedLanguages] = useState<string[]>([]);
98
+
99
+ const changeInCache = (values: [language: string, value: string][]) => {
100
+ const changers = values.map(([language, value]) =>
101
+ props.uiProps.changeTranslation(
102
+ {
103
+ language,
104
+ namespace: selectedNs,
105
+ },
106
+ props.keyName,
107
+ value
108
+ )
109
+ );
110
+ return { revert: () => changers.forEach((ch) => ch.revert()) };
111
+ };
112
+
113
+ const translationsLoadable = useApiQuery({
114
+ url: '/v2/projects/translations',
115
+ method: 'get',
116
+ query: {
117
+ filterKeyName: [props.keyName],
118
+ languages: selectedLanguages,
119
+ },
120
+ options: {
121
+ keepPreviousData: true,
122
+ enabled: Boolean(selectedLanguages.length),
123
+ onSuccess(data) {
124
+ const result: FormTranslations = {};
125
+ Object.entries(data._embedded?.keys?.[0].translations || {}).forEach(
126
+ ([key, value]) => {
127
+ result[key] = value.text || '';
128
+ }
129
+ );
130
+ setTranslationsForm(result);
131
+ setScreenshots(
132
+ data._embedded?.keys?.[0].screenshots?.map((sc) => ({
133
+ ...sc,
134
+ justUploaded: false,
135
+ })) || []
136
+ );
137
+ },
138
+ },
139
+ });
140
+
141
+ const uploadImage = useApiMutation({
142
+ url: '/v2/image-upload',
143
+ method: 'post',
144
+ options: {
145
+ onSuccess(data) {
146
+ setScreenshots((screenshots) => [
147
+ ...screenshots,
148
+ { ...data, justUploaded: true },
149
+ ]);
150
+ },
151
+ },
152
+ });
153
+
154
+ const deleteImage = useApiMutation({
155
+ url: '/v2/image-upload/{ids}',
156
+ method: 'delete',
157
+ });
158
+
159
+ const createKey = useApiMutation({
160
+ url: '/v2/projects/keys/create',
161
+ method: 'post',
162
+ });
163
+
164
+ const updateKey = useApiMutation({
165
+ url: '/v2/projects/keys/{id}/complex-update',
166
+ method: 'put',
167
+ });
168
+
169
+ const translations = translationsLoadable.data?._embedded?.keys?.[0];
170
+
171
+ const linkToPlatform =
172
+ scopesLoadable.data?.projectId !== undefined
173
+ ? `${props.uiProps.apiUrl}/projects/${scopesLoadable.data?.projectId}/translations/single?key=${props.keyName}`
174
+ : undefined;
175
+
176
+ const [container, setContainer] = useState(
177
+ undefined as Element | undefined
178
+ );
179
+ const [useBrowserWindow, setUseBrowserWindow] = useState(false);
180
+ const [screenshots, setScreenshots] = useState<ScreenshotInterface[]>([]);
181
+ const [screenshotDetail, setScreenshotDetail] =
182
+ useState<ScreenshotInterface | null>(null);
183
+
184
+ const permittedLanguageIds = scopesLoadable.data?.permittedLanguageIds;
185
+
186
+ const uploadScreenshot = (blob: Blob) =>
187
+ uploadImage.mutate({
188
+ content: { 'multipart/form-data': { image: blob as any } },
189
+ });
190
+
191
+ const dispatch = async (action: Actions) => {
192
+ switch (action.type) {
193
+ case 'ON_INPUT_CHANGE':
194
+ setSuccess(false);
195
+ setTranslationsFormTouched(true);
196
+ setTranslationsForm({
197
+ ...translationsForm,
198
+ [action.payload.key]: action.payload.value,
199
+ });
200
+ setSelectedNs(getFallbackArray(props.ns)[0]);
201
+ break;
202
+
203
+ case 'HANDLE_UPLOAD_IMAGES':
204
+ await Promise.all(
205
+ action.payload.files.map((content) => uploadScreenshot(content))
206
+ );
207
+ break;
208
+
209
+ case 'HANDLE_TAKE_SCREENSHOT': {
210
+ setTakingScreenshot(true);
211
+ const { unhighlight } = props.uiProps.highlight(
212
+ props.keyName,
213
+ selectedNs
214
+ );
215
+ const { revert } = changeInCache(Object.entries(translationsForm));
216
+ await sleep(100);
217
+ let screenshot: string;
218
+ try {
219
+ screenshot = await takeScreenshot();
220
+ } catch (e) {
221
+ // eslint-disable-next-line no-console
222
+ console.error(e);
223
+ break;
224
+ } finally {
225
+ revert();
226
+ unhighlight();
227
+ setTakingScreenshot(false);
228
+ }
229
+
230
+ const blob = await fetch(screenshot).then((r) => r.blob());
231
+
232
+ uploadScreenshot(blob);
233
+ break;
234
+ }
235
+
236
+ case 'HANDLE_REMOVE_SCREENSHOT': {
237
+ const { id } = action.payload;
238
+ const screenshot = screenshots.find((sc) => sc.id === id);
239
+ if (screenshot?.justUploaded) {
240
+ deleteImage.mutate({ path: { ids: [screenshot.id] } });
241
+ }
242
+ setScreenshots(screenshots.filter((sc) => sc.id !== id));
243
+ break;
244
+ }
245
+
246
+ case 'ON_SAVE': {
247
+ try {
248
+ const newTranslations = {} as typeof translationsForm;
249
+ Object.entries(translationsForm).forEach(([language, value]) => {
250
+ if (
251
+ isLanguagePermitted(
252
+ language,
253
+ permittedLanguageIds,
254
+ availableLanguages
255
+ )
256
+ ) {
257
+ newTranslations[language] = value;
258
+ }
259
+ });
260
+
261
+ if (translations === undefined) {
262
+ await createKey.mutateAsync({
263
+ content: {
264
+ 'application/json': {
265
+ name: props.keyName,
266
+ translations: newTranslations,
267
+ screenshotUploadedImageIds: screenshots.map((sc) => sc.id),
268
+ },
269
+ },
270
+ });
271
+ } else {
272
+ await updateKey.mutateAsync({
273
+ content: {
274
+ 'application/json': {
275
+ name: props.keyName,
276
+ translations: newTranslations,
277
+ screenshotIdsToDelete: getRemovedScreenshots(),
278
+ screenshotUploadedImageIds: getJustUploadedScreenshots(),
279
+ },
280
+ },
281
+ path: { id: translations.keyId },
282
+ });
283
+ }
284
+ changeInCache(Object.entries(newTranslations));
285
+ setNamespaces([selectedNs]);
286
+ translationsLoadable.refetch();
287
+ setSuccess(true);
288
+ if (useBrowserWindow) {
289
+ await sleep(2000);
290
+ setSuccess(false);
291
+ } else {
292
+ await sleep(400);
293
+ props.onClose();
294
+ }
295
+ } catch (e) {
296
+ // eslint-disable-next-line no-console
297
+ console.error(e);
298
+ } finally {
299
+ setSuccess(false);
300
+ }
301
+ break;
302
+ }
303
+
304
+ case 'ON_CLOSE': {
305
+ if (screenshotDetail) {
306
+ setScreenshotDetail(null);
307
+ } else {
308
+ props.onClose();
309
+ setUseBrowserWindow(false);
310
+ const uploadedScreenshots = getJustUploadedScreenshots();
311
+ if (uploadedScreenshots.length) {
312
+ deleteImage.mutate({ path: { ids: uploadedScreenshots } });
313
+ }
314
+ setScreenshots([]);
315
+ }
316
+ break;
317
+ }
318
+
319
+ case 'ON_SELECTED_LANGUAGES_CHANGE': {
320
+ const { languages } = action.payload;
321
+ if (languages.length) {
322
+ setSelectedLanguages(languages);
323
+ setPreferredLanguages(languages);
324
+ }
325
+ break;
326
+ }
327
+
328
+ case 'SET_CONTAINER':
329
+ setContainer(action.payload);
330
+ break;
331
+
332
+ case 'SET_USE_BROWSER_WINDOW':
333
+ setUseBrowserWindow(action.payload);
334
+ break;
335
+
336
+ case 'OPEN_SCREENSHOT_DETAIL':
337
+ setScreenshotDetail(action.payload);
338
+ break;
339
+
340
+ case 'CLOSE_SCREENSHOT_DETAIL':
341
+ setScreenshotDetail(null);
342
+ break;
343
+ case 'SELECTED_NS_CHANGE':
344
+ setSelectedNs(action.payload.ns);
345
+ break;
346
+ }
347
+ };
348
+
349
+ useEffect(() => {
350
+ const onKeyDown = (e: any) => {
351
+ if (e.key === 'Escape') {
352
+ dispatch({ type: 'ON_ESCAPE' });
353
+ }
354
+ };
355
+ if (!useBrowserWindow) {
356
+ window.addEventListener('keydown', onKeyDown);
357
+ return () => {
358
+ window.removeEventListener('keydown', onKeyDown);
359
+ };
360
+ }
361
+ }, [useBrowserWindow]);
362
+
363
+ const getJustUploadedScreenshots = () => {
364
+ return screenshots.filter((sc) => sc.justUploaded).map((sc) => sc.id);
365
+ };
366
+
367
+ const getRemovedScreenshots = () => {
368
+ return (
369
+ translations?.screenshots
370
+ ?.map((sc) => sc.id)
371
+ .filter((scId) => !screenshots.find((sc) => sc.id === scId)) || []
372
+ );
373
+ };
374
+
375
+ // sets the default value for base language if is not stored already
376
+ useEffect(() => {
377
+ if (
378
+ props.defaultValue &&
379
+ availableLanguages &&
380
+ selectedLanguages &&
381
+ translationsForm
382
+ ) {
383
+ const baseLanguageDefinition = availableLanguages.find((l) => l.base);
384
+ if (
385
+ baseLanguageDefinition &&
386
+ selectedLanguages.includes(baseLanguageDefinition.tag) &&
387
+ !translationsFormTouched
388
+ ) {
389
+ const wasBaseTranslationProvided =
390
+ translations?.translations[baseLanguageDefinition.tag] !==
391
+ undefined;
392
+
393
+ if (
394
+ !translationsForm[baseLanguageDefinition.tag] &&
395
+ !wasBaseTranslationProvided
396
+ ) {
397
+ setTranslationsForm({
398
+ ...translationsForm,
399
+ [baseLanguageDefinition.tag]: props.defaultValue,
400
+ });
401
+ }
402
+ }
403
+ }
404
+ }, [
405
+ availableLanguages,
406
+ translationsForm,
407
+ selectedLanguages,
408
+ props.defaultValue,
409
+ ]);
410
+
411
+ const baseLang = availableLanguages?.find(({ base }) => base);
412
+ const loading =
413
+ languagesLoadable.isFetching ||
414
+ (translationsLoadable.isLoading && !translationsLoadable.data) ||
415
+ scopesLoadable.isFetching;
416
+ const saving = updateKey.isLoading || createKey.isLoading;
417
+ const error =
418
+ languagesLoadable.error ||
419
+ translationsLoadable.error ||
420
+ scopesLoadable.error;
421
+
422
+ const screenshotsUploading = uploadImage.isLoading;
423
+
424
+ const scopes = scopesLoadable.data?.scopes;
425
+
426
+ const formDisabled =
427
+ loading ||
428
+ (translationsLoadable.data?._embedded?.keys?.length
429
+ ? !isAuthorizedTo('translations.edit', scopes)
430
+ : !isAuthorizedTo('keys.edit', scopes));
431
+
432
+ const keyExists = Boolean(
433
+ translationsLoadable.data?._embedded?.keys?.length
434
+ );
435
+
436
+ const contextValue = {
437
+ input: props.keyName,
438
+ open: props.open,
439
+ ns: namespaces || props.ns,
440
+ selectedNs,
441
+ loading,
442
+ saving,
443
+ success,
444
+ error,
445
+ availableLanguages,
446
+ selectedLanguages: putBaseLangFirstTags(selectedLanguages, baseLang?.tag),
447
+ formDisabled,
448
+ translations,
449
+ translationsForm,
450
+ container,
451
+ useBrowserWindow,
452
+ pluginAvailable,
453
+ takingScreenshot,
454
+ screenshotsUploading,
455
+ screenshots,
456
+ screenshotDetail,
457
+ linkToPlatform,
458
+ keyExists,
459
+ scopes,
460
+ permittedLanguageIds,
461
+ };
462
+
463
+ return [contextValue, dispatch];
464
+ });
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+ import Dialog from '@mui/material/Dialog';
3
+
4
+ import {
5
+ useDialogContext,
6
+ useDialogDispatch,
7
+ } from './TranslationDialogContextProvider';
8
+ import { NewWindow } from './NewWindow';
9
+ import { DEVTOOLS_Z_INDEX } from '../constants';
10
+
11
+ export const TranslationDialogWrapper: React.FC = ({ children }) => {
12
+ const dispatch = useDialogDispatch();
13
+ const useBrowserWindow = useDialogContext((c) => c.useBrowserWindow);
14
+ const open = useDialogContext((c) => c.open);
15
+ const takingScreenshot = useDialogContext((c) => c.takingScreenshot);
16
+
17
+ const onClose = () => {
18
+ dispatch({ type: 'ON_CLOSE' });
19
+ };
20
+
21
+ return (
22
+ <>
23
+ {useBrowserWindow ? (
24
+ <NewWindow>{children}</NewWindow>
25
+ ) : (
26
+ <Dialog
27
+ disableRestoreFocus
28
+ disablePortal
29
+ disableEnforceFocus
30
+ open={open}
31
+ onClose={onClose}
32
+ aria-labelledby="form-dialog-title"
33
+ maxWidth="lg"
34
+ style={{
35
+ zIndex: DEVTOOLS_Z_INDEX,
36
+ visibility: takingScreenshot ? 'hidden' : 'visible',
37
+ }}
38
+ >
39
+ <>{children}</>
40
+ </Dialog>
41
+ )}
42
+ </>
43
+ );
44
+ };