mirror of
https://github.com/cloudreve/frontend.git
synced 2025-12-26 04:02:47 +00:00
feat: platform self-adaptation for file viewer application (#276)
* feat: platform self-adaptation for file viewer application * pref: optimize the default values of viewerPlatform * perf: moved the device filter logic to redux * perf: removed unused dependencies
This commit is contained in:
parent
2c56116153
commit
37320610e8
|
|
@ -202,6 +202,11 @@
|
|||
"addViewer": "Add an application",
|
||||
"viewerGroupTitle": "Application group #{{index}}",
|
||||
"viewerType": "Type",
|
||||
"viewerPlatform": "Platform",
|
||||
"viewerPlatformDes": "Select the corresponding platform to display the application only on that platform.",
|
||||
"viewerPlatformPC": "Desktop",
|
||||
"viewerPlatformMobile": "Mobile",
|
||||
"viewerPlatformAll": "All",
|
||||
"displayName": "Display name",
|
||||
"displayNameDes": "Display name to users, support i18next key.",
|
||||
"viewerEnabled": "Enabled",
|
||||
|
|
|
|||
|
|
@ -202,6 +202,11 @@
|
|||
"addViewer": "アプリを追加",
|
||||
"viewerGroupTitle": "アプリグループ #{{index}}",
|
||||
"viewerType": "タイプ",
|
||||
"viewerPlatform": "プラットフォーム",
|
||||
"viewerPlatformDes": "対応するプラットフォームを選択し、アプリをそのプラットフォームでのみ表示します。",
|
||||
"viewerPlatformPC": " パソコン",
|
||||
"viewerPlatformMobile": "モバイル",
|
||||
"viewerPlatformAll": "全対応",
|
||||
"displayName": "名称",
|
||||
"displayNameDes": "表示名(i18nextキー対応)",
|
||||
"viewerEnabled": "有効化",
|
||||
|
|
|
|||
|
|
@ -202,6 +202,11 @@
|
|||
"addViewer": "添加应用",
|
||||
"viewerGroupTitle": "应用分组 #{{index}}",
|
||||
"viewerType": "类型",
|
||||
"viewerPlatform": "平台",
|
||||
"viewerPlatformDes": "选择对应的平台,使应用仅在对应平台上展示。",
|
||||
"viewerPlatformPC": "PC端",
|
||||
"viewerPlatformMobile": "移动端",
|
||||
"viewerPlatformAll": "全平台",
|
||||
"displayName": "名称",
|
||||
"displayNameDes": "展示名称,支持 i18next 键值。",
|
||||
"viewerEnabled": "启用",
|
||||
|
|
|
|||
|
|
@ -198,6 +198,11 @@
|
|||
"addViewer": "新增應用",
|
||||
"viewerGroupTitle": "應用分組 #{{index}}",
|
||||
"viewerType": "型別",
|
||||
"viewerPlatform": "平台",
|
||||
"viewerPlatformDes": "選擇對應的平台,讓應用僅在對應平台上展示。",
|
||||
"viewerPlatformPC": "電腦端",
|
||||
"viewerPlatformMobile": "行動端",
|
||||
"viewerPlatformAll": "全平台",
|
||||
"displayName": "名稱",
|
||||
"displayNameDes": "展示名稱,支援 i18next 鍵值。",
|
||||
"viewerEnabled": "啟用",
|
||||
|
|
|
|||
|
|
@ -412,6 +412,12 @@ export const ViewerType = {
|
|||
custom: "custom",
|
||||
};
|
||||
|
||||
export enum ViewerPlatform {
|
||||
pc = "pc",
|
||||
mobile = "mobile",
|
||||
all = "all",
|
||||
}
|
||||
|
||||
export interface Viewer {
|
||||
id: string;
|
||||
type: string;
|
||||
|
|
@ -430,6 +436,7 @@ export interface Viewer {
|
|||
};
|
||||
};
|
||||
templates?: NewFileTemplate[];
|
||||
platform?: ViewerPlatform;
|
||||
}
|
||||
|
||||
export interface NewFileTemplate {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import Grid from "@mui/material/Grid2";
|
|||
import { useSnackbar } from "notistack";
|
||||
import React, { lazy, Suspense, useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { Viewer, ViewerType } from "../../../../../api/explorer.ts";
|
||||
import { Viewer, ViewerPlatform, ViewerType } from "../../../../../api/explorer.ts";
|
||||
import { builtInViewers } from "../../../../../redux/thunks/viewer.ts";
|
||||
import { isTrueVal } from "../../../../../session/utils.ts";
|
||||
import CircularProgress from "../../../../Common/CircularProgress.tsx";
|
||||
|
|
@ -110,7 +110,17 @@ interface DraggableTemplateRowProps {
|
|||
template: any;
|
||||
}
|
||||
|
||||
function DraggableTemplateRow({ i, moveRow, onExtChange, onNameChange, onDelete, isFirst, isLast, extList, template }: DraggableTemplateRowProps) {
|
||||
function DraggableTemplateRow({
|
||||
i,
|
||||
moveRow,
|
||||
onExtChange,
|
||||
onNameChange,
|
||||
onDelete,
|
||||
isFirst,
|
||||
isLast,
|
||||
extList,
|
||||
template,
|
||||
}: DraggableTemplateRowProps) {
|
||||
const ref = React.useRef<HTMLTableRowElement>(null);
|
||||
const [, drop] = useDrop({
|
||||
accept: DND_TYPE,
|
||||
|
|
@ -150,11 +160,7 @@ function DraggableTemplateRow({ i, moveRow, onExtChange, onNameChange, onDelete,
|
|||
hover
|
||||
>
|
||||
<NoWrapTableCell>
|
||||
<DenseSelect
|
||||
value={template.ext}
|
||||
required
|
||||
onChange={onExtChange}
|
||||
>
|
||||
<DenseSelect value={template.ext} required onChange={onExtChange}>
|
||||
{extList.map((ext) => (
|
||||
<SquareMenuItem value={ext} key={ext}>
|
||||
<ListItemText slotProps={{ primary: { variant: "body2" } }}>{ext}</ListItemText>
|
||||
|
|
@ -163,12 +169,7 @@ function DraggableTemplateRow({ i, moveRow, onExtChange, onNameChange, onDelete,
|
|||
</DenseSelect>
|
||||
</NoWrapTableCell>
|
||||
<NoWrapTableCell>
|
||||
<DenseFilledTextField
|
||||
fullWidth
|
||||
required
|
||||
value={template.display_name}
|
||||
onChange={onNameChange}
|
||||
/>
|
||||
<DenseFilledTextField fullWidth required value={template.display_name} onChange={onNameChange} />
|
||||
</NoWrapTableCell>
|
||||
<NoWrapTableCell>
|
||||
<IconButton size={"small"} onClick={onDelete}>
|
||||
|
|
@ -353,6 +354,48 @@ const FileViewerEditDialog = ({ viewer, onChange, open, onClose }: FileViewerEdi
|
|||
<NoMarginHelperText>{t("settings.maxSizeDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
<SettingForm noContainer lgWidth={6} title={t("settings.viewerPlatform")}>
|
||||
<FormControl fullWidth>
|
||||
<DenseSelect
|
||||
value={viewerShadowed.platform || ViewerPlatform.all}
|
||||
onChange={(e) =>
|
||||
setViewerShadowed((v) => ({
|
||||
...(v as Viewer),
|
||||
platform: e.target.value as ViewerPlatform,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<SquareMenuItem value="pc">
|
||||
<ListItemText
|
||||
slotProps={{
|
||||
primary: { variant: "body2" },
|
||||
}}
|
||||
>
|
||||
{t("settings.viewerPlatformPC")}
|
||||
</ListItemText>
|
||||
</SquareMenuItem>
|
||||
<SquareMenuItem value="mobile">
|
||||
<ListItemText
|
||||
slotProps={{
|
||||
primary: { variant: "body2" },
|
||||
}}
|
||||
>
|
||||
{t("settings.viewerPlatformMobile")}
|
||||
</ListItemText>
|
||||
</SquareMenuItem>
|
||||
<SquareMenuItem value="all">
|
||||
<ListItemText
|
||||
slotProps={{
|
||||
primary: { variant: "body2" },
|
||||
}}
|
||||
>
|
||||
{t("settings.viewerPlatformAll")}
|
||||
</ListItemText>
|
||||
</SquareMenuItem>
|
||||
</DenseSelect>
|
||||
<NoMarginHelperText>{t("settings.viewerPlatformDes")}</NoMarginHelperText>
|
||||
</FormControl>
|
||||
</SettingForm>
|
||||
{viewer.type == ViewerType.custom && (
|
||||
<SettingForm noContainer lgWidth={6}>
|
||||
<FormControlLabel
|
||||
|
|
|
|||
|
|
@ -124,14 +124,17 @@ const ViewerGroupRow = memo(({ group, index, onDelete, onGroupChange, dndType }:
|
|||
React.useEffect(() => {
|
||||
setViewers(group.viewers);
|
||||
}, [group.viewers]);
|
||||
const moveRow = useCallback((from: number, to: number) => {
|
||||
if (from === to) return;
|
||||
const updated = [...viewers];
|
||||
const [moved] = updated.splice(from, 1);
|
||||
updated.splice(to, 0, moved);
|
||||
setViewers(updated);
|
||||
onGroupChange({ viewers: updated });
|
||||
}, [viewers, onGroupChange]);
|
||||
const moveRow = useCallback(
|
||||
(from: number, to: number) => {
|
||||
if (from === to) return;
|
||||
const updated = [...viewers];
|
||||
const [moved] = updated.splice(from, 1);
|
||||
updated.splice(to, 0, moved);
|
||||
setViewers(updated);
|
||||
onGroupChange({ viewers: updated });
|
||||
},
|
||||
[viewers, onGroupChange],
|
||||
);
|
||||
const handleMoveUp = (idx: number) => {
|
||||
if (idx <= 0) return;
|
||||
moveRow(idx, idx - 1);
|
||||
|
|
@ -178,10 +181,11 @@ const ViewerGroupRow = memo(({ group, index, onDelete, onGroupChange, dndType }:
|
|||
<NoWrapTableCell width={100}>{t("settings.viewerType")}</NoWrapTableCell>
|
||||
<NoWrapTableCell width={200}>{t("settings.displayName")}</NoWrapTableCell>
|
||||
<NoWrapTableCell width={250}>{t("settings.exts")}</NoWrapTableCell>
|
||||
<NoWrapTableCell width={150}>{t("settings.viewerPlatform")}</NoWrapTableCell>
|
||||
<NoWrapTableCell width={100}>{t("settings.newFileAction")}</NoWrapTableCell>
|
||||
<NoWrapTableCell width={64}>{t("settings.viewerEnabled")}</NoWrapTableCell>
|
||||
<NoWrapTableCell width={64}>{t("settings.actions")}</NoWrapTableCell>
|
||||
<NoWrapTableCell width={64}></NoWrapTableCell>
|
||||
<NoWrapTableCell width={100}>{t("settings.actions")}</NoWrapTableCell>
|
||||
<NoWrapTableCell width={100}></NoWrapTableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import * as React from "react";
|
||||
import { memo, useCallback, useState } from "react";
|
||||
import { Viewer, ViewerType } from "../../../../../api/explorer.ts";
|
||||
import { Viewer, ViewerPlatform, ViewerType } from "../../../../../api/explorer.ts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { IconButton, TableRow } from "@mui/material";
|
||||
import { DenseFilledTextField, NoWrapCell, StyledCheckbox } from "../../../../Common/StyledComponents.tsx";
|
||||
import { IconButton, TableRow, ListItemText } from "@mui/material";
|
||||
import { DenseFilledTextField, NoWrapCell, StyledCheckbox, DenseSelect } from "../../../../Common/StyledComponents.tsx";
|
||||
import { SquareMenuItem } from "../../../../FileManager/ContextMenu/ContextMenu.tsx";
|
||||
import { ViewerIcon } from "../../../../FileManager/Dialogs/OpenWith.tsx";
|
||||
import Dismiss from "../../../../Icons/Dismiss.tsx";
|
||||
import Edit from "../../../../Icons/Edit.tsx";
|
||||
|
|
@ -23,19 +24,7 @@ export interface FileViewerRowProps {
|
|||
|
||||
const FileViewerRow = React.memo(
|
||||
React.forwardRef<HTMLTableRowElement, FileViewerRowProps>(
|
||||
(
|
||||
{
|
||||
viewer,
|
||||
onChange,
|
||||
onDelete,
|
||||
onMoveUp,
|
||||
onMoveDown,
|
||||
isFirst,
|
||||
isLast,
|
||||
style,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
({ viewer, onChange, onDelete, onMoveUp, onMoveDown, isFirst, isLast, style }, ref) => {
|
||||
const { t } = useTranslation("dashboard");
|
||||
const [extCached, setExtCached] = useState("");
|
||||
const [editOpen, setEditOpen] = useState(false);
|
||||
|
|
@ -43,12 +32,7 @@ const FileViewerRow = React.memo(
|
|||
setEditOpen(false);
|
||||
}, [setEditOpen]);
|
||||
return (
|
||||
<TableRow
|
||||
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
|
||||
hover
|
||||
ref={ref}
|
||||
style={style}
|
||||
>
|
||||
<TableRow sx={{ "&:last-child td, &:last-child th": { border: 0 } }} hover ref={ref} style={style}>
|
||||
<FileViewerEditDialog viewer={viewer} onChange={onChange} open={editOpen} onClose={onClose} />
|
||||
<NoWrapCell>
|
||||
<ViewerIcon viewer={viewer} />
|
||||
|
|
@ -75,6 +59,45 @@ const FileViewerRow = React.memo(
|
|||
onChange={(e) => setExtCached(e.target.value)}
|
||||
/>
|
||||
</NoWrapCell>
|
||||
<NoWrapCell>
|
||||
<DenseSelect
|
||||
value={viewer.platform || ViewerPlatform.all}
|
||||
onChange={(e) =>
|
||||
onChange({
|
||||
...viewer,
|
||||
platform: e.target.value as ViewerPlatform,
|
||||
})
|
||||
}
|
||||
>
|
||||
<SquareMenuItem value="pc">
|
||||
<ListItemText
|
||||
slotProps={{
|
||||
primary: { variant: "body2" },
|
||||
}}
|
||||
>
|
||||
{t("settings.viewerPlatformPC")}
|
||||
</ListItemText>
|
||||
</SquareMenuItem>
|
||||
<SquareMenuItem value="mobile">
|
||||
<ListItemText
|
||||
slotProps={{
|
||||
primary: { variant: "body2" },
|
||||
}}
|
||||
>
|
||||
{t("settings.viewerPlatformMobile")}
|
||||
</ListItemText>
|
||||
</SquareMenuItem>
|
||||
<SquareMenuItem value="all">
|
||||
<ListItemText
|
||||
slotProps={{
|
||||
primary: { variant: "body2" },
|
||||
}}
|
||||
>
|
||||
{t("settings.viewerPlatformAll")}
|
||||
</ListItemText>
|
||||
</SquareMenuItem>
|
||||
</DenseSelect>
|
||||
</NoWrapCell>
|
||||
<NoWrapCell>
|
||||
{viewer.templates?.length ? t("settings.nMapping", { num: viewer.templates?.length }) : t("share.none")}
|
||||
</NoWrapCell>
|
||||
|
|
@ -121,8 +144,8 @@ const FileViewerRow = React.memo(
|
|||
</NoWrapCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
export default FileViewerRow;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
|||
import { SiteConfig } from "../api/site.ts";
|
||||
import { ExpandedIconSettings, FileTypeIconSetting } from "../component/FileManager/Explorer/FileTypeIcon.tsx";
|
||||
import { ExpandedViewerSetting } from "./thunks/viewer.ts";
|
||||
import { Viewer } from "../api/explorer.ts";
|
||||
import { Viewer, ViewerPlatform } from "../api/explorer.ts";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
|
@ -68,12 +68,18 @@ const preProcessors: {
|
|||
|
||||
Viewers = {};
|
||||
ViewersByID = {};
|
||||
const isMobile = window.matchMedia("(max-width: 768px)").matches;
|
||||
config.file_viewers?.forEach((group) => {
|
||||
group.viewers.forEach((viewer) => {
|
||||
if (viewer.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const platform = viewer.platform || ViewerPlatform.all;
|
||||
if (platform !== ViewerPlatform.all && platform !== (isMobile ? ViewerPlatform.mobile : ViewerPlatform.pc)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ViewersByID[viewer.id] = viewer;
|
||||
const simplified: Viewer = viewer;
|
||||
viewer.exts.forEach((ext) => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue