909 lines
31 KiB
JavaScript
909 lines
31 KiB
JavaScript
// Copyright 2023 The casbin Authors. All Rights Reserved.
|
||
//
|
||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
// you may not use this file except in compliance with the License.
|
||
// You may obtain a copy of the License at
|
||
//
|
||
// http://www.apache.org/licenses/LICENSE-2.0
|
||
//
|
||
// Unless required by applicable law or agreed to in writing, software
|
||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
// See the License for the specific language governing permissions and
|
||
// limitations under the License.
|
||
|
||
import React from "react";
|
||
import {withRouter} from "react-router-dom";
|
||
import {Button, Card, Col, DatePicker, Descriptions, Empty, Input, Modal, Popconfirm, Radio, Result, Row, Select, Spin, Tooltip, Tree, Upload} from "antd";
|
||
import {CloudUploadOutlined, DeleteOutlined, DownloadOutlined, FileDoneOutlined, FolderAddOutlined, InfoCircleTwoTone, createFromIconfontCN} from "@ant-design/icons";
|
||
import moment from "moment";
|
||
import * as Setting from "./Setting";
|
||
import * as FileBackend from "./backend/FileBackend";
|
||
import DocViewer, {DocViewerRenderers} from "@cyntler/react-doc-viewer";
|
||
import FileViewer from "react-file-viewer";
|
||
import i18next from "i18next";
|
||
import * as PermissionBackend from "./backend/PermissionBackend";
|
||
import * as PermissionUtil from "./PermissionUtil";
|
||
import * as Conf from "./Conf";
|
||
import FileTable from "./FileTable";
|
||
|
||
import {Controlled as CodeMirror} from "react-codemirror2";
|
||
import "codemirror/lib/codemirror.css";
|
||
// require("codemirror/theme/material-darker.css");
|
||
// require("codemirror/mode/javascript/javascript");
|
||
|
||
const {Search} = Input;
|
||
const {Option} = Select;
|
||
|
||
const IconFont = createFromIconfontCN({
|
||
scriptUrl: "https://cdn.open-ct.com/icon/iconfont.js",
|
||
});
|
||
|
||
class FileTree extends React.Component {
|
||
constructor(props) {
|
||
super(props);
|
||
this.state = {
|
||
classes: props,
|
||
expandedKeys: ["0-0", "0-0-0", "0-0-0-0"],
|
||
checkedKeys: [],
|
||
checkedFiles: [],
|
||
selectedKeys: [],
|
||
selectedFile: null,
|
||
loading: false,
|
||
text: null,
|
||
newFolder: null,
|
||
permissions: null,
|
||
permissionMap: null,
|
||
searchValue: "",
|
||
isUploadFileModalVisible: false,
|
||
uploadFileType: null,
|
||
file: null,
|
||
info: null,
|
||
};
|
||
|
||
this.filePane = React.createRef();
|
||
this.uploadedFileIdMap = {};
|
||
}
|
||
|
||
UNSAFE_componentWillMount() {
|
||
this.getPermissions();
|
||
}
|
||
|
||
getPermissionMap(permissions) {
|
||
const permissionMap = {};
|
||
permissions.forEach((permission, index) => {
|
||
if (permissionMap[permission.resources[0]] === undefined) {
|
||
permissionMap[permission.resources[0]] = [];
|
||
}
|
||
permissionMap[permission.resources[0]].push(permission);
|
||
});
|
||
return permissionMap;
|
||
}
|
||
|
||
getPermissions() {
|
||
PermissionBackend.getPermissions(Conf.AuthConfig.organizationName)
|
||
.then((res) => {
|
||
if (res.status === "ok") {
|
||
const permissions = res.data.filter(permission => (permission.domains[0] === this.props.store.name) && permission.users.length !== 0);
|
||
this.setState({
|
||
permissions: permissions,
|
||
permissionMap: this.getPermissionMap(permissions),
|
||
});
|
||
} else {
|
||
Setting.showMessage("error", `Failed to get permissions: ${res.msg}`);
|
||
}
|
||
});
|
||
}
|
||
|
||
updateStore(store) {
|
||
this.props.onUpdateStore(store);
|
||
}
|
||
|
||
checkUploadFile(info) {
|
||
for (let i = 0; i < info.fileList.length; i++) {
|
||
const filename = info.fileList[i].name;
|
||
if (this.getCacheApp(filename) === "" && filename.endsWith(".txt")) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
renderUploadFileModal() {
|
||
return (
|
||
<Modal title={
|
||
<div>
|
||
<InfoCircleTwoTone twoToneColor="rgb(45,120,213)" />
|
||
{" " + i18next.t("store:Please choose the type of your data")}
|
||
</div>
|
||
}
|
||
open={this.state.isUploadFileModalVisible}
|
||
onCancel={() => {
|
||
this.setState({
|
||
isUploadFileModalVisible: false,
|
||
});
|
||
}}
|
||
width={"360px"}
|
||
footer={null} >
|
||
<Radio.Group buttonStyle="solid" onChange={e => {
|
||
this.setState({
|
||
uploadFileType: e.target.value,
|
||
isUploadFileModalVisible: false,
|
||
});
|
||
|
||
const uploadFileType = e.target.value;
|
||
// console.log(this.state.file);
|
||
// console.log(this.state.info);
|
||
|
||
const newInfo = Setting.deepCopy(this.state.info);
|
||
if (uploadFileType !== "Other") {
|
||
for (let i = 0; i < newInfo.fileList.length; i++) {
|
||
const filename = newInfo.fileList[i].name;
|
||
if (this.getCacheApp(filename) === "" && filename.endsWith(".txt")) {
|
||
newInfo.fileList[i].name = `${uploadFileType}_${newInfo.fileList[i].name}`;
|
||
}
|
||
}
|
||
}
|
||
this.uploadFile(this.state.file, newInfo);
|
||
}} value={this.state.uploadFileType}>
|
||
<Radio.Button value={"ECG"}>ECG</Radio.Button>
|
||
<Radio.Button value={"Impedance"}>Impedance</Radio.Button>
|
||
{/* <Radio.Button value={"EEG"}>EEG</Radio.Button>*/}
|
||
<Radio.Button value={"Other"}>{i18next.t("store:Other")}</Radio.Button>
|
||
</Radio.Group>
|
||
</Modal>
|
||
);
|
||
}
|
||
|
||
uploadFile(file, info) {
|
||
const storeId = `${this.props.store.owner}/${this.props.store.name}`;
|
||
|
||
const promises = [];
|
||
info.fileList.forEach((uploadedFile, index) => {
|
||
if (this.uploadedFileIdMap[uploadedFile.originFileObj.uid] === 1) {
|
||
return;
|
||
} else {
|
||
this.uploadedFileIdMap[uploadedFile.originFileObj.uid] = 1;
|
||
}
|
||
|
||
promises.push(FileBackend.addFile(storeId, file.key, true, uploadedFile.name, uploadedFile.originFileObj));
|
||
});
|
||
|
||
Promise.all(promises)
|
||
.then((values) => {
|
||
Setting.showMessage("success", "File uploaded successfully");
|
||
this.props.onRefresh();
|
||
})
|
||
.catch(error => {
|
||
Setting.showMessage("error", `File failed to upload: ${error}`);
|
||
});
|
||
}
|
||
|
||
addFile(file, newFolder) {
|
||
const storeId = `${this.props.store.owner}/${this.props.store.name}`;
|
||
FileBackend.addFile(storeId, file.key, false, newFolder, null)
|
||
.then((res) => {
|
||
if (res.status === "ok") {
|
||
Setting.showMessage("success", "File added successfully");
|
||
this.props.onRefresh();
|
||
} else {
|
||
Setting.showMessage("error", `File failed to add: ${res.msg}`);
|
||
}
|
||
})
|
||
.catch(error => {
|
||
Setting.showMessage("error", `File failed to add: ${error}`);
|
||
});
|
||
}
|
||
|
||
deleteFile(file, isLeaf) {
|
||
const storeId = `${this.props.store.owner}/${this.props.store.name}`;
|
||
FileBackend.deleteFile(storeId, file.key, isLeaf)
|
||
.then((res) => {
|
||
if (res.status === "ok") {
|
||
if (res.data === true) {
|
||
Setting.showMessage("success", "File deleted successfully");
|
||
this.props.onRefresh();
|
||
} else {
|
||
Setting.showMessage("error", "File failed to delete:server side failure");
|
||
}
|
||
} else {
|
||
Setting.showMessage("error", `File failed to delete: ${res.msg}`);
|
||
}
|
||
})
|
||
.catch(error => {
|
||
Setting.showMessage("error", `File failed to delete: ${error}`);
|
||
});
|
||
}
|
||
|
||
renderPermission(permission, isReadable) {
|
||
if (!isReadable) {
|
||
const userId = `${this.props.account.owner}/${this.props.account.name}`;
|
||
if (!permission.users.includes(userId)) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
return (
|
||
<span key={permission.name}
|
||
onClick={(e) => {
|
||
Setting.openLink(Setting.getMyProfileUrl(this.props.account).replace("/account", `/permissions/${permission.owner}/${permission.name}`));
|
||
e.stopPropagation();
|
||
}}
|
||
>
|
||
{
|
||
permission.users.map(user => {
|
||
const username = user.split("/")[1];
|
||
return (
|
||
<span key={username}>
|
||
{
|
||
Setting.getTag(username, permission.actions[0], permission.state)
|
||
}
|
||
</span>
|
||
);
|
||
})
|
||
}
|
||
</span>
|
||
);
|
||
}
|
||
|
||
renderPermissions(permissions, isReadable) {
|
||
if (permissions === undefined) {
|
||
return null;
|
||
}
|
||
|
||
return permissions.map(permission => this.renderPermission(permission, isReadable)).filter(permission => permission !== null);
|
||
}
|
||
|
||
isActionIncluded(action1, action2) {
|
||
if (action1 === "Read") {
|
||
return true;
|
||
} else if (action1 === "Write" && action2 !== "Read") {
|
||
return true;
|
||
} else if (action1 === "Admin" && action2 === "Admin") {
|
||
return true;
|
||
} else {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
isFileOk(file, action) {
|
||
if (this.props.account.isAdmin) {
|
||
return true;
|
||
}
|
||
|
||
if (this.state.permissionMap === null) {
|
||
return false;
|
||
}
|
||
|
||
const permissions = this.state.permissionMap[file.key];
|
||
if (permissions !== undefined) {
|
||
for (let i = 0; i < permissions.length; i++) {
|
||
const permission = permissions[i];
|
||
|
||
const userId = `${this.props.account.owner}/${this.props.account.name}`;
|
||
if (permission.state === "Approved" && permission.isEnabled === true && permission.resources[0] === file.key && permission.users.includes(userId) && this.isActionIncluded(action, permission.actions[0])) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (file.parent !== undefined) {
|
||
return this.isFileOk(file.parent, action);
|
||
}
|
||
return false;
|
||
}
|
||
|
||
isFileReadable(file) {
|
||
return this.isFileOk(file, "Read");
|
||
}
|
||
|
||
isFileWritable(file) {
|
||
return this.isFileOk(file, "Write");
|
||
}
|
||
|
||
isFileAdmin(file) {
|
||
if (Setting.isLocalAdminUser(this.props.account)) {
|
||
return true;
|
||
}
|
||
|
||
return this.isFileOk(file, "Admin");
|
||
}
|
||
|
||
renderSearch() {
|
||
return (
|
||
<Search placeholder={i18next.t("store:Please input your search term")} onChange={(e) => {
|
||
this.setState({
|
||
searchValue: e.target.value,
|
||
selectedKeys: [],
|
||
selectedFile: null,
|
||
});
|
||
}} />
|
||
);
|
||
}
|
||
|
||
getCacheApp(filename) {
|
||
if (!filename.startsWith("ECG_") && !filename.startsWith("EEG_") && !filename.startsWith("Impedance_")) {
|
||
return "";
|
||
}
|
||
|
||
return filename;
|
||
}
|
||
|
||
renderTree(store) {
|
||
const onSelect = (selectedKeys, info) => {
|
||
if (!this.isFileReadable(info.node)) {
|
||
Setting.showMessage("error", i18next.t("store:Sorry, you are unauthorized to access this file or folder"));
|
||
return;
|
||
}
|
||
|
||
if (selectedKeys.length !== 0) {
|
||
const fetchFile = () => {
|
||
const path = selectedKeys[0];
|
||
const ext = Setting.getExtFromPath(path);
|
||
if (ext !== "") {
|
||
const url = info.node.url;
|
||
|
||
if (!this.isExtForDocViewer((ext) && !this.isExtForFileViewer(ext))) {
|
||
this.setState({
|
||
loading: true,
|
||
});
|
||
|
||
fetch(url, {method: "GET"})
|
||
.then(res => res.text())
|
||
.then(res => {
|
||
this.setState({
|
||
text: res,
|
||
loading: false,
|
||
});
|
||
});
|
||
}
|
||
}
|
||
};
|
||
|
||
const key = info.node.key;
|
||
const filename = info.node.title;
|
||
if (this.getCacheApp(filename) !== "") {
|
||
FileBackend.activateFile(key, filename)
|
||
.then((res) => {
|
||
if (res.status === "ok") {
|
||
if (res.data === true) {
|
||
// Setting.showMessage("success", `File activated successfully`);
|
||
fetchFile();
|
||
} else {
|
||
Setting.showMessage("error", "File failed to activate: server side failure");
|
||
}
|
||
} else {
|
||
Setting.showMessage("error", `File failed to activate: ${res.msg}`);
|
||
}
|
||
})
|
||
.catch(error => {
|
||
Setting.showMessage("error", `File failed to activate: ${error}`);
|
||
});
|
||
} else {
|
||
fetchFile();
|
||
}
|
||
}
|
||
|
||
this.setState({
|
||
checkedKeys: [],
|
||
checkedFiles: [],
|
||
selectedKeys: selectedKeys,
|
||
selectedFile: info.node,
|
||
});
|
||
};
|
||
|
||
const onCheck = (checkedKeys, info) => {
|
||
this.setState({
|
||
checkedKeys: checkedKeys,
|
||
checkedFiles: info.checkedNodes,
|
||
selectedKeys: [],
|
||
selectedFile: null,
|
||
});
|
||
};
|
||
|
||
let fileTree = Setting.getTreeWithParents(store.fileTree);
|
||
if (this.state.searchValue !== "") {
|
||
fileTree = Setting.getTreeWithSearch(fileTree, this.state.searchValue);
|
||
}
|
||
|
||
return (
|
||
<Tree
|
||
height={"calc(100vh - 220px)"}
|
||
virtual={true}
|
||
className="draggable-tree"
|
||
multiple={false}
|
||
checkable
|
||
defaultExpandAll={true}
|
||
// defaultExpandedKeys={tree.children.map(file => file.key)}
|
||
draggable={false}
|
||
blockNode
|
||
showLine={true}
|
||
showIcon={true}
|
||
onCheck={onCheck}
|
||
checkedKeys={this.state.checkedKeys}
|
||
onSelect={onSelect}
|
||
selectedKeys={this.state.selectedKeys}
|
||
treeData={[fileTree]}
|
||
titleRender={(file) => {
|
||
const isReadable = this.isFileReadable(file);
|
||
const isWritable = this.isFileWritable(file);
|
||
const isAdmin = this.isFileAdmin(file);
|
||
|
||
let tagStyle = {};
|
||
if (!isReadable && !isWritable && !isAdmin) {
|
||
tagStyle = {color: "rgba(100,100,100,0.6)", backgroundColor: "rgba(225,225,225,0.4)"};
|
||
}
|
||
|
||
if (file.isLeaf) {
|
||
return (
|
||
<Tooltip color={"rgb(255,255,255,0.8)"} placement="right" title={
|
||
<div>
|
||
{
|
||
!isReadable ? null : (
|
||
<Tooltip title={i18next.t("store:Download")}>
|
||
<Button style={{marginRight: "5px"}} icon={<DownloadOutlined />} size="small" onClick={(e) => {
|
||
Setting.showMessage("success", "Successfully downloaded");
|
||
Setting.openLink(file.url);
|
||
e.stopPropagation();
|
||
}} />
|
||
</Tooltip>
|
||
)
|
||
}
|
||
{
|
||
!isWritable ? null : (
|
||
<React.Fragment>
|
||
{/* <Tooltip title={i18next.t("store:Rename")}>*/}
|
||
{/* <Button style={{marginRight: "5px"}} icon={<EditOutlined />} size="small" onClick={(e) => {*/}
|
||
{/* Setting.showMessage("error", "Rename");*/}
|
||
{/* e.stopPropagation();*/}
|
||
{/* }} />*/}
|
||
{/* </Tooltip>*/}
|
||
{/* <Tooltip title={i18next.t("store:Move")}>*/}
|
||
{/* <Button style={{marginRight: "5px"}} icon={<RadiusSettingOutlined />} size="small" onClick={(e) => {*/}
|
||
{/* Setting.showMessage("error", "Move");*/}
|
||
{/* e.stopPropagation();*/}
|
||
{/* }} />*/}
|
||
{/* </Tooltip>*/}
|
||
<Tooltip title={i18next.t("store:Delete")}>
|
||
<span onClick={(e) => e.stopPropagation()}>
|
||
<Popconfirm
|
||
title={`Sure to delete file: ${file.title} ?`}
|
||
onConfirm={(e) => {
|
||
this.deleteFile(file, true);
|
||
}}
|
||
okText="OK"
|
||
cancelText="Cancel"
|
||
>
|
||
<Button style={{marginRight: "5px"}} icon={<DeleteOutlined />} size="small" />
|
||
</Popconfirm>
|
||
</span>
|
||
</Tooltip>
|
||
</React.Fragment>
|
||
)
|
||
}
|
||
<Tooltip title={isAdmin ? i18next.t("store:Add Permission") :
|
||
i18next.t("store:Apply for Permission")}>
|
||
<Button icon={<FileDoneOutlined />} size="small" onClick={(e) => {
|
||
PermissionUtil.addPermission(this.props.account, this.props.store, file);
|
||
e.stopPropagation();
|
||
}} />
|
||
</Tooltip>
|
||
</div>
|
||
}>
|
||
<span style={tagStyle}>
|
||
{`${file.title} (${Setting.getFriendlyFileSize(file.size)})`}
|
||
</span>
|
||
|
||
|
||
{
|
||
(this.state.permissionMap === null) ? null : this.renderPermissions(this.state.permissionMap[file.key], isReadable)
|
||
}
|
||
</Tooltip>
|
||
);
|
||
} else {
|
||
return (
|
||
<Tooltip color={"rgb(255,255,255,0.8)"} placement="right" title={
|
||
<div>
|
||
{
|
||
!isWritable ? null : (
|
||
<React.Fragment>
|
||
<Tooltip color={"rgb(255,255,255)"} placement="top" title={
|
||
<span onClick={(e) => e.stopPropagation()}>
|
||
<div style={{color: "black"}}>
|
||
{i18next.t("store:New folder")}:
|
||
</div>
|
||
<Input.Group style={{marginTop: "5px"}} compact>
|
||
<Input style={{width: "100px"}} value={this.state.newFolder} onChange={e => {
|
||
this.setState({
|
||
newFolder: e.target.value,
|
||
});
|
||
}} />
|
||
<Button type="primary" onClick={(e) => {
|
||
this.addFile(file, this.state.newFolder);
|
||
e.stopPropagation();
|
||
}}
|
||
>
|
||
OK
|
||
</Button>
|
||
</Input.Group>
|
||
</span>
|
||
}>
|
||
<span onClick={(e) => e.stopPropagation()}>
|
||
<Button style={{marginRight: "5px"}} icon={<FolderAddOutlined />} size="small" onClick={(e) => {
|
||
this.addFile();
|
||
e.stopPropagation();
|
||
}} />
|
||
</span>
|
||
</Tooltip>
|
||
<Tooltip title={i18next.t("store:Upload file")}>
|
||
<span onClick={(e) => e.stopPropagation()}>
|
||
<Upload multiple={true} accept="*" showUploadList={false} beforeUpload={file => {return false;}} onChange={(info) => {
|
||
if (this.checkUploadFile(info)) {
|
||
this.setState({
|
||
isUploadFileModalVisible: true,
|
||
file: file,
|
||
info: info,
|
||
});
|
||
} else {
|
||
this.uploadFile(file, info);
|
||
}
|
||
}}
|
||
>
|
||
<Button style={{marginRight: "5px"}} icon={<CloudUploadOutlined />} size="small" />
|
||
</Upload>
|
||
</span>
|
||
</Tooltip>
|
||
{
|
||
file.key === "/" ? null : (
|
||
<Tooltip title={i18next.t("store:Delete")}>
|
||
<span onClick={(e) => e.stopPropagation()}>
|
||
<Popconfirm
|
||
title={`Sure to delete folder: ${file.title} ?`}
|
||
onConfirm={(e) => {
|
||
this.deleteFile(file, false);
|
||
}}
|
||
okText="OK"
|
||
cancelText="Cancel"
|
||
>
|
||
<Button style={{marginRight: "5px"}} icon={<DeleteOutlined />} size="small" />
|
||
</Popconfirm>
|
||
</span>
|
||
</Tooltip>
|
||
)
|
||
}
|
||
</React.Fragment>
|
||
)
|
||
}
|
||
<Tooltip title={isAdmin ? i18next.t("store:Add Permission") :
|
||
i18next.t("store:Apply for Permission")}>
|
||
<Button icon={<FileDoneOutlined />} size="small" onClick={(e) => {
|
||
PermissionUtil.addPermission(this.props.account, this.props.store, file);
|
||
e.stopPropagation();
|
||
}} />
|
||
</Tooltip>
|
||
</div>
|
||
}>
|
||
<span style={tagStyle}>
|
||
{file.title}
|
||
</span>
|
||
|
||
|
||
{
|
||
(this.state.permissionMap === null) ? null : this.renderPermissions(this.state.permissionMap[file.key], isReadable)
|
||
}
|
||
</Tooltip>
|
||
);
|
||
}
|
||
}}
|
||
icon={(file) => {
|
||
if (file.isLeaf) {
|
||
const ext = Setting.getExtFromPath(file.data.key);
|
||
if (ext === "pdf") {
|
||
return <IconFont type="icon-testpdf" />;
|
||
} else if (ext === "doc" || ext === "docx") {
|
||
return <IconFont type="icon-testdocx" />;
|
||
} else if (ext === "ppt" || ext === "pptx") {
|
||
return <IconFont type="icon-testpptx" />;
|
||
} else if (ext === "xls" || ext === "xlsx") {
|
||
return <IconFont type="icon-testxlsx" />;
|
||
} else if (ext === "txt") {
|
||
return <IconFont type="icon-testdocument" />;
|
||
} else if (ext === "png" || ext === "bmp" || ext === "jpg" || ext === "jpeg" || ext === "svg") {
|
||
return <IconFont type="icon-testPicture" />;
|
||
} else if (ext === "html") {
|
||
return <IconFont type="icon-testhtml" />;
|
||
} else if (ext === "js") {
|
||
return <IconFont type="icon-testjs" />;
|
||
} else if (ext === "css") {
|
||
return <IconFont type="icon-testcss" />;
|
||
} else {
|
||
return <IconFont type="icon-testfile-unknown" />;
|
||
}
|
||
} else {
|
||
return <IconFont type="icon-testfolder" />;
|
||
}
|
||
}}
|
||
/>
|
||
);
|
||
}
|
||
|
||
isExtForDocViewer(ext) {
|
||
return ["bmp", "jpg", "jpeg", "png", "tiff", "doc", "docx", "ppt", "pptx", "xls", "xlsx", "pdf", "csv"].includes(ext);
|
||
}
|
||
|
||
isExtForFileViewer(ext) {
|
||
return ["png", "jpg", "jpeg", "gif", "bmp", "pdf", "xlsx", "docx", "mp4", "webm", "mp3"].includes(ext);
|
||
}
|
||
|
||
renderFileViewer(store) {
|
||
if (this.state.checkedFiles.length !== 0) {
|
||
const outerFile = {children: this.state.checkedFiles};
|
||
return (
|
||
<FileTable account={this.props.account} store={this.props.store} onRefresh={() => this.props.onRefresh()} file={outerFile} isCheckMode={true} />
|
||
);
|
||
}
|
||
|
||
if (this.state.selectedKeys.length === 0) {
|
||
return null;
|
||
}
|
||
|
||
const file = this.state.selectedFile;
|
||
if (file === null) {
|
||
return null;
|
||
}
|
||
|
||
const path = this.state.selectedKeys[0];
|
||
const filename = path.split("/").pop();
|
||
|
||
if (!file.isLeaf) {
|
||
return (
|
||
<FileTable account={this.props.account} store={this.props.store} onRefresh={() => this.props.onRefresh()} file={file} isCheckMode={false} />
|
||
);
|
||
}
|
||
|
||
if (!filename.includes(".")) {
|
||
return (
|
||
<div style={{height: this.getEditorHeightCss()}}>
|
||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||
</div>
|
||
);
|
||
}
|
||
|
||
const ext = Setting.getExtFromPath(path);
|
||
const url = this.state.selectedFile.url;
|
||
|
||
const app = this.getCacheApp(filename);
|
||
if (app !== "") {
|
||
return (
|
||
<iframe key={path} title={app} src={`${Conf.AppUrl}${app}`} width={"100%"} height={"100%"} />
|
||
// <DataChart filename={filename} url={url} height={this.getEditorHeightCss()} />
|
||
);
|
||
} else if (this.isExtForDocViewer(ext)) {
|
||
// https://github.com/Alcumus/react-doc-viewer
|
||
return (
|
||
<DocViewer
|
||
key={path}
|
||
style={{height: this.getEditorHeightCss()}}
|
||
pluginRenderers={DocViewerRenderers}
|
||
documents={[{uri: url}]}
|
||
theme={{
|
||
primary: "rgb(92,48,125)",
|
||
secondary: "#ffffff",
|
||
tertiary: "rgba(92,48,125,0.55)",
|
||
text_primary: "#ffffff",
|
||
text_secondary: "rgb(92,48,125)",
|
||
text_tertiary: "#00000099",
|
||
disableThemeScrollbar: false,
|
||
}}
|
||
config={{
|
||
header: {
|
||
disableHeader: true,
|
||
disableFileName: true,
|
||
retainURLParams: false,
|
||
},
|
||
}}
|
||
/>
|
||
);
|
||
} else if (this.isExtForFileViewer(ext)) {
|
||
// https://github.com/plangrid/react-file-viewer
|
||
return (
|
||
<a target="_blank" rel="noreferrer" href={url}>
|
||
<FileViewer
|
||
key={path}
|
||
fileType={ext}
|
||
filePath={url}
|
||
errorComponent={<div>error</div>}
|
||
onError={(error) => {
|
||
Setting.showMessage("error", error);
|
||
}}
|
||
/>
|
||
</a>
|
||
);
|
||
} else {
|
||
// https://github.com/scniro/react-codemirror2
|
||
if (this.state.loading) {
|
||
return (
|
||
<div className="App">
|
||
<Spin size="large" tip={i18next.t("general:Loading...")} style={{paddingTop: "10%"}} />
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div style={{height: this.getEditorHeightCss()}}>
|
||
<CodeMirror
|
||
key={path}
|
||
value={this.state.text}
|
||
// options={{mode: "javascript", theme: "material-darker"}}
|
||
onBeforeChange={(editor, data, value) => {}}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|
||
}
|
||
|
||
getPropertyValue(file, propertyName) {
|
||
const properties = this.props.store.propertiesMap[file.key];
|
||
if (properties === undefined) {
|
||
return "";
|
||
} else {
|
||
return properties[propertyName];
|
||
}
|
||
}
|
||
|
||
setPropertyValue(file, propertyName, value) {
|
||
const store = this.props.store;
|
||
if (store.propertiesMap[file.key] === undefined) {
|
||
store.propertiesMap[file.key] = {};
|
||
}
|
||
store.propertiesMap[file.key][propertyName] = value;
|
||
this.updateStore(store);
|
||
}
|
||
|
||
getMomentTime(t) {
|
||
if (t === "") {
|
||
return "";
|
||
} else {
|
||
return new moment(t);
|
||
}
|
||
}
|
||
|
||
renderProperties() {
|
||
if (this.state.selectedKeys.length === 0) {
|
||
return null;
|
||
}
|
||
|
||
const file = this.state.selectedFile;
|
||
if (file === null) {
|
||
return null;
|
||
}
|
||
|
||
const subjectOptions = [
|
||
{id: "Math", name: i18next.t("store:Math")},
|
||
{id: "Chinese", name: i18next.t("store:Chinese")},
|
||
{id: "English", name: i18next.t("store:English")},
|
||
{id: "Science", name: i18next.t("store:Science")},
|
||
{id: "Physics", name: i18next.t("store:Physics")},
|
||
{id: "Chemistry", name: i18next.t("store:Chemistry")},
|
||
{id: "Biology", name: i18next.t("store:Biology")},
|
||
{id: "History", name: i18next.t("store:History")},
|
||
];
|
||
|
||
const getSubjectDisplayName = (id) => {
|
||
const options = subjectOptions.filter(option => option.id === id);
|
||
if (options.length === 0) {
|
||
return "";
|
||
} else {
|
||
return options[0].name;
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div ref={this.filePane}>
|
||
<Descriptions
|
||
style={{backgroundColor: "white"}}
|
||
labelStyle={{backgroundColor: "rgb(245,245,245)"}}
|
||
bordered
|
||
// title="Custom Size"
|
||
size="small"
|
||
// extra={<Button type="primary">Edit</Button>}
|
||
>
|
||
<Descriptions.Item label={i18next.t("factorset:File name")}>
|
||
{file.title}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label={i18next.t("store:File type")}>
|
||
{Setting.getExtFromFile(file)}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label={i18next.t("factorset:File size")}>
|
||
{Setting.getFriendlyFileSize(file.size)}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label={i18next.t("general:Created time")}>
|
||
{Setting.getFormattedDate(file.createdTime)}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label={i18next.t("store:Collected time")}>
|
||
{Setting.getFormattedDate(Setting.getCollectedTime(file.title))}
|
||
<DatePicker key={file.key} showTime defaultValue={this.getMomentTime(this.getPropertyValue(file, "collectedTime"))} onChange={(value, dateString) => {
|
||
this.setPropertyValue(file, "collectedTime", value.format());
|
||
}} onOk={(value) => {}} />
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label={i18next.t("store:Subject")}>
|
||
<Select virtual={false} style={{width: "120px"}} value={getSubjectDisplayName(this.getPropertyValue(file, "subject"))} onChange={(value => {
|
||
this.setPropertyValue(file, "subject", value);
|
||
})}>
|
||
{
|
||
subjectOptions.map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||
}
|
||
</Select>
|
||
</Descriptions.Item>
|
||
</Descriptions>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
getEditorHeightCss() {
|
||
// 79, 123
|
||
let filePaneHeight = this.filePane.current?.offsetHeight;
|
||
if (!filePaneHeight) {
|
||
filePaneHeight = 0;
|
||
}
|
||
|
||
return `calc(100vh - ${filePaneHeight + 186}px)`;
|
||
}
|
||
|
||
render() {
|
||
if (this.props.store.fileTree === null) {
|
||
return (
|
||
<div className="App">
|
||
<Result
|
||
status="error"
|
||
title={`${this.props.store.error}`}
|
||
extra={
|
||
<Button type="primary" onClick={() => this.props.history.push(`/stores/${this.props.store.owner}/${this.props.store.name}`)}>
|
||
Go to Store
|
||
</Button>
|
||
}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div>
|
||
<Row>
|
||
<Col span={8}>
|
||
<Card className="content-warp-card-filetreeleft" style={{marginRight: "10px"}}>
|
||
<div style={{margin: "-25px"}}>
|
||
{
|
||
this.renderSearch(this.props.store)
|
||
}
|
||
{
|
||
this.renderTree(this.props.store)
|
||
}
|
||
</div>
|
||
</Card>
|
||
</Col>
|
||
<Col span={16}>
|
||
<Card className="content-warp-card-filetreeright">
|
||
<div style={{margin: "-25px"}}>
|
||
<div style={{height: this.getEditorHeightCss()}}>
|
||
{
|
||
this.renderFileViewer(this.props.store)
|
||
}
|
||
</div>
|
||
{
|
||
this.renderProperties()
|
||
}
|
||
</div>
|
||
</Card>
|
||
</Col>
|
||
</Row>
|
||
{
|
||
this.renderUploadFileModal()
|
||
}
|
||
</div>
|
||
);
|
||
}
|
||
}
|
||
|
||
export default withRouter(FileTree);
|