From a4bd3960556b5810698bc40d87c50176d5f12df0 Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Sat, 30 Aug 2025 17:36:15 +0900 Subject: [PATCH 01/27] add runtimeConfig to state tree --- .dockerignore | 4 ---- .gitignore | 1 + public/moeflow-runtime-config.sample.json | 6 ++++++ src/apis/index.ts | 2 +- src/configs.tsx | 8 ++++++-- src/index.tsx | 7 ++++++- src/store/site/slice.ts | 17 ++++++++++++----- 7 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 public/moeflow-runtime-config.sample.json diff --git a/.dockerignore b/.dockerignore index 18b416f..c6f6238 100644 --- a/.dockerignore +++ b/.dockerignore @@ -15,10 +15,6 @@ # misc **/.DS_Store -**/.env.local -**/.env.development.local -**/.env.test.local -**/.env.production.local **/npm-debug.log* **/yarn-debug.log* diff --git a/.gitignore b/.gitignore index d3c9431..dd07774 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ yarn-error.log* .vscode/* .idea stats.html +/public/moeflow-runtime-config.json diff --git a/public/moeflow-runtime-config.sample.json b/public/moeflow-runtime-config.sample.json new file mode 100644 index 0000000..7720883 --- /dev/null +++ b/public/moeflow-runtime-config.sample.json @@ -0,0 +1,6 @@ +{ + "comment": "Runtime configuration overrides. See src/configs.tsx", + "moeflowCompanion": { + "gradioUrl": "http://localhost:7860" + } +} diff --git a/src/apis/index.ts b/src/apis/index.ts index e0c1888..263941a 100644 --- a/src/apis/index.ts +++ b/src/apis/index.ts @@ -9,7 +9,7 @@ import axios, { import qs from 'qs'; import { createElement } from 'react'; import { Icon } from '../components'; -import { configs, runtimeConfig } from '@/configs'; +import { runtimeConfig } from '@/configs'; import { createDebugLogger } from '@/utils/debug-logger'; import { getIntl } from '@/locales'; import store from '../store'; diff --git a/src/configs.tsx b/src/configs.tsx index ee316ab..39a401b 100644 --- a/src/configs.tsx +++ b/src/configs.tsx @@ -1,8 +1,12 @@ import { lazyThenable } from '@jokester/ts-commonutil/lib/concurrency/lazy-thenable'; -interface RuntimeConfig { +export interface RuntimeConfig { // base URL for API requests baseURL: string; + + moeflowCompanion?: { + gradioUrl?: string; + }; } /** @@ -16,7 +20,7 @@ export const runtimeConfig = lazyThenable(async () => { const overriden: RuntimeConfig = await fetch('/moeflow-runtime-config.json') .then((res) => res.json()) .catch(() => null); - const merged = { + const merged: RuntimeConfig = { ...{ // defaults baseURL: process.env.REACT_APP_BASE_URL || '/api/', diff --git a/src/index.tsx b/src/index.tsx index 9a47988..9881c44 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -11,10 +11,11 @@ import App from './App'; import './fontAwesome'; // Font Awesome import './index.css'; import store from './store'; -import { setOSName, setPlatform } from './store/site/slice'; +import { setOSName, setPlatform, setRuntimeConfig } from './store/site/slice'; import { setUserToken } from './store/user/slice'; import { getToken } from './utils/cookie'; import { OSName, Platform } from './interfaces'; +import { runtimeConfig } from './configs'; import { getDefaultHotKey, hotKeyInitialState, @@ -22,6 +23,8 @@ import { setHotKey, } from './store/hotKey/slice'; import { loadHotKey } from './utils/storage'; +import { createDebugLogger } from './utils/debug-logger'; +const debugLogger = createDebugLogger('app'); // 时间插件 if (false && process.env.NODE_ENV === 'development') { @@ -37,6 +40,7 @@ const platform = browser.getPlatformType() as Platform; const osName = browser.getOSName(true) as OSName; store.dispatch(setPlatform(platform)); store.dispatch(setOSName(osName)); +store.dispatch(setRuntimeConfig(await runtimeConfig)); // 恢复自定义快捷键 for (const hotKeyName in hotKeyInitialState) { const name = hotKeyName as keyof HotKeyState; @@ -57,6 +61,7 @@ for (const hotKeyName in hotKeyInitialState) { async function mountApp() { const { intlMessages, locale, antdLocale, antdValidateMessages } = await initI18n; + debugLogger('initial state', store.getState()); /** * Set user token from cookie */ diff --git a/src/store/site/slice.ts b/src/store/site/slice.ts index 56bce4e..e7c4d34 100644 --- a/src/store/site/slice.ts +++ b/src/store/site/slice.ts @@ -1,18 +1,21 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { OSName, Platform } from '../../interfaces'; +import { OSName, Platform } from '@/interfaces'; +import { RuntimeConfig } from '@/configs'; export interface SiteState { - osName?: OSName; - platform?: Platform; + osName: OSName; + platform: Platform; newInvitationsCount: number; relatedApplicationsCount: number; + runtimeConfig: RuntimeConfig; } const initialState: SiteState = { - osName: 'windows', - platform: 'desktop', + osName: null!, + platform: null!, relatedApplicationsCount: 0, newInvitationsCount: 0, + runtimeConfig: null!, }; const slice = createSlice({ name: 'site', @@ -30,6 +33,9 @@ const slice = createSlice({ setNewInvitationsCount(state, action: PayloadAction) { state.newInvitationsCount = action.payload; }, + setRuntimeConfig(state, action: PayloadAction) { + state.runtimeConfig = action.payload; + }, }, }); @@ -38,5 +44,6 @@ export const { setOSName, setRelatedApplicationsCount, setNewInvitationsCount, + setRuntimeConfig, } = slice.actions; export default slice.reducer; From 4fcf8a4fd2b287157430d4cff18ddc5ba6da2b31 Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Sat, 30 Aug 2025 17:36:34 +0900 Subject: [PATCH 02/27] cleanup --- src/store/project/sagas.ts | 2 +- src/store/projectSet/sagas.ts | 4 ++-- src/store/user/sagas.ts | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/store/project/sagas.ts b/src/store/project/sagas.ts index 50e39e6..4a23dc3 100644 --- a/src/store/project/sagas.ts +++ b/src/store/project/sagas.ts @@ -34,7 +34,7 @@ function* setCurrentProjectWorker( configs: { cancelToken }, }); yield put(setCurrentProject(toLowerCamelCase(result.data))); - } catch (error) { + } catch (error: any) { error.default(); } finally { if (yield cancelled()) { diff --git a/src/store/projectSet/sagas.ts b/src/store/projectSet/sagas.ts index a97b3c5..3cbc420 100644 --- a/src/store/projectSet/sagas.ts +++ b/src/store/projectSet/sagas.ts @@ -16,7 +16,7 @@ function* setCurrentProjectSetWorker( ) { // 清空当前 projectSet yield put(clearCurrentProjectSet()); - const projectSets = yield select( + const projectSets: UserProjectSet[] = yield select( (state: AppState) => state.projectSet.projectSets, ); const projectSet = projectSets.find( @@ -35,7 +35,7 @@ function* setCurrentProjectSetWorker( configs: { cancelToken }, }); yield put(setCurrentProjectSet(toLowerCamelCase(result.data))); - } catch (error) { + } catch (error: any) { error.default(); } finally { if (yield cancelled()) { diff --git a/src/store/user/sagas.ts b/src/store/user/sagas.ts index 105a062..eeb85b8 100644 --- a/src/store/user/sagas.ts +++ b/src/store/user/sagas.ts @@ -10,6 +10,7 @@ import type { Axios } from 'axios'; function* getUserInfoAsync(action: ReturnType) { const token = action.payload.token; const instance: Axios = yield api.getAxiosInstance(); + // console.debug('instance', instance); if (token === '') { // 清除 Axios Authorization 头 delete instance.defaults.headers.common['Authorization']; From e399d85a1e45c9d2f31933d1475cdb8399f9137b Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Sat, 30 Aug 2025 18:16:04 +0900 Subject: [PATCH 03/27] remove tla --- src/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.tsx b/src/index.tsx index 9881c44..e7701dd 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -40,7 +40,6 @@ const platform = browser.getPlatformType() as Platform; const osName = browser.getOSName(true) as OSName; store.dispatch(setPlatform(platform)); store.dispatch(setOSName(osName)); -store.dispatch(setRuntimeConfig(await runtimeConfig)); // 恢复自定义快捷键 for (const hotKeyName in hotKeyInitialState) { const name = hotKeyName as keyof HotKeyState; @@ -59,6 +58,7 @@ for (const hotKeyName in hotKeyInitialState) { } async function mountApp() { + store.dispatch(setRuntimeConfig(await runtimeConfig)); const { intlMessages, locale, antdLocale, antdValidateMessages } = await initI18n; debugLogger('initial state', store.getState()); From f6f82fff7b1ffab29dd42cbb69773a7d2de942da Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Sat, 30 Aug 2025 21:09:03 +0900 Subject: [PATCH 04/27] move old service --- src/services/moeflow_companion/TranslateCompanion.tsx | 10 +++++----- .../moeflow_companion}/mit_preprocess.ts | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) rename src/{apis => services/moeflow_companion}/mit_preprocess.ts (96%) diff --git a/src/services/moeflow_companion/TranslateCompanion.tsx b/src/services/moeflow_companion/TranslateCompanion.tsx index 3371989..8aed7ba 100644 --- a/src/services/moeflow_companion/TranslateCompanion.tsx +++ b/src/services/moeflow_companion/TranslateCompanion.tsx @@ -7,7 +7,7 @@ import { createMoeflowProjectZip, LPFile } from '../labelplus_packager'; import { FailureResults } from '@/apis'; import { measureImgSize } from '@jokester/ts-commonutil/lib/web/measure-img'; import { clamp } from 'lodash-es'; -import { BBox, mitPreprocess, TextQuad } from '@/apis/mit_preprocess'; +import { BBox, mitPreprocess, TextQuad } from './mit_preprocess'; import { ResourcePool } from '@jokester/ts-commonutil/lib/concurrency/resource-pool'; const MAX_FILE_COUNT = 30; @@ -248,10 +248,10 @@ export const DemoOcrFiles: FC<{}> = (props) => { setWorking((s) => s?.nonce === initState.nonce ? { - ...s, - finished: Math.max(s.finished, finished), - numPages: total, - } + ...s, + finished: Math.max(s.finished, finished), + numPages: total, + } : s, ), ), diff --git a/src/apis/mit_preprocess.ts b/src/services/moeflow_companion/mit_preprocess.ts similarity index 96% rename from src/apis/mit_preprocess.ts rename to src/services/moeflow_companion/mit_preprocess.ts index 0013e15..f1574a5 100644 --- a/src/apis/mit_preprocess.ts +++ b/src/services/moeflow_companion/mit_preprocess.ts @@ -1,5 +1,5 @@ -import { request } from '.'; -import { uploadRequest } from './_request'; +import { request } from '../../apis'; +import { uploadRequest } from '../../apis/_request'; import { wait } from '@jokester/ts-commonutil/lib/concurrency/timing'; const mitApiPrefix = `/v1/mit`; From 8cdce06a713c6210131540c08854436f96a6119c Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Sat, 30 Aug 2025 21:09:28 +0900 Subject: [PATCH 05/27] init gradio client --- package-lock.json | 63 +------------------ package.json | 2 +- .../use_moeflow_companion.ts | 49 +++++++++++++++ 3 files changed, 52 insertions(+), 62 deletions(-) create mode 100644 src/services/moeflow_companion/use_moeflow_companion.ts diff --git a/package-lock.json b/package-lock.json index 9531b86..b778619 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@fortawesome/free-regular-svg-icons": "^5.15.4", "@fortawesome/free-solid-svg-icons": "^5.15.4", "@fortawesome/react-fontawesome": "^0.1.19", - "@gradio/client": "1.14.0", + "@gradio/client": "^1.14.0", "@jokester/ts-commonutil": "^0.6.1", "@reduxjs/toolkit": "^1.9.7", "@zip.js/zip.js": "^2.7.60", @@ -1472,6 +1472,7 @@ "version": "1.14.0", "resolved": "https://registry.npmjs.org/@gradio/client/-/client-1.14.0.tgz", "integrity": "sha512-BqM4D6RNCjInJG0OcGrAbik+DX4kRht2XrB7Mkd6bT5rtP3pUStZFRqb+mOuw+wvZvDfm4K0SngB/9X2i+RtTg==", + "license": "ISC", "dependencies": { "@types/eventsource": "^1.1.15", "bufferutil": "^4.0.7", @@ -7661,41 +7662,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - } - }, - "node_modules/jest-circus/node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/jest-circus/node_modules/dedent": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", @@ -7710,17 +7676,6 @@ } } }, - "node_modules/jest-circus/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/jest-cli": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", @@ -13595,20 +13550,6 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, - "node_modules/yaml": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", - "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", - "dev": true, - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index 079d09e..883e824 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "@fortawesome/free-regular-svg-icons": "^5.15.4", "@fortawesome/free-solid-svg-icons": "^5.15.4", "@fortawesome/react-fontawesome": "^0.1.19", - "@gradio/client": "1.14.0", + "@gradio/client": "^1.14.0", "@jokester/ts-commonutil": "^0.6.1", "@reduxjs/toolkit": "^1.9.7", "@zip.js/zip.js": "^2.7.60", diff --git a/src/services/moeflow_companion/use_moeflow_companion.ts b/src/services/moeflow_companion/use_moeflow_companion.ts new file mode 100644 index 0000000..50be972 --- /dev/null +++ b/src/services/moeflow_companion/use_moeflow_companion.ts @@ -0,0 +1,49 @@ +import { useState, useRef } from 'react'; +import { Client } from '@gradio/client'; +import { useAsyncEffect } from '@jokester/ts-commonutil/lib/react/hook/use-async-effect'; +import { useSelector } from 'react-redux'; +import { AppState } from '@/store'; +import { createDebugLogger } from '@/utils/debug-logger'; + +export const moeflowCompanionServiceState = { + disabled: 'disabled', + connecting: 'connecting', + connected: 'connected', + disconnected: 'disconnected', +} as const; + +const logger = createDebugLogger('service:moeflow_companion'); + +export function useMoeflowCompanion() { + const clientRef = useRef(null); + const [clientState, setClientState] = useState( + moeflowCompanionServiceState.disabled, + ); + const serviceConf = useSelector( + (s: AppState) => s.site.runtimeConfig.moeflowCompanion, + ); + + useAsyncEffect( + async (_, released) => { + if (!serviceConf?.gradioUrl) { + clientRef.current = null; + setClientState(moeflowCompanionServiceState.disabled); + return; + } + try { + const client = await Client.connect(serviceConf.gradioUrl); + clientRef.current = client; + setClientState(moeflowCompanionServiceState.connected); + released.then(() => client.close()); + } catch (e) { + logger('error connecting', e, serviceConf.gradioUrl); + clientRef.current = null; + setClientState(moeflowCompanionServiceState.disconnected); + } + }, + [serviceConf], + ); + return [clientState, clientRef.current] as const; +} + +export async function x(client: Client) {} From 0a36d0ce7f582b696cf8054e28c183cb3d74bf50 Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Sat, 30 Aug 2025 21:13:49 +0900 Subject: [PATCH 06/27] simplify FileList --- src/components/FileList.tsx | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/src/components/FileList.tsx b/src/components/FileList.tsx index d1dd6a0..0268e7e 100644 --- a/src/components/FileList.tsx +++ b/src/components/FileList.tsx @@ -3,14 +3,13 @@ import { Button as AntdButton, Drawer, message, Modal, Spin } from 'antd'; import { CancelToken } from 'axios'; import loadImage from 'blueimp-load-image'; import classNames from 'classnames'; -import React, { useEffect, useRef, useState } from 'react'; +import { useRef, useState } from 'react'; import { FilePond } from 'react-filepond'; import { useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; import { Button, EmptyTip, FileItem, List, OutputList } from '.'; import { api, resultTypes } from '../apis'; -import { runtimeConfig } from '@/configs'; import { FILE_NOT_EXIST_REASON, FILE_SAFE_STATUS, @@ -25,7 +24,7 @@ import { setFilesState } from '@/store/file/slice'; import style from '../style'; import { toLowerCamelCase } from '@/utils'; import { can } from '@/utils/user'; -import { usePromised } from '@jokester/ts-commonutil/lib/react/hook/use-promised'; +import { routes } from '@/pages/routes'; /** 文件列表的属性接口 */ interface FileListProps { @@ -49,10 +48,8 @@ export const FileList: FC = ({ const [loading, setLoading] = useState(true); const [listMode] = useState<'image' | 'text'>('image'); const [total, setTotal] = useState(0); // 元素总个数 - const runtimeConfigLoaded = usePromised(runtimeConfig); - const uploadAPI = - runtimeConfigLoaded.fulfilled && - `${runtimeConfigLoaded.value.baseURL}/v1/projects/${project.id}/files`; + const runtimeConfig = useSelector((state: AppState) => state.site.runtimeConfig); + const uploadAPI = `${runtimeConfig.baseURL}/v1/projects/${project.id}/files`; const token = useSelector((state: AppState) => state.user.token); const platform = useSelector((state: AppState) => state.site.platform); const isMobile = platform === 'mobile'; @@ -64,9 +61,6 @@ export const FileList: FC = ({ const [spinningIDs, setSpinningIDs] = useState([]); // 删除请求中 const filePondRef = useRef(); - const [team, setTeam] = useState(); - const currentTeam = useSelector((state: AppState) => state.team.currentTeam); - const defaultPage = useSelector( (state: AppState) => state.file.filesState.page, ); @@ -80,20 +74,8 @@ export const FileList: FC = ({ (state: AppState) => state.file.filesState.selectedFileIds, ); - useEffect(() => { - if (!currentTeam) { - api.project.getProject({ id: project.id }).then((result) => { - const data = toLowerCamelCase(result.data); - setTeam(data.team); - }); - } else { - setTeam(currentTeam); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [project.id]); - const toTranslator = (file: File) => { - history.push(`/image-translator/${file.id}-${target?.id}`); + history.push(routes.imageTranslator.build(file.id, target.id)); }; const deleteFile = (file: File) => { @@ -123,7 +105,7 @@ export const FileList: FC = ({ setSpinningIDs((ids) => ids.filter((id) => id !== file.id)); }); }, - onCancel: () => {}, + onCancel: () => { }, okText: formatMessage({ id: 'form.ok' }), cancelText: formatMessage({ id: 'form.cancel' }), }); From 507fa2174adaee3870a6873e43a02926c084614a Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Sat, 30 Aug 2025 23:35:29 +0900 Subject: [PATCH 07/27] config: add default model name --- public/moeflow-runtime-config.sample.json | 5 +++-- src/configs.tsx | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/public/moeflow-runtime-config.sample.json b/public/moeflow-runtime-config.sample.json index 7720883..58fcb94 100644 --- a/public/moeflow-runtime-config.sample.json +++ b/public/moeflow-runtime-config.sample.json @@ -1,6 +1,7 @@ { "comment": "Runtime configuration overrides. See src/configs.tsx", "moeflowCompanion": { - "gradioUrl": "http://localhost:7860" + "gradioUrl": "http://localhost:7860", + "defaultMultimodalModel": "gemini-2.0-flash-lite" } -} +} \ No newline at end of file diff --git a/src/configs.tsx b/src/configs.tsx index 39a401b..ccce88e 100644 --- a/src/configs.tsx +++ b/src/configs.tsx @@ -5,7 +5,8 @@ export interface RuntimeConfig { baseURL: string; moeflowCompanion?: { - gradioUrl?: string; + gradioUrl: string; + defaultMultimodalModel?: string }; } From 31930fe202157f6f0bf18b9cbe2e7960b3df350a Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Sat, 30 Aug 2025 23:35:41 +0900 Subject: [PATCH 08/27] format --- src/pages/Project.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Project.tsx b/src/pages/Project.tsx index e39c265..48a66c2 100644 --- a/src/pages/Project.tsx +++ b/src/pages/Project.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Route, Switch, useParams, useRouteMatch } from 'react-router-dom'; import { useTitle } from '@/hooks'; @@ -13,7 +13,7 @@ import ProjectPreview from './ProjectPreview'; import ProjectSetting from './ProjectSetting'; /** 项目路由的属性接口 */ -interface ProjectProps {} +interface ProjectProps { } /** * 项目路由 */ From d4c89d9bdd8cb44bb429c9d510322139cc94b03e Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Sat, 30 Aug 2025 23:36:03 +0900 Subject: [PATCH 09/27] format code --- src/components/project/ProjectTargetList.tsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/components/project/ProjectTargetList.tsx b/src/components/project/ProjectTargetList.tsx index 0fa37ba..86707ea 100644 --- a/src/components/project/ProjectTargetList.tsx +++ b/src/components/project/ProjectTargetList.tsx @@ -1,10 +1,10 @@ import { css } from '@emotion/core'; import { CancelToken } from 'axios'; import classNames from 'classnames'; -import React, { useState } from 'react'; +import { useState } from 'react'; import { useIntl } from 'react-intl'; import { useSelector } from 'react-redux'; -import api, { resultTypes } from '../../apis'; +import { api, resultTypes } from '../../apis'; import { EmptyTip, List, @@ -38,7 +38,7 @@ export const ProjectTargetList: FC = ({ const isMobile = platform === 'mobile'; const [loading, setLoading] = useState(false); const [total, setTotal] = useState(0); // 元素总个数 - const [items, setItems] = useState([]); // 元素 + const [targetLangs, setTargetLangs] = useState([]); // 元素 /** 获取元素 */ const handleChange = ({ @@ -53,7 +53,7 @@ export const ProjectTargetList: FC = ({ cancelToken: CancelToken; }) => { setLoading(true); - api + api.target .getProjectTargets({ projectID: project.id, params: { @@ -71,7 +71,7 @@ export const ProjectTargetList: FC = ({ setLoading(false); // 转成大写 const items = result.data.map((item: any) => toLowerCamelCase(item)); - setItems(items); + setTargetLangs(items); onLoad && onLoad(items); }) .catch((error) => { @@ -95,7 +95,7 @@ export const ProjectTargetList: FC = ({ searchInputVisible={false} loading={loading} total={total} - items={items} + items={targetLangs} itemHeight={LIST_ITEM_DEFAULT_HEIGHT} itemCreater={(item) => { return ( @@ -123,9 +123,7 @@ export const ProjectTargetList: FC = ({ columnWidth={250} autoPageSize={false} defaultPageSize={100000} - emptyTipCreater={() => { - return ; - }} + emptyTipCreater={() => } /> ); }; From 492b055ce26f5d528874b747e326419a0d575972 Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Sat, 30 Aug 2025 23:36:32 +0900 Subject: [PATCH 10/27] simplify ProjectFiles --- src/pages/ProjectFiles.tsx | 169 +++++++++++++++++++------------------ 1 file changed, 89 insertions(+), 80 deletions(-) diff --git a/src/pages/ProjectFiles.tsx b/src/pages/ProjectFiles.tsx index 198e9c9..c3807fe 100644 --- a/src/pages/ProjectFiles.tsx +++ b/src/pages/ProjectFiles.tsx @@ -1,6 +1,6 @@ import { css } from '@emotion/core'; import { message, Spin } from 'antd'; -import React, { useEffect, useState } from 'react'; +import { useEffect, useState, cloneElement } from 'react'; import { useIntl } from 'react-intl'; import { useSelector } from 'react-redux'; import { FileList, Icon, ListItem } from '@/components'; @@ -45,7 +45,23 @@ const ProjectFiles: FC = ({ project }) => { return ; } - return project ? ( + if (!project) { + return ( +
+ +
+ ); + } + + const wrapper =
= ({ project }) => { } } `} - > - {!currentTarget ? ( - <> - {targets && ( - - ) - } - name={formatMessage({ id: 'project.selectTarget' })} - /> - )} - { + />; + + if (!currentTarget) { + // target selector + return cloneElement(wrapper, undefined, + targets && ( + + ) + } + name={formatMessage({ id: 'project.selectTarget' })} + /> + ), + { + setCurrentTarget(target); + saveDefaultTargetID({ + projectID: project.id, + targetID: target.id, + }); + }} + onLoad={(targets) => { + setTargets(targets); + // 只有一个时候,直接选中 + if (targets.length === 1) { + setCurrentTarget(targets[0]); + } + // 自动选中默认的 + const defaultTargetID = loadDefaultTargetID({ + projectID: project.id, + }); + if (defaultTargetID) { + const target = targets.find( + (target) => target.id === defaultTargetID, + ); + if (target) { setCurrentTarget(target); - saveDefaultTargetID({ - projectID: project.id, - targetID: target.id, - }); - }} - onLoad={(targets) => { - setTargets(targets); - // 只有一个时候,直接选中 - if (targets.length === 1) { - setCurrentTarget(targets[0]); - } - // 自动选中默认的 - const defaultTargetID = loadDefaultTargetID({ - projectID: project.id, - }); - if (defaultTargetID) { - const target = targets.find( - (target) => target.id === defaultTargetID, - ); - if (target) { - setCurrentTarget(target); - } - } - }} - /> - - ) : project.importFromLabelplusStatus === - IMPORT_FROM_LABELPLUS_STATUS.SUCCEEDED ? ( - { - if (targets.length > 1) { - setCurrentTarget(undefined); - clearDefaultTargetID({ projectID: project.id }); - } else { - message.info( - formatMessage({ id: 'project.onlyOneTargetTip' }), - 1, - ); } - }} - /> - ) : ( - - )} -
- ) : ( - + ) + } + + if (project.importFromLabelplusStatus !== IMPORT_FROM_LABELPLUS_STATUS.SUCCEEDED) { + return cloneElement(wrapper, undefined, ) + } + + return cloneElement(wrapper, undefined, + { + if (targets.length > 1) { + setCurrentTarget(undefined); + clearDefaultTargetID({ projectID: project.id }); + } else { + message.info( + formatMessage({ id: 'project.onlyOneTargetTip' }), + 1, + ); + } + }} /> - ); + ) }; export default ProjectFiles; From 74e40025df451765bc57fc23eadc2f11a626629b Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Sat, 30 Aug 2025 23:45:10 +0900 Subject: [PATCH 11/27] locale --- src/locales/en.json | 14 +++++++------- src/locales/messages.yaml | 16 ++++++++-------- src/locales/zh-cn.json | 6 +++--- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/locales/en.json b/src/locales/en.json index 2930c36..6511566 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -149,7 +149,7 @@ "projectSet.emptySearchTip": "No Project set contain \"{word}\". Please try a different search term.", "projectSet.default": "Ungrouped", "site.projectSet": "Project sets", - "site.createProjectSet": "Create project set", + "projectSet.createProjectSet": "Create project set", "projectSet.name": "Project set name", "projectSet.info": "Projection set Information", "projectSet.intro": "Project set description", @@ -166,8 +166,8 @@ "project.finish": "Mark Project as complete", "project.name": "Project Name", "project.intro": "Project Description", - "site.createProject": "Create Project", - "site.importProject": "Import Project", + "project.createProject": "Create Project", + "project.importProject": "project Project", "project.startImport": "Start Import", "project.finishTip": "This action will delete all files and images for this item.", "project.deleteTip": "\"Delete this item permanently after one day, no information will be retained. It is recommended to use the completion function. (You can cancel this operation before it is deleted)\"", @@ -321,13 +321,13 @@ "output.outputPartial": "Export {count} selected files", "output.outputPartialExclude": "Export all except the selected {count} files", "output.invert": "Invert selection", - "project.createViaLabelplus": "Import from LabelPlus txt file:", + "project.createViaLabelplus": "Import marks from LabelPlus txt:", "site.delete": "Delete", "project.createViaLabelplusNotSupport": "Unavailable for project of multiple target languages", "file.needUploadTip": "Pending Upload", - "site.selectPageAll": "Select All on This Page", - "site.selectPageInverse": "Deselect This Page", - "site.selectCancel": "Deselect", + "site.selectPageAll": "Select All", + "site.selectPageInverse": "Invert selection", + "site.selectCancel": "Deselect All", "site.selectFile": "Select File", "imageTranslator.translatorMode": "Translations", "imageTranslator.godMode": "Overview", diff --git a/src/locales/messages.yaml b/src/locales/messages.yaml index b4fa568..2c4eb02 100644 --- a/src/locales/messages.yaml +++ b/src/locales/messages.yaml @@ -455,7 +455,7 @@ projectSet.default: site.projectSet: zhCn: 项目集 en: Project sets -site.createProjectSet: +projectSet.createProjectSet: zhCn: 创建项目集 en: Create project set projectSet.name: @@ -506,12 +506,12 @@ project.name: project.intro: zhCn: 项目介绍 en: Project Description -site.createProject: +project.createProject: zhCn: 创建项目 en: Create Project -site.importProject: +project.importProject: zhCn: 导入项目 - en: Import Project + en: project Project project.startImport: zhCn: 开始导入 en: Start Import @@ -982,7 +982,7 @@ output.invert: en: Invert selection project.createViaLabelplus: zhCn: 通过 LabalPlus “翻译数据.txt”创建: - en: 'Import from LabelPlus txt file:' + en: 'Import marks from LabelPlus txt:' site.delete: zhCn: 删除 en: Delete @@ -994,13 +994,13 @@ file.needUploadTip: en: Pending Upload site.selectPageAll: zhCn: 全选本页 - en: Select All on This Page + en: Select All site.selectPageInverse: zhCn: 反选本页 - en: Deselect This Page + en: Invert selection site.selectCancel: zhCn: 取消选择 - en: Deselect + en: Deselect All site.selectFile: zhCn: 选择文件 en: Select File diff --git a/src/locales/zh-cn.json b/src/locales/zh-cn.json index 65bfd69..8674080 100644 --- a/src/locales/zh-cn.json +++ b/src/locales/zh-cn.json @@ -149,7 +149,7 @@ "projectSet.emptySearchTip": "没有名称含有 “{word}” 的项目集,请换个搜索词试试。", "projectSet.default": "未分组", "site.projectSet": "项目集", - "site.createProjectSet": "创建项目集", + "projectSet.createProjectSet": "创建项目集", "projectSet.name": "项目集名称", "projectSet.info": "项目集信息", "projectSet.intro": "项目集介绍", @@ -166,8 +166,8 @@ "project.finish": "完结项目", "project.name": "项目名称", "project.intro": "项目介绍", - "site.createProject": "创建项目", - "site.importProject": "导入项目", + "project.createProject": "创建项目", + "project.importProject": "导入项目", "project.startImport": "开始导入", "project.finishTip": "此操作将删除此项目的所有文件和图片。", "project.deleteTip": "一天后彻底删除此项目,不会留存任何信息,推荐使用完结功能。(您可以在正式删除前取消此操作)", From d0e7ab9a149bd15de0f14e6262abf5cd8846fc9a Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Sat, 30 Aug 2025 23:45:40 +0900 Subject: [PATCH 12/27] apply locale --- src/components/project-set/ProjectCreateForm.tsx | 12 +++++------- src/pages/Team.tsx | 6 +++--- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/components/project-set/ProjectCreateForm.tsx b/src/components/project-set/ProjectCreateForm.tsx index d022bee..3bf37ee 100644 --- a/src/components/project-set/ProjectCreateForm.tsx +++ b/src/components/project-set/ProjectCreateForm.tsx @@ -9,7 +9,7 @@ import { TypeRadioGroup, LanguageSelect, } from '..'; -import api from '@/apis'; +import { api } from '@/apis'; import { FC, UserProjectSet, UserTeam } from '@/interfaces'; import { useDispatch, useSelector } from 'react-redux'; import { createProject, resetProjectsState } from '@/store/project/slice'; @@ -17,10 +17,9 @@ import { useHistory } from 'react-router-dom'; import { AppState } from '@/store'; import { toLowerCamelCase } from '@/utils'; import { GROUP_ALLOW_APPLY_TYPE } from '@/constants'; -import { configs, runtimeConfig } from '@/configs'; +import { configs, } from '@/configs'; import style from '../../style'; import { resetFilesState } from '@/store/file/slice'; -import { usePromised } from '@jokester/ts-commonutil/lib/react/hook/use-promised'; /** 创建项目表单的属性接口 */ interface ProjectCreateFormProps { @@ -36,7 +35,6 @@ export const ProjectCreateForm: FC = ({ projectSetID, className, }) => { - const runtimeConfigLoaded = usePromised(runtimeConfig); const { formatMessage } = useIntl(); // i18n const [form] = AntdForm.useForm(); const dispatch = useDispatch(); @@ -76,7 +74,7 @@ export const ProjectCreateForm: FC = ({ const handleFinish = (values: any) => { setSubmitting(true); - api + api.project .createProject({ teamID: currentTeam.id, data: { ...values, labelplusTXT }, @@ -142,7 +140,7 @@ export const ProjectCreateForm: FC = ({ sourceLanguage: configs.default.project.sourceLanugageCode, targetLanguages: configs.default.project.targetLanguageCodes, }} - hideRequiredMark + requiredMark={false} onValuesChange={(values) => { // 关闭加入时,隐藏加入选项 if (values.allowApplyType) { @@ -158,7 +156,7 @@ export const ProjectCreateForm: FC = ({ if (values.targetLanguages) { setDisableSourceLanugageIDs(values.targetLanguages); } - // 当幕布语言大于 1 个的时候,不显示从“翻译数据.txt”导入 + // 当目标语言大于 1 个的时候,不显示从“翻译数据.txt”导入 if (values.targetLanguages && values.targetLanguages.length > 1) { setSupportLabelplusTXT(false); setLabelplusTXT(undefined); diff --git a/src/pages/Team.tsx b/src/pages/Team.tsx index 06171cd..f4ed8a1 100644 --- a/src/pages/Team.tsx +++ b/src/pages/Team.tsx @@ -32,7 +32,7 @@ import ProjectSet from './ProjectSet'; import ProjectSetSetting from './ProjectSetSetting'; /** 团队页的属性接口 */ -interface TeamProps {} +interface TeamProps { } /** * 团队页 */ @@ -57,7 +57,7 @@ const Team: FC = () => { const rightButton = (
@@ -166,7 +166,7 @@ const Team: FC = () => { `} > - {formatMessage({ id: 'site.createProjectSet' })} + {formatMessage({ id: 'projectSet.createProjectSet' })} From 4517128d7aac7527ace30dc35422cccb6a8e1848 Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Sat, 30 Aug 2025 23:45:59 +0900 Subject: [PATCH 13/27] apply locale --- src/pages/ProjectSet.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/ProjectSet.tsx b/src/pages/ProjectSet.tsx index bce971b..5d367cc 100644 --- a/src/pages/ProjectSet.tsx +++ b/src/pages/ProjectSet.tsx @@ -32,7 +32,7 @@ import { ProjectCreateForm } from '@/components/project-set/ProjectCreateForm'; import { ProjectImportForm } from '@/components/project-set/ProjectImportForm'; /** 项目集页的属性接口 */ -interface ProjectSetProps {} +interface ProjectSetProps { } /** * 项目集页 */ @@ -60,7 +60,7 @@ const ProjectSet: FC = () => { const rightButton = (
@@ -172,7 +172,7 @@ const ProjectSet: FC = () => { `} > - {formatMessage({ id: 'site.createProject' })} + {formatMessage({ id: 'project.createProject' })} Date: Sat, 30 Aug 2025 23:56:58 +0900 Subject: [PATCH 14/27] locale --- src/components/project-set/ProjectCreateForm.tsx | 1 + src/locales/en.json | 4 ++-- src/locales/messages.yaml | 6 +++--- src/locales/zh-cn.json | 2 +- src/pages/ProjectSet.tsx | 4 ++-- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/components/project-set/ProjectCreateForm.tsx b/src/components/project-set/ProjectCreateForm.tsx index 3bf37ee..6ca799a 100644 --- a/src/components/project-set/ProjectCreateForm.tsx +++ b/src/components/project-set/ProjectCreateForm.tsx @@ -112,6 +112,7 @@ export const ProjectCreateForm: FC = ({ margin-bottom: 0; } .ProjectCreateForm__Label { + margin-right: 8px; color: rgba(0, 0, 0, 0.85); } .ProjectCreateForm__Tip { diff --git a/src/locales/en.json b/src/locales/en.json index 6511566..dde98a5 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -167,7 +167,7 @@ "project.name": "Project Name", "project.intro": "Project Description", "project.createProject": "Create Project", - "project.importProject": "project Project", + "project.importProject": "Import Project", "project.startImport": "Start Import", "project.finishTip": "This action will delete all files and images for this item.", "project.deleteTip": "\"Delete this item permanently after one day, no information will be retained. It is recommended to use the completion function. (You can cancel this operation before it is deleted)\"", @@ -321,7 +321,7 @@ "output.outputPartial": "Export {count} selected files", "output.outputPartialExclude": "Export all except the selected {count} files", "output.invert": "Invert selection", - "project.createViaLabelplus": "Import marks from LabelPlus txt:", + "project.createViaLabelplus": "(Optional) Import marks from LabelPlus txt:", "site.delete": "Delete", "project.createViaLabelplusNotSupport": "Unavailable for project of multiple target languages", "file.needUploadTip": "Pending Upload", diff --git a/src/locales/messages.yaml b/src/locales/messages.yaml index 2c4eb02..5104589 100644 --- a/src/locales/messages.yaml +++ b/src/locales/messages.yaml @@ -511,7 +511,7 @@ project.createProject: en: Create Project project.importProject: zhCn: 导入项目 - en: project Project + en: Import Project project.startImport: zhCn: 开始导入 en: Start Import @@ -981,8 +981,8 @@ output.invert: zhCn: 反选 en: Invert selection project.createViaLabelplus: - zhCn: 通过 LabalPlus “翻译数据.txt”创建: - en: 'Import marks from LabelPlus txt:' + zhCn: (可选) 通过 LabalPlus “翻译数据.txt”创建: + en: '(Optional) Import marks from LabelPlus txt:' site.delete: zhCn: 删除 en: Delete diff --git a/src/locales/zh-cn.json b/src/locales/zh-cn.json index 8674080..aa75ffd 100644 --- a/src/locales/zh-cn.json +++ b/src/locales/zh-cn.json @@ -321,7 +321,7 @@ "output.outputPartial": "导出已选中的 {count} 个文件", "output.outputPartialExclude": "导出除了已选中的 {count} 个文件", "output.invert": "反选", - "project.createViaLabelplus": "通过 LabalPlus “翻译数据.txt”创建:", + "project.createViaLabelplus": "(可选) 通过 LabalPlus “翻译数据.txt”创建:", "site.delete": "删除", "project.createViaLabelplusNotSupport": "多个目标语言时不支持", "file.needUploadTip": "待上传", diff --git a/src/pages/ProjectSet.tsx b/src/pages/ProjectSet.tsx index 5d367cc..c4475b3 100644 --- a/src/pages/ProjectSet.tsx +++ b/src/pages/ProjectSet.tsx @@ -1,5 +1,5 @@ import { css } from '@emotion/core'; -import React, { useEffect } from 'react'; +import { useEffect } from 'react'; import { useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; import { @@ -187,7 +187,7 @@ const ProjectSet: FC = () => { border-top: 1px solid ${style.borderColorBase}; `} > - {formatMessage({ id: 'site.importProject' })} + {formatMessage({ id: 'project.importProject' })} Date: Sun, 31 Aug 2025 00:00:55 +0900 Subject: [PATCH 15/27] add Makefile --- Makefile | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a0f88ad --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +locale-json: src/locales/en.json src/locales/zh-cn.json + +locale-json-watch: + watch make locale-json + +src/locales/en.json: src/locales/messages.yaml + node_modules/.bin/tsx scripts/generate-locale-json.ts + +src/locales/zh-cn.json: src/locales/messages.yaml + node_modules/.bin/tsx scripts/generate-locale-json.ts + From f6285fa23587f229c62da51d013346f3312cad49 Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Sun, 31 Aug 2025 00:36:11 +0900 Subject: [PATCH 16/27] extract smaller hooks --- src/pages/ImageTranslator.tsx | 292 ++++++++++++++++++---------------- 1 file changed, 151 insertions(+), 141 deletions(-) diff --git a/src/pages/ImageTranslator.tsx b/src/pages/ImageTranslator.tsx index fd507c2..bbb5833 100644 --- a/src/pages/ImageTranslator.tsx +++ b/src/pages/ImageTranslator.tsx @@ -1,13 +1,13 @@ import { css, Global } from '@emotion/core'; import { Modal } from 'antd'; -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; import { api } from '@/apis'; import { useHotKey } from '@/components'; import { ImageViewer, ImageSourceViewer } from '@/components/project-file'; -import { FC } from '@/interfaces'; +import { FC, Source } from '@/interfaces'; import { AppState } from '@/store'; import { setCurrentProjectSaga } from '@/store/project/slice'; import { fetchSourcesSaga, focusSource } from '@/store/source/slice'; @@ -15,20 +15,20 @@ import style from '../style'; import { toLowerCamelCase } from '@/utils'; import { getCancelToken } from '@/utils/api'; import { useTitle } from '@/hooks'; -import { ImageTranslatorSettingMouse } from '@/components/project-file/ImageTranslatorSettingMouse'; -import { ImageTranslatorSettingHotKey } from '@/components/project-file/ImageTranslatorSettingHotKey'; +import { ImageTranslatorSettingMouse } from '@/components/project-file'; +import { ImageTranslatorSettingHotKey } from '@/components/project-file'; import { GetFileReturn } from '@/apis/file'; /** * 全屏显示的图片翻译器 */ const ImageTranslator: FC = () => { + const dispatch = useDispatch(); const { formatMessage } = useIntl(); const { fileID, targetID } = useParams<{ fileID: string; targetID: string; }>(); - const dispatch = useDispatch(); const sources = useSelector((state: AppState) => state.source.sources); const sourcesLoading = useSelector((state: AppState) => state.source.loading); const focusedSourceID = useSelector( @@ -48,144 +48,13 @@ const ImageTranslator: FC = () => { useTitle({ prefix: file?.name }, [file?.name]); // 设置标题 - const focusNextSource = () => { - if (sources.length === 0) { - return; - } - let nextFocusedSourceIndex = 0; - if (focusedSourceID) { - const focusedSourceIndex = sources.findIndex( - (source) => source.id === focusedSourceID, - ); - if (focusedSourceIndex + 1 >= sources.length) { - nextFocusedSourceIndex = 0; - } else { - nextFocusedSourceIndex = focusedSourceIndex + 1; - } - } - const nextFocusedSourceID = sources[nextFocusedSourceIndex].id; - dispatch( - focusSource({ - id: nextFocusedSourceID, - effects: ['focusInput', 'focusLabel', 'scrollIntoView'], - noises: ['focusInput', 'focusLabel'], - }), - ); - }; - - const focusPrevSource = () => { - if (sources.length === 0) { - return; - } - let prevFocusedSourceIndex = sources.length - 1; - if (focusedSourceID) { - const focusedSourceIndex = sources.findIndex( - (source) => source.id === focusedSourceID, - ); - if (focusedSourceIndex - 1 < 0) { - prevFocusedSourceIndex = sources.length - 1; - } else { - prevFocusedSourceIndex = focusedSourceIndex - 1; - } - } - const prevFocusedSourceID = sources[prevFocusedSourceIndex].id; - dispatch( - focusSource({ - id: prevFocusedSourceID, - effects: ['focusInput', 'focusLabel', 'scrollIntoView'], - noises: ['focusInput', 'focusLabel'], - }), - ); - }; - - // 快捷键 - 当 ImageViewer 未加载完成是,忽略所有快捷键 - useHotKey( - { - disabled: Boolean(file?.id), - ignoreKeyboardElement: false, - }, - () => {}, - [file?.id], - ); - - // 快捷键 - 下一个输入框 - const focusNextSourceHotKeyOptions = useSelector( - (state: AppState) => state.hotKey.focusNextSource, - ); - useHotKey( - { - disabled: !Boolean(focusNextSourceHotKeyOptions[0]), - ...focusNextSourceHotKeyOptions[0], - }, - focusNextSource, - [focusedSourceID, sources.length], - ); - useHotKey( - { - disabled: !Boolean(focusNextSourceHotKeyOptions[1]), - ...focusNextSourceHotKeyOptions[1], - }, - focusNextSource, - [focusedSourceID, sources.length], - ); - - // 快捷键 - 上一个输入框 - const focusPrevSourceHotKeyOptions = useSelector( - (state: AppState) => state.hotKey.focusPrevSource, - ); - useHotKey( - { - disabled: !Boolean(focusPrevSourceHotKeyOptions[0]), - ...focusPrevSourceHotKeyOptions[0], - }, - focusPrevSource, - [focusedSourceID, sources.length], - ); - useHotKey( - { - disabled: !Boolean(focusPrevSourceHotKeyOptions[1]), - ...focusPrevSourceHotKeyOptions[1], - }, - focusPrevSource, - [focusedSourceID, sources.length], - ); - - // 页面尺寸 - const [windowSize, setWindowSize] = useState({ - width: 0, - height: 0, - }); - - useEffect(() => { - const handleResize = () => { - const width = window.innerWidth; - const height = window.innerHeight; - setWindowSize({ width, height }); - }; - handleResize(); - window.addEventListener('resize', handleResize); - const setTimeoutHandleResize = () => { - setTimeout(handleResize, 250); - }; - if (isIOS) { - window.addEventListener('focusin', setTimeoutHandleResize); - window.addEventListener('focusout', setTimeoutHandleResize); - } - return () => { - window.removeEventListener('resize', handleResize); - if (isIOS) { - window.removeEventListener('focusin', setTimeoutHandleResize); - window.removeEventListener('focusout', setTimeoutHandleResize); - } - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - + useImageTranslatorHotkeys(file, sources, focusedSourceID); // 翻译器尺寸 const [imageTranslatorSize, setImageTranslatorSize] = useState({ width: 0, height: 0, }); + const windowSize = useWindowSize(); useEffect(() => { setImageTranslatorSize({ @@ -234,7 +103,7 @@ const ImageTranslator: FC = () => { .ImageTranslator__ImageViewer { z-index: 1; ${isMobile && - css` + css` position: absolute; bottom: ${sourceListHeightMobile}px; left: 0; @@ -246,7 +115,7 @@ const ImageTranslator: FC = () => { box-shadow: ${style.boxShadowBase}; overflow: hidden; ${isMobile - ? css` + ? css` bottom: 0; left: 0; height: ${sourceListHeightMobile}px; @@ -254,7 +123,7 @@ const ImageTranslator: FC = () => { border-radius: ${style.borderRadiusBase} ${style.borderRadiusBase} 0 0; ` - : css` + : css` top: 0; right: 0; height: 100%; @@ -322,4 +191,145 @@ const ImageTranslator: FC = () => {
); }; + +function useImageTranslatorHotkeys(file: GetFileReturn | undefined, sources: Source[], focusedSourceID: string | null) { + const dispatch = useDispatch(); + const focusNextSource = () => { + if (sources.length === 0) { + return; + } + let nextFocusedSourceIndex = 0; + if (focusedSourceID) { + const focusedSourceIndex = sources.findIndex( + (source) => source.id === focusedSourceID, + ); + if (focusedSourceIndex + 1 >= sources.length) { + nextFocusedSourceIndex = 0; + } else { + nextFocusedSourceIndex = focusedSourceIndex + 1; + } + } + const nextFocusedSourceID = sources[nextFocusedSourceIndex].id; + dispatch( + focusSource({ + id: nextFocusedSourceID, + effects: ['focusInput', 'focusLabel', 'scrollIntoView'], + noises: ['focusInput', 'focusLabel'], + }), + ); + }; + const focusPrevSource = () => { + if (sources.length === 0) { + return; + } + let prevFocusedSourceIndex = sources.length - 1; + if (focusedSourceID) { + const focusedSourceIndex = sources.findIndex( + (source) => source.id === focusedSourceID, + ); + if (focusedSourceIndex - 1 < 0) { + prevFocusedSourceIndex = sources.length - 1; + } else { + prevFocusedSourceIndex = focusedSourceIndex - 1; + } + } + const prevFocusedSourceID = sources[prevFocusedSourceIndex].id; + dispatch( + focusSource({ + id: prevFocusedSourceID, + effects: ['focusInput', 'focusLabel', 'scrollIntoView'], + noises: ['focusInput', 'focusLabel'], + }), + ); + }; + + // 快捷键 - 下一个输入框 + const focusNextSourceHotKeyOptions = useSelector( + (state: AppState) => state.hotKey.focusNextSource, + ); + useHotKey( + { + disabled: !Boolean(focusNextSourceHotKeyOptions[0]), + ...focusNextSourceHotKeyOptions[0], + }, + focusNextSource, + [focusedSourceID, sources.length], + ); + useHotKey( + { + disabled: !Boolean(focusNextSourceHotKeyOptions[1]), + ...focusNextSourceHotKeyOptions[1], + }, + focusNextSource, + [focusedSourceID, sources.length], + ); + + // 快捷键 - 上一个输入框 + const focusPrevSourceHotKeyOptions = useSelector( + (state: AppState) => state.hotKey.focusPrevSource, + ); + useHotKey( + { + disabled: !Boolean(focusPrevSourceHotKeyOptions[0]), + ...focusPrevSourceHotKeyOptions[0], + }, + focusPrevSource, + [focusedSourceID, sources.length], + ); + useHotKey( + { + disabled: !Boolean(focusPrevSourceHotKeyOptions[1]), + ...focusPrevSourceHotKeyOptions[1], + }, + focusPrevSource, + [focusedSourceID, sources.length], + ); + + + // 快捷键 - 当 ImageViewer 未加载完成是,忽略所有快捷键 + useHotKey( + { + disabled: Boolean(file?.id), + ignoreKeyboardElement: false, + }, + () => { }, + [file?.id], + ); +} + +function useWindowSize() { + + // 页面尺寸 + const [windowSize, setWindowSize] = useState({ + width: 0, + height: 0, + }); + + useEffect(() => { + const handleResize = () => { + const width = window.innerWidth; + const height = window.innerHeight; + setWindowSize({ width, height }); + }; + handleResize(); + window.addEventListener('resize', handleResize); + const setTimeoutHandleResize = () => { + setTimeout(handleResize, 250); + }; + if (isIOS) { + window.addEventListener('focusin', setTimeoutHandleResize); + window.addEventListener('focusout', setTimeoutHandleResize); + } + return () => { + window.removeEventListener('resize', handleResize); + if (isIOS) { + window.removeEventListener('focusin', setTimeoutHandleResize); + window.removeEventListener('focusout', setTimeoutHandleResize); + } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return windowSize +} export default ImageTranslator; From 219c384e186a73f1429d3af75d4570a68aa81fd6 Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Sun, 31 Aug 2025 00:39:22 +0900 Subject: [PATCH 17/27] fix --- src/pages/ImageTranslator.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/ImageTranslator.tsx b/src/pages/ImageTranslator.tsx index bbb5833..ca6bd90 100644 --- a/src/pages/ImageTranslator.tsx +++ b/src/pages/ImageTranslator.tsx @@ -36,8 +36,6 @@ const ImageTranslator: FC = () => { ); const platform = useSelector((state: AppState) => state.site.platform); const isMobile = platform === 'mobile'; - const osName = useSelector((state: AppState) => state.site.osName); - const isIOS = osName === 'ios'; const [file, setFile] = useState(); const sourceListWidth = 400; const sourceListHeightMobile = 200; @@ -298,6 +296,8 @@ function useImageTranslatorHotkeys(file: GetFileReturn | undefined, sources: Sou } function useWindowSize() { + const osName = useSelector((state: AppState) => state.site.osName); + const isIOS = osName === 'ios'; // 页面尺寸 const [windowSize, setWindowSize] = useState({ From c8279e3c5c4a1f3890353a1d113ae23c097966dd Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Sun, 31 Aug 2025 00:39:29 +0900 Subject: [PATCH 18/27] update translations & format code --- src/components/FileList.tsx | 6 ++- src/components/project-file/ImageSelect.tsx | 28 ++++++----- src/components/project-file/ImageViewer.tsx | 8 +++- .../project-set/ProjectCreateForm.tsx | 2 +- src/components/project/ProjectTargetList.tsx | 4 +- src/configs.tsx | 2 +- src/locales/en.json | 4 +- src/locales/messages.yaml | 4 +- src/pages/Project.tsx | 2 +- src/pages/ProjectFiles.tsx | 36 ++++++++------ src/pages/ProjectSet.tsx | 2 +- src/pages/Team.tsx | 2 +- .../moeflow_companion/TranslateCompanion.tsx | 8 ++-- .../use_moeflow_companion.ts | 47 +++++++++++++++++-- src/store/source/slice.ts | 2 + 15 files changed, 109 insertions(+), 48 deletions(-) diff --git a/src/components/FileList.tsx b/src/components/FileList.tsx index 0268e7e..01a0578 100644 --- a/src/components/FileList.tsx +++ b/src/components/FileList.tsx @@ -18,7 +18,7 @@ import { PARSE_STATUS, PROJECT_PERMISSION, } from '@/constants'; -import { FC, File, Project, Target, Team } from '@/interfaces'; +import { FC, File, Project, Target, } from '@/interfaces'; import { AppState } from '@/store'; import { setFilesState } from '@/store/file/slice'; import style from '../style'; @@ -48,7 +48,9 @@ export const FileList: FC = ({ const [loading, setLoading] = useState(true); const [listMode] = useState<'image' | 'text'>('image'); const [total, setTotal] = useState(0); // 元素总个数 - const runtimeConfig = useSelector((state: AppState) => state.site.runtimeConfig); + const runtimeConfig = useSelector( + (state: AppState) => state.site.runtimeConfig, + ); const uploadAPI = `${runtimeConfig.baseURL}/v1/projects/${project.id}/files`; const token = useSelector((state: AppState) => state.user.token); const platform = useSelector((state: AppState) => state.site.platform); diff --git a/src/components/project-file/ImageSelect.tsx b/src/components/project-file/ImageSelect.tsx index dfa48e1..37e980e 100644 --- a/src/components/project-file/ImageSelect.tsx +++ b/src/components/project-file/ImageSelect.tsx @@ -1,16 +1,20 @@ import { css } from '@emotion/core'; import { Button } from 'antd'; import classNames from 'classnames'; -import React, { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useIntl } from 'react-intl'; import { useSelector } from 'react-redux'; import { useClickAway } from 'react-use'; -import apis from '@/apis'; +import { api } from '@/apis'; import { FC, File } from '@/interfaces'; import { AppState } from '@/store'; import style from '@/style'; import { toLowerCamelCase } from '@/utils'; import { clickEffect } from '@/utils/style'; +import { + useMoeflowCompanion, + moeflowCompanionServiceState, +} from '@/services/moeflow_companion/use_moeflow_companion'; /** 图片文件选择下拉框的属性接口 */ interface ImageSelectProps { @@ -53,7 +57,7 @@ export const ImageSelect: FC = ({ }) => { setLoading(true); if (!currentProject) return; - apis + api.file .getProjectFiles({ projectID: currentProject.id, params: { page, limit }, @@ -105,13 +109,13 @@ export const ImageSelect: FC = ({ padding: 0 10px; line-height: 40px; ${clickEffect( - css` + css` background-color: ${style.widgetButtonHoverBackgroundColor}; `, - css` + css` color: ${style.widgetButtonActiveColor}; `, - )}; + )}; } .ImageSelect__MenuWrapper { opacity: 0; @@ -146,13 +150,13 @@ export const ImageSelect: FC = ({ overflow: hidden; white-space: nowrap; ${clickEffect( - css` + css` background-color: ${style.widgetButtonHoverBackgroundColor}; `, - css` + css` color: ${style.widgetButtonActiveColor}; `, - )}; + )}; } .ImageSelect__MenuItem--active { background-color: ${style.widgetButtonActiveBackgroundColor}; @@ -165,13 +169,13 @@ export const ImageSelect: FC = ({ border-radius: 0; border-width: 0; ${clickEffect( - css` + css` background-color: ${style.widgetButtonHoverBackgroundColor}; `, - css` + css` color: ${style.widgetButtonActiveColor}; `, - )}; + )}; } `} ref={domRef} diff --git a/src/components/project-file/ImageViewer.tsx b/src/components/project-file/ImageViewer.tsx index 7aa439b..9f26f17 100644 --- a/src/components/project-file/ImageViewer.tsx +++ b/src/components/project-file/ImageViewer.tsx @@ -3,7 +3,7 @@ import { css } from '@emotion/core'; import { Modal, Spin } from 'antd'; import Bowser from 'bowser'; import { debounce } from 'lodash-es'; -import React, { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; @@ -34,7 +34,10 @@ import { MovableAreaImageBackground } from './MovableAreaImageBackground'; import { MovableLabel } from './MovableLabel'; import { Tooltip } from '@/components/Tooltip'; import { routes } from '@/pages/routes'; +import { createDebugLogger } from '@/utils/debug-logger'; +import { Client } from '@gradio/client'; +const debugLogger = createDebugLogger('components:project-file:ImageViewer'); /** * 🖥浏览器识别 */ @@ -70,6 +73,7 @@ interface ImageViewerProps { loading: boolean; onSettingButtonClick?: () => void; className?: string; + companionClient?: Client; } /** * 图片翻译标记器 @@ -671,7 +675,7 @@ export const ImageViewer: FC = ({ + /> = ({ columnWidth={250} autoPageSize={false} defaultPageSize={100000} - emptyTipCreater={() => } + emptyTipCreater={() => ( + + )} /> ); }; diff --git a/src/configs.tsx b/src/configs.tsx index ccce88e..8929e78 100644 --- a/src/configs.tsx +++ b/src/configs.tsx @@ -6,7 +6,7 @@ export interface RuntimeConfig { moeflowCompanion?: { gradioUrl: string; - defaultMultimodalModel?: string + defaultMultimodalModel?: string; }; } diff --git a/src/locales/en.json b/src/locales/en.json index dde98a5..ce1d87b 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -2,8 +2,8 @@ "imageTranslator.imageViewerZoomPanel.back": "Back", "imageTranslator.imageViewerZoomPanel.setting": "Setting", "imageTranslator.imageViewerZoomPanel.originSize": "Origin Size", - "imageTranslator.imageViewerZoomPanel.fixWidth": "Fix Width", - "imageTranslator.imageViewerZoomPanel.fixHeight": "Fix Height", + "imageTranslator.imageViewerZoomPanel.fixWidth": "Fit Width", + "imageTranslator.imageViewerZoomPanel.fixHeight": "Fit Height", "imageTranslator.imageViewerZoomPanel.zoomIn": "Zoom In", "imageTranslator.imageViewerZoomPanel.zoomOut": "Zoom Out", "auth.login": "Log In", diff --git a/src/locales/messages.yaml b/src/locales/messages.yaml index 5104589..dbe5340 100644 --- a/src/locales/messages.yaml +++ b/src/locales/messages.yaml @@ -11,10 +11,10 @@ imageTranslator.imageViewerZoomPanel.originSize: en: Origin Size imageTranslator.imageViewerZoomPanel.fixWidth: zhCn: 适应宽度 - en: Fix Width + en: Fit Width imageTranslator.imageViewerZoomPanel.fixHeight: zhCn: 适应高度 - en: Fix Height + en: Fit Height imageTranslator.imageViewerZoomPanel.zoomIn: zhCn: 放大 en: Zoom In diff --git a/src/pages/Project.tsx b/src/pages/Project.tsx index 48a66c2..21c5ad3 100644 --- a/src/pages/Project.tsx +++ b/src/pages/Project.tsx @@ -13,7 +13,7 @@ import ProjectPreview from './ProjectPreview'; import ProjectSetting from './ProjectSetting'; /** 项目路由的属性接口 */ -interface ProjectProps { } +interface ProjectProps {} /** * 项目路由 */ diff --git a/src/pages/ProjectFiles.tsx b/src/pages/ProjectFiles.tsx index c3807fe..5e2e5a0 100644 --- a/src/pages/ProjectFiles.tsx +++ b/src/pages/ProjectFiles.tsx @@ -61,7 +61,7 @@ const ProjectFiles: FC = ({ project }) => { ); } - const wrapper = + const wrapper = (
= ({ project }) => { } } `} - />; + /> + ); if (!currentTarget) { // target selector - return cloneElement(wrapper, undefined, + return cloneElement( + wrapper, + undefined, targets && ( = ({ project }) => { } } }} - /> - ) + />, + ); } - if (project.importFromLabelplusStatus !== IMPORT_FROM_LABELPLUS_STATUS.SUCCEEDED) { - return cloneElement(wrapper, undefined, ) + if ( + project.importFromLabelplusStatus !== IMPORT_FROM_LABELPLUS_STATUS.SUCCEEDED + ) { + return cloneElement( + wrapper, + undefined, + , + ); } - return cloneElement(wrapper, undefined, + return cloneElement( + wrapper, + undefined, = ({ project }) => { setCurrentTarget(undefined); clearDefaultTargetID({ projectID: project.id }); } else { - message.info( - formatMessage({ id: 'project.onlyOneTargetTip' }), - 1, - ); + message.info(formatMessage({ id: 'project.onlyOneTargetTip' }), 1); } }} - /> - ) + />, + ); }; export default ProjectFiles; diff --git a/src/pages/ProjectSet.tsx b/src/pages/ProjectSet.tsx index c4475b3..8f3f672 100644 --- a/src/pages/ProjectSet.tsx +++ b/src/pages/ProjectSet.tsx @@ -32,7 +32,7 @@ import { ProjectCreateForm } from '@/components/project-set/ProjectCreateForm'; import { ProjectImportForm } from '@/components/project-set/ProjectImportForm'; /** 项目集页的属性接口 */ -interface ProjectSetProps { } +interface ProjectSetProps {} /** * 项目集页 */ diff --git a/src/pages/Team.tsx b/src/pages/Team.tsx index f4ed8a1..7971096 100644 --- a/src/pages/Team.tsx +++ b/src/pages/Team.tsx @@ -32,7 +32,7 @@ import ProjectSet from './ProjectSet'; import ProjectSetSetting from './ProjectSetSetting'; /** 团队页的属性接口 */ -interface TeamProps { } +interface TeamProps {} /** * 团队页 */ diff --git a/src/services/moeflow_companion/TranslateCompanion.tsx b/src/services/moeflow_companion/TranslateCompanion.tsx index 8aed7ba..9e604ca 100644 --- a/src/services/moeflow_companion/TranslateCompanion.tsx +++ b/src/services/moeflow_companion/TranslateCompanion.tsx @@ -248,10 +248,10 @@ export const DemoOcrFiles: FC<{}> = (props) => { setWorking((s) => s?.nonce === initState.nonce ? { - ...s, - finished: Math.max(s.finished, finished), - numPages: total, - } + ...s, + finished: Math.max(s.finished, finished), + numPages: total, + } : s, ), ), diff --git a/src/services/moeflow_companion/use_moeflow_companion.ts b/src/services/moeflow_companion/use_moeflow_companion.ts index 50be972..b1f8d4d 100644 --- a/src/services/moeflow_companion/use_moeflow_companion.ts +++ b/src/services/moeflow_companion/use_moeflow_companion.ts @@ -12,12 +12,12 @@ export const moeflowCompanionServiceState = { disconnected: 'disconnected', } as const; -const logger = createDebugLogger('service:moeflow_companion'); +const debugLogger = createDebugLogger('service:moeflow_companion'); export function useMoeflowCompanion() { const clientRef = useRef(null); const [clientState, setClientState] = useState( - moeflowCompanionServiceState.disabled, + moeflowCompanionServiceState.connecting, ); const serviceConf = useSelector( (s: AppState) => s.site.runtimeConfig.moeflowCompanion, @@ -36,7 +36,7 @@ export function useMoeflowCompanion() { setClientState(moeflowCompanionServiceState.connected); released.then(() => client.close()); } catch (e) { - logger('error connecting', e, serviceConf.gradioUrl); + debugLogger('error connecting', e, serviceConf.gradioUrl); clientRef.current = null; setClientState(moeflowCompanionServiceState.disconnected); } @@ -46,4 +46,43 @@ export function useMoeflowCompanion() { return [clientState, clientRef.current] as const; } -export async function x(client: Client) {} +export async function multimodalTranslate( + client: Client, + files: File[], + targetLang: string, + model: string, +): Promise { + // const uploadRes = await client.upload_files(hfSpaceUrl, files) + // files.forEach(file => formData.append('files[]', file)); + // debugLogger('Upload response:', uploadRes); + const predictRes = await client.predict('/multimodal_llm_process_files', { + gradio_temp_files: files, // uploadRes.files!.map(handle_file), + model, + target_language: targetLang, + export_moeflow_project_name: 'Hello!!', + }); + const [{ files: translated }] = predictRes.data as MoeflowMultimodalResData; + + debugLogger('Predict response:', translated); + return translated; +} +interface TranslatedFile { + local_path: string; + image_w: number; + image_h: number; + text_blocks: Array<{ + left: number; + top: number; + right: number; + bottom: number; + source: string; + translated: string; + }>; +} +/** + * the type in gradio https://github.com/moeflow-com/manga-image-translator/blob/moeflow-companion-main/moeflow_companion/gradio/multimodal.py#L62 + */ +type MoeflowMultimodalResData = [ + { files: TranslatedFile[] }, + /* the ignored packaged zip */ unknown, +]; diff --git a/src/store/source/slice.ts b/src/store/source/slice.ts index 84e9257..81ebe44 100644 --- a/src/store/source/slice.ts +++ b/src/store/source/slice.ts @@ -47,6 +47,8 @@ export interface EditProofreadSagaAction { proofreadContent: string; noDebounce?: boolean; } + +export interface Singl {} const slice = createSlice({ name: 'source', initialState, From b673879535ea36b1a460d7c53014d55b8e077f2c Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Sun, 31 Aug 2025 01:18:54 +0900 Subject: [PATCH 19/27] wip --- Makefile | 3 + src/components/FileList.tsx | 82 ++++++++++++--------- src/components/List.tsx | 14 ++-- src/components/TeamInsightProjectList.tsx | 2 +- src/components/project-file/ImageSelect.tsx | 18 ++--- src/locales/en.json | 3 +- src/locales/messages.yaml | 8 +- src/locales/zh-cn.json | 3 +- src/pages/ImageTranslator.tsx | 17 +++-- 9 files changed, 90 insertions(+), 60 deletions(-) diff --git a/Makefile b/Makefile index a0f88ad..47cc170 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,9 @@ locale-json: src/locales/en.json src/locales/zh-cn.json locale-json-watch: watch make locale-json +format: + npm run format:fix + src/locales/en.json: src/locales/messages.yaml node_modules/.bin/tsx scripts/generate-locale-json.ts diff --git a/src/components/FileList.tsx b/src/components/FileList.tsx index 01a0578..13e8b65 100644 --- a/src/components/FileList.tsx +++ b/src/components/FileList.tsx @@ -1,6 +1,5 @@ import { css, Global } from '@emotion/core'; import { Button as AntdButton, Drawer, message, Modal, Spin } from 'antd'; -import { CancelToken } from 'axios'; import loadImage from 'blueimp-load-image'; import classNames from 'classnames'; import { useRef, useState } from 'react'; @@ -18,13 +17,18 @@ import { PARSE_STATUS, PROJECT_PERMISSION, } from '@/constants'; -import { FC, File, Project, Target, } from '@/interfaces'; +import { FC, File as MFile, Project, Target } from '@/interfaces'; import { AppState } from '@/store'; import { setFilesState } from '@/store/file/slice'; import style from '../style'; import { toLowerCamelCase } from '@/utils'; import { can } from '@/utils/user'; import { routes } from '@/pages/routes'; +import { + moeflowCompanionServiceState, + useMoeflowCompanion, +} from '@/services/moeflow_companion/use_moeflow_companion'; +import { ListPageSpec } from './List'; /** 文件列表的属性接口 */ interface FileListProps { @@ -58,10 +62,12 @@ export const FileList: FC = ({ const [outputDrawerVisible, setOutputDrawerVisible] = useState(false); const coverWidth = IMAGE_COVER.WIDTH; const coverHeight = IMAGE_COVER.HEIGHT; + const companionService = useMoeflowCompanionBatchProcess(); - const [items, setItems] = useState([]); + const [items, setItems] = useState([]); const [spinningIDs, setSpinningIDs] = useState([]); // 删除请求中 const filePondRef = useRef(); + const currentPageSpecRef = useRef(null); const defaultPage = useSelector( (state: AppState) => state.file.filesState.page, @@ -76,11 +82,11 @@ export const FileList: FC = ({ (state: AppState) => state.file.filesState.selectedFileIds, ); - const toTranslator = (file: File) => { + const openInTranslator = (file: MFile) => { history.push(routes.imageTranslator.build(file.id, target.id)); }; - const deleteFile = (file: File) => { + const deleteFile = (file: MFile) => { Modal.confirm({ title: formatMessage({ id: 'project.deleteFile' }), content: formatMessage( @@ -107,25 +113,21 @@ export const FileList: FC = ({ setSpinningIDs((ids) => ids.filter((id) => id !== file.id)); }); }, - onCancel: () => { }, + onCancel: () => {}, okText: formatMessage({ id: 'form.ok' }), cancelText: formatMessage({ id: 'form.cancel' }), }); }; /** 获取元素 */ - const handleChange = ({ - page, - pageSize, - word, - cancelToken, - }: { - page: number; - pageSize: number; - word?: string; - cancelToken: CancelToken; - }) => { + const loadPage = ({ page, pageSize, word, cancelToken }: ListPageSpec) => { setLoading(true); + currentPageSpecRef.current = { + page, + pageSize, + word: word || '', + cancelToken, + }; return api.file .getProjectFiles({ projectID: project.id, @@ -140,7 +142,7 @@ export const FileList: FC = ({ }, }) .then((result) => { - const data = (result.data as File[]).map((d) => toLowerCamelCase(d)); + const data = (result.data as MFile[]).map((d) => toLowerCamelCase(d)); setItems(data); setTotal(Number(result.headers['x-pagination-count'])); setLoading(false); @@ -179,10 +181,6 @@ export const FileList: FC = ({ }; // */ - const handleOutputDrawerOpen = () => { - setOutputDrawerVisible(true); - }; - return (
= ({ }, ); } - const uploadingFile: File = { + const uploadingFile: MFile = { id: file.id, name: file.filename, saveName: '', @@ -315,7 +313,7 @@ export const FileList: FC = ({ // 上传成功 onprocessfile={(error, file) => { if (error) return; - const result = toLowerCamelCase(JSON.parse(file.serverId) as File); + const result = toLowerCamelCase(JSON.parse(file.serverId) as MFile); setItems((items) => { // 覆盖时删除列表中原来的文件 const itemsWithoutSameID = items.filter( @@ -368,14 +366,18 @@ export const FileList: FC = ({ ? formatMessage({ id: 'project.changeTarget' }) + ' - ' : '') + target?.language.i18nName} - {/* {can(team, TEAM_PERMISSION.USE_OCR_QUOTA) && ( + {companionService && ( + // onClick={} + > + {formatMessage({ id: 'fileList.aiTranslate' })} + + )} + {/* {can(team, TEAM_PERMISSION.USE_OCR_QUOTA) && ( )} */} {/* */} {can(project, PROJECT_PERMISSION.OUTPUT_TRA) && ( - )} {can(project, PROJECT_PERMISSION.ADD_FILE) && ( @@ -459,7 +461,7 @@ export const FileList: FC = ({ = ({ onClick={() => { (file.uploadState === undefined || file.uploadState === 'success') && - toTranslator(file); + openInTranslator(file); }} selectVisible={can(project, PROJECT_PERMISSION.OUTPUT_TRA)} selected={selectedFileIds.includes(file.id)} @@ -550,7 +552,7 @@ export const FileList: FC = ({ = ({
); }; + +function useMoeflowCompanionBatchProcess() { + const [serviceState, client] = useMoeflowCompanion(); + + if (serviceState !== moeflowCompanionServiceState.connected) { + return null; + } + + return { + async f(files: MFile[]) {}, + } as const; +} diff --git a/src/components/List.tsx b/src/components/List.tsx index 6030bcc..19dd712 100644 --- a/src/components/List.tsx +++ b/src/components/List.tsx @@ -13,6 +13,13 @@ import { AppState } from '../store'; import { getCancelToken } from '../utils/api'; import { ListSearchInputProps } from './ListSearchInput'; +export interface ListPageSpec { + page: number; + pageSize: number; + word: string; + cancelToken: CancelToken; +} + /** 列表的属性接口 */ interface ListProps { id?: string; @@ -21,12 +28,7 @@ interface ListProps { pageSize, word, cancelToken, - }: { - page: number; - pageSize: number; - word: string; - cancelToken: CancelToken; - }) => Promise | void; + }: ListPageSpec) => Promise | void; loading: boolean; onSearchRightButtonClick?: (e: React.MouseEvent) => void; total: number; diff --git a/src/components/TeamInsightProjectList.tsx b/src/components/TeamInsightProjectList.tsx index 12f2830..0a653f1 100644 --- a/src/components/TeamInsightProjectList.tsx +++ b/src/components/TeamInsightProjectList.tsx @@ -324,7 +324,7 @@ export const TeamInsightProjectList: FC = ({ }} /> { diff --git a/src/components/project-file/ImageSelect.tsx b/src/components/project-file/ImageSelect.tsx index 37e980e..8052bb3 100644 --- a/src/components/project-file/ImageSelect.tsx +++ b/src/components/project-file/ImageSelect.tsx @@ -109,13 +109,13 @@ export const ImageSelect: FC = ({ padding: 0 10px; line-height: 40px; ${clickEffect( - css` + css` background-color: ${style.widgetButtonHoverBackgroundColor}; `, - css` + css` color: ${style.widgetButtonActiveColor}; `, - )}; + )}; } .ImageSelect__MenuWrapper { opacity: 0; @@ -150,13 +150,13 @@ export const ImageSelect: FC = ({ overflow: hidden; white-space: nowrap; ${clickEffect( - css` + css` background-color: ${style.widgetButtonHoverBackgroundColor}; `, - css` + css` color: ${style.widgetButtonActiveColor}; `, - )}; + )}; } .ImageSelect__MenuItem--active { background-color: ${style.widgetButtonActiveBackgroundColor}; @@ -169,13 +169,13 @@ export const ImageSelect: FC = ({ border-radius: 0; border-width: 0; ${clickEffect( - css` + css` background-color: ${style.widgetButtonHoverBackgroundColor}; `, - css` + css` color: ${style.widgetButtonActiveColor}; `, - )}; + )}; } `} ref={domRef} diff --git a/src/locales/en.json b/src/locales/en.json index ce1d87b..65c71be 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -232,7 +232,7 @@ "imageTranslator.sourceViewer.rightClickToMarkSource": "Right click on image to mark out-of-balloon text", "imageTranslator.sourceViewer.rightClickMarkToRemove": "Right click a mark to remove it", "site.noPermission": "No permission", - "site.output": "Export", + "project.export": "Export", "site.loading": "Loading", "output.empty": "No export available", "output.refresh": "Refresh", @@ -263,6 +263,7 @@ "fileList.changeMode": "Toggle Display Mode", "file.parseNotStart": "Auto Mark as Not Started", "fileList.ocrButtonTip": "Image Auto Tagging", + "fileList.autoTranslate": "Detect marks and Translate", "project.startOCR": "Start Auto Tagging", "project.startOCRTip": "Do you want to start auto-tagging images? (Only images marked as \"Not Started\" or \"Tagging Failed\" will be auto-tagged, and the quota will only be deducted upon successful tagging)", "site.ocrQuota": "Auto Tagging Limit", diff --git a/src/locales/messages.yaml b/src/locales/messages.yaml index dbe5340..7ca6874 100644 --- a/src/locales/messages.yaml +++ b/src/locales/messages.yaml @@ -711,7 +711,7 @@ imageTranslator.sourceViewer.rightClickMarkToRemove: site.noPermission: zhCn: 没有权限 en: No permission -site.output: +project.export: zhCn: 导出 en: Export site.loading: @@ -804,6 +804,12 @@ file.parseNotStart: fileList.ocrButtonTip: zhCn: 图片自动标记 en: Image Auto Tagging +fileList.aiTranslate: + zhCn: AI翻译 + en: Translate with AI +fileList.aiTranslateTip: + zhCn: 自动识别并翻译 + en: Detect marks and Translate project.startOCR: zhCn: 开始自动标记 en: Start Auto Tagging diff --git a/src/locales/zh-cn.json b/src/locales/zh-cn.json index aa75ffd..064546e 100644 --- a/src/locales/zh-cn.json +++ b/src/locales/zh-cn.json @@ -232,7 +232,7 @@ "imageTranslator.sourceViewer.rightClickToMarkSource": "右键点击图片新增框外标记", "imageTranslator.sourceViewer.rightClickMarkToRemove": "右键点击标记来删除标记", "site.noPermission": "没有权限", - "site.output": "导出", + "project.export": "导出", "site.loading": "加载中", "output.empty": "暂无导出", "output.refresh": "刷新", @@ -263,6 +263,7 @@ "fileList.changeMode": "切换显示模式", "file.parseNotStart": "自动标记未开始", "fileList.ocrButtonTip": "图片自动标记", + "fileList.autoTranslate": "自动识别并翻译", "project.startOCR": "开始自动标记", "project.startOCRTip": "您要开始图片自动标记吗?(仅自动标记“未开始”、“标记失败”的图片,且仅成功时扣减限额)", "site.ocrQuota": "自动标记限额", diff --git a/src/pages/ImageTranslator.tsx b/src/pages/ImageTranslator.tsx index ca6bd90..77aba19 100644 --- a/src/pages/ImageTranslator.tsx +++ b/src/pages/ImageTranslator.tsx @@ -101,7 +101,7 @@ const ImageTranslator: FC = () => { .ImageTranslator__ImageViewer { z-index: 1; ${isMobile && - css` + css` position: absolute; bottom: ${sourceListHeightMobile}px; left: 0; @@ -113,7 +113,7 @@ const ImageTranslator: FC = () => { box-shadow: ${style.boxShadowBase}; overflow: hidden; ${isMobile - ? css` + ? css` bottom: 0; left: 0; height: ${sourceListHeightMobile}px; @@ -121,7 +121,7 @@ const ImageTranslator: FC = () => { border-radius: ${style.borderRadiusBase} ${style.borderRadiusBase} 0 0; ` - : css` + : css` top: 0; right: 0; height: 100%; @@ -190,7 +190,11 @@ const ImageTranslator: FC = () => { ); }; -function useImageTranslatorHotkeys(file: GetFileReturn | undefined, sources: Source[], focusedSourceID: string | null) { +function useImageTranslatorHotkeys( + file: GetFileReturn | undefined, + sources: Source[], + focusedSourceID: string | null, +) { const dispatch = useDispatch(); const focusNextSource = () => { if (sources.length === 0) { @@ -283,14 +287,13 @@ function useImageTranslatorHotkeys(file: GetFileReturn | undefined, sources: Sou [focusedSourceID, sources.length], ); - // 快捷键 - 当 ImageViewer 未加载完成是,忽略所有快捷键 useHotKey( { disabled: Boolean(file?.id), ignoreKeyboardElement: false, }, - () => { }, + () => {}, [file?.id], ); } @@ -330,6 +333,6 @@ function useWindowSize() { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - return windowSize + return windowSize; } export default ImageTranslator; From 88b2938ea286b1a2b6b553a65c81abb9e1097339 Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Sun, 31 Aug 2025 18:07:32 +0900 Subject: [PATCH 20/27] move files --- src/components/ApplicationList.tsx | 2 +- src/components/AuthLoginedTip.tsx | 2 +- src/components/CAPTCHAInput.tsx | 12 +- src/components/InvitationList.tsx | 2 +- src/components/InviteUser.tsx | 4 +- src/components/MemberList.tsx | 2 +- src/components/ProjectSetList.tsx | 2 +- src/components/TeamInsightProjectList.tsx | 2 +- src/components/TeamList.tsx | 12 +- src/components/TeamSearchList.tsx | 2 +- src/components/TeamSettingBase.tsx | 2 +- src/components/UserInvitationList.tsx | 2 +- src/components/index.ts | 42 +++--- src/components/project-file/ImageViewer.tsx | 4 +- src/components/project-file/MovableLabel.tsx | 4 +- .../{ => project-list}/ProjectItem.tsx | 15 +-- .../{ => project-list}/ProjectList.tsx | 19 +-- src/components/{ => project}/FileItem.tsx | 5 +- src/components/{ => project}/FileList.tsx | 123 +++++++++--------- .../{ => project}/FileUploadProgress.tsx | 7 +- .../{ => project}/LanguageSelect.tsx | 2 +- src/components/{ => project}/Output.tsx | 13 +- src/components/{ => project}/OutputList.tsx | 20 +-- .../ProjectCreateForm.tsx | 11 +- .../ProjectImportForm.tsx | 3 +- .../project/ProjectSettingTarget.tsx | 4 +- src/components/setting/LocalePicker.tsx | 7 +- src/components/setting/UserBasicSettings.tsx | 2 +- src/components/{ => shared}/Avatar.tsx | 4 +- src/components/{ => shared}/AvatarUpload.tsx | 12 +- src/components/{ => shared}/Button.tsx | 4 +- src/components/{ => shared}/Dropdown.tsx | 0 src/components/{ => shared}/Header.tsx | 16 +-- src/components/{ => shared}/Label.tsx | 11 +- src/components/{ => shared}/List.tsx | 6 +- src/components/{ => shared}/ListItem.tsx | 6 +- .../{ => shared}/ListSearchInput.tsx | 6 +- .../{ => shared}/ListSkeletonItem.tsx | 7 +- src/components/{ => shared}/LoadingIcon.tsx | 0 src/components/{ => shared}/Movable.tsx | 6 +- src/components/{ => shared}/Spin.tsx | 3 +- src/components/{ => shared}/Tooltip.tsx | 3 +- src/components/{ => unused}/FileCover.tsx | 5 +- .../{ => unused}/ImageOCRProgress.tsx | 8 +- src/locales/en.json | 3 +- src/locales/messages.yaml | 6 +- src/locales/zh-cn.json | 3 +- src/pages/ProjectSet.tsx | 4 +- .../moeflow_companion/BatchProcessModal.ts | 6 + .../moeflow_companion/TranslateCompanion.tsx | 2 +- src/style.ts | 2 +- 51 files changed, 221 insertions(+), 229 deletions(-) rename src/components/{ => project-list}/ProjectItem.tsx (95%) rename src/components/{ => project-list}/ProjectList.tsx (95%) rename src/components/{ => project}/FileItem.tsx (99%) rename src/components/{ => project}/FileList.tsx (88%) rename src/components/{ => project}/FileUploadProgress.tsx (95%) rename src/components/{ => project}/LanguageSelect.tsx (97%) rename src/components/{ => project}/Output.tsx (91%) rename src/components/{ => project}/OutputList.tsx (93%) rename src/components/{project-set => project}/ProjectCreateForm.tsx (98%) rename src/components/{project-set => project}/ProjectImportForm.tsx (98%) rename src/components/{ => shared}/Avatar.tsx (90%) rename src/components/{ => shared}/AvatarUpload.tsx (91%) rename src/components/{ => shared}/Button.tsx (98%) rename src/components/{ => shared}/Dropdown.tsx (100%) rename src/components/{ => shared}/Header.tsx (92%) rename src/components/{ => shared}/Label.tsx (97%) rename src/components/{ => shared}/List.tsx (98%) rename src/components/{ => shared}/ListItem.tsx (97%) rename src/components/{ => shared}/ListSearchInput.tsx (96%) rename src/components/{ => shared}/ListSkeletonItem.tsx (87%) rename src/components/{ => shared}/LoadingIcon.tsx (100%) rename src/components/{ => shared}/Movable.tsx (99%) rename src/components/{ => shared}/Spin.tsx (92%) rename src/components/{ => shared}/Tooltip.tsx (96%) rename src/components/{ => unused}/FileCover.tsx (93%) rename src/components/{ => unused}/ImageOCRProgress.tsx (91%) create mode 100644 src/services/moeflow_companion/BatchProcessModal.ts diff --git a/src/components/ApplicationList.tsx b/src/components/ApplicationList.tsx index 1622dea..f82a244 100644 --- a/src/components/ApplicationList.tsx +++ b/src/components/ApplicationList.tsx @@ -22,7 +22,7 @@ import { toLowerCamelCase } from '@/utils'; import { formatGroupType } from '@/utils/i18n'; import { clickEffect } from '@/utils/style'; import { can } from '@/utils/user'; -import { Spin } from './Spin'; +import { Spin } from './shared/Spin'; /** 申请管理页的属性接口 */ interface ApplicationListProps { diff --git a/src/components/AuthLoginedTip.tsx b/src/components/AuthLoginedTip.tsx index 9040037..1ec5a1e 100644 --- a/src/components/AuthLoginedTip.tsx +++ b/src/components/AuthLoginedTip.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router'; -import { Avatar } from './Avatar'; +import { Avatar } from './shared/Avatar'; import { AppState } from '../store'; import { setUserToken } from '../store/user/slice'; import style from '../style'; diff --git a/src/components/CAPTCHAInput.tsx b/src/components/CAPTCHAInput.tsx index fdcf3b7..449bead 100644 --- a/src/components/CAPTCHAInput.tsx +++ b/src/components/CAPTCHAInput.tsx @@ -4,12 +4,12 @@ import { CancelToken } from 'axios'; import classNames from 'classnames'; import React, { useEffect, useImperativeHandle, useRef, useState } from 'react'; import { useIntl } from 'react-intl'; -import api from '../apis'; -import { LoadingIcon } from './LoadingIcon'; -import { getIntl } from '../locales'; -import style from '../style'; -import { getCancelToken } from '../utils/api'; -import { clickEffect, imageClickEffect } from '../utils/style'; +import api from '@/apis'; +import { LoadingIcon } from '@/components'; +import { getIntl } from '@/locales'; +import style from '@/style'; +import { getCancelToken } from '@/utils/api'; +import { clickEffect, imageClickEffect } from '@/utils/style'; /** 用于 Form.Item 校验验证码,rules={[{validator: checkCAPTCHA}]} */ export const checkCAPTCHA = (rule: any, captcha: CAPTCHAInputValue) => { diff --git a/src/components/InvitationList.tsx b/src/components/InvitationList.tsx index 3c29fc2..095b359 100644 --- a/src/components/InvitationList.tsx +++ b/src/components/InvitationList.tsx @@ -17,7 +17,7 @@ import { toLowerCamelCase } from '../utils'; import { getCancelToken } from '../utils/api'; import { FC } from '../interfaces'; import { clickEffect } from '../utils/style'; -import { Spin } from './Spin'; +import { Spin } from './shared/Spin'; import { INVITATION_STATUS } from '../constants'; const { Option } = Select; diff --git a/src/components/InviteUser.tsx b/src/components/InviteUser.tsx index e68090a..0237ff5 100644 --- a/src/components/InviteUser.tsx +++ b/src/components/InviteUser.tsx @@ -13,8 +13,8 @@ import { FC, Role, Project, UserTeam } from '../interfaces'; import style from '../style'; import { toLowerCamelCase } from '../utils'; import { getCancelToken } from '../utils/api'; -import { Spin } from './Spin'; -import { LIST_ITEM_DEFAULT_HEIGHT } from './ListItem'; +import { Spin } from './shared/Spin'; +import { LIST_ITEM_DEFAULT_HEIGHT } from '@/components/shared/ListItem'; const { Option } = Select; diff --git a/src/components/MemberList.tsx b/src/components/MemberList.tsx index e079afc..c0cb37c 100644 --- a/src/components/MemberList.tsx +++ b/src/components/MemberList.tsx @@ -15,7 +15,7 @@ import style from '../style'; import { toLowerCamelCase } from '../utils'; import { getCancelToken } from '../utils/api'; import { can } from '../utils/user'; -import { Spin } from './Spin'; +import { Spin } from './shared/Spin'; import { TypeData } from './TypeRadioGroup'; /** 成员设置页的属性接口 */ diff --git a/src/components/ProjectSetList.tsx b/src/components/ProjectSetList.tsx index c1d334a..b13e1a0 100644 --- a/src/components/ProjectSetList.tsx +++ b/src/components/ProjectSetList.tsx @@ -21,7 +21,7 @@ import { } from '../store/projectSet/slice'; import { toLowerCamelCase } from '../utils'; import { can } from '../utils/user'; -import { LIST_ITEM_DEFAULT_HEIGHT } from './ListItem'; +import { LIST_ITEM_DEFAULT_HEIGHT } from '@/components/shared/ListItem'; /** 项目集的属性接口 */ interface ProjectSetListProps { diff --git a/src/components/TeamInsightProjectList.tsx b/src/components/TeamInsightProjectList.tsx index 0a653f1..767ff9d 100644 --- a/src/components/TeamInsightProjectList.tsx +++ b/src/components/TeamInsightProjectList.tsx @@ -8,7 +8,7 @@ import { Tag, message, } from 'antd'; -import { Button as CustomButton } from './Button'; +import { Button as CustomButton } from './shared/Button'; import classNames from 'classnames'; import produce from 'immer'; import qs from 'qs'; diff --git a/src/components/TeamList.tsx b/src/components/TeamList.tsx index bc60dbd..147e86a 100644 --- a/src/components/TeamList.tsx +++ b/src/components/TeamList.tsx @@ -7,12 +7,12 @@ import { useDispatch, useSelector } from 'react-redux'; import { matchPath, useHistory, useLocation } from 'react-router-dom'; import { Avatar, EmptyTip, Icon, List, ListItem } from '.'; import api, { resultTypes } from '../apis'; -import { FC, UserTeam } from '../interfaces'; -import { AppState } from '../store'; -import { resetProjectSetsState } from '../store/projectSet/slice'; -import { clearTeams, createTeam, setTeamsState } from '../store/team/slice'; -import { toLowerCamelCase } from '../utils'; -import { LIST_ITEM_DEFAULT_HEIGHT } from './ListItem'; +import { FC, UserTeam } from '@/interfaces'; +import { AppState } from '@/store'; +import { resetProjectSetsState } from '@/store/projectSet/slice'; +import { clearTeams, createTeam, setTeamsState } from '@/store/team/slice'; +import { toLowerCamelCase } from '@/utils'; +import { LIST_ITEM_DEFAULT_HEIGHT } from '@/components/shared/ListItem'; /** 团队列表的属性接口 */ interface TeamListProps { diff --git a/src/components/TeamSearchList.tsx b/src/components/TeamSearchList.tsx index 563350d..e8c2981 100644 --- a/src/components/TeamSearchList.tsx +++ b/src/components/TeamSearchList.tsx @@ -15,7 +15,7 @@ import { createTeam } from '../store/team/slice'; import { toLowerCamelCase } from '../utils'; import { FC, UserTeam } from '../interfaces'; import { Team } from '../interfaces'; -import { LIST_ITEM_DEFAULT_HEIGHT } from './ListItem'; +import { LIST_ITEM_DEFAULT_HEIGHT } from '@/components/shared/ListItem'; /** 搜索团队的属性接口 */ interface TeamSearchListProps { diff --git a/src/components/TeamSettingBase.tsx b/src/components/TeamSettingBase.tsx index 146be2d..5eb2d4d 100644 --- a/src/components/TeamSettingBase.tsx +++ b/src/components/TeamSettingBase.tsx @@ -17,7 +17,7 @@ import style from '../style'; import { FC, UserTeam } from '../interfaces'; import { can } from '../utils/user'; import copy from 'copy-to-clipboard'; -import { AvatarUpload } from './AvatarUpload'; +import { AvatarUpload } from './shared/AvatarUpload'; /** 团队基础设置的属性接口 */ interface TeamSettingBaseProps { diff --git a/src/components/UserInvitationList.tsx b/src/components/UserInvitationList.tsx index aaa0fb2..9364abb 100644 --- a/src/components/UserInvitationList.tsx +++ b/src/components/UserInvitationList.tsx @@ -16,7 +16,7 @@ import { createTeam } from '../store/team/slice'; import style from '../style'; import { toLowerCamelCase } from '../utils'; import { clickEffect } from '../utils/style'; -import { Spin } from './Spin'; +import { Spin } from './shared/Spin'; /** 申请管理页的属性接口 */ interface UserInvitationListProps { diff --git a/src/components/index.ts b/src/components/index.ts index bff7626..42c24a2 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -2,8 +2,8 @@ export { Icon } from './icon'; export { ApplicationList } from './ApplicationList'; export { AuthFormWrapper } from './AuthFormWrapper'; export { AuthLoginedTip } from './AuthLoginedTip'; -export { Avatar } from './Avatar'; -export { Button } from './Button'; +export { Avatar } from './shared/Avatar'; +export { Button } from './shared/Button'; export { CAPTCHAInput } from './CAPTCHAInput'; export { CAPTCHAModal } from './CAPTCHAModal'; export { Content } from './Content'; @@ -12,44 +12,40 @@ export { ContentTitle } from './ContentTitle'; export { DashboardBox } from './DashboardBox'; export { DashboardMenu } from './DashboardMenu'; export { DebounceStatus } from './DebounceStatus'; -export { Dropdown } from './Dropdown'; +export { Dropdown } from './shared/Dropdown'; export { EmailInput } from './EmailInput'; export { EmailVCodeInputItem } from './EmailVCodeInputItem'; export { EmptyTip } from './EmptyTip'; -export { FileCover } from './FileCover'; -export { FileItem } from './FileItem'; -export { FileList } from './FileList'; -export { FileUploadProgress } from './FileUploadProgress'; +export { FileList } from './project/FileList'; +export { FileUploadProgress } from './project/FileUploadProgress'; export { Form } from './Form'; export { FormItem } from './FormItem'; export { GroupJoinForm } from './GroupJoinForm'; -export { Header } from './Header'; +export { Header } from './shared/Header'; export { useHotKey } from './HotKey'; -export { ImageOCRProgress } from './ImageOCRProgress'; +export { ImageOCRProgress } from './unused/ImageOCRProgress'; export { InvitationList } from './InvitationList'; export { InviteUser } from './InviteUser'; -export { Label } from './Label'; -export { LanguageSelect } from './LanguageSelect'; -export { List } from './List'; -export { ListItem, LIST_ITEM_DEFAULT_HEIGHT } from './ListItem'; -export { ListSearchInput } from './ListSearchInput'; -export { ListSkeletonItem } from './ListSkeletonItem'; -export { LoadingIcon } from './LoadingIcon'; +export { Label } from './shared/Label'; +export { LanguageSelect } from './project/LanguageSelect'; +export { List } from './shared/List'; +export { ListItem, LIST_ITEM_DEFAULT_HEIGHT } from './shared/ListItem'; +export { ListSearchInput } from './shared/ListSearchInput'; +export { ListSkeletonItem } from './shared/ListSkeletonItem'; +export { LoadingIcon } from './shared/LoadingIcon'; export { MemberList } from './MemberList'; -export { MovableArea, MovableItem } from './Movable'; +export { MovableArea, MovableItem } from './shared/Movable'; export { NavTab } from './NavTab'; export { NavTabs } from './NavTabs'; -export { Output } from './Output'; -export { OutputList } from './OutputList'; -export { ProjectItem } from './ProjectItem'; -export { ProjectList } from './ProjectList'; +export { OutputList } from './project/OutputList'; +export { ProjectList } from './project-list/ProjectList'; export { ProjectSetCreateForm } from './ProjectSetCreateForm'; export { ProjectSetEditForm } from './ProjectSetEditForm'; export { ProjectSetList } from './ProjectSetList'; export { ProjectSetSettingBase } from './ProjectSetSettingBase'; export { RoleRadioGroup } from './RoleRadioGroup'; export { RoleSelect } from './RoleSelect'; -export { Spin } from './Spin'; +export { Spin } from './shared/Spin'; export { TabBarM } from './TabBarM'; export { TeamCreateForm } from './TeamCreateForm'; export { TeamEditForm } from './TeamEditForm'; @@ -58,7 +54,7 @@ export { TeamInsightUserList } from './TeamInsightUserList'; export { TeamList } from './TeamList'; export { TeamSearchList } from './TeamSearchList'; export { TeamSettingBase } from './TeamSettingBase'; -export { Tooltip } from './Tooltip'; +export { Tooltip } from './shared/Tooltip'; export { TranslationProgress } from './TranslationProgress'; export { TypeRadioGroup } from './TypeRadioGroup'; export { UserEmailEditForm } from './UserEmailEditForm'; diff --git a/src/components/project-file/ImageViewer.tsx b/src/components/project-file/ImageViewer.tsx index 9f26f17..a4915fc 100644 --- a/src/components/project-file/ImageViewer.tsx +++ b/src/components/project-file/ImageViewer.tsx @@ -18,7 +18,7 @@ import { OnZoomEnd, OnZooming, OnZoomStart, -} from '@/components/Movable'; +} from '@/components/shared/Movable'; import { SOURCE_POSITION_TYPE } from '@/constants/source'; import { FC, File, Source } from '@/interfaces'; import { AppState } from '@/store'; @@ -32,7 +32,7 @@ import { ImageViewerZoomPanel } from './ImageViewerZoomPanel'; import { MovableAreaColorBackground } from './MovableAreaColorBackground'; import { MovableAreaImageBackground } from './MovableAreaImageBackground'; import { MovableLabel } from './MovableLabel'; -import { Tooltip } from '@/components/Tooltip'; +import { Tooltip } from '@/components/shared/Tooltip'; import { routes } from '@/pages/routes'; import { createDebugLogger } from '@/utils/debug-logger'; import { Client } from '@gradio/client'; diff --git a/src/components/project-file/MovableLabel.tsx b/src/components/project-file/MovableLabel.tsx index be41825..dd0c6ff 100644 --- a/src/components/project-file/MovableLabel.tsx +++ b/src/components/project-file/MovableLabel.tsx @@ -6,14 +6,14 @@ import { FC, labelSavingStatuses } from '@/interfaces'; import { AppState } from '@/store'; import { deleteSourceSaga, editSourceSaga } from '@/store/source/slice'; import { can } from '@/utils/user'; -import { Label, LabelProps } from '@/components/Label'; +import { Label, LabelProps } from '@/components/shared/Label'; import { MovableInfoContext, MovableItem, OnLongPress, OnMoveEnd, OnTap, -} from '@/components/Movable'; +} from '@/components/shared/Movable'; /** * 标签(使用 Context,进行自动缩放/激活) diff --git a/src/components/ProjectItem.tsx b/src/components/project-list/ProjectItem.tsx similarity index 95% rename from src/components/ProjectItem.tsx rename to src/components/project-list/ProjectItem.tsx index ddc3ff0..be461ed 100644 --- a/src/components/ProjectItem.tsx +++ b/src/components/project-list/ProjectItem.tsx @@ -1,16 +1,15 @@ import { css } from '@emotion/core'; import classNames from 'classnames'; -import React from 'react'; import { useIntl } from 'react-intl'; import { useDispatch } from 'react-redux'; import { useHistory, useLocation, useRouteMatch } from 'react-router-dom'; -import { Icon, TranslationProgress } from '.'; -import { PROJECT_PERMISSION, PROJECT_STATUS } from '../constants'; -import { FC, Project } from '../interfaces'; -import { resetFilesState } from '../store/file/slice'; -import style from '../style'; -import { cardActiveEffect, cardClickEffect, clickEffect } from '../utils/style'; -import { can } from '../utils/user'; +import { Icon, TranslationProgress } from '@/components'; +import { PROJECT_PERMISSION, PROJECT_STATUS } from '@/constants'; +import { FC, Project } from '@/interfaces'; +import { resetFilesState } from '@/store/file/slice'; +import style from '@/style'; +import { cardActiveEffect, cardClickEffect, clickEffect } from '@/utils/style'; +import { can } from '@/utils/user'; /** 项目列表元素的属性接口 */ interface ProjectItemProps { diff --git a/src/components/ProjectList.tsx b/src/components/project-list/ProjectList.tsx similarity index 95% rename from src/components/ProjectList.tsx rename to src/components/project-list/ProjectList.tsx index 9a9c009..2e25935 100644 --- a/src/components/ProjectList.tsx +++ b/src/components/project-list/ProjectList.tsx @@ -6,20 +6,21 @@ import React, { useState } from 'react'; import { useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory, useRouteMatch } from 'react-router-dom'; -import { EmptyTip, List, ProjectItem } from '.'; -import api, { resultTypes } from '../apis'; -import { PROJECT_STATUS } from '../constants'; -import { FC } from '../interfaces'; -import { AppState } from '../store'; +import { EmptyTip, List } from '@/components'; +import { ProjectItem } from './ProjectItem'; +import api, { resultTypes } from '@/apis'; +import { PROJECT_STATUS } from '@/constants'; +import { FC } from '@/interfaces'; +import { AppState } from '@/store'; import { clearProjects, createProject, resetProjectsState, setProjectsState, -} from '../store/project/slice'; -import style from '../style'; -import { toLowerCamelCase } from '../utils'; -import { clickEffect } from '../utils/style'; +} from '@/store/project/slice'; +import style from '@/style'; +import { toLowerCamelCase } from '@/utils'; +import { clickEffect } from '@/utils/style'; /** 项目列表的属性接口 */ interface ProjectListProps { diff --git a/src/components/FileItem.tsx b/src/components/project/FileItem.tsx similarity index 99% rename from src/components/FileItem.tsx rename to src/components/project/FileItem.tsx index 8719b92..db405e3 100644 --- a/src/components/FileItem.tsx +++ b/src/components/project/FileItem.tsx @@ -1,21 +1,20 @@ import { css } from '@emotion/core'; import { Checkbox } from 'antd'; import classNames from 'classnames'; -import React from 'react'; import { useIntl } from 'react-intl'; import { FileUploadProgress, Icon, ImageOCRProgress, TranslationProgress, -} from '.'; +} from '@/components'; import { FILE_NOT_EXIST_REASON, FILE_SAFE_STATUS, IMAGE_COVER, } from '@/constants'; import { FC, File } from '@/interfaces'; -import style from '../style'; +import style from '@/style'; import { cardClickEffect, clickEffect } from '@/utils/style'; /** 文件条目的属性接口 */ diff --git a/src/components/FileList.tsx b/src/components/project/FileList.tsx similarity index 88% rename from src/components/FileList.tsx rename to src/components/project/FileList.tsx index 13e8b65..cc8cf5c 100644 --- a/src/components/FileList.tsx +++ b/src/components/project/FileList.tsx @@ -7,8 +7,10 @@ import { FilePond } from 'react-filepond'; import { useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; -import { Button, EmptyTip, FileItem, List, OutputList } from '.'; -import { api, resultTypes } from '../apis'; +import { Button, EmptyTip, List } from '@/components'; +import { OutputList } from './OutputList'; +import { FileItem } from './FileItem'; +import { api, resultTypes } from '@/apis'; import { FILE_NOT_EXIST_REASON, FILE_SAFE_STATUS, @@ -20,7 +22,7 @@ import { import { FC, File as MFile, Project, Target } from '@/interfaces'; import { AppState } from '@/store'; import { setFilesState } from '@/store/file/slice'; -import style from '../style'; +import style from '@/style'; import { toLowerCamelCase } from '@/utils'; import { can } from '@/utils/user'; import { routes } from '@/pages/routes'; @@ -28,7 +30,8 @@ import { moeflowCompanionServiceState, useMoeflowCompanion, } from '@/services/moeflow_companion/use_moeflow_companion'; -import { ListPageSpec } from './List'; +import { ListPageSpec } from '@/components/List'; +import { FilePondFile } from 'filepond'; /** 文件列表的属性接口 */ interface FileListProps { @@ -119,6 +122,61 @@ export const FileList: FC = ({ }); }; + /** 处理文件添加 */ + const handleFileAdd = (error: any, file: FilePondFile) => { + // 加载预览图 + const realFile = file.file; + if (/^image.+/.test(file.file.type)) { + loadImage( + realFile, + (img) => { + setItems((items) => + items.map((item) => { + if (item.id === file.id && img) { + return { + ...item, + coverUrl: (img as HTMLCanvasElement).toDataURL(), + }; + } + return item; + }), + ); + }, + { + maxWidth: coverWidth, + maxHeight: coverHeight, + crop: true, + canvas: true, + ...({ imageSmoothingQuality: 'high' } as any), // @type 版本太旧 + }, + ); + } + const uploadingFile: MFile = { + id: file.id, + name: file.filename, + saveName: '', + type: FILE_TYPE.IMAGE, + sourceCount: 0, + translatedSourceCount: 0, + checkedSourceCount: 0, + fileNotExistReason: FILE_NOT_EXIST_REASON.UNKNOWN, + safeStatus: FILE_SAFE_STATUS.NEED_MACHINE_CHECK, + parseStatus: PARSE_STATUS.NOT_START, + parseStatusDetailName: formatMessage({ id: 'file.parseNotStart' }), + parseErrorTypeDetailName: '', + url: '', + parentID: null, + fileTargetCache: { + translatedSourceCount: 0, + checkedSourceCount: 0, + }, + uploading: true, + uploadState: 'uploading', + uploadPercent: 0, + }; + setItems((items) => [uploadingFile, ...items]); + }; + /** 获取元素 */ const loadPage = ({ page, pageSize, word, cancelToken }: ListPageSpec) => { setLoading(true); @@ -194,12 +252,6 @@ export const FileList: FC = ({ flex-direction: column; justify-content: flex-start; align-items: stretch; - .FileList__ImageOCRProgressWrapper { - width: 100%; - } - .FileList__ImageOCRProgress { - padding: 0 ${style.paddingBase}px; - } .FileList__Header { width: 100%; height: 45px; @@ -249,56 +301,7 @@ export const FileList: FC = ({ dropOnElement={false} allowMultiple maxParallelUploads={5} - onaddfile={(_, file) => { - // 加载预览图 - const realFile = file.file; - if (/^image.+/.test(file.file.type)) { - loadImage( - realFile, - (img) => { - setItems((items) => { - return items.map((item) => { - if (item.id === file.id && img) { - item.coverUrl = (img as HTMLCanvasElement).toDataURL(); - } - return item; - }); - }); - }, - { - maxWidth: coverWidth, - maxHeight: coverHeight, - crop: true, - canvas: true, - ...({ imageSmoothingQuality: 'high' } as any), // @type 版本太旧 - }, - ); - } - const uploadingFile: MFile = { - id: file.id, - name: file.filename, - saveName: '', - type: FILE_TYPE.IMAGE, - sourceCount: 0, - translatedSourceCount: 0, - checkedSourceCount: 0, - fileNotExistReason: FILE_NOT_EXIST_REASON.UNKNOWN, - safeStatus: FILE_SAFE_STATUS.NEED_MACHINE_CHECK, - parseStatus: PARSE_STATUS.NOT_START, - parseStatusDetailName: formatMessage({ id: 'file.parseNotStart' }), - parseErrorTypeDetailName: '', - url: '', - parentID: null, - fileTargetCache: { - translatedSourceCount: 0, - checkedSourceCount: 0, - }, - uploading: true, - uploadState: 'uploading', - uploadPercent: 0, - }; - setItems((items) => [uploadingFile, ...items]); - }} + onaddfile={handleFileAdd} // 上传中 onprocessfileprogress={(file, progress) => { setItems((items) => diff --git a/src/components/FileUploadProgress.tsx b/src/components/project/FileUploadProgress.tsx similarity index 95% rename from src/components/FileUploadProgress.tsx rename to src/components/project/FileUploadProgress.tsx index 2e5e07d..3e7a54d 100644 --- a/src/components/FileUploadProgress.tsx +++ b/src/components/project/FileUploadProgress.tsx @@ -1,10 +1,9 @@ import { css } from '@emotion/core'; -import { Icon } from '.'; +import { Icon } from '@/components'; import classNames from 'classnames'; -import React from 'react'; import { useIntl } from 'react-intl'; -import { FC, File } from '../interfaces'; -import style from '../style'; +import { FC, File } from '@/interfaces'; +import style from '@/style'; /** 文件上传进度的属性接口 */ interface FileUploadProgressProps { file: File; diff --git a/src/components/LanguageSelect.tsx b/src/components/project/LanguageSelect.tsx similarity index 97% rename from src/components/LanguageSelect.tsx rename to src/components/project/LanguageSelect.tsx index 25ccdd5..2900dea 100644 --- a/src/components/LanguageSelect.tsx +++ b/src/components/project/LanguageSelect.tsx @@ -2,7 +2,7 @@ import { css } from '@emotion/core'; import { Select } from 'antd'; import { SelectProps } from 'antd/lib/select'; import classNames from 'classnames'; -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { api } from '@/apis'; import { FC } from '@/interfaces'; diff --git a/src/components/Output.tsx b/src/components/project/Output.tsx similarity index 91% rename from src/components/Output.tsx rename to src/components/project/Output.tsx index acdbc20..a0937b8 100644 --- a/src/components/Output.tsx +++ b/src/components/project/Output.tsx @@ -1,14 +1,13 @@ import { css } from '@emotion/core'; import classNames from 'classnames'; import dayjs from 'dayjs'; -import React from 'react'; import { useIntl } from 'react-intl'; -import { Avatar } from '.'; -import { APIOutput } from '../apis/output'; -import { OUTPUT_STATUS, OUTPUT_TYPE } from '../constants/output'; -import { FC } from '../interfaces'; -import style from '../style'; -import { Button } from './Button'; +import { Avatar } from '..'; +import { APIOutput } from '../../apis/output'; +import { OUTPUT_STATUS, OUTPUT_TYPE } from '../../constants/output'; +import { FC } from '../../interfaces'; +import style from '../../style'; +import { Button } from '../shared/Button'; /** 导出的属性接口 */ interface OutputProps { diff --git a/src/components/OutputList.tsx b/src/components/project/OutputList.tsx similarity index 93% rename from src/components/OutputList.tsx rename to src/components/project/OutputList.tsx index a39ce7a..5bafa93 100644 --- a/src/components/OutputList.tsx +++ b/src/components/project/OutputList.tsx @@ -2,17 +2,17 @@ import { css } from '@emotion/core'; import { Switch } from 'antd'; import { Canceler } from 'axios'; import classNames from 'classnames'; -import React, { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useIntl } from 'react-intl'; -import apis from '../apis'; -import { APIOutput, CreateOutputData } from '../apis/output'; -import { OUTPUT_STATUS, OUTPUT_TYPE } from '../constants/output'; -import { FC } from '../interfaces'; -import style from '../style'; -import { toLowerCamelCase } from '../utils'; -import { getCancelToken } from '../utils/api'; -import { useStateRef } from '../hooks'; -import { Button } from './Button'; +import apis from '@/apis'; +import { APIOutput, CreateOutputData } from '@/apis/output'; +import { OUTPUT_STATUS, OUTPUT_TYPE } from '@/constants/output'; +import { FC } from '@/interfaces'; +import style from '@/style'; +import { toLowerCamelCase } from '@/utils'; +import { getCancelToken } from '@/utils/api'; +import { useStateRef } from '@/hooks'; +import { Button } from '@/components'; import { Output } from './Output'; /** 模板的属性接口 */ diff --git a/src/components/project-set/ProjectCreateForm.tsx b/src/components/project/ProjectCreateForm.tsx similarity index 98% rename from src/components/project-set/ProjectCreateForm.tsx rename to src/components/project/ProjectCreateForm.tsx index 4858433..354563e 100644 --- a/src/components/project-set/ProjectCreateForm.tsx +++ b/src/components/project/ProjectCreateForm.tsx @@ -2,13 +2,8 @@ import { css } from '@emotion/core'; import { Button, Form as AntdForm, Input, message, Modal, Upload } from 'antd'; import { useState } from 'react'; import { useIntl } from 'react-intl'; -import { - Form, - FormItem, - RoleRadioGroup, - TypeRadioGroup, - LanguageSelect, -} from '..'; +import { Form, FormItem, RoleRadioGroup, TypeRadioGroup } from '@/components'; +import { LanguageSelect } from './LanguageSelect'; import { api } from '@/apis'; import { FC, UserProjectSet, UserTeam } from '@/interfaces'; import { useDispatch, useSelector } from 'react-redux'; @@ -18,7 +13,7 @@ import { AppState } from '@/store'; import { toLowerCamelCase } from '@/utils'; import { GROUP_ALLOW_APPLY_TYPE } from '@/constants'; import { configs } from '@/configs'; -import style from '../../style'; +import style from '@/style'; import { resetFilesState } from '@/store/file/slice'; /** 创建项目表单的属性接口 */ diff --git a/src/components/project-set/ProjectImportForm.tsx b/src/components/project/ProjectImportForm.tsx similarity index 98% rename from src/components/project-set/ProjectImportForm.tsx rename to src/components/project/ProjectImportForm.tsx index c89896b..0a657e6 100644 --- a/src/components/project-set/ProjectImportForm.tsx +++ b/src/components/project/ProjectImportForm.tsx @@ -1,6 +1,6 @@ import { css } from '@emotion/core'; import { Button, message, Upload } from 'antd'; -import React, { useState } from 'react'; +import { useState } from 'react'; import { useIntl } from 'react-intl'; import { api } from '@/apis'; import { FC, UserProjectSet, UserTeam } from '@/interfaces'; @@ -30,7 +30,6 @@ export const ProjectImportForm: FC = ({ }) => { const { formatMessage } = useIntl(); // i18n const dispatch = useDispatch(); - const history = useHistory(); const [importing, setImporting] = useState(false); const [importStatuses, setImportStatuses] = useState([]); const [importFileList, setImportFileList] = useState(); diff --git a/src/components/project/ProjectSettingTarget.tsx b/src/components/project/ProjectSettingTarget.tsx index aed669b..6fb42dc 100644 --- a/src/components/project/ProjectSettingTarget.tsx +++ b/src/components/project/ProjectSettingTarget.tsx @@ -2,19 +2,19 @@ import { css } from '@emotion/core'; import { Button, message, Modal } from 'antd'; import { CancelToken } from 'axios'; import classNames from 'classnames'; -import React, { useState } from 'react'; +import { useState } from 'react'; import { useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; import api, { resultTypes } from '@/apis'; import { EmptyTip, Icon, - LanguageSelect, List, ListItem, LIST_ITEM_DEFAULT_HEIGHT, Spin, } from '@/components'; +import { LanguageSelect } from './LanguageSelect'; import { FC, Target, Project } from '@/interfaces'; import { AppState } from '@/store'; import { increaseCurrentProjectTargetCount } from '@/store/project/slice'; diff --git a/src/components/setting/LocalePicker.tsx b/src/components/setting/LocalePicker.tsx index db915ed..f7c58a3 100644 --- a/src/components/setting/LocalePicker.tsx +++ b/src/components/setting/LocalePicker.tsx @@ -1,8 +1,7 @@ -import { FC } from '../../interfaces'; +import { FC } from '@/interfaces'; import { MenuProps } from 'antd'; -import { availableLocales, setLocale } from '../../locales'; -import { Dropdown } from '../Dropdown'; -import { Icon } from '../icon'; +import { availableLocales, setLocale } from '@/locales'; +import { Dropdown, Icon } from '@/components'; import { css } from '@emotion/core'; const dropDownMenuItemStyle = css` diff --git a/src/components/setting/UserBasicSettings.tsx b/src/components/setting/UserBasicSettings.tsx index 2f66996..9e9fac6 100644 --- a/src/components/setting/UserBasicSettings.tsx +++ b/src/components/setting/UserBasicSettings.tsx @@ -4,7 +4,7 @@ import { useIntl } from 'react-intl'; import { Content, ContentItem, ContentTitle } from '@/components'; import { FC } from '@/interfaces'; import style from '../../style'; -import { AvatarUpload } from '../AvatarUpload'; +import { AvatarUpload } from '../shared/AvatarUpload'; import { UserEditForm } from './UserEditForm'; import { LocalePicker } from './LocalePicker'; diff --git a/src/components/Avatar.tsx b/src/components/shared/Avatar.tsx similarity index 90% rename from src/components/Avatar.tsx rename to src/components/shared/Avatar.tsx index 032b50c..aa68191 100644 --- a/src/components/Avatar.tsx +++ b/src/components/shared/Avatar.tsx @@ -3,8 +3,8 @@ import { Avatar as AntdAvatar, Badge } from 'antd'; import { AvatarProps as AntdAvatarProps } from 'antd/lib/avatar'; import React from 'react'; import { useIntl } from 'react-intl'; -import defaultTeamAvatar from '../images/common/default-team-avatar.jpg'; -import defaultUserAvatar from '../images/common/default-user-avatar.jpg'; +import defaultTeamAvatar from '@/images/common/default-team-avatar.jpg'; +import defaultUserAvatar from '@/images/common/default-user-avatar.jpg'; /** 头像的属性接口 */ interface AvatarProps { diff --git a/src/components/AvatarUpload.tsx b/src/components/shared/AvatarUpload.tsx similarity index 91% rename from src/components/AvatarUpload.tsx rename to src/components/shared/AvatarUpload.tsx index 482bca0..7be510b 100644 --- a/src/components/AvatarUpload.tsx +++ b/src/components/shared/AvatarUpload.tsx @@ -1,18 +1,18 @@ import { css } from '@emotion/core'; import React, { useState } from 'react'; import { useIntl } from 'react-intl'; -import { FC } from '../interfaces'; +import { FC } from '../../interfaces'; import classNames from 'classnames'; import ImgCrop from 'antd-img-crop'; import { Button, Upload } from 'antd'; import { UploadOutlined } from '@ant-design/icons'; -import { runtimeConfig } from '../configs'; +import { runtimeConfig } from '../../configs'; import { useDispatch, useSelector } from 'react-redux'; -import { AppState } from '../store'; -import { setUserInfo } from '../store/user/slice'; -import { setCurrentTeamInfo } from '../store/team/slice'; -import { Avatar } from '.'; +import { AppState } from '../../store'; +import { setUserInfo } from '../../store/user/slice'; +import { setCurrentTeamInfo } from '../../store/team/slice'; +import { Avatar } from '..'; import { usePromised } from '@jokester/ts-commonutil/lib/react/hook/use-promised'; /** 头像上传的属性接口 */ interface AvatarUploadProps { diff --git a/src/components/Button.tsx b/src/components/shared/Button.tsx similarity index 98% rename from src/components/Button.tsx rename to src/components/shared/Button.tsx index c0104ce..72b5f87 100644 --- a/src/components/Button.tsx +++ b/src/components/shared/Button.tsx @@ -3,9 +3,9 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIconProps } from '@fortawesome/react-fontawesome'; import classNames from 'classnames'; import type React from 'react'; -import { Icon } from '.'; +import { Icon } from '..'; import { FC } from '@/interfaces'; -import style from '../style'; +import style from '../../style'; import { clickEffect } from '@/utils/style'; import { Tooltip, TooltipProps } from './Tooltip'; diff --git a/src/components/Dropdown.tsx b/src/components/shared/Dropdown.tsx similarity index 100% rename from src/components/Dropdown.tsx rename to src/components/shared/Dropdown.tsx diff --git a/src/components/Header.tsx b/src/components/shared/Header.tsx similarity index 92% rename from src/components/Header.tsx rename to src/components/shared/Header.tsx index cdc7057..529c629 100644 --- a/src/components/Header.tsx +++ b/src/components/shared/Header.tsx @@ -1,19 +1,19 @@ import { css } from '@emotion/core'; import { MenuProps } from 'antd'; -import { Icon } from './icon'; +import { Icon } from '@/components'; import { useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router'; import { Avatar } from './Avatar'; import { Dropdown } from './Dropdown'; -import { AppState } from '../store'; -import { setUserToken } from '../store/user/slice'; -import style from '../style'; -import { FC } from '../interfaces'; -import { clickEffect } from '../utils/style'; +import { AppState } from '@/store'; +import { setUserToken } from '@/store/user/slice'; +import style from '@/style'; +import { FC } from '@/interfaces'; +import { clickEffect } from '@/utils/style'; import classNames from 'classnames'; -import { routes } from '../pages/routes'; -import { LocalePicker } from './setting/LocalePicker'; +import { routes } from '@/pages/routes'; +import { LocalePicker } from '@/components/setting/LocalePicker'; /** 头部的属性接口 */ interface HeaderProps { diff --git a/src/components/Label.tsx b/src/components/shared/Label.tsx similarity index 97% rename from src/components/Label.tsx rename to src/components/shared/Label.tsx index b09f682..e4804b9 100644 --- a/src/components/Label.tsx +++ b/src/components/shared/Label.tsx @@ -1,18 +1,17 @@ import { css } from '@emotion/core'; import classNames from 'classnames'; -import React from 'react'; import { useSelector } from 'react-redux'; -import { AppState } from '../store'; -import style from '../style'; +import { AppState } from '@/store'; +import style from '@/style'; import { Direction, FC, labelSavingStatuses, LabelStatus, WritingMode, -} from '../interfaces'; -import { Spin } from './Spin'; -import { SOURCE_POSITION_TYPE } from '../constants/source'; +} from '@/interfaces'; +import { Spin } from '@/components/shared/Spin'; +import { SOURCE_POSITION_TYPE } from '@/constants/source'; import { useIntl } from 'react-intl'; /** 标签的属性接口 */ diff --git a/src/components/List.tsx b/src/components/shared/List.tsx similarity index 98% rename from src/components/List.tsx rename to src/components/shared/List.tsx index 19dd712..cff85ba 100644 --- a/src/components/List.tsx +++ b/src/components/shared/List.tsx @@ -8,9 +8,9 @@ import React, { useEffect, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; import ReactResizeDetector from 'react-resize-detector'; import { useDebouncedCallback } from 'use-debounce'; -import { ListSearchInput, ListSkeletonItem } from '.'; -import { AppState } from '../store'; -import { getCancelToken } from '../utils/api'; +import { ListSearchInput, ListSkeletonItem } from '@/components'; +import { AppState } from '@/store'; +import { getCancelToken } from '@/utils/api'; import { ListSearchInputProps } from './ListSearchInput'; export interface ListPageSpec { diff --git a/src/components/ListItem.tsx b/src/components/shared/ListItem.tsx similarity index 97% rename from src/components/ListItem.tsx rename to src/components/shared/ListItem.tsx index a3c32ff..6e7e05a 100644 --- a/src/components/ListItem.tsx +++ b/src/components/shared/ListItem.tsx @@ -2,9 +2,9 @@ import { css } from '@emotion/core'; import classNames from 'classnames'; import React, { useEffect, useState } from 'react'; import { matchPath, useHistory, useLocation } from 'react-router-dom'; -import { FC } from '../interfaces'; -import style from '../style'; -import { clickEffect } from '../utils/style'; +import { FC } from '@/interfaces'; +import style from '@/style'; +import { clickEffect } from '@/utils/style'; export const LIST_ITEM_DEFAULT_HEIGHT = 45; /** 列表元素的属性接口 */ diff --git a/src/components/ListSearchInput.tsx b/src/components/shared/ListSearchInput.tsx similarity index 96% rename from src/components/ListSearchInput.tsx rename to src/components/shared/ListSearchInput.tsx index 5d5d70a..ebbb7c3 100644 --- a/src/components/ListSearchInput.tsx +++ b/src/components/shared/ListSearchInput.tsx @@ -3,9 +3,9 @@ import { Input } from 'antd'; import { SearchProps } from 'antd/lib/input/Search'; import classNames from 'classnames'; import React, { useState } from 'react'; -import { FC } from '../interfaces'; -import style from '../style'; -import { clickEffect } from '../utils/style'; +import { FC } from '@/interfaces'; +import style from '@/style'; +import { clickEffect } from '@/utils/style'; /** 列表搜索框的属性接口 */ export interface ListSearchInputProps extends SearchProps { diff --git a/src/components/ListSkeletonItem.tsx b/src/components/shared/ListSkeletonItem.tsx similarity index 87% rename from src/components/ListSkeletonItem.tsx rename to src/components/shared/ListSkeletonItem.tsx index 438a132..7688b2c 100644 --- a/src/components/ListSkeletonItem.tsx +++ b/src/components/shared/ListSkeletonItem.tsx @@ -1,9 +1,8 @@ import { css } from '@emotion/core'; import { Skeleton } from 'antd'; -import React from 'react'; -import { FC } from '../interfaces'; -import style from '../style'; -import { listItemStyle } from '../utils/style'; +import { FC } from '@/interfaces'; +import style from '@/style'; +import { listItemStyle } from '@/utils/style'; /** 列表元素骨架的属性接口 */ interface ListSkeletonItemProps { diff --git a/src/components/LoadingIcon.tsx b/src/components/shared/LoadingIcon.tsx similarity index 100% rename from src/components/LoadingIcon.tsx rename to src/components/shared/LoadingIcon.tsx diff --git a/src/components/Movable.tsx b/src/components/shared/Movable.tsx similarity index 99% rename from src/components/Movable.tsx rename to src/components/shared/Movable.tsx index 10b0c9a..c05abbf 100644 --- a/src/components/Movable.tsx +++ b/src/components/shared/Movable.tsx @@ -10,8 +10,8 @@ import React, { useMemo, useRef, } from 'react'; -import { FC } from '../interfaces'; -import { useStateRef } from '../hooks'; +import { FC } from '../../interfaces'; +import { useStateRef } from '../../hooks'; /** * ========== Context ========= @@ -963,7 +963,7 @@ const MovableItemWithoutRef: React.ForwardRefRenderFunction< clientY, }; // 设置当前激活的子组件 - onFocusIndexChange && onFocusIndexChange(itemIndex); + onFocusIndexChange && onFocusIndexChange(itemIndex!); // 执行移动开始回调 if (allowMoveRef.current && onMoveStart && button === 0) { onMoveStart(state); diff --git a/src/components/Spin.tsx b/src/components/shared/Spin.tsx similarity index 92% rename from src/components/Spin.tsx rename to src/components/shared/Spin.tsx index af438ea..a9e0c21 100644 --- a/src/components/Spin.tsx +++ b/src/components/shared/Spin.tsx @@ -1,9 +1,8 @@ import { css } from '@emotion/core'; import { Spin as AntdSpin } from 'antd'; import { SpinProps as AntdSpinProps } from 'antd/lib/spin'; -import React from 'react'; import { LoadingIcon } from './LoadingIcon'; -import { FC } from '../interfaces'; +import { FC } from '@/interfaces'; /** Spin 的属性接口 */ interface SpinProps extends AntdSpinProps { diff --git a/src/components/Tooltip.tsx b/src/components/shared/Tooltip.tsx similarity index 96% rename from src/components/Tooltip.tsx rename to src/components/shared/Tooltip.tsx index 1694288..9e11c26 100644 --- a/src/components/Tooltip.tsx +++ b/src/components/shared/Tooltip.tsx @@ -3,12 +3,11 @@ import { TooltipPropsWithTitle as AntdTooltipPropsWithTitle, TooltipPropsWithOverlay as AntdTooltipPropsWithOverlay, } from 'antd/lib/tooltip'; -import React from 'react'; import { useSelector } from 'react-redux'; import { AppState } from '@/store'; import { FC } from '@/interfaces'; import { css, Global } from '@emotion/core'; -import style from '../style'; +import style from '@/style'; /** * 手机版自动隐藏的 Tooltip diff --git a/src/components/FileCover.tsx b/src/components/unused/FileCover.tsx similarity index 93% rename from src/components/FileCover.tsx rename to src/components/unused/FileCover.tsx index cd2fca6..9d18ffc 100644 --- a/src/components/FileCover.tsx +++ b/src/components/unused/FileCover.tsx @@ -1,8 +1,7 @@ import { css } from '@emotion/core'; import classNames from 'classnames'; -import React from 'react'; -import { FC, File } from '../interfaces'; -import style from '../style'; +import { FC, File } from '../../interfaces'; +import style from '../../style'; /** 文件封面的属性接口 */ interface FileCoverProps { diff --git a/src/components/ImageOCRProgress.tsx b/src/components/unused/ImageOCRProgress.tsx similarity index 91% rename from src/components/ImageOCRProgress.tsx rename to src/components/unused/ImageOCRProgress.tsx index 747c129..71adb40 100644 --- a/src/components/ImageOCRProgress.tsx +++ b/src/components/unused/ImageOCRProgress.tsx @@ -1,10 +1,10 @@ import { css } from '@emotion/core'; import classNames from 'classnames'; import React from 'react'; -import { Icon, Tooltip } from '.'; -import { ParseStatuses, PARSE_STATUS } from '../constants'; -import { FC } from '../interfaces'; -import style from '../style'; +import { Icon, Tooltip } from '..'; +import { ParseStatuses, PARSE_STATUS } from '../../constants'; +import { FC } from '../../interfaces'; +import style from '../../style'; const HEIGHT = 12; export const IMAGE_OCR_PROGRESS_HEIGHT = HEIGHT; diff --git a/src/locales/en.json b/src/locales/en.json index 65c71be..792ab6d 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -263,7 +263,8 @@ "fileList.changeMode": "Toggle Display Mode", "file.parseNotStart": "Auto Mark as Not Started", "fileList.ocrButtonTip": "Image Auto Tagging", - "fileList.autoTranslate": "Detect marks and Translate", + "fileList.aiTranslate": "Auto Translate", + "fileList.aiTranslateTip": "Detect marks and Translate", "project.startOCR": "Start Auto Tagging", "project.startOCRTip": "Do you want to start auto-tagging images? (Only images marked as \"Not Started\" or \"Tagging Failed\" will be auto-tagged, and the quota will only be deducted upon successful tagging)", "site.ocrQuota": "Auto Tagging Limit", diff --git a/src/locales/messages.yaml b/src/locales/messages.yaml index 7ca6874..8902bc0 100644 --- a/src/locales/messages.yaml +++ b/src/locales/messages.yaml @@ -805,10 +805,10 @@ fileList.ocrButtonTip: zhCn: 图片自动标记 en: Image Auto Tagging fileList.aiTranslate: - zhCn: AI翻译 - en: Translate with AI + zhCn: 自动翻译 + en: Auto Translate fileList.aiTranslateTip: - zhCn: 自动识别并翻译 + zhCn: 自动识别文字并翻译 en: Detect marks and Translate project.startOCR: zhCn: 开始自动标记 diff --git a/src/locales/zh-cn.json b/src/locales/zh-cn.json index 064546e..788302f 100644 --- a/src/locales/zh-cn.json +++ b/src/locales/zh-cn.json @@ -263,7 +263,8 @@ "fileList.changeMode": "切换显示模式", "file.parseNotStart": "自动标记未开始", "fileList.ocrButtonTip": "图片自动标记", - "fileList.autoTranslate": "自动识别并翻译", + "fileList.aiTranslate": "自动翻译", + "fileList.aiTranslateTip": "自动识别文字并翻译", "project.startOCR": "开始自动标记", "project.startOCRTip": "您要开始图片自动标记吗?(仅自动标记“未开始”、“标记失败”的图片,且仅成功时扣减限额)", "site.ocrQuota": "自动标记限额", diff --git a/src/pages/ProjectSet.tsx b/src/pages/ProjectSet.tsx index 8f3f672..4e09839 100644 --- a/src/pages/ProjectSet.tsx +++ b/src/pages/ProjectSet.tsx @@ -28,8 +28,8 @@ import { setCurrentProjectSetSaga } from '@/store/projectSet/slice'; import style from '../style'; import { can } from '@/utils/user'; import Project from './Project'; -import { ProjectCreateForm } from '@/components/project-set/ProjectCreateForm'; -import { ProjectImportForm } from '@/components/project-set/ProjectImportForm'; +import { ProjectCreateForm } from '@/components/project/ProjectCreateForm'; +import { ProjectImportForm } from '@/components/project/ProjectImportForm'; /** 项目集页的属性接口 */ interface ProjectSetProps {} diff --git a/src/services/moeflow_companion/BatchProcessModal.ts b/src/services/moeflow_companion/BatchProcessModal.ts new file mode 100644 index 0000000..f197610 --- /dev/null +++ b/src/services/moeflow_companion/BatchProcessModal.ts @@ -0,0 +1,6 @@ +import { FC } from '@/interfaces'; +import { Modal } from 'antd'; + +export const MoeflowCompanionBatchProcessModal: FC = (props: {}) => { + return null; +}; diff --git a/src/services/moeflow_companion/TranslateCompanion.tsx b/src/services/moeflow_companion/TranslateCompanion.tsx index 9e604ca..7688434 100644 --- a/src/services/moeflow_companion/TranslateCompanion.tsx +++ b/src/services/moeflow_companion/TranslateCompanion.tsx @@ -2,7 +2,7 @@ import { FC } from '@/interfaces'; import { RefObject, useRef, useState } from 'react'; import { FilePond } from 'react-filepond'; import { css } from '@emotion/core'; -import { Button } from '@/components/Button'; +import { Button } from '@/components/shared/Button'; import { createMoeflowProjectZip, LPFile } from '../labelplus_packager'; import { FailureResults } from '@/apis'; import { measureImgSize } from '@jokester/ts-commonutil/lib/web/measure-img'; diff --git a/src/style.ts b/src/style.ts index e5f223f..8f035e8 100644 --- a/src/style.ts +++ b/src/style.ts @@ -98,7 +98,7 @@ const antdVarsM = { export default { ...antdVars, ...otherVars, -}; +} as const; // 供 config-overrides.js 引用,转换成 antd Less 连字符格式,用于覆盖其 Less 配置 export const antdLessVars = toHyphenCase(antdVars) as Record; export const antdLessVarsM = toHyphenCase(antdVarsM) as Record; From 6744e82004d48bc81d7c4e097372137a48a58244 Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Sun, 31 Aug 2025 18:38:07 +0900 Subject: [PATCH 21/27] move files --- src/components/admin/AdminSiteSetting.tsx | 4 +- src/components/admin/AdminUserList.tsx | 6 +- .../{ => dashboard}/DashboardMenu.tsx | 4 +- src/components/index.ts | 85 +++++++++---------- .../ProjectSetCreateForm.tsx | 14 +-- .../{ => project-set}/ProjectSetEditForm.tsx | 19 ++--- .../{ => project-set}/ProjectSetList.tsx | 18 ++-- .../ProjectSetSettingBase.tsx | 12 ++- src/components/project/FileItem.tsx | 10 +-- src/components/project/FileList.tsx | 2 +- src/components/project/ProjectTargetList.tsx | 12 +-- .../{ => shared-form}/AuthFormWrapper.tsx | 11 ++- .../{ => shared-form}/AuthLoginedTip.tsx | 11 ++- .../{ => shared-form}/CAPTCHAInput.tsx | 0 .../{ => shared-form}/CAPTCHAModal.tsx | 12 +-- .../{ => shared-form}/EmailInput.tsx | 0 .../{ => shared-form}/EmailVCodeInputItem.tsx | 10 +-- src/components/{ => shared-form}/Form.tsx | 3 +- src/components/{ => shared-form}/FormItem.tsx | 3 +- .../{ => shared-form}/GroupJoinForm.tsx | 7 +- .../{ => shared-form}/MemberList.tsx | 31 ++++--- .../{ => shared-form}/RoleRadioGroup.tsx | 6 +- .../{ => shared-form}/RoleSelect.tsx | 8 +- .../{ => shared-form}/TypeRadioGroup.tsx | 2 +- .../{ => shared-form}/UserEmailEditForm.tsx | 14 +-- .../UserPasswordEditForm.tsx | 12 +-- .../{ => shared-form}/VCodeInput.tsx | 0 .../{ => shared-member}/ApplicationList.tsx | 15 +++- .../{ => shared-member}/InvitationList.tsx | 27 +++--- .../{ => shared-member}/InviteUser.tsx | 20 ++--- .../UserInvitationList.tsx | 26 +++--- src/components/{ => shared}/Content.tsx | 5 +- src/components/{ => shared}/ContentItem.tsx | 5 +- src/components/{ => shared}/ContentTitle.tsx | 5 +- src/components/{ => shared}/DashboardBox.tsx | 2 +- .../{ => shared}/DebounceStatus.tsx | 5 +- src/components/{ => shared}/EmptyTip.tsx | 4 +- src/components/{ => shared}/NavTab.tsx | 11 ++- src/components/{ => shared}/NavTabs.tsx | 12 +-- src/components/{ => shared}/TabBarM.tsx | 9 +- .../{ => shared}/TranslationProgress.tsx | 7 +- src/components/{ => team}/TeamCreateForm.tsx | 18 ++-- src/components/{ => team}/TeamEditForm.tsx | 18 ++-- .../{ => team}/TeamInsightProjectList.tsx | 24 +++--- .../{ => team}/TeamInsightUserList.tsx | 20 ++--- src/components/{ => team}/TeamList.tsx | 6 +- src/components/{ => team}/TeamSearchList.tsx | 20 ++--- src/components/{ => team}/TeamSettingBase.tsx | 22 ++--- src/pages/Dashboard.tsx | 2 +- src/pages/Login.tsx | 5 +- src/pages/ProjectSetSetting.tsx | 12 +-- 51 files changed, 308 insertions(+), 308 deletions(-) rename src/components/{ => dashboard}/DashboardMenu.tsx (99%) rename src/components/{ => project-set}/ProjectSetCreateForm.tsx (88%) rename src/components/{ => project-set}/ProjectSetEditForm.tsx (86%) rename src/components/{ => project-set}/ProjectSetList.tsx (91%) rename src/components/{ => project-set}/ProjectSetSettingBase.tsx (81%) rename src/components/{ => shared-form}/AuthFormWrapper.tsx (94%) rename src/components/{ => shared-form}/AuthLoginedTip.tsx (89%) rename src/components/{ => shared-form}/CAPTCHAInput.tsx (100%) rename src/components/{ => shared-form}/CAPTCHAModal.tsx (95%) rename src/components/{ => shared-form}/EmailInput.tsx (100%) rename src/components/{ => shared-form}/EmailVCodeInputItem.tsx (98%) rename src/components/{ => shared-form}/Form.tsx (92%) rename src/components/{ => shared-form}/FormItem.tsx (86%) rename src/components/{ => shared-form}/GroupJoinForm.tsx (91%) rename src/components/{ => shared-form}/MemberList.tsx (92%) rename src/components/{ => shared-form}/RoleRadioGroup.tsx (95%) rename src/components/{ => shared-form}/RoleSelect.tsx (89%) rename src/components/{ => shared-form}/TypeRadioGroup.tsx (98%) rename src/components/{ => shared-form}/UserEmailEditForm.tsx (93%) rename src/components/{ => shared-form}/UserPasswordEditForm.tsx (90%) rename src/components/{ => shared-form}/VCodeInput.tsx (100%) rename src/components/{ => shared-member}/ApplicationList.tsx (98%) rename src/components/{ => shared-member}/InvitationList.tsx (94%) rename src/components/{ => shared-member}/InviteUser.tsx (94%) rename src/components/{ => shared-member}/UserInvitationList.tsx (94%) rename src/components/{ => shared}/Content.tsx (83%) rename src/components/{ => shared}/ContentItem.tsx (84%) rename src/components/{ => shared}/ContentTitle.tsx (86%) rename src/components/{ => shared}/DashboardBox.tsx (97%) rename src/components/{ => shared}/DebounceStatus.tsx (97%) rename src/components/{ => shared}/EmptyTip.tsx (93%) rename src/components/{ => shared}/NavTab.tsx (91%) rename src/components/{ => shared}/NavTabs.tsx (91%) rename src/components/{ => shared}/TabBarM.tsx (95%) rename src/components/{ => shared}/TranslationProgress.tsx (97%) rename src/components/{ => team}/TeamCreateForm.tsx (91%) rename src/components/{ => team}/TeamEditForm.tsx (92%) rename src/components/{ => team}/TeamInsightProjectList.tsx (95%) rename src/components/{ => team}/TeamInsightUserList.tsx (95%) rename src/components/{ => team}/TeamList.tsx (96%) rename src/components/{ => team}/TeamSearchList.tsx (94%) rename src/components/{ => team}/TeamSettingBase.tsx (94%) diff --git a/src/components/admin/AdminSiteSetting.tsx b/src/components/admin/AdminSiteSetting.tsx index f5280fd..2f75daf 100644 --- a/src/components/admin/AdminSiteSetting.tsx +++ b/src/components/admin/AdminSiteSetting.tsx @@ -8,8 +8,8 @@ import { api } from '@/apis'; import { APISiteSetting } from '@/apis/siteSetting'; import { FC } from '@/interfaces'; import { toLowerCamelCase } from '@/utils'; -import { Form } from '@/components/Form'; -import { FormItem } from '@/components/FormItem'; +import { Form } from '@/components/shared-form/Form'; +import { FormItem } from '@/components/shared-form/FormItem'; function textareaToArray(textarea: string): string[] { return textarea.trim() === '' diff --git a/src/components/admin/AdminUserList.tsx b/src/components/admin/AdminUserList.tsx index d03e5ca..b6faf31 100644 --- a/src/components/admin/AdminUserList.tsx +++ b/src/components/admin/AdminUserList.tsx @@ -17,9 +17,9 @@ import type { FilterValue, SorterResult } from 'antd/es/table/interface'; import { api } from '@/apis'; import { toLowerCamelCase } from '@/utils'; import { APIUser } from '@/apis/user'; -import { FormItem } from '@/components/FormItem'; -import { Form } from '@/components/Form'; -import { EmailInput } from '@/components/EmailInput'; +import { FormItem } from '@/components/shared-form/FormItem'; +import { Form } from '@/components/shared-form/Form'; +import { EmailInput } from '@/components/shared-form/EmailInput'; import { EMAIL_REGEX, USER_NAME_REGEX } from '@/utils/regex'; interface TableParams { diff --git a/src/components/DashboardMenu.tsx b/src/components/dashboard/DashboardMenu.tsx similarity index 99% rename from src/components/DashboardMenu.tsx rename to src/components/dashboard/DashboardMenu.tsx index cc12d71..ee0605f 100644 --- a/src/components/DashboardMenu.tsx +++ b/src/components/dashboard/DashboardMenu.tsx @@ -5,12 +5,12 @@ import React from 'react'; import { useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; import { matchPath, useHistory, useLocation } from 'react-router-dom'; -import { Avatar, Dropdown, Icon, ListItem, TeamList, Tooltip } from '.'; +import { Avatar, Dropdown, Icon, ListItem, TeamList, Tooltip } from '..'; import { FC } from '@/interfaces'; import { AppState } from '@/store'; import { resetProjectsState } from '@/store/project/slice'; import { setUserToken, UserState } from '@/store/user/slice'; -import style from '../style'; +import style from '../../style'; import { clickEffect } from '@/utils/style'; import { routes } from '@/pages/routes'; diff --git a/src/components/index.ts b/src/components/index.ts index 42c24a2..6d99775 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,31 +1,28 @@ export { Icon } from './icon'; -export { ApplicationList } from './ApplicationList'; -export { AuthFormWrapper } from './AuthFormWrapper'; -export { AuthLoginedTip } from './AuthLoginedTip'; +export { ApplicationList } from './shared-member/ApplicationList'; +export { AuthFormWrapper } from './shared-form/AuthFormWrapper'; export { Avatar } from './shared/Avatar'; export { Button } from './shared/Button'; -export { CAPTCHAInput } from './CAPTCHAInput'; -export { CAPTCHAModal } from './CAPTCHAModal'; -export { Content } from './Content'; -export { ContentItem } from './ContentItem'; -export { ContentTitle } from './ContentTitle'; -export { DashboardBox } from './DashboardBox'; -export { DashboardMenu } from './DashboardMenu'; -export { DebounceStatus } from './DebounceStatus'; +export { CAPTCHAInput } from './shared-form/CAPTCHAInput'; +export { CAPTCHAModal } from './shared-form/CAPTCHAModal'; +export { Content } from './shared/Content'; +export { ContentItem } from './shared/ContentItem'; +export { ContentTitle } from './shared/ContentTitle'; +export { DashboardBox } from './shared/DashboardBox'; +export { DashboardMenu } from './dashboard/DashboardMenu'; +export { DebounceStatus } from './shared/DebounceStatus'; export { Dropdown } from './shared/Dropdown'; -export { EmailInput } from './EmailInput'; -export { EmailVCodeInputItem } from './EmailVCodeInputItem'; -export { EmptyTip } from './EmptyTip'; +export { EmailInput } from './shared-form/EmailInput'; +export { EmailVCodeInputItem } from './shared-form/EmailVCodeInputItem'; +export { EmptyTip } from './shared/EmptyTip'; export { FileList } from './project/FileList'; -export { FileUploadProgress } from './project/FileUploadProgress'; -export { Form } from './Form'; -export { FormItem } from './FormItem'; -export { GroupJoinForm } from './GroupJoinForm'; +export { Form } from './shared-form/Form'; +export { FormItem } from './shared-form/FormItem'; +export { GroupJoinForm } from './shared-form/GroupJoinForm'; export { Header } from './shared/Header'; export { useHotKey } from './HotKey'; -export { ImageOCRProgress } from './unused/ImageOCRProgress'; -export { InvitationList } from './InvitationList'; -export { InviteUser } from './InviteUser'; +export { InvitationList } from './shared-member/InvitationList'; +export { InviteUser } from './shared-member/InviteUser'; export { Label } from './shared/Label'; export { LanguageSelect } from './project/LanguageSelect'; export { List } from './shared/List'; @@ -33,31 +30,31 @@ export { ListItem, LIST_ITEM_DEFAULT_HEIGHT } from './shared/ListItem'; export { ListSearchInput } from './shared/ListSearchInput'; export { ListSkeletonItem } from './shared/ListSkeletonItem'; export { LoadingIcon } from './shared/LoadingIcon'; -export { MemberList } from './MemberList'; +export { MemberList } from './shared-form/MemberList'; export { MovableArea, MovableItem } from './shared/Movable'; -export { NavTab } from './NavTab'; -export { NavTabs } from './NavTabs'; +export { NavTab } from './shared/NavTab'; +export { NavTabs } from './shared/NavTabs'; export { OutputList } from './project/OutputList'; export { ProjectList } from './project-list/ProjectList'; -export { ProjectSetCreateForm } from './ProjectSetCreateForm'; -export { ProjectSetEditForm } from './ProjectSetEditForm'; -export { ProjectSetList } from './ProjectSetList'; -export { ProjectSetSettingBase } from './ProjectSetSettingBase'; -export { RoleRadioGroup } from './RoleRadioGroup'; -export { RoleSelect } from './RoleSelect'; +export { ProjectSetCreateForm } from './project-set/ProjectSetCreateForm'; +export { ProjectSetEditForm } from './project-set/ProjectSetEditForm'; +export { ProjectSetList } from './project-set/ProjectSetList'; +export { ProjectSetSettingBase } from './project-set/ProjectSetSettingBase'; +export { RoleRadioGroup } from './shared-form/RoleRadioGroup'; +export { RoleSelect } from './shared-form/RoleSelect'; export { Spin } from './shared/Spin'; -export { TabBarM } from './TabBarM'; -export { TeamCreateForm } from './TeamCreateForm'; -export { TeamEditForm } from './TeamEditForm'; -export { TeamInsightProjectList } from './TeamInsightProjectList'; -export { TeamInsightUserList } from './TeamInsightUserList'; -export { TeamList } from './TeamList'; -export { TeamSearchList } from './TeamSearchList'; -export { TeamSettingBase } from './TeamSettingBase'; +export { TabBarM } from './shared/TabBarM'; +export { TeamCreateForm } from './team/TeamCreateForm'; +export { TeamEditForm } from './team/TeamEditForm'; +export { TeamInsightProjectList } from './team/TeamInsightProjectList'; +export { TeamInsightUserList } from './team/TeamInsightUserList'; +export { TeamList } from './team/TeamList'; +export { TeamSearchList } from './team/TeamSearchList'; +export { TeamSettingBase } from './team/TeamSettingBase'; export { Tooltip } from './shared/Tooltip'; -export { TranslationProgress } from './TranslationProgress'; -export { TypeRadioGroup } from './TypeRadioGroup'; -export { UserEmailEditForm } from './UserEmailEditForm'; -export { UserInvitationList } from './UserInvitationList'; -export { UserPasswordEditForm } from './UserPasswordEditForm'; -export { VCodeInput } from './VCodeInput'; +export { TranslationProgress } from './shared/TranslationProgress'; +export { TypeRadioGroup } from './shared-form/TypeRadioGroup'; +export { UserEmailEditForm } from './shared-form/UserEmailEditForm'; +export { UserInvitationList } from './shared-member/UserInvitationList'; +export { UserPasswordEditForm } from './shared-form/UserPasswordEditForm'; +export { VCodeInput } from './shared-form/VCodeInput'; diff --git a/src/components/ProjectSetCreateForm.tsx b/src/components/project-set/ProjectSetCreateForm.tsx similarity index 88% rename from src/components/ProjectSetCreateForm.tsx rename to src/components/project-set/ProjectSetCreateForm.tsx index 1f344ca..d884107 100644 --- a/src/components/ProjectSetCreateForm.tsx +++ b/src/components/project-set/ProjectSetCreateForm.tsx @@ -1,18 +1,18 @@ import { css } from '@emotion/core'; import { Button, Form as AntdForm, Input, message } from 'antd'; -import React, { useState } from 'react'; +import { useState } from 'react'; import { useIntl } from 'react-intl'; import { useDispatch } from 'react-redux'; import { useHistory } from 'react-router-dom'; -import { Form, FormItem } from '.'; -import api from '../apis'; -import { FC, ProjectSet } from '../interfaces'; -import { resetProjectsState } from '../store/project/slice'; +import { Form, FormItem } from '@/components'; +import api from '@/apis'; +import { FC, ProjectSet } from '@/interfaces'; +import { resetProjectsState } from '@/store/project/slice'; import { createProjectSet, resetProjectSetsState, -} from '../store/projectSet/slice'; -import { toLowerCamelCase } from '../utils'; +} from '@/store/projectSet/slice'; +import { toLowerCamelCase } from '@/utils'; /** 创建团队表单的属性接口 */ interface ProjectSetCreateFormProps { diff --git a/src/components/ProjectSetEditForm.tsx b/src/components/project-set/ProjectSetEditForm.tsx similarity index 86% rename from src/components/ProjectSetEditForm.tsx rename to src/components/project-set/ProjectSetEditForm.tsx index f2373ba..3af8e4d 100644 --- a/src/components/ProjectSetEditForm.tsx +++ b/src/components/project-set/ProjectSetEditForm.tsx @@ -1,18 +1,15 @@ import { css } from '@emotion/core'; import { Button, Form as AntdForm, Input, message } from 'antd'; -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; -import { Form, FormItem } from '.'; -import api from '../apis'; -import { FC, UserProjectSet } from '../interfaces'; -import { AppState } from '../store'; -import { - editProjectSet, - setCurrentProjectSet, -} from '../store/projectSet/slice'; -import style from '../style'; -import { toLowerCamelCase } from '../utils'; +import { Form, FormItem } from '@/components'; +import api from '@/apis'; +import { FC, UserProjectSet } from '@/interfaces'; +import { AppState } from '@/store'; +import { editProjectSet, setCurrentProjectSet } from '@/store/projectSet/slice'; +import style from '@/style'; +import { toLowerCamelCase } from '@/utils'; /** 修改项目集表单的属性接口 */ interface ProjectSetEditFormProps { diff --git a/src/components/ProjectSetList.tsx b/src/components/project-set/ProjectSetList.tsx similarity index 91% rename from src/components/ProjectSetList.tsx rename to src/components/project-set/ProjectSetList.tsx index b13e1a0..e3a5429 100644 --- a/src/components/ProjectSetList.tsx +++ b/src/components/project-set/ProjectSetList.tsx @@ -8,19 +8,19 @@ import { useLocation, useRouteMatch, } from 'react-router-dom'; -import { EmptyTip, Icon, List, ListItem } from '.'; -import api, { resultTypes } from '../apis'; -import { TEAM_PERMISSION } from '../constants'; -import { FC, UserProjectSet, UserTeam } from '../interfaces'; -import { AppState } from '../store'; -import { resetProjectsState } from '../store/project/slice'; +import { EmptyTip, Icon, List, ListItem } from '@/components'; +import api, { resultTypes } from '@/apis'; +import { TEAM_PERMISSION } from '@/constants'; +import { FC, UserProjectSet, UserTeam } from '@/interfaces'; +import { AppState } from '@/store'; +import { resetProjectsState } from '@/store/project/slice'; import { clearProjectSets, createProjectSet, setProjectSetsState, -} from '../store/projectSet/slice'; -import { toLowerCamelCase } from '../utils'; -import { can } from '../utils/user'; +} from '@/store/projectSet/slice'; +import { toLowerCamelCase } from '@/utils'; +import { can } from '@/utils/user'; import { LIST_ITEM_DEFAULT_HEIGHT } from '@/components/shared/ListItem'; /** 项目集的属性接口 */ diff --git a/src/components/ProjectSetSettingBase.tsx b/src/components/project-set/ProjectSetSettingBase.tsx similarity index 81% rename from src/components/ProjectSetSettingBase.tsx rename to src/components/project-set/ProjectSetSettingBase.tsx index b40a657..52d5251 100644 --- a/src/components/ProjectSetSettingBase.tsx +++ b/src/components/project-set/ProjectSetSettingBase.tsx @@ -1,9 +1,13 @@ import { css } from '@emotion/core'; -import React from 'react'; import { useIntl } from 'react-intl'; -import { Content, ContentItem, ContentTitle, ProjectSetEditForm } from '.'; -import { FC } from '../interfaces'; -import style from '../style'; +import { + Content, + ContentItem, + ContentTitle, + ProjectSetEditForm, +} from '@/components'; +import { FC } from '@/interfaces'; +import style from '@/style'; /** 项目集基础设置的属性接口 */ interface ProjectSetSettingBaseProps { diff --git a/src/components/project/FileItem.tsx b/src/components/project/FileItem.tsx index db405e3..61f48f8 100644 --- a/src/components/project/FileItem.tsx +++ b/src/components/project/FileItem.tsx @@ -2,12 +2,10 @@ import { css } from '@emotion/core'; import { Checkbox } from 'antd'; import classNames from 'classnames'; import { useIntl } from 'react-intl'; -import { - FileUploadProgress, - Icon, - ImageOCRProgress, - TranslationProgress, -} from '@/components'; +import { Icon } from '@/components'; +import { FileUploadProgress } from '@/components/project/FileUploadProgress'; +import { TranslationProgress } from '@/components/shared/TranslationProgress'; +import { ImageOCRProgress } from '@/components/unused/ImageOCRProgress'; import { FILE_NOT_EXIST_REASON, FILE_SAFE_STATUS, diff --git a/src/components/project/FileList.tsx b/src/components/project/FileList.tsx index cc8cf5c..dde7166 100644 --- a/src/components/project/FileList.tsx +++ b/src/components/project/FileList.tsx @@ -30,7 +30,7 @@ import { moeflowCompanionServiceState, useMoeflowCompanion, } from '@/services/moeflow_companion/use_moeflow_companion'; -import { ListPageSpec } from '@/components/List'; +import { ListPageSpec } from '@/components/shared/List'; import { FilePondFile } from 'filepond'; /** 文件列表的属性接口 */ diff --git a/src/components/project/ProjectTargetList.tsx b/src/components/project/ProjectTargetList.tsx index a057902..4384fb7 100644 --- a/src/components/project/ProjectTargetList.tsx +++ b/src/components/project/ProjectTargetList.tsx @@ -4,18 +4,18 @@ import classNames from 'classnames'; import { useState } from 'react'; import { useIntl } from 'react-intl'; import { useSelector } from 'react-redux'; -import { api, resultTypes } from '../../apis'; +import { api, resultTypes } from '@/apis'; import { EmptyTip, List, ListItem, LIST_ITEM_DEFAULT_HEIGHT, TranslationProgress, -} from '..'; -import { FC, Project, Target } from '../../interfaces'; -import { AppState } from '../../store'; -import { toLowerCamelCase } from '../../utils'; -import style from '../../style'; +} from '@/components'; +import { FC, Project, Target } from '@/interfaces'; +import { AppState } from '@/store'; +import { toLowerCamelCase } from '@/utils'; +import style from '@/style'; /** 项目目标列表页的属性接口 */ interface ProjectTargetListProps { diff --git a/src/components/AuthFormWrapper.tsx b/src/components/shared-form/AuthFormWrapper.tsx similarity index 94% rename from src/components/AuthFormWrapper.tsx rename to src/components/shared-form/AuthFormWrapper.tsx index 6b3c059..c93f376 100644 --- a/src/components/AuthFormWrapper.tsx +++ b/src/components/shared-form/AuthFormWrapper.tsx @@ -1,16 +1,15 @@ import { css, Global } from '@emotion/core'; -import { Icon } from '.'; +import { Icon } from '@/components'; import { Button } from 'antd'; import { FormProps } from 'antd/lib/form'; import classNames from 'classnames'; -import React from 'react'; import { useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; import { AuthLoginedTip } from './AuthLoginedTip'; -import mascot from '../images/brand/mascot-jump1.png'; -import { AppState } from '../store'; -import style from '../style'; -import { FC } from '../interfaces'; +import mascot from '@/images/brand/mascot-jump1.png'; +import { AppState } from '@/store'; +import style from '@/style'; +import { FC } from '@/interfaces'; /** 身份验证页面通用的表单外框的属性接口 */ interface AuthFormWrapperProps { diff --git a/src/components/AuthLoginedTip.tsx b/src/components/shared-form/AuthLoginedTip.tsx similarity index 89% rename from src/components/AuthLoginedTip.tsx rename to src/components/shared-form/AuthLoginedTip.tsx index 1ec5a1e..13da6a9 100644 --- a/src/components/AuthLoginedTip.tsx +++ b/src/components/shared-form/AuthLoginedTip.tsx @@ -1,14 +1,13 @@ import { css } from '@emotion/core'; import { Button } from 'antd'; -import React from 'react'; import { useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router'; -import { Avatar } from './shared/Avatar'; -import { AppState } from '../store'; -import { setUserToken } from '../store/user/slice'; -import style from '../style'; -import { FC } from '../interfaces'; +import { Avatar } from '@/components'; +import { AppState } from '@/store'; +import { setUserToken } from '@/store/user/slice'; +import style from '@/style'; +import { FC } from '@/interfaces'; /** 已经登陆提示的属性接口 */ interface AuthLoginedTipProps { diff --git a/src/components/CAPTCHAInput.tsx b/src/components/shared-form/CAPTCHAInput.tsx similarity index 100% rename from src/components/CAPTCHAInput.tsx rename to src/components/shared-form/CAPTCHAInput.tsx diff --git a/src/components/CAPTCHAModal.tsx b/src/components/shared-form/CAPTCHAModal.tsx similarity index 95% rename from src/components/CAPTCHAModal.tsx rename to src/components/shared-form/CAPTCHAModal.tsx index a9d34e6..363bc50 100644 --- a/src/components/CAPTCHAModal.tsx +++ b/src/components/shared-form/CAPTCHAModal.tsx @@ -1,14 +1,14 @@ import { css } from '@emotion/core'; -import { Icon } from '.'; +import { Icon } from '@/components'; import { Button } from 'antd'; import { ValidateStatus as AntdValidateStatus } from 'antd/lib/form/FormItem'; import React, { useEffect, useRef, useState } from 'react'; import { useIntl } from 'react-intl'; -import { CAPTCHAInput } from '../components'; -import { CAPTCHAInputRef, CAPTCHAInputValue } from '../components/CAPTCHAInput'; -import style from '../style'; -import { FC } from '../interfaces'; -import { clickEffect } from '../utils/style'; +import { CAPTCHAInput } from '@/components'; +import { CAPTCHAInputRef, CAPTCHAInputValue } from './CAPTCHAInput'; +import style from '@/style'; +import { FC } from '@/interfaces'; +import { clickEffect } from '@/utils/style'; export type ValidateStatus = AntdValidateStatus; export interface OnSubmit { diff --git a/src/components/EmailInput.tsx b/src/components/shared-form/EmailInput.tsx similarity index 100% rename from src/components/EmailInput.tsx rename to src/components/shared-form/EmailInput.tsx diff --git a/src/components/EmailVCodeInputItem.tsx b/src/components/shared-form/EmailVCodeInputItem.tsx similarity index 98% rename from src/components/EmailVCodeInputItem.tsx rename to src/components/shared-form/EmailVCodeInputItem.tsx index 6659777..a39805a 100644 --- a/src/components/EmailVCodeInputItem.tsx +++ b/src/components/shared-form/EmailVCodeInputItem.tsx @@ -5,11 +5,11 @@ import { InputProps } from 'antd/lib/input'; import classNames from 'classnames'; import React, { useRef, useState, useEffect } from 'react'; import { useIntl } from 'react-intl'; -import api, { FailureResults, resultTypes } from '../apis'; -import style from '../style'; -import { FC } from '../interfaces'; -import { EMAIL_REGEX } from '../utils/regex'; -import { clickEffect } from '../utils/style'; +import api, { FailureResults, resultTypes } from '@/apis'; +import style from '@/style'; +import { FC } from '@/interfaces'; +import { EMAIL_REGEX } from '@/utils/regex'; +import { clickEffect } from '@/utils/style'; import { CAPTCHAInputValue } from './CAPTCHAInput'; import { CAPTCHAModal, OnSubmit, ValidateStatus } from './CAPTCHAModal'; import { EmailInput } from './EmailInput'; diff --git a/src/components/Form.tsx b/src/components/shared-form/Form.tsx similarity index 92% rename from src/components/Form.tsx rename to src/components/shared-form/Form.tsx index f8a4402..eb3c9da 100644 --- a/src/components/Form.tsx +++ b/src/components/shared-form/Form.tsx @@ -1,8 +1,7 @@ import { Form as AntdForm } from 'antd'; import { FormProps as AntdFormProps } from 'antd/lib/form'; import { Store } from 'rc-field-form/es/interface'; -import React from 'react'; -import { FC } from '../interfaces'; +import { FC } from '@/interfaces'; /** 表单的属性接口 */ interface FormProps {} diff --git a/src/components/FormItem.tsx b/src/components/shared-form/FormItem.tsx similarity index 86% rename from src/components/FormItem.tsx rename to src/components/shared-form/FormItem.tsx index 5103771..307061d 100644 --- a/src/components/FormItem.tsx +++ b/src/components/shared-form/FormItem.tsx @@ -1,7 +1,6 @@ import { Form as AntdForm } from 'antd'; import { FormItemProps as AntdFormItemProps } from 'antd/lib/form'; -import React from 'react'; -import { FC } from '../interfaces'; +import { FC } from '@/interfaces'; /** 表单的属性接口 */ interface FormItemProps {} diff --git a/src/components/GroupJoinForm.tsx b/src/components/shared-form/GroupJoinForm.tsx similarity index 91% rename from src/components/GroupJoinForm.tsx rename to src/components/shared-form/GroupJoinForm.tsx index e793138..53f9699 100644 --- a/src/components/GroupJoinForm.tsx +++ b/src/components/shared-form/GroupJoinForm.tsx @@ -1,12 +1,11 @@ import { css } from '@emotion/core'; import { Button, Form as AntdForm, Input } from 'antd'; import classNames from 'classnames'; -import React from 'react'; import { useIntl } from 'react-intl'; import { useHistory } from 'react-router-dom'; -import { GroupTypes } from '../apis/type'; -import { FC } from '../interfaces'; -import { ID_REGEX } from '../utils/regex'; +import { GroupTypes } from '@/apis/type'; +import { FC } from '@/interfaces'; +import { ID_REGEX } from '@/utils/regex'; import { Form } from './Form'; import { FormItem } from './FormItem'; diff --git a/src/components/MemberList.tsx b/src/components/shared-form/MemberList.tsx similarity index 92% rename from src/components/MemberList.tsx rename to src/components/shared-form/MemberList.tsx index c0cb37c..484b140 100644 --- a/src/components/MemberList.tsx +++ b/src/components/shared-form/MemberList.tsx @@ -5,18 +5,25 @@ import classNames from 'classnames'; import React, { useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; import { useSelector } from 'react-redux'; -import { Avatar, EmptyTip, Icon, List, ListItem, RoleSelect } from '.'; -import api, { resultTypes } from '../apis'; -import { GroupTypes } from '../apis/type'; -import { TEAM_PERMISSION } from '../constants'; -import { FC, Project, Role, UserTeam } from '../interfaces'; -import { AppState } from '../store'; -import style from '../style'; -import { toLowerCamelCase } from '../utils'; -import { getCancelToken } from '../utils/api'; -import { can } from '../utils/user'; -import { Spin } from './shared/Spin'; -import { TypeData } from './TypeRadioGroup'; +import { + Avatar, + EmptyTip, + Icon, + List, + ListItem, + RoleSelect, +} from '@/components'; +import api, { resultTypes } from '@/apis'; +import { GroupTypes } from '@/apis/type'; +import { TEAM_PERMISSION } from '@/constants'; +import { FC, Project, Role, UserTeam } from '@/interfaces'; +import { AppState } from '@/store'; +import style from '@/style'; +import { toLowerCamelCase } from '@/utils'; +import { getCancelToken } from '@/utils/api'; +import { can } from '../../utils/user'; +import { Spin } from '@/components'; +import { TypeData } from '@/components/shared-form/TypeRadioGroup'; /** 成员设置页的属性接口 */ interface MemberListProps { diff --git a/src/components/RoleRadioGroup.tsx b/src/components/shared-form/RoleRadioGroup.tsx similarity index 95% rename from src/components/RoleRadioGroup.tsx rename to src/components/shared-form/RoleRadioGroup.tsx index 80010a6..0488970 100644 --- a/src/components/RoleRadioGroup.tsx +++ b/src/components/shared-form/RoleRadioGroup.tsx @@ -2,9 +2,9 @@ import { css, Global } from '@emotion/core'; import { Button, Tag } from 'antd'; import React, { useState } from 'react'; import { useIntl } from 'react-intl'; -import { Icon, Tooltip, TypeRadioGroup } from '.'; -import { GroupTypes } from '../apis/type'; -import { FC } from '../interfaces'; +import { Icon, Tooltip, TypeRadioGroup } from '@/components'; +import { GroupTypes } from '@/apis/type'; +import { FC } from '@/interfaces'; import { TypeRadioGroupProps } from './TypeRadioGroup'; export interface RoleData { diff --git a/src/components/RoleSelect.tsx b/src/components/shared-form/RoleSelect.tsx similarity index 89% rename from src/components/RoleSelect.tsx rename to src/components/shared-form/RoleSelect.tsx index 76a6d0d..fc27d5b 100644 --- a/src/components/RoleSelect.tsx +++ b/src/components/shared-form/RoleSelect.tsx @@ -3,10 +3,10 @@ import { Select } from 'antd'; import { SelectValue } from 'antd/lib/select'; import classNames from 'classnames'; import React from 'react'; -import { TEAM_PERMISSION } from '../constants'; -import { FC, Project, Role, UserTeam } from '../interfaces'; -import { User } from '../interfaces/user'; -import { can } from '../utils/user'; +import { TEAM_PERMISSION } from '@/constants'; +import { FC, Project, Role, UserTeam } from '@/interfaces'; +import { User } from '@/interfaces/user'; +import { can } from '@/utils/user'; const { Option } = Select; diff --git a/src/components/TypeRadioGroup.tsx b/src/components/shared-form/TypeRadioGroup.tsx similarity index 98% rename from src/components/TypeRadioGroup.tsx rename to src/components/shared-form/TypeRadioGroup.tsx index 839f55b..c3d8ae9 100644 --- a/src/components/TypeRadioGroup.tsx +++ b/src/components/shared-form/TypeRadioGroup.tsx @@ -1,7 +1,7 @@ import { css } from '@emotion/core'; import { Radio, Skeleton } from 'antd'; import { RadioChangeEvent, RadioGroupProps } from 'antd/lib/radio'; -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { api } from '@/apis'; import { GroupTypes, TypeNames } from '@/apis/type'; import { configs } from '@/configs'; diff --git a/src/components/UserEmailEditForm.tsx b/src/components/shared-form/UserEmailEditForm.tsx similarity index 93% rename from src/components/UserEmailEditForm.tsx rename to src/components/shared-form/UserEmailEditForm.tsx index 6c39ef5..a2def29 100644 --- a/src/components/UserEmailEditForm.tsx +++ b/src/components/shared-form/UserEmailEditForm.tsx @@ -1,14 +1,14 @@ import { css } from '@emotion/core'; import { Button, Form as AntdForm, type InputRef, message } from 'antd'; -import React, { useRef, useState } from 'react'; +import { useRef, useState } from 'react'; import { useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; -import { EmailVCodeInputItem, Form, FormItem, VCodeInput } from '.'; -import api from '../apis'; -import { FC } from '../interfaces'; -import { AppState } from '../store'; -import { setUserInfo } from '../store/user/slice'; -import { toLowerCamelCase } from '../utils'; +import { EmailVCodeInputItem, Form, FormItem, VCodeInput } from '@/components'; +import api from '@/apis'; +import { FC } from '@/interfaces'; +import { AppState } from '@/store'; +import { setUserInfo } from '@/store/user/slice'; +import { toLowerCamelCase } from '../../utils'; /** 修改项目表单的属性接口 */ interface UserEmailEditFormProps { diff --git a/src/components/UserPasswordEditForm.tsx b/src/components/shared-form/UserPasswordEditForm.tsx similarity index 90% rename from src/components/UserPasswordEditForm.tsx rename to src/components/shared-form/UserPasswordEditForm.tsx index df8c9a2..eb7c2d1 100644 --- a/src/components/UserPasswordEditForm.tsx +++ b/src/components/shared-form/UserPasswordEditForm.tsx @@ -1,14 +1,14 @@ import { css } from '@emotion/core'; import { Button, Form as AntdForm, Input, message } from 'antd'; -import React, { useState } from 'react'; +import { useState } from 'react'; import { useIntl } from 'react-intl'; import { useDispatch } from 'react-redux'; import { useHistory } from 'react-router-dom'; -import { Form, FormItem } from '.'; -import api from '../apis'; -import { FC } from '../interfaces'; -import { setUserToken } from '../store/user/slice'; -import { toLowerCamelCase } from '../utils'; +import { Form, FormItem } from '@/components'; +import api from '@/apis'; +import { FC } from '@/interfaces'; +import { setUserToken } from '@/store/user/slice'; +import { toLowerCamelCase } from '@/utils'; /** 修改项目表单的属性接口 */ interface UserPasswordEditFormProps { diff --git a/src/components/VCodeInput.tsx b/src/components/shared-form/VCodeInput.tsx similarity index 100% rename from src/components/VCodeInput.tsx rename to src/components/shared-form/VCodeInput.tsx diff --git a/src/components/ApplicationList.tsx b/src/components/shared-member/ApplicationList.tsx similarity index 98% rename from src/components/ApplicationList.tsx rename to src/components/shared-member/ApplicationList.tsx index f82a244..87acf77 100644 --- a/src/components/ApplicationList.tsx +++ b/src/components/shared-member/ApplicationList.tsx @@ -2,10 +2,17 @@ import { css } from '@emotion/core'; import { message } from 'antd'; import { CancelToken } from 'axios'; import classNames from 'classnames'; -import React, { useState } from 'react'; +import { useState } from 'react'; import { useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; -import { Avatar, EmptyTip, Icon, List, ListItem, RoleSelect } from '.'; +import { + Avatar, + EmptyTip, + Icon, + List, + ListItem, + RoleSelect, +} from '@/components'; import { api, resultTypes } from '@/apis'; import { APIApplication } from '@/apis/application'; import { GroupTypes } from '@/apis/type'; @@ -17,12 +24,12 @@ import { import { FC, Project, UserTeam } from '@/interfaces'; import { AppState } from '@/store'; import { setRelatedApplicationsCount } from '@/store/site/slice'; -import style from '../style'; +import style from '@/style'; import { toLowerCamelCase } from '@/utils'; import { formatGroupType } from '@/utils/i18n'; import { clickEffect } from '@/utils/style'; import { can } from '@/utils/user'; -import { Spin } from './shared/Spin'; +import { Spin } from '@/components'; /** 申请管理页的属性接口 */ interface ApplicationListProps { diff --git a/src/components/InvitationList.tsx b/src/components/shared-member/InvitationList.tsx similarity index 94% rename from src/components/InvitationList.tsx rename to src/components/shared-member/InvitationList.tsx index 095b359..783de8c 100644 --- a/src/components/InvitationList.tsx +++ b/src/components/shared-member/InvitationList.tsx @@ -1,24 +1,23 @@ import { css } from '@emotion/core'; -import { Icon } from '.'; +import { Icon, Spin } from '@/components'; import { Drawer, message, Modal, Select } from 'antd'; import { SelectValue } from 'antd/lib/select'; import { CancelToken } from 'axios'; import classNames from 'classnames'; -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; import { useSelector } from 'react-redux'; -import { Avatar, EmptyTip, InviteUser, List, ListItem } from '.'; -import api, { resultTypes } from '../apis'; -import { GroupTypes } from '../apis/type'; -import { AppState } from '../store'; -import style from '../style'; -import { UserTeam, Role, Project } from '../interfaces'; -import { toLowerCamelCase } from '../utils'; -import { getCancelToken } from '../utils/api'; -import { FC } from '../interfaces'; -import { clickEffect } from '../utils/style'; -import { Spin } from './shared/Spin'; -import { INVITATION_STATUS } from '../constants'; +import { Avatar, EmptyTip, InviteUser, List, ListItem } from '@/components'; +import api, { resultTypes } from '@/apis'; +import { GroupTypes } from '@/apis/type'; +import { AppState } from '@/store'; +import style from '@/style'; +import { UserTeam, Role, Project } from '@/interfaces'; +import { toLowerCamelCase } from '@/utils'; +import { getCancelToken } from '@/utils/api'; +import { FC } from '@/interfaces'; +import { clickEffect } from '@/utils/style'; +import { INVITATION_STATUS } from '@/constants'; const { Option } = Select; diff --git a/src/components/InviteUser.tsx b/src/components/shared-member/InviteUser.tsx similarity index 94% rename from src/components/InviteUser.tsx rename to src/components/shared-member/InviteUser.tsx index 0237ff5..6930aa1 100644 --- a/src/components/InviteUser.tsx +++ b/src/components/shared-member/InviteUser.tsx @@ -1,19 +1,19 @@ import { css } from '@emotion/core'; -import { Icon } from '.'; +import { Icon } from '@/components'; import { message, Select } from 'antd'; import { SelectValue } from 'antd/lib/select'; import { CancelToken } from 'axios'; import classNames from 'classnames'; -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; -import { Avatar, EmptyTip, List, ListItem } from '.'; -import api, { resultTypes } from '../apis'; -import { GroupTypes } from '../apis/type'; -import { FC, Role, Project, UserTeam } from '../interfaces'; -import style from '../style'; -import { toLowerCamelCase } from '../utils'; -import { getCancelToken } from '../utils/api'; -import { Spin } from './shared/Spin'; +import { Avatar, EmptyTip, List, ListItem } from '@/components'; +import api, { resultTypes } from '@/apis'; +import { GroupTypes } from '@/apis/type'; +import { FC, Role, Project, UserTeam } from '@/interfaces'; +import style from '@/style'; +import { toLowerCamelCase } from '@/utils'; +import { getCancelToken } from '@/utils/api'; +import { Spin } from '@/components'; import { LIST_ITEM_DEFAULT_HEIGHT } from '@/components/shared/ListItem'; const { Option } = Select; diff --git a/src/components/UserInvitationList.tsx b/src/components/shared-member/UserInvitationList.tsx similarity index 94% rename from src/components/UserInvitationList.tsx rename to src/components/shared-member/UserInvitationList.tsx index 9364abb..0b71d70 100644 --- a/src/components/UserInvitationList.tsx +++ b/src/components/shared-member/UserInvitationList.tsx @@ -2,21 +2,21 @@ import { css } from '@emotion/core'; import { message } from 'antd'; import { CancelToken } from 'axios'; import classNames from 'classnames'; -import React, { useState } from 'react'; +import { useState } from 'react'; import { useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; -import { Avatar, EmptyTip, Icon, List, ListItem } from '.'; -import api, { resultTypes } from '../apis'; -import { APIInvitation } from '../apis/invitation'; -import { INVITATION_STATUS } from '../constants'; -import { FC, UserTeam } from '../interfaces'; -import { AppState } from '../store'; -import { setNewInvitationsCount } from '../store/site/slice'; -import { createTeam } from '../store/team/slice'; -import style from '../style'; -import { toLowerCamelCase } from '../utils'; -import { clickEffect } from '../utils/style'; -import { Spin } from './shared/Spin'; +import { Avatar, EmptyTip, Icon, List, ListItem } from '@/components'; +import api, { resultTypes } from '@/apis'; +import { APIInvitation } from '@/apis/invitation'; +import { INVITATION_STATUS } from '@/constants'; +import { FC, UserTeam } from '@/interfaces'; +import { AppState } from '@/store'; +import { setNewInvitationsCount } from '@/store/site/slice'; +import { createTeam } from '@/store/team/slice'; +import style from '@/style'; +import { toLowerCamelCase } from '@/utils'; +import { clickEffect } from '@/utils/style'; +import { Spin } from '@/components'; /** 申请管理页的属性接口 */ interface UserInvitationListProps { diff --git a/src/components/Content.tsx b/src/components/shared/Content.tsx similarity index 83% rename from src/components/Content.tsx rename to src/components/shared/Content.tsx index 75fe1c6..1ecea69 100644 --- a/src/components/Content.tsx +++ b/src/components/shared/Content.tsx @@ -1,8 +1,7 @@ import { css } from '@emotion/core'; import classNames from 'classnames'; -import React from 'react'; -import { FC } from '../interfaces'; -import style from '../style'; +import { FC } from '@/interfaces'; +import style from '@/style'; /** 一般内容 Body 的属性接口 */ interface ContentProps { diff --git a/src/components/ContentItem.tsx b/src/components/shared/ContentItem.tsx similarity index 84% rename from src/components/ContentItem.tsx rename to src/components/shared/ContentItem.tsx index 2adffae..c0ae494 100644 --- a/src/components/ContentItem.tsx +++ b/src/components/shared/ContentItem.tsx @@ -1,8 +1,7 @@ import { css } from '@emotion/core'; import classNames from 'classnames'; -import React from 'react'; -import { FC } from '../interfaces'; -import style from '../style'; +import { FC } from '@/interfaces'; +import style from '@/style'; /** 一般内容体中一行 的属性接口 */ interface ContentItemProps { diff --git a/src/components/ContentTitle.tsx b/src/components/shared/ContentTitle.tsx similarity index 86% rename from src/components/ContentTitle.tsx rename to src/components/shared/ContentTitle.tsx index feb7112..389d675 100644 --- a/src/components/ContentTitle.tsx +++ b/src/components/shared/ContentTitle.tsx @@ -1,8 +1,7 @@ import { css } from '@emotion/core'; import classNames from 'classnames'; -import React from 'react'; -import { FC } from '../interfaces'; -import style from '../style'; +import { FC } from '@/interfaces'; +import style from '@/style'; /** 一般内容标题的属性接口 */ interface ContentTitleProps { diff --git a/src/components/DashboardBox.tsx b/src/components/shared/DashboardBox.tsx similarity index 97% rename from src/components/DashboardBox.tsx rename to src/components/shared/DashboardBox.tsx index 8abdaf8..99ee461 100644 --- a/src/components/DashboardBox.tsx +++ b/src/components/shared/DashboardBox.tsx @@ -1,7 +1,7 @@ import { css } from '@emotion/core'; import React from 'react'; import { FC } from '@/interfaces'; -import style from '../style'; +import style from '../../style'; /** 带有导航栏的布局的属性接口 */ interface DashboardBoxProps { diff --git a/src/components/DebounceStatus.tsx b/src/components/shared/DebounceStatus.tsx similarity index 97% rename from src/components/DebounceStatus.tsx rename to src/components/shared/DebounceStatus.tsx index fb7fddd..618c1f8 100644 --- a/src/components/DebounceStatus.tsx +++ b/src/components/shared/DebounceStatus.tsx @@ -1,10 +1,9 @@ import { css } from '@emotion/core'; import classNames from 'classnames'; -import React from 'react'; import { useIntl } from 'react-intl'; -import { Icon } from '.'; +import { Icon } from '@/components'; import { FC, InputDebounceStatus } from '@/interfaces'; -import style from '../style'; +import style from '@/style'; /** 输入框防抖状态的属性接口 */ interface DebounceStatusProps { diff --git a/src/components/EmptyTip.tsx b/src/components/shared/EmptyTip.tsx similarity index 93% rename from src/components/EmptyTip.tsx rename to src/components/shared/EmptyTip.tsx index 2f4a40b..71a1c71 100644 --- a/src/components/EmptyTip.tsx +++ b/src/components/shared/EmptyTip.tsx @@ -1,8 +1,8 @@ import { css } from '@emotion/core'; import classNames from 'classnames'; import React from 'react'; -import { FC } from '../interfaces'; -import style from '../style'; +import { FC } from '@/interfaces'; +import style from '@/style'; /** 空提示的属性接口 */ interface EmptyTipProps { diff --git a/src/components/NavTab.tsx b/src/components/shared/NavTab.tsx similarity index 91% rename from src/components/NavTab.tsx rename to src/components/shared/NavTab.tsx index a4c277b..dc71d14 100644 --- a/src/components/NavTab.tsx +++ b/src/components/shared/NavTab.tsx @@ -1,12 +1,11 @@ import { css } from '@emotion/core'; -import React from 'react'; -import { FC } from '../interfaces'; +import { FC } from '@/interfaces'; import { NavLinkProps, NavLink } from 'react-router-dom'; -import style from '../style'; -import { clickEffect } from '../utils/style'; +import style from '@/style'; +import { clickEffect } from '@/utils/style'; import { useSelector } from 'react-redux'; -import { AppState } from '../store'; -import { Icon } from '.'; +import { AppState } from '@/store'; +import { Icon } from '@/components'; import classNames from 'classnames'; /** 带导航的 Tab 的属性接口 */ diff --git a/src/components/NavTabs.tsx b/src/components/shared/NavTabs.tsx similarity index 91% rename from src/components/NavTabs.tsx rename to src/components/shared/NavTabs.tsx index 21beeac..dcab8bf 100644 --- a/src/components/NavTabs.tsx +++ b/src/components/shared/NavTabs.tsx @@ -1,14 +1,14 @@ import { css } from '@emotion/core'; -import { Dropdown, Menu, MenuProps } from 'antd'; +import { Dropdown, MenuProps } from 'antd'; import classNames from 'classnames'; import React, { isValidElement } from 'react'; import { useSelector } from 'react-redux'; import { useMeasure } from 'react-use'; -import { Icon } from '.'; -import { FC } from '../interfaces'; -import { AppState } from '../store'; -import style from '../style'; -import { clickEffect } from '../utils/style'; +import { Icon } from '@/components'; +import { FC } from '@/interfaces'; +import { AppState } from '@/store'; +import style from '@/style'; +import { clickEffect } from '@/utils/style'; /** 标签栏的属性接口 */ interface NavTabsProps { diff --git a/src/components/TabBarM.tsx b/src/components/shared/TabBarM.tsx similarity index 95% rename from src/components/TabBarM.tsx rename to src/components/shared/TabBarM.tsx index 5f44639..4fb2d69 100644 --- a/src/components/TabBarM.tsx +++ b/src/components/shared/TabBarM.tsx @@ -1,13 +1,12 @@ import { css } from '@emotion/core'; import { TabBar } from 'antd-mobile'; -import React from 'react'; import { useIntl } from 'react-intl'; import { useSelector } from 'react-redux'; import { matchPath, useHistory, useLocation } from 'react-router-dom'; -import { Icon } from '.'; -import { FC } from '../interfaces'; -import { AppState } from '../store'; -import style from '../style'; +import { Icon } from '@/components'; +import { FC } from '@/interfaces'; +import { AppState } from '@/store'; +import style from '@/style'; /** 手机版首页底部 TabBar 的属性接口 */ interface TabBarProps { diff --git a/src/components/TranslationProgress.tsx b/src/components/shared/TranslationProgress.tsx similarity index 97% rename from src/components/TranslationProgress.tsx rename to src/components/shared/TranslationProgress.tsx index 5db0f90..12ae40c 100644 --- a/src/components/TranslationProgress.tsx +++ b/src/components/shared/TranslationProgress.tsx @@ -1,10 +1,9 @@ import { css } from '@emotion/core'; import classNames from 'classnames'; -import React from 'react'; import { useIntl } from 'react-intl'; -import { Icon, Tooltip } from '.'; -import { FC } from '../interfaces'; -import style from '../style'; +import { Icon, Tooltip } from '@/components'; +import { FC } from '@/interfaces'; +import style from '@/style'; /** 文件翻译的属性接口 */ interface TranslationProgressProps { sourceCount: number; diff --git a/src/components/TeamCreateForm.tsx b/src/components/team/TeamCreateForm.tsx similarity index 91% rename from src/components/TeamCreateForm.tsx rename to src/components/team/TeamCreateForm.tsx index 9f37ba2..a56cf54 100644 --- a/src/components/TeamCreateForm.tsx +++ b/src/components/team/TeamCreateForm.tsx @@ -1,17 +1,17 @@ import { css } from '@emotion/core'; import { Button, Form as AntdForm, Input, message } from 'antd'; -import React, { useState } from 'react'; +import { useState } from 'react'; import { useIntl } from 'react-intl'; import { useDispatch } from 'react-redux'; import { useHistory } from 'react-router-dom'; -import { Form, FormItem, RoleRadioGroup, TypeRadioGroup } from '.'; -import api from '../apis'; -import { GROUP_ALLOW_APPLY_TYPE } from '../constants'; -import { FC, UserTeam } from '../interfaces'; -import { resetProjectSetsState } from '../store/projectSet/slice'; -import { createTeam } from '../store/team/slice'; -import { toLowerCamelCase } from '../utils'; -import { TEAM_NAME_REGEX } from '../utils/regex'; +import { Form, FormItem, RoleRadioGroup, TypeRadioGroup } from '@/components'; +import api from '@/apis'; +import { GROUP_ALLOW_APPLY_TYPE } from '@/constants'; +import { FC, UserTeam } from '@/interfaces'; +import { resetProjectSetsState } from '@/store/projectSet/slice'; +import { createTeam } from '@/store/team/slice'; +import { toLowerCamelCase } from '@/utils'; +import { TEAM_NAME_REGEX } from '@/utils/regex'; /** 创建团队表单的属性接口 */ interface TeamCreateFormProps { diff --git a/src/components/TeamEditForm.tsx b/src/components/team/TeamEditForm.tsx similarity index 92% rename from src/components/TeamEditForm.tsx rename to src/components/team/TeamEditForm.tsx index 1af4111..ae1d5d5 100644 --- a/src/components/TeamEditForm.tsx +++ b/src/components/team/TeamEditForm.tsx @@ -3,15 +3,15 @@ import { Button, Form as AntdForm, Input, message } from 'antd'; import React, { useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; -import { Form, FormItem, RoleRadioGroup, TypeRadioGroup } from '.'; -import api from '../apis'; -import { GROUP_ALLOW_APPLY_TYPE, TEAM_PERMISSION } from '../constants'; -import { FC, UserTeam } from '../interfaces'; -import { AppState } from '../store'; -import { editTeam, setCurrentTeam } from '../store/team/slice'; -import { toLowerCamelCase } from '../utils'; -import { TEAM_NAME_REGEX } from '../utils/regex'; -import { can } from '../utils/user'; +import { Form, FormItem, RoleRadioGroup, TypeRadioGroup } from '@/components'; +import api from '@/apis'; +import { GROUP_ALLOW_APPLY_TYPE, TEAM_PERMISSION } from '@/constants'; +import { FC, UserTeam } from '@/interfaces'; +import { AppState } from '@/store'; +import { editTeam, setCurrentTeam } from '@/store/team/slice'; +import { toLowerCamelCase } from '@/utils'; +import { TEAM_NAME_REGEX } from '@/utils/regex'; +import { can } from '@/utils/user'; /** 修改团队表单的属性接口 */ interface TeamEditFormProps { diff --git a/src/components/TeamInsightProjectList.tsx b/src/components/team/TeamInsightProjectList.tsx similarity index 95% rename from src/components/TeamInsightProjectList.tsx rename to src/components/team/TeamInsightProjectList.tsx index 767ff9d..9e18446 100644 --- a/src/components/TeamInsightProjectList.tsx +++ b/src/components/team/TeamInsightProjectList.tsx @@ -8,25 +8,25 @@ import { Tag, message, } from 'antd'; -import { Button as CustomButton } from './shared/Button'; +import { Button as CustomButton } from '../shared/Button'; import classNames from 'classnames'; import produce from 'immer'; import qs from 'qs'; -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; import { useSelector } from 'react-redux'; import { Link, useHistory, useLocation, useRouteMatch } from 'react-router-dom'; -import { Icon, ListSearchInput } from '.'; -import apis from '../apis'; -import { APIInsightProject, APIInsightUserProject } from '../apis/insight'; -import { usePagination } from '../hooks'; -import { FC, Team } from '../interfaces'; -import { AppState } from '../store'; -import style from '../style'; -import { toLowerCamelCase } from '../utils'; +import { Icon, ListSearchInput } from '..'; +import apis from '@/apis'; +import { APIInsightProject, APIInsightUserProject } from '@/apis/insight'; +import { usePagination } from '@/hooks'; +import { FC, Team } from '@/interfaces'; +import { AppState } from '@/store'; +import style from '@/style'; +import { toLowerCamelCase } from '@/utils'; import dayjs from 'dayjs'; -import { OUTPUT_STATUS, OUTPUT_TYPE } from '../constants/output'; -import { clickEffect } from '../utils/style'; +import { OUTPUT_STATUS, OUTPUT_TYPE } from '@/constants/output'; +import { clickEffect } from '@/utils/style'; const { Column } = Table; diff --git a/src/components/TeamInsightUserList.tsx b/src/components/team/TeamInsightUserList.tsx similarity index 95% rename from src/components/TeamInsightUserList.tsx rename to src/components/team/TeamInsightUserList.tsx index f7aba73..1f7d383 100644 --- a/src/components/TeamInsightUserList.tsx +++ b/src/components/team/TeamInsightUserList.tsx @@ -3,19 +3,19 @@ import { Button, Pagination, Result, Table, Tag } from 'antd'; import classNames from 'classnames'; import produce from 'immer'; import qs from 'qs'; -import React, { useState } from 'react'; +import { useState } from 'react'; import { useIntl } from 'react-intl'; import { useSelector } from 'react-redux'; import { Link, useHistory, useLocation, useRouteMatch } from 'react-router-dom'; -import { Icon, ListSearchInput } from '.'; -import apis from '../apis'; -import { APIInsightUser } from '../apis/insight'; -import { APIUser } from '../apis/user'; -import { usePagination } from '../hooks'; -import { FC, Team } from '../interfaces'; -import { AppState } from '../store'; -import style from '../style'; -import { toLowerCamelCase } from '../utils'; +import { Icon, ListSearchInput } from '@/components'; +import apis from '@/apis'; +import { APIInsightUser } from '@/apis/insight'; +import { APIUser } from '@/apis/user'; +import { usePagination } from '@/hooks'; +import { FC, Team } from '@/interfaces'; +import { AppState } from '@/store'; +import style from '@/style'; +import { toLowerCamelCase } from '@/utils'; const { Column } = Table; diff --git a/src/components/TeamList.tsx b/src/components/team/TeamList.tsx similarity index 96% rename from src/components/TeamList.tsx rename to src/components/team/TeamList.tsx index 147e86a..33d568b 100644 --- a/src/components/TeamList.tsx +++ b/src/components/team/TeamList.tsx @@ -1,12 +1,12 @@ import { Button } from 'antd'; import { CancelToken } from 'axios'; import classNames from 'classnames'; -import React, { useState } from 'react'; +import { useState } from 'react'; import { useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; import { matchPath, useHistory, useLocation } from 'react-router-dom'; -import { Avatar, EmptyTip, Icon, List, ListItem } from '.'; -import api, { resultTypes } from '../apis'; +import { Avatar, EmptyTip, Icon, List, ListItem } from '@/components'; +import api, { resultTypes } from '@/apis'; import { FC, UserTeam } from '@/interfaces'; import { AppState } from '@/store'; import { resetProjectSetsState } from '@/store/projectSet/slice'; diff --git a/src/components/TeamSearchList.tsx b/src/components/team/TeamSearchList.tsx similarity index 94% rename from src/components/TeamSearchList.tsx rename to src/components/team/TeamSearchList.tsx index e8c2981..619e082 100644 --- a/src/components/TeamSearchList.tsx +++ b/src/components/team/TeamSearchList.tsx @@ -1,20 +1,20 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { Icon } from '.'; +import { Icon } from '@/components'; import { Button, Input, message, Modal } from 'antd'; import { CancelToken } from 'axios'; import classNames from 'classnames'; -import React, { useState } from 'react'; +import { useState } from 'react'; import { useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; -import { Avatar, EmptyTip, List, ListItem } from '.'; -import api, { resultTypes } from '../apis'; -import { TEAM_ALLOW_APPLY_TYPE } from '../constants'; -import { AppState } from '../store'; -import { createTeam } from '../store/team/slice'; -import { toLowerCamelCase } from '../utils'; -import { FC, UserTeam } from '../interfaces'; -import { Team } from '../interfaces'; +import { Avatar, EmptyTip, List, ListItem } from '@/components'; +import api, { resultTypes } from '@/apis'; +import { TEAM_ALLOW_APPLY_TYPE } from '@/constants'; +import { AppState } from '@/store'; +import { createTeam } from '@/store/team/slice'; +import { toLowerCamelCase } from '@/utils'; +import { FC, UserTeam } from '@/interfaces'; +import { Team } from '@/interfaces'; import { LIST_ITEM_DEFAULT_HEIGHT } from '@/components/shared/ListItem'; /** 搜索团队的属性接口 */ diff --git a/src/components/TeamSettingBase.tsx b/src/components/team/TeamSettingBase.tsx similarity index 94% rename from src/components/TeamSettingBase.tsx rename to src/components/team/TeamSettingBase.tsx index 5eb2d4d..4032856 100644 --- a/src/components/TeamSettingBase.tsx +++ b/src/components/team/TeamSettingBase.tsx @@ -1,23 +1,23 @@ import { css } from '@emotion/core'; -import { FormItem, Icon, Tooltip } from '.'; +import { FormItem, Icon, Tooltip } from '@/components'; import { Button, message, Modal, Tag } from 'antd'; -import React, { useState } from 'react'; +import { useState } from 'react'; import { useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; -import { Content, ContentItem, ContentTitle, TeamEditForm } from '.'; -import api from '../apis'; -import { TEAM_PERMISSION } from '../constants'; -import { AppState } from '../store'; +import { Content, ContentItem, ContentTitle, TeamEditForm } from '@/components'; +import api from '@/apis'; +import { TEAM_PERMISSION } from '@/constants'; +import { AppState } from '@/store'; import { clearCurrentTeam, deleteTeam as deleteTeamActionCreator, -} from '../store/team/slice'; -import style from '../style'; -import { FC, UserTeam } from '../interfaces'; -import { can } from '../utils/user'; +} from '@/store/team/slice'; +import style from '@/style'; +import { FC, UserTeam } from '@/interfaces'; +import { can } from '@/utils/user'; import copy from 'copy-to-clipboard'; -import { AvatarUpload } from './shared/AvatarUpload'; +import { AvatarUpload } from '@/components/shared/AvatarUpload'; /** 团队基础设置的属性接口 */ interface TeamSettingBaseProps { diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index e3e6530..8e98f8a 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -34,7 +34,7 @@ import MyProject from './MyProject'; import Team from './Team'; import TeamSetting from './TeamSetting'; import UserSetting from './UserSetting'; -import { MENU_COLLAPSED_WIDTH } from '@/components/DashboardMenu'; +import { MENU_COLLAPSED_WIDTH } from '@/components/dashboard/DashboardMenu'; /** 仪表盘的属性接口 */ interface DashboardProps {} diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 49eaaca..12535ff 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -13,7 +13,10 @@ import { Header, Form, } from '../components'; -import { CAPTCHAInputRef, checkCAPTCHA } from '../components/CAPTCHAInput'; +import { + CAPTCHAInputRef, + checkCAPTCHA, +} from '../components/shared-form/CAPTCHAInput'; import { setUserToken } from '../store/user/slice'; import { useTitle } from '../hooks'; import { FC } from '../interfaces'; diff --git a/src/pages/ProjectSetSetting.tsx b/src/pages/ProjectSetSetting.tsx index bb8d54d..7559b3f 100644 --- a/src/pages/ProjectSetSetting.tsx +++ b/src/pages/ProjectSetSetting.tsx @@ -14,12 +14,12 @@ import { NavTab, NavTabs, ProjectSetSettingBase, -} from '../components'; -import { Spin } from '../components'; -import { FC } from '../interfaces'; -import { AppState } from '../store'; -import { setCurrentProjectSetSaga } from '../store/projectSet/slice'; -import { useTitle } from '../hooks'; +} from '@/components'; +import { Spin } from '@/components'; +import { FC } from '@/interfaces'; +import { AppState } from '@/store'; +import { setCurrentProjectSetSaga } from '@/store/projectSet/slice'; +import { useTitle } from '@/hooks'; /** 项目集设置页的属性接口 */ interface ProjectSetSettingProps {} From d6fa56a73d136d9f4cfe5c7ae7f4a818646e7c0f Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Mon, 1 Sep 2025 02:51:46 +0900 Subject: [PATCH 22/27] dev only: when hot reloading, create api client with store.user.token --- src/apis/index.ts | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/apis/index.ts b/src/apis/index.ts index 263941a..c7717a1 100644 --- a/src/apis/index.ts +++ b/src/apis/index.ts @@ -39,11 +39,22 @@ import { lazyThenable } from '@jokester/ts-commonutil/lib/concurrency/lazy-thena const debugLogger = createDebugLogger('apis'); -const instanceP = lazyThenable(async () => - axios.create({ - baseURL: `${(await runtimeConfig).baseURL}`, - }), -); +const instanceP = lazyThenable(() => { + return runtimeConfig.then((r) => { + const token = store.getState().user.token; + // dev-only workaround: hot reload may create another instanceP with no common headers sets + const commonHeaders = + process.env.NODE_ENV === 'development' && token + ? { Authorization: `Bearer ${token}` } + : {}; + return axios.create({ + baseURL: `${r.baseURL}`, + headers: { + common: commonHeaders, + }, + }); + }); +}); let languageInterceptor: number | null = null; @@ -205,7 +216,7 @@ export async function request( data: error.response.data, default: () => { // 清理 token - store.dispatch(setUserToken({ token: '' })); + // store.dispatch(setUserToken({ token: '' })); }, }; throw result; From 0ddc876c82ccc27681971a8f39e32605350201ed Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Mon, 1 Sep 2025 03:10:59 +0900 Subject: [PATCH 23/27] cleanup --- src/components/team/TeamList.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/team/TeamList.tsx b/src/components/team/TeamList.tsx index 33d568b..a91287e 100644 --- a/src/components/team/TeamList.tsx +++ b/src/components/team/TeamList.tsx @@ -6,7 +6,7 @@ import { useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; import { matchPath, useHistory, useLocation } from 'react-router-dom'; import { Avatar, EmptyTip, Icon, List, ListItem } from '@/components'; -import api, { resultTypes } from '@/apis'; +import { api, resultTypes } from '@/apis'; import { FC, UserTeam } from '@/interfaces'; import { AppState } from '@/store'; import { resetProjectSetsState } from '@/store/projectSet/slice'; @@ -54,7 +54,7 @@ export const TeamList: FC = ({ className } = {}) => { }) => { setLoading(true); dispatch(clearTeams()); - return api + return api.team .getUserTeams({ params: { page, From a8c67003e6a8cb8a876eb683e62503310f035647 Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Mon, 1 Sep 2025 03:11:15 +0900 Subject: [PATCH 24/27] comment --- src/interfaces/file.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/interfaces/file.ts b/src/interfaces/file.ts index 6127d55..039368b 100644 --- a/src/interfaces/file.ts +++ b/src/interfaces/file.ts @@ -35,9 +35,14 @@ export interface File { prevImage?: File; imageOcrPercent?: number; imageOcrPercentDetailName?: string; + + /** + * NOTE fields below are browser only + */ // 上传中的文件 uploading?: boolean; uploadOverwrite?: boolean; + /** undefined when fetched from server */ uploadState?: 'uploading' | 'success' | 'failure'; uploadPercent?: number; // 1-100 } From 5eb766802c26973203d1a45023ffb20d884e33c9 Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Mon, 1 Sep 2025 03:11:37 +0900 Subject: [PATCH 25/27] cleanup --- src/components/project/FileItem.tsx | 262 ++++++++++++++-------------- 1 file changed, 132 insertions(+), 130 deletions(-) diff --git a/src/components/project/FileItem.tsx b/src/components/project/FileItem.tsx index 61f48f8..5aa8961 100644 --- a/src/components/project/FileItem.tsx +++ b/src/components/project/FileItem.tsx @@ -3,7 +3,7 @@ import { Checkbox } from 'antd'; import classNames from 'classnames'; import { useIntl } from 'react-intl'; import { Icon } from '@/components'; -import { FileUploadProgress } from '@/components/project/FileUploadProgress'; +import { FileUploadProgress } from './FileUploadProgress'; import { TranslationProgress } from '@/components/shared/TranslationProgress'; import { ImageOCRProgress } from '@/components/unused/ImageOCRProgress'; import { @@ -27,6 +27,136 @@ interface FileItemProps { onDeleteButtonClick?: () => void; className?: string; } + +const width = IMAGE_COVER.WIDTH; +const height = 240; +const imageHeight = IMAGE_COVER.HEIGHT; + +const fileItemStyle = css` + position: relative; + width: ${width}px; + height: ${height}px; + border-radius: ${style.borderRadiusBase}; + overflow: hidden; + transition: + box-shadow 100ms, + border-color 100ms; + border: 1px solid ${style.borderColorLight}; + .FileItem__ImageOCRProgressWrapper { + display: none; + position: absolute; + top: ${imageHeight - 17}px; + left: 6px; + padding: 3px 5px; + background-color: rgba(255, 255, 255, 0.8); + border-radius: 4px; + border: 1px solid #fff; + } + .FileItem__ImageWrapper { + display: block; + width: ${width - 2}px; + height: ${imageHeight}px; + overflow: hidden; + } + .FileItem__Image { + display: block; + width: ${width - 2}px; + height: ${imageHeight}px; + transition: transform 400ms; + user-select: none; + /* 禁止 iOS 上 Safari/Chrome/Firefox,重按/长按图片弹出菜单 */ + -webkit-touch-callout: none; + } + .FileItem__ImageTip { + width: 100%; + height: 100%; + padding: 20px 10px; + display: flex; + justify-content: center; + align-items: center; + text-align: center; + background-color: #f3f3f3; + font-weight: bold; + } + .FileItem__Name { + font-size: 14px; + line-height: 18px; + } + .FileItem__SelectWrapper { + position: absolute; + top: 0; + left: 0; + width: 36px; + height: 36px; + display: flex; + justify-content: center; + align-items: center; + border-radius: ${style.borderRadiusBase} 0 ${style.borderRadiusBase} 0; + background-color: rgba(0, 0, 0, 0.04); + ${clickEffect( + css` + background-color: rgba(0, 0, 0, 0.2); + `, + css` + background-color: rgba(0, 0, 0, 0.4); + `, + )}; + } + .FileItem__Select { + padding: 7px 10px; + } + .FileItem__DeleteButton { + position: absolute; + top: 0; + right: 0; + width: 36px; + height: 36px; + display: flex; + justify-content: center; + align-items: center; + border-radius: 0 ${style.borderRadiusBase} 0 ${style.borderRadiusBase}; + background-color: rgba(0, 0, 0, 0.04); + ${clickEffect( + css` + background-color: rgba(0, 0, 0, 0.2); + `, + css` + background-color: rgba(0, 0, 0, 0.4); + `, + )}; + } + .FileItem__DeleteButtonIcon { + width: 18px; + height: 18px; + color: rgba(255, 255, 255, 0.8); + } + .FileItem__Info { + display: flex; + flex-direction: column; + justify-content: space-between; + height: ${height - imageHeight - 2}px; + padding: 10px; + } + .FileItem__Name { + height: 36px; + overflow: hidden; + word-break: break-all; + } + ${cardClickEffect()}; + ${clickEffect( + css` + .FileItem__Image { + transform: scale(1.1); + } + `, + css` + .FileItem__Image { + transform: scale(1.08); + transition: transform 100ms; + } + `, + )}; +`; /** * 文件条目 */ @@ -50,138 +180,10 @@ export const FileItem: FC = ({ ? file.fileTargetCache!.checkedSourceCount : file.checkedSourceCount; - const width = IMAGE_COVER.WIDTH; - const height = 240; - const imageHeight = IMAGE_COVER.HEIGHT; - return (
From 4538da36975bfceb2a1e43279f40d5b75f69936c Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Mon, 1 Sep 2025 03:14:29 +0900 Subject: [PATCH 26/27] export more --- .../use_moeflow_companion.ts | 40 ++++++++++++++----- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/src/services/moeflow_companion/use_moeflow_companion.ts b/src/services/moeflow_companion/use_moeflow_companion.ts index b1f8d4d..afaae4f 100644 --- a/src/services/moeflow_companion/use_moeflow_companion.ts +++ b/src/services/moeflow_companion/use_moeflow_companion.ts @@ -4,6 +4,7 @@ import { useAsyncEffect } from '@jokester/ts-commonutil/lib/react/hook/use-async import { useSelector } from 'react-redux'; import { AppState } from '@/store'; import { createDebugLogger } from '@/utils/debug-logger'; +import { RuntimeConfig } from '@/configs'; export const moeflowCompanionServiceState = { disabled: 'disabled', @@ -14,8 +15,17 @@ export const moeflowCompanionServiceState = { const debugLogger = createDebugLogger('service:moeflow_companion'); -export function useMoeflowCompanion() { - const clientRef = useRef(null); +export interface MoeflowCompanionService { + client: Client; + serviceConf: RuntimeConfig['moeflowCompanion']; + multimodalTranslate: typeof multimodalTranslate; +} + +export function useMoeflowCompanion(): [ + string, + MoeflowCompanionService | null, +] { + const serviceRef = useRef(null); const [clientState, setClientState] = useState( moeflowCompanionServiceState.connecting, ); @@ -25,30 +35,40 @@ export function useMoeflowCompanion() { useAsyncEffect( async (_, released) => { - if (!serviceConf?.gradioUrl) { - clientRef.current = null; + if ( + !( + serviceConf && + serviceConf.gradioUrl && + serviceConf.defaultMultimodalModel + ) + ) { + serviceRef.current = null; setClientState(moeflowCompanionServiceState.disabled); return; } try { const client = await Client.connect(serviceConf.gradioUrl); - clientRef.current = client; + serviceRef.current = { + client, + multimodalTranslate, + serviceConf, + }; setClientState(moeflowCompanionServiceState.connected); released.then(() => client.close()); } catch (e) { debugLogger('error connecting', e, serviceConf.gradioUrl); - clientRef.current = null; + serviceRef.current = null; setClientState(moeflowCompanionServiceState.disconnected); } }, [serviceConf], ); - return [clientState, clientRef.current] as const; + return [clientState, serviceRef.current] as const; } -export async function multimodalTranslate( +async function multimodalTranslate( client: Client, - files: File[], + files: Blob[], targetLang: string, model: string, ): Promise { @@ -66,7 +86,7 @@ export async function multimodalTranslate( debugLogger('Predict response:', translated); return translated; } -interface TranslatedFile { +export interface TranslatedFile { local_path: string; image_w: number; image_h: number; From 83ac745338176ca68ff0b5e0ee70d4e96226fb9a Mon Sep 17 00:00:00 2001 From: Wang Guan Date: Mon, 1 Sep 2025 03:15:17 +0900 Subject: [PATCH 27/27] wip --- src/components/project/FileList.tsx | 56 ++--- .../project/FileListAiTranslate.tsx | 205 ++++++++++++++++++ 2 files changed, 233 insertions(+), 28 deletions(-) create mode 100644 src/components/project/FileListAiTranslate.tsx diff --git a/src/components/project/FileList.tsx b/src/components/project/FileList.tsx index dde7166..6512cc6 100644 --- a/src/components/project/FileList.tsx +++ b/src/components/project/FileList.tsx @@ -26,12 +26,10 @@ import style from '@/style'; import { toLowerCamelCase } from '@/utils'; import { can } from '@/utils/user'; import { routes } from '@/pages/routes'; -import { - moeflowCompanionServiceState, - useMoeflowCompanion, -} from '@/services/moeflow_companion/use_moeflow_companion'; import { ListPageSpec } from '@/components/shared/List'; import { FilePondFile } from 'filepond'; +import { useMoeflowCompanionAiTranslate } from './FileListAiTranslate'; +import { createDebugLogger } from '@/utils/debug-logger'; /** 文件列表的属性接口 */ interface FileListProps { @@ -40,6 +38,8 @@ interface FileListProps { target: Target; className?: string; } + +const debugLogger = createDebugLogger('components:project:FileList'); /** * 文件列表 */ @@ -65,7 +65,8 @@ export const FileList: FC = ({ const [outputDrawerVisible, setOutputDrawerVisible] = useState(false); const coverWidth = IMAGE_COVER.WIDTH; const coverHeight = IMAGE_COVER.HEIGHT; - const companionService = useMoeflowCompanionBatchProcess(); + const [aiTranslateAvailable, startAiTranslate, modalContextHolder] = + useMoeflowCompanionAiTranslate(); const [items, setItems] = useState([]); const [spinningIDs, setSpinningIDs] = useState([]); // 删除请求中 @@ -152,6 +153,7 @@ export const FileList: FC = ({ ); } const uploadingFile: MFile = { + // this is the random id generated by filepond id: file.id, name: file.filename, saveName: '', @@ -193,7 +195,7 @@ export const FileList: FC = ({ page, limit: pageSize, word, - target: target?.id, + target: target.id, }, configs: { cancelToken, @@ -214,6 +216,7 @@ export const FileList: FC = ({ }); }; /* + const startOCR = () => { Modal.confirm({ title: formatMessage({ id: 'project.startOCR' }), @@ -239,6 +242,7 @@ export const FileList: FC = ({ }; // */ + debugLogger('items', items); return (
= ({ setItems((items) => items.map((item) => { if (item.id === file.id) { - item.uploadPercent = Math.floor(progress * 100); + return { + ...item, + uploadPercent: Math.floor(progress * 100), + }; } return item; }), @@ -322,16 +329,19 @@ export const FileList: FC = ({ const itemsWithoutSameID = items.filter( (item) => item.id !== result.id, ); - return itemsWithoutSameID.map((item) => { + const updated = itemsWithoutSameID.map((item) => { if (item.id === file.id) { - item = { + // return a new MFile item , overwriting its (Filepond-created) id + return { ...item, ...result, uploadState: 'success', - }; + } as MFile; } return item; }); + debugLogger('updated on upload success', updated); + return updated; }); }} // 上传失败 @@ -340,7 +350,10 @@ export const FileList: FC = ({ setItems((items) => items.map((item) => { if (item.id === file.id) { - item.uploadState = 'failure'; + return { + ...item, + uploadState: 'failure', + }; } return item; }), @@ -361,21 +374,19 @@ export const FileList: FC = ({ iconProps={{ style: { height: '16px', width: '16px' }, }} - onClick={() => { - onChangeTargetClick && onChangeTargetClick(); - }} + onClick={onChangeTargetClick} > {(!isMobile ? formatMessage({ id: 'project.changeTarget' }) + ' - ' : '') + target?.language.i18nName} - {companionService && ( + {aiTranslateAvailable && ( @@ -571,18 +582,7 @@ export const FileList: FC = ({ selectedFileIds={selectedFileIds} /> + {modalContextHolder}
); }; - -function useMoeflowCompanionBatchProcess() { - const [serviceState, client] = useMoeflowCompanion(); - - if (serviceState !== moeflowCompanionServiceState.connected) { - return null; - } - - return { - async f(files: MFile[]) {}, - } as const; -} diff --git a/src/components/project/FileListAiTranslate.tsx b/src/components/project/FileListAiTranslate.tsx new file mode 100644 index 0000000..80517ae --- /dev/null +++ b/src/components/project/FileListAiTranslate.tsx @@ -0,0 +1,205 @@ +import { Modal } from 'antd'; +import { FC, File as MFile } from '@/interfaces'; +import { + useMoeflowCompanion, + moeflowCompanionServiceState, + MoeflowCompanionService, + TranslatedFile, +} from '@/services/moeflow_companion/use_moeflow_companion'; +import { useAsyncEffect } from '@jokester/ts-commonutil/lib/react/hook/use-async-effect'; +import { createDebugLogger } from '@/utils/debug-logger'; +import { api, resultTypes } from '@/apis'; +import { useIntl } from 'react-intl'; +import { ModalStaticFunctions } from 'antd/lib/modal/confirm'; +import { useState } from 'react'; +import { ResourcePool } from '@jokester/ts-commonutil/lib/concurrency/resource-pool-basic'; +import { getCancelToken } from '@/utils/api'; + +const debugLogger = createDebugLogger('components:project:FileListAiTranslate'); + +type ModalHandle = ReturnType; + +interface TranslatorFunc { + (files: MFile[]): void; +} +function startAiTranslate( + service: MoeflowCompanionService, + files: MFile[], + modal: ModalStaticFunctions, +) { + const handle = modal.confirm({ + content: ( + handle} /> + ), + okButtonProps: { disabled: true }, + onOk: () => { + console.log('ok'); + }, + onCancel: () => { + console.log('cancel'); + }, + }); +} + +export function useMoeflowCompanionAiTranslate(): + | [true, TranslatorFunc, React.ReactNode] + | [false, null, null] { + const [serviceState, service] = useMoeflowCompanion(); + const [modal, contextHolder] = Modal.useModal(); + + debugLogger('service', serviceState, service); + if (serviceState !== moeflowCompanionServiceState.connected) { + return [false, null, null]; + } + + return [ + true, + (files) => startAiTranslate(service!, files, modal as ModalStaticFunctions), + contextHolder, + ]; +} + +interface TranslateTaskState { + file: MFile; + status: string; +} + +const ModalContent: FC<{ + service: MoeflowCompanionService; + files: MFile[]; + getHandle(): ModalHandle; +}> = ({ + service: { client, serviceConf, multimodalTranslate }, + files, + getHandle, +}) => { + const intl = useIntl(); + const [fileStates, setFileStates] = useState(() => + files.map((file) => ({ file, status: 'waiting' })), + ); + useAsyncEffect(async (running, released) => { + const [cancelToken, fillCancelToken] = getCancelToken(); + const fileLimiter = ResourcePool.multiple([1, 2]); + const moeflowApiLimiter = ResourcePool.multiple([1, 2, 3, 4]); + const abort = new AbortController(); + released.then(() => fillCancelToken('unmounted')); + released.then(() => abort.abort('unmounted')); + + if (!running.current) { + debugLogger('canceled'); + return; + } + const tasksEnded = Promise.allSettled([ + files.map((f, idx) => fileLimiter.use(() => translateFile(f, idx))), + ]); + const cancelled = await Promise.race([ + released.then(() => true), + tasksEnded.then(() => false), + ]); + if (!cancelled) { + const handle = getHandle(); + handle.update({ okButtonProps: { disabled: false } }); + } + return; + + function setFileState(f: MFile, status: string) { + setFileStates((prev) => + prev.map((state) => (state.file === f ? { ...state, status } : state)), + ); + } + + async function translateFile(f: MFile, idx: number) { + setFileState(f, 'working'); + if (![undefined, null, 'success'].includes(f.uploadState)) { + setFileState(f, 'skip: upload not finished'); + return; + } + const refetched = await api.file.getFile({ fileID: f.id }); + if (refetched.type !== resultTypes.SUCCESS) { + setFileState(f, 'skip: fetch file failed'); + return; + } + if (refetched.data.sourceCount) { + setFileState(f, 'skip: source count not 0'); + } + const imgBlob = await fetch(refetched.data.url!, { + // mode: 'no-cors', + }).then( + (r) => r.blob(), + () => null, + ); + if (!imgBlob) { + setFileState(f, 'skip: fetch image blob failed'); + return; + } + + const [r] = await multimodalTranslate( + client, + [imgBlob], + 'Chinese Traditional', + serviceConf!.defaultMultimodalModel!, + ).catch((e) => { + debugLogger('translate failed', e); + return []; + }); + + if (!r) { + setFileState(f, 'skip: translate failed'); + } else { + return commit(f, r); + } + } + + async function saveTextBlock( + f: MFile, + tb: TranslatedFile['text_blocks'][number], + ) { + const src = await api.source.createSource({ + fileID: f.id, + data: { + x: (tb.left + tb.right) / 2 / r.image_w, + y: (tb.top + tb.bottom) / 2 / r.image_h, + content: tb.source, + }, + configs: { cancelToken }, + }); + await api.translation.createTranslation({ + sourceID: src.data.id, + data: { + content: tb.translated, + targetID: '', + }, + configs: { cancelToken }, + }); + } + + async function commit(f: MFile, r: TranslatedFile) { + if (r.text_blocks.length === 0) { + setFileState(f, 'done: no text blocks'); + } + setFileState(f, 'saving'); + try { + await Promise.all( + r.text_blocks.map((tb) => + moeflowApiLimiter.use(() => saveTextBlock(f, tb)), + ), + ); + setFileState(f, 'success'); + } catch (e) { + setFileState(f, 'skip: save file failed'); + } + } + }, []); + return ( +
+ {files.length} files to translate +
    + {fileStates.map((state) => ( +
  • + {state.file.name} - {state.status} +
  • + ))} +
+
+ ); +};