mirror of
https://github.com/wassname/optuna-dashboard.git
synced 2026-10-04 12:50:44 +08:00
wip
This commit is contained in:
1 parent
70a142b613
commit
516f06a19c
3 files changed
+535
-341
No files matched your search
@@ -7,17 +7,59 @@ import {
|
||||
CardContent,
|
||||
CardActions,
|
||||
CardActionArea,
|
||||
CardMedia,
|
||||
MenuItem,
|
||||
Select,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
TextField,
|
||||
Modal,
|
||||
} from "@mui/material"
|
||||
import ClearIcon from "@mui/icons-material/Clear"
|
||||
import IconButton from "@mui/material/IconButton"
|
||||
import OpenInFullIcon from "@mui/icons-material/OpenInFull"
|
||||
import ReplayIcon from "@mui/icons-material/Replay"
|
||||
import Modal from "@mui/material/Modal"
|
||||
import { red } from "@mui/material/colors"
|
||||
|
||||
import ClearIcon from "@mui/icons-material/Clear"
|
||||
import IconButton from "@mui/material/IconButton"
|
||||
import SettingsIcon from "@mui/icons-material/Settings"
|
||||
import red from "@mui/material/colors/red"
|
||||
import { useRecoilValue, useSetRecoilState } from "recoil"
|
||||
import { actionCreator } from "../action"
|
||||
import { TrialListDetail } from "./TrialList"
|
||||
import { MarkdownRenderer } from "./Note"
|
||||
import {
|
||||
feedbackComponent,
|
||||
FeedbackComponentType,
|
||||
feedbackArtifactKey,
|
||||
} from "../state"
|
||||
import {
|
||||
TrialArtifactActions,
|
||||
TrialArtifactContent,
|
||||
TrialListDetail,
|
||||
} from "./TrialList"
|
||||
|
||||
const FeedbackContent: FC<{
|
||||
trial: Trial
|
||||
artifact?: Artifact
|
||||
}> = ({ trial, artifact }) => {
|
||||
const componentId = useRecoilValue(feedbackComponent)
|
||||
|
||||
if (componentId === "note") {
|
||||
return <MarkdownRenderer body={trial.note.body} />
|
||||
}
|
||||
if (componentId === "artifact") {
|
||||
if (artifact === undefined) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<TrialArtifactContent
|
||||
trial={trial}
|
||||
artifact={artifact}
|
||||
width={"100%"}
|
||||
height={"100%"}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const PreferentialTrial: FC<{
|
||||
trial?: Trial
|
||||
@@ -29,6 +71,9 @@ const PreferentialTrial: FC<{
|
||||
const trialWidth = 500
|
||||
const trialHeight = 300
|
||||
const [detailShown, setDetailShown] = useState(false)
|
||||
const componentId = useRecoilValue(feedbackComponent)
|
||||
const artifactKey = useRecoilValue(feedbackArtifactKey)
|
||||
const artifact = trial?.artifacts.find((a) => a.filename === artifactKey)
|
||||
|
||||
if (trial == undefined) {
|
||||
return (
|
||||
@@ -52,7 +97,19 @@ const PreferentialTrial: FC<{
|
||||
}}
|
||||
>
|
||||
<CardActions>
|
||||
<Typography variant="h5">Trial {trial.number}</Typography>
|
||||
<Typography variant="h5">
|
||||
Trial {trial.number}
|
||||
{componentId === "artifact" && artifact !== undefined
|
||||
? ` (${artifact.filename})`
|
||||
: ""}
|
||||
</Typography>
|
||||
{componentId === "artifact" && artifact !== undefined ? (
|
||||
<TrialArtifactActions
|
||||
trial={trial}
|
||||
artifact={artifact}
|
||||
sx={{ marginLeft: "auto" }}
|
||||
/>
|
||||
) : null}
|
||||
<IconButton
|
||||
sx={{
|
||||
marginLeft: "auto",
|
||||
@@ -112,7 +169,7 @@ const PreferentialTrial: FC<{
|
||||
padding: theme.spacing(2),
|
||||
}}
|
||||
>
|
||||
<MarkdownRenderer body={trial.note.body} />
|
||||
<FeedbackContent trial={trial} artifact={artifact} />
|
||||
</Box>
|
||||
|
||||
<ClearIcon
|
||||
@@ -189,6 +246,11 @@ export const PreferentialTrials: FC<{ studyDetail: StudyDetail | null }> = ({
|
||||
numbers: studyDetail.best_trials.map((t) => t.number),
|
||||
last_number: Math.max(...studyDetail.best_trials.map((t) => t.number), -1),
|
||||
})
|
||||
const [settingShown, setSettingShown] = useState(false)
|
||||
const outputComponent = useRecoilValue(feedbackComponent)
|
||||
const setOutputComponent = useSetRecoilState(feedbackComponent)
|
||||
const outputartifactKey = useRecoilValue(feedbackArtifactKey)
|
||||
const setOutputartifactKey = useSetRecoilState(feedbackArtifactKey)
|
||||
const new_trails = studyDetail.best_trials.filter(
|
||||
(t) =>
|
||||
displayTrials.last_number < t.number &&
|
||||
@@ -228,7 +290,23 @@ export const PreferentialTrials: FC<{ studyDetail: StudyDetail | null }> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Box padding={theme.spacing(2)}>
|
||||
<Box
|
||||
padding={theme.spacing(2)}
|
||||
sx={{
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
right: 0,
|
||||
margin: theme.spacing(1),
|
||||
}}
|
||||
onClick={() => setSettingShown(true)}
|
||||
>
|
||||
<SettingsIcon />
|
||||
</IconButton>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
@@ -250,6 +328,48 @@ export const PreferentialTrials: FC<{ studyDetail: StudyDetail | null }> = ({
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
<ModalPage displayFlag={settingShown} setDisplayFlag={setSettingShown}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{
|
||||
padding: theme.spacing(2),
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
}}
|
||||
>
|
||||
Settings
|
||||
<Box
|
||||
sx={{
|
||||
padding: theme.spacing(2),
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">Output Component:</FormLabel>
|
||||
<Select
|
||||
value={outputComponent}
|
||||
onChange={(e) => {
|
||||
setOutputComponent(e.target.value as FeedbackComponentType)
|
||||
}}
|
||||
>
|
||||
<MenuItem value="note">Note</MenuItem>
|
||||
<MenuItem value="artifact">Artifact</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
{outputComponent === "artifact" ? (
|
||||
<FormControl>
|
||||
<FormLabel component="legend">Output File:</FormLabel>
|
||||
<TextField
|
||||
defaultValue={outputartifactKey}
|
||||
onBlur={(e) => {
|
||||
setOutputartifactKey(e.target.value)
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
) : null}
|
||||
</Box>
|
||||
</Typography>
|
||||
</ModalPage>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
CardActionArea,
|
||||
Modal,
|
||||
} from "@mui/material"
|
||||
import { SxProps } from "@mui/system"
|
||||
import Chip from "@mui/material/Chip"
|
||||
import Divider from "@mui/material/Divider"
|
||||
import List from "@mui/material/List"
|
||||
@@ -319,16 +320,397 @@ export const TrialListDetail: FC<{
|
||||
value !== null ? renderInfo(key, value) : null
|
||||
)}
|
||||
</Box>
|
||||
{artifactEnabled && <TrialArtifact trial={trial} />}
|
||||
{artifactEnabled && <TrialArtifacts trial={trial} />}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
const TrialArtifact: FC<{ trial: Trial }> = ({ trial }) => {
|
||||
export const TrialArtifactContent: FC<{
|
||||
trial: Trial
|
||||
artifact: Artifact
|
||||
width: string
|
||||
height: string
|
||||
}> = ({ trial, artifact, width, height }) => {
|
||||
if (artifact.mimetype.startsWith("image")) {
|
||||
return (
|
||||
<CardMedia
|
||||
component="img"
|
||||
height={height}
|
||||
image={`/artifacts/${trial.study_id}/${trial.trial_id}/${artifact.artifact_id}`}
|
||||
alt={artifact.filename}
|
||||
/>
|
||||
)
|
||||
} else if (
|
||||
artifact.filename.endsWith(".stl") ||
|
||||
artifact.filename.endsWith(".3dm")
|
||||
) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<ThreejsArtifactViewer
|
||||
src={`/artifacts/${trial.study_id}/${trial.trial_id}/${artifact.artifact_id}`}
|
||||
width={width}
|
||||
height={height}
|
||||
hasGizmo={false}
|
||||
filetype={artifact.filename.split(".").pop()}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
} else if (artifact.mimetype.startsWith("audio")) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<audio controls>
|
||||
<source
|
||||
src={`/artifacts/${trial.study_id}/${trial.trial_id}/${artifact.artifact_id}`}
|
||||
type={artifact.mimetype}
|
||||
/>
|
||||
</audio>
|
||||
</Box>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<InsertDriveFileIcon sx={{ fontSize: 80 }} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const TrialArtifactActions: FC<{
|
||||
trial: Trial
|
||||
artifact: Artifact
|
||||
sx: SxProps
|
||||
}> = ({ trial, artifact, sx }) => {
|
||||
const [open3dModelViewer, setOpen3dModelViewer] = useState<boolean>(false)
|
||||
|
||||
if (artifact.mimetype.startsWith("image")) {
|
||||
return null
|
||||
} else if (
|
||||
artifact.filename.endsWith(".stl") ||
|
||||
artifact.filename.endsWith(".3dm")
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
<IconButton
|
||||
aria-label="show artifact 3d model"
|
||||
size="small"
|
||||
color="inherit"
|
||||
sx={sx}
|
||||
onClick={() => {
|
||||
setOpen3dModelViewer(true)
|
||||
}}
|
||||
>
|
||||
<FullscreenIcon />
|
||||
</IconButton>
|
||||
<Modal
|
||||
open={open3dModelViewer}
|
||||
onClose={() => {
|
||||
setOpen3dModelViewer(false)
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
bgcolor: "background.paper",
|
||||
borderRadius: "15px",
|
||||
}}
|
||||
>
|
||||
<ThreejsArtifactViewer
|
||||
src={`/artifacts/${trial.study_id}/${trial.trial_id}/${artifact.artifact_id}`}
|
||||
width={`${innerWidth * 0.8}px`}
|
||||
height={`${innerHeight * 0.8}px`}
|
||||
hasGizmo={true}
|
||||
filetype={artifact.filename.split(".").pop()}
|
||||
/>
|
||||
</Box>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const TrialArtifact: FC<{
|
||||
trial: Trial
|
||||
artifact: Artifact
|
||||
width: string
|
||||
height: string
|
||||
}> = ({ trial, artifact, width, height }) => {
|
||||
const [openDeleteArtifactDialog, _] = useDeleteArtifactDialog()
|
||||
const theme = useTheme()
|
||||
if (artifact.mimetype.startsWith("image")) {
|
||||
return (
|
||||
<Card
|
||||
key={artifact.artifact_id}
|
||||
sx={{
|
||||
marginBottom: theme.spacing(2),
|
||||
width: width,
|
||||
margin: theme.spacing(0, 1, 1, 0),
|
||||
}}
|
||||
>
|
||||
<TrialArtifactContent
|
||||
trial={trial}
|
||||
artifact={artifact}
|
||||
width={width}
|
||||
height={height}
|
||||
/>
|
||||
<CardContent
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
padding: `${theme.spacing(1)} !important`,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
p: theme.spacing(0.5, 0),
|
||||
flexGrow: 1,
|
||||
wordWrap: "break-word",
|
||||
maxWidth: `calc(100% - ${theme.spacing(8)})`,
|
||||
}}
|
||||
>
|
||||
{artifact.filename}
|
||||
</Typography>
|
||||
<IconButton
|
||||
aria-label="delete artifact"
|
||||
size="small"
|
||||
color="inherit"
|
||||
sx={{ margin: "auto 0" }}
|
||||
onClick={() => {
|
||||
openDeleteArtifactDialog(trial.study_id, trial.trial_id, artifact)
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label="download artifact"
|
||||
size="small"
|
||||
color="inherit"
|
||||
download={artifact.filename}
|
||||
sx={{ margin: "auto 0" }}
|
||||
href={`/artifacts/${trial.study_id}/${trial.trial_id}/${artifact.artifact_id}`}
|
||||
>
|
||||
<DownloadIcon />
|
||||
</IconButton>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
} else if (
|
||||
artifact.filename.endsWith(".stl") ||
|
||||
artifact.filename.endsWith(".3dm")
|
||||
) {
|
||||
return (
|
||||
<Card
|
||||
key={artifact.artifact_id}
|
||||
sx={{
|
||||
marginBottom: theme.spacing(2),
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: width,
|
||||
minHeight: "100%",
|
||||
margin: theme.spacing(0, 1, 1, 0),
|
||||
}}
|
||||
>
|
||||
<TrialArtifactContent
|
||||
trial={trial}
|
||||
artifact={artifact}
|
||||
width={width}
|
||||
height={height}
|
||||
/>
|
||||
<CardContent
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
padding: `${theme.spacing(1)} !important`,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
p: theme.spacing(0.5, 0),
|
||||
flexGrow: 1,
|
||||
wordWrap: "break-word",
|
||||
maxWidth: `calc(100% - ${theme.spacing(12)})`,
|
||||
}}
|
||||
>
|
||||
{artifact.filename}
|
||||
</Typography>
|
||||
<TrialArtifactActions
|
||||
trial={trial}
|
||||
artifact={artifact}
|
||||
sx={{ margin: "auto 0" }}
|
||||
/>
|
||||
<IconButton
|
||||
aria-label="delete artifact"
|
||||
size="small"
|
||||
color="inherit"
|
||||
sx={{ margin: "auto 0" }}
|
||||
onClick={() => {
|
||||
openDeleteArtifactDialog(trial.study_id, trial.trial_id, artifact)
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label="download artifact"
|
||||
size="small"
|
||||
color="inherit"
|
||||
sx={{ margin: "auto 0" }}
|
||||
download={artifact.filename}
|
||||
href={`/artifacts/${trial.study_id}/${trial.trial_id}/${artifact.artifact_id}`}
|
||||
>
|
||||
<DownloadIcon />
|
||||
</IconButton>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
} else if (artifact.mimetype.startsWith("audio")) {
|
||||
return (
|
||||
<Card
|
||||
key={artifact.artifact_id}
|
||||
sx={{
|
||||
marginBottom: theme.spacing(2),
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: width,
|
||||
minHeight: "100%",
|
||||
margin: theme.spacing(0, 1, 1, 0),
|
||||
}}
|
||||
>
|
||||
<TrialArtifactContent
|
||||
trial={trial}
|
||||
artifact={artifact}
|
||||
width={width}
|
||||
height={height}
|
||||
/>
|
||||
<CardContent
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
padding: `${theme.spacing(1)} !important`,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
p: theme.spacing(0.5, 0),
|
||||
flexGrow: 1,
|
||||
maxWidth: `calc(100% - ${theme.spacing(8)})`,
|
||||
}}
|
||||
>
|
||||
{artifact.filename}
|
||||
</Typography>
|
||||
<IconButton
|
||||
aria-label="delete artifact"
|
||||
size="small"
|
||||
color="inherit"
|
||||
sx={{ margin: "auto 0" }}
|
||||
onClick={() => {
|
||||
openDeleteArtifactDialog(trial.study_id, trial.trial_id, artifact)
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label="download artifact"
|
||||
size="small"
|
||||
color="inherit"
|
||||
sx={{ margin: "auto 0" }}
|
||||
download={artifact.filename}
|
||||
href={`/artifacts/${trial.study_id}/${trial.trial_id}/${artifact.artifact_id}`}
|
||||
>
|
||||
<DownloadIcon />
|
||||
</IconButton>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Card
|
||||
key={artifact.artifact_id}
|
||||
sx={{
|
||||
marginBottom: theme.spacing(2),
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: width,
|
||||
minHeight: "100%",
|
||||
margin: theme.spacing(0, 1, 1, 0),
|
||||
}}
|
||||
>
|
||||
<TrialArtifactContent
|
||||
trial={trial}
|
||||
artifact={artifact}
|
||||
width={width}
|
||||
height={height}
|
||||
/>
|
||||
<CardContent
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
padding: `${theme.spacing(1)} !important`,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
p: theme.spacing(0.5, 0),
|
||||
flexGrow: 1,
|
||||
maxWidth: `calc(100% - ${theme.spacing(8)})`,
|
||||
}}
|
||||
>
|
||||
{artifact.filename}
|
||||
</Typography>
|
||||
<IconButton
|
||||
aria-label="delete artifact"
|
||||
size="small"
|
||||
color="inherit"
|
||||
sx={{ margin: "auto 0" }}
|
||||
onClick={() => {
|
||||
openDeleteArtifactDialog(trial.study_id, trial.trial_id, artifact)
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label="download artifact"
|
||||
size="small"
|
||||
color="inherit"
|
||||
sx={{ margin: "auto 0" }}
|
||||
download={artifact.filename}
|
||||
href={`/artifacts/${trial.study_id}/${trial.trial_id}/${artifact.artifact_id}`}
|
||||
>
|
||||
<DownloadIcon />
|
||||
</IconButton>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const TrialArtifacts: FC<{ trial: Trial }> = ({ trial }) => {
|
||||
const theme = useTheme()
|
||||
const action = actionCreator()
|
||||
const [openDeleteArtifactDialog, renderDeleteArtifactDialog] =
|
||||
useDeleteArtifactDialog()
|
||||
const [_, renderDeleteArtifactDialog] = useDeleteArtifactDialog()
|
||||
const [dragOver, setDragOver] = useState<boolean>(false)
|
||||
const [open3dModelViewer, setOpen3dModelViewer] = useState<{
|
||||
[key: string]: boolean
|
||||
@@ -382,334 +764,15 @@ const TrialArtifact: FC<{ trial: Trial }> = ({ trial }) => {
|
||||
Artifacts
|
||||
</Typography>
|
||||
<Box sx={{ display: "flex", flexWrap: "wrap", p: theme.spacing(1, 0) }}>
|
||||
{trial.artifacts.map((a) => {
|
||||
if (a.mimetype.startsWith("image")) {
|
||||
return (
|
||||
<Card
|
||||
key={a.artifact_id}
|
||||
sx={{
|
||||
marginBottom: theme.spacing(2),
|
||||
width: width,
|
||||
margin: theme.spacing(0, 1, 1, 0),
|
||||
}}
|
||||
>
|
||||
<CardMedia
|
||||
component="img"
|
||||
height={height}
|
||||
image={`/artifacts/${trial.study_id}/${trial.trial_id}/${a.artifact_id}`}
|
||||
alt={a.filename}
|
||||
/>
|
||||
<CardContent
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
padding: `${theme.spacing(1)} !important`,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
p: theme.spacing(0.5, 0),
|
||||
flexGrow: 1,
|
||||
wordWrap: "break-word",
|
||||
maxWidth: `calc(100% - ${theme.spacing(8)})`,
|
||||
}}
|
||||
>
|
||||
{a.filename}
|
||||
</Typography>
|
||||
<IconButton
|
||||
aria-label="delete artifact"
|
||||
size="small"
|
||||
color="inherit"
|
||||
sx={{ margin: "auto 0" }}
|
||||
onClick={() => {
|
||||
openDeleteArtifactDialog(
|
||||
trial.study_id,
|
||||
trial.trial_id,
|
||||
a
|
||||
)
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label="download artifact"
|
||||
size="small"
|
||||
color="inherit"
|
||||
download={a.filename}
|
||||
sx={{ margin: "auto 0" }}
|
||||
href={`/artifacts/${trial.study_id}/${trial.trial_id}/${a.artifact_id}`}
|
||||
>
|
||||
<DownloadIcon />
|
||||
</IconButton>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
} else if (
|
||||
a.filename.endsWith(".stl") ||
|
||||
a.filename.endsWith(".3dm")
|
||||
) {
|
||||
return (
|
||||
<Card
|
||||
key={a.artifact_id}
|
||||
sx={{
|
||||
marginBottom: theme.spacing(2),
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: width,
|
||||
minHeight: "100%",
|
||||
margin: theme.spacing(0, 1, 1, 0),
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<ThreejsArtifactViewer
|
||||
src={`/artifacts/${trial.study_id}/${trial.trial_id}/${a.artifact_id}`}
|
||||
width={width}
|
||||
height={height}
|
||||
hasGizmo={false}
|
||||
filetype={a.filename.split(".").pop()}
|
||||
/>
|
||||
</Box>
|
||||
<CardContent
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
padding: `${theme.spacing(1)} !important`,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
p: theme.spacing(0.5, 0),
|
||||
flexGrow: 1,
|
||||
wordWrap: "break-word",
|
||||
maxWidth: `calc(100% - ${theme.spacing(12)})`,
|
||||
}}
|
||||
>
|
||||
{a.filename}
|
||||
</Typography>
|
||||
<IconButton
|
||||
aria-label="show artifact 3d model"
|
||||
size="small"
|
||||
color="inherit"
|
||||
sx={{ margin: "auto 0" }}
|
||||
onClick={() => {
|
||||
setOpen3dModelViewer(() => {
|
||||
const obj = { ...open3dModelViewer }
|
||||
obj[a.artifact_id] = true
|
||||
return obj
|
||||
})
|
||||
}}
|
||||
>
|
||||
<FullscreenIcon />
|
||||
</IconButton>
|
||||
<Modal
|
||||
open={
|
||||
a.artifact_id in open3dModelViewer
|
||||
? open3dModelViewer[a.artifact_id]
|
||||
: false
|
||||
}
|
||||
onClose={() => {
|
||||
setOpen3dModelViewer(() => {
|
||||
const obj = { ...open3dModelViewer }
|
||||
obj[a.artifact_id] = false
|
||||
return obj
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
bgcolor: "background.paper",
|
||||
borderRadius: "15px",
|
||||
}}
|
||||
>
|
||||
<ThreejsArtifactViewer
|
||||
src={`/artifacts/${trial.study_id}/${trial.trial_id}/${a.artifact_id}`}
|
||||
width={`${innerWidth * 0.8}px`}
|
||||
height={`${innerHeight * 0.8}px`}
|
||||
hasGizmo={true}
|
||||
filetype={a.filename.split(".").pop()}
|
||||
/>
|
||||
</Box>
|
||||
</Modal>
|
||||
<IconButton
|
||||
aria-label="delete artifact"
|
||||
size="small"
|
||||
color="inherit"
|
||||
sx={{ margin: "auto 0" }}
|
||||
onClick={() => {
|
||||
openDeleteArtifactDialog(
|
||||
trial.study_id,
|
||||
trial.trial_id,
|
||||
a
|
||||
)
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label="download artifact"
|
||||
size="small"
|
||||
color="inherit"
|
||||
sx={{ margin: "auto 0" }}
|
||||
download={a.filename}
|
||||
href={`/artifacts/${trial.study_id}/${trial.trial_id}/${a.artifact_id}`}
|
||||
>
|
||||
<DownloadIcon />
|
||||
</IconButton>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
} else if (a.mimetype.startsWith("audio")) {
|
||||
return (
|
||||
<Card
|
||||
key={a.artifact_id}
|
||||
sx={{
|
||||
marginBottom: theme.spacing(2),
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: width,
|
||||
minHeight: "100%",
|
||||
margin: theme.spacing(0, 1, 1, 0),
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<audio controls>
|
||||
<source
|
||||
src={`/artifacts/${trial.study_id}/${trial.trial_id}/${a.artifact_id}`}
|
||||
type={a.mimetype}
|
||||
/>
|
||||
</audio>
|
||||
</Box>
|
||||
<CardContent
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
padding: `${theme.spacing(1)} !important`,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
p: theme.spacing(0.5, 0),
|
||||
flexGrow: 1,
|
||||
maxWidth: `calc(100% - ${theme.spacing(8)})`,
|
||||
}}
|
||||
>
|
||||
{a.filename}
|
||||
</Typography>
|
||||
<IconButton
|
||||
aria-label="delete artifact"
|
||||
size="small"
|
||||
color="inherit"
|
||||
sx={{ margin: "auto 0" }}
|
||||
onClick={() => {
|
||||
openDeleteArtifactDialog(
|
||||
trial.study_id,
|
||||
trial.trial_id,
|
||||
a
|
||||
)
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label="download artifact"
|
||||
size="small"
|
||||
color="inherit"
|
||||
sx={{ margin: "auto 0" }}
|
||||
download={a.filename}
|
||||
href={`/artifacts/${trial.study_id}/${trial.trial_id}/${a.artifact_id}`}
|
||||
>
|
||||
<DownloadIcon />
|
||||
</IconButton>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Card
|
||||
key={a.artifact_id}
|
||||
sx={{
|
||||
marginBottom: theme.spacing(2),
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: width,
|
||||
minHeight: "100%",
|
||||
margin: theme.spacing(0, 1, 1, 0),
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<InsertDriveFileIcon sx={{ fontSize: 80 }} />
|
||||
</Box>
|
||||
<CardContent
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
padding: `${theme.spacing(1)} !important`,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
p: theme.spacing(0.5, 0),
|
||||
flexGrow: 1,
|
||||
maxWidth: `calc(100% - ${theme.spacing(8)})`,
|
||||
}}
|
||||
>
|
||||
{a.filename}
|
||||
</Typography>
|
||||
<IconButton
|
||||
aria-label="delete artifact"
|
||||
size="small"
|
||||
color="inherit"
|
||||
sx={{ margin: "auto 0" }}
|
||||
onClick={() => {
|
||||
openDeleteArtifactDialog(
|
||||
trial.study_id,
|
||||
trial.trial_id,
|
||||
a
|
||||
)
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label="download artifact"
|
||||
size="small"
|
||||
color="inherit"
|
||||
sx={{ margin: "auto 0" }}
|
||||
download={a.filename}
|
||||
href={`/artifacts/${trial.study_id}/${trial.trial_id}/${a.artifact_id}`}
|
||||
>
|
||||
<DownloadIcon />
|
||||
</IconButton>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
})}
|
||||
{trial.artifacts.map((a) => (
|
||||
<TrialArtifact
|
||||
key={a.artifact_id}
|
||||
trial={trial}
|
||||
artifact={a}
|
||||
width={width}
|
||||
height={height}
|
||||
/>
|
||||
))}
|
||||
{trial.state === "Running" || trial.state === "Waiting" ? (
|
||||
<Card
|
||||
sx={{
|
||||
|
||||
@@ -56,6 +56,17 @@ export const artifactIsAvailable = atom<boolean>({
|
||||
default: false,
|
||||
})
|
||||
|
||||
export type FeedbackComponentType = "note" | "artifact"
|
||||
export const feedbackComponent = atom<FeedbackComponentType>({
|
||||
key: "feedbackComponent",
|
||||
default: "note",
|
||||
})
|
||||
|
||||
export const feedbackArtifactKey = atom<string>({
|
||||
key: "feedbackArtifactKey",
|
||||
default: "",
|
||||
})
|
||||
|
||||
export const useStudyDetailValue = (studyId: number): StudyDetail | null => {
|
||||
const studyDetails = useRecoilValue<StudyDetails>(studyDetailsState)
|
||||
return studyDetails[studyId] || null
|
||||
|
||||
Reference in new issue
Block a user