This commit is contained in:
moririn2528 committed 2023-08-31 14:08:05 +09:00
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>
)
}
+395 -332
View File
@@ -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={{
+11
View File
@@ -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