Skip to content

Commit

Permalink
fix image drop zone message showing under image (#1189)
Browse files Browse the repository at this point in the history
  • Loading branch information
mipyykko committed May 22, 2023
1 parent 5c76e4c commit a9a548f
Show file tree
Hide file tree
Showing 10 changed files with 71 additions and 51 deletions.
14 changes: 7 additions & 7 deletions backend/graphql/Course/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,11 @@ export const CourseMutations = extendType({
"course_stats_email_id",
]),
name: course.name ?? "",
photo: !!photo ? { connect: { id: photo } } : undefined,
photo: photo ? { connect: { id: photo } } : undefined,
course_translations: {
create: course_translations?.filter(isNotNullOrUndefined),
},
study_modules: !!study_modules
study_modules: study_modules
? {
connect: study_modules.map((s) => ({
id: nullToUndefined(s?.id),
Expand All @@ -90,20 +90,20 @@ export const CourseMutations = extendType({
},
course_variants: { create: course_variants ?? undefined },
course_aliases: { create: course_aliases ?? undefined },
inherit_settings_from: !!inherit_settings_from
inherit_settings_from: inherit_settings_from
? { connect: { id: inherit_settings_from } }
: undefined,
completions_handled_by: !!completions_handled_by
completions_handled_by: completions_handled_by
? { connect: { id: completions_handled_by } }
: undefined,
user_course_settings_visibilities: {
create: user_course_settings_visibilities ?? undefined,
},
// don't think these will be passed by parameter, but let's be sure
completion_email: !!completion_email_id
completion_email: completion_email_id
? { connect: { id: completion_email_id } }
: undefined,
course_stats_email: !!course_stats_email_id
course_stats_email: course_stats_email_id
? { connect: { id: course_stats_email_id } }
: undefined,
tags: { connect: (tags ?? []).map((tag) => ({ id: tag.id })) },
Expand Down Expand Up @@ -236,7 +236,7 @@ export const CourseMutations = extendType({
end_date,
// FIXME: disconnect removed photos?
...updatedFields,
photo: !!photo ? { connect: { id: photo } } : undefined,
photo: photo ? { connect: { id: photo } } : undefined,
study_modules: studyModuleMutation,
completion_email: completion_email_id
? { connect: { id: completion_email_id } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ function ControlledImageInputImpl(props: ControlledImageInputProps) {
}}
onImageLoad={onImageLoad}
onImageAccepted={onImageAccepted}
thumbnail={thumbnail}
>
<ImagePreview file={thumbnail} onImageRemove={onImageRemove} />
</ImageDropzoneInput>
Expand Down
11 changes: 6 additions & 5 deletions frontend/components/Dashboard/Editor/Course/CourseImageForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ function CourseImageForm(props: CourseImageFormProps) {
const { defaultValues } = useCourseEditorData()
const [dialogOpen, setDialogOpen] = useState(false)

const thumbnail = watch("thumbnail")

const onImageLoad = useEventCallback((value: string | ArrayBuffer | null) =>
setValue("thumbnail", value),
)
Expand All @@ -55,6 +57,8 @@ function CourseImageForm(props: CourseImageFormProps) {
[defaultValues],
)

const thumbnailWithDomain = useMemo(() => addDomain(thumbnail), [thumbnail])

const slug = watch("slug")

const coursesWithPhotos = useMemo(
Expand Down Expand Up @@ -85,10 +89,7 @@ function CourseImageForm(props: CourseImageFormProps) {
<FormSubtitle variant="h6" component="h3" align="center">
{t("coursePhoto")}
</FormSubtitle>
<ControlledHiddenField
name="thumbnail"
defaultValue={watch("thumbnail") ?? ""}
/>
<ControlledHiddenField name="thumbnail" defaultValue={thumbnail ?? ""} />
<ControlledHiddenField name="photo" defaultValue={watch("photo") ?? ""} />
<ControlledHiddenField
name="delete_photo"
Expand All @@ -100,7 +101,7 @@ function CourseImageForm(props: CourseImageFormProps) {
onImageLoad={onImageLoad}
onImageAccepted={onImageAccepted}
onImageRemove={onImageRemove}
thumbnail={addDomain(watch("thumbnail"))}
thumbnail={thumbnailWithDomain}
/>
<ImportButton color="primary" onClick={onImportPhotoDialogOpen}>
{t("importPhotoButton")}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
DialogTitle,
} from "@mui/material"
import { styled } from "@mui/material/styles"
import { useEventCallback } from "@mui/material/utils"

import { ControlledSelect } from "../Common/Fields"
import ContainedImage from "/components/Images/ContainedImage"
Expand Down Expand Up @@ -52,10 +53,10 @@ function ImportPhotoDialog({
onClose,
courses = [],
}: ImportPhotoDialogProps) {
const { setValue, getValues, watch } = useFormContext()
const { setValue, watch } = useFormContext()
const t = useTranslator(CoursesTranslations)

const fetchBase64 = useCallback(
const fetchBase64 = useEventCallback(
(photo: ImageCoreFieldsFragment, filename: string) => {
fetch(filename, {
mode: "no-cors",
Expand All @@ -67,13 +68,12 @@ function ImportPhotoDialog({
const file = new File([blob], photo?.name ?? "", {
type: photo?.original_mimetype ?? "image/png",
})
setValue("new_photo", file)
setValue("new_photo", file, { shouldDirty: true })
})
},
[setValue],
)

const fetchURL = useCallback(
const fetchURL = useEventCallback(
(photo: ImageCoreFieldsFragment, filename: string) => {
const req = new XMLHttpRequest()
req.open("GET", filename, true)
Expand All @@ -82,11 +82,10 @@ function ImportPhotoDialog({
const file = new File([req.response], photo?.name ?? "", {
type: photo?.original_mimetype ?? "image/png",
})
setValue("new_photo", file)
setValue("new_photo", file, { shouldDirty: true })
}
req.send()
},
[setValue],
)

const photo = watch("import_photo")
Expand All @@ -109,7 +108,7 @@ function ImportPhotoDialog({
fetchURL(selectedPhoto, filename)
}
onClose()
}, [photo, courses, getValues, onClose, setValue, watch])
}, [photo, courses, onClose])

const selected = useMemo(
() => courses?.find((course) => course.id === photo) ?? null,
Expand Down
3 changes: 1 addition & 2 deletions frontend/components/Dashboard/Editor/Course/serialization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,7 @@ export const toCourseForm = ({
exercise_completions_needed:
course?.exercise_completions_needed ?? undefined,
points_needed: course?.points_needed ?? undefined,
// TODO: fix
new_photo: null, // course?.photo ?? null,
new_photo: null,
photo: course?.photo ?? "",
open_university_registration_links:
course?.open_university_registration_links?.map((link) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ interface ToCourseFormArgs {
modules?: StudyModuleDetailedFieldsFragment[] | null
}

const statusMap: Record<string, CourseStatus> = {
Upcoming: CourseStatus.Upcoming,
Active: CourseStatus.Active,
Ended: CourseStatus.Ended,
}

export const toCourseForm = ({
course,
modules,
Expand Down Expand Up @@ -236,14 +242,7 @@ export function fromCourseForm<Values extends CourseFormValues>({
new_slug: string
})

const status =
values.status === "Active"
? CourseStatus.Active
: values.status === "Ended"
? CourseStatus.Ended
: values.status === "Upcoming"
? CourseStatus.Upcoming
: undefined
const status = statusMap[values.status]

const c: FromCourseFormReturn<Values> = {
...formValues,
Expand Down
52 changes: 33 additions & 19 deletions frontend/components/Dashboard/ImageDropzoneInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,39 +19,49 @@ const DropzoneContainer = styled("div", {
shouldForwardProp: (prop) =>
typeof prop !== "string" ||
!["isDragActive", "isDragAccept", "error"].includes(prop), // TODO: should I list _all_ dropzonestate things
})<DropzoneState & { error: MessageProps["error"] }>`
})<DropzoneState & { error: MessageProps["error"] }>(
({ isDragActive, isDragAccept, error }) => `
display: flex;
width: 100%;
min-height: 250px;
align-items: center;
border-width: 2px;
border-radius: 4px;
border-style: ${({ isDragActive }) => (isDragActive ? "solid" : "dashed")};
border-style: ${isDragActive ? "solid" : "dashed"};
padding: 20px;
background-color: ${({ isDragActive, isDragAccept, error }) =>
isDragActive
? isDragAccept
? "#E0FFE0"
: "#FFC0C0"
: error
? "#FFC0C0"
: "#FFFFFF"};
border-color: ${({ isDragActive, isDragAccept }) =>
isDragActive ? (isDragAccept ? "#00A000" : "#FF0000") : "rgba(0,0,0,0.23)"};
background-color: ${() => {
if (isDragActive) {
return isDragAccept ? "#E0FFE0" : "#FFC0C0"
}
if (error) {
return "#FFC0C0"
}
return "#FFFFFF"
}};
border-color: ${() => {
if (isDragActive) {
return isDragAccept ? "#00A000" : "#FF0000"
}
return "rgba(0,0,0,0.23)"
}};
transition: border 0.24s ease-in-out;
&:hover {
cursor: pointer;
border-color: #00a000;
}
justify-content: center;
position: relative;
`
`,
)

const ErrorMessage = styled(Typography, {
shouldForwardProp: (prop) => prop !== "error",
})<{ error: MessageProps["error"] }>`
color: ${({ error }) => (error ? "#FF0000" : "#000000")};
`
})<{ error: MessageProps["error"] }>(
({ error }) => `
color: ${error ? "#FF0000" : "#000000"};
`,
)

interface MessageProps {
message: string
error?: boolean
Expand All @@ -61,12 +71,14 @@ interface DropzoneProps {
inputRef?: React.RefCallback<HTMLDivElement>
onImageLoad: (result: string | ArrayBuffer | null) => void
onImageAccepted: (field: File) => void
thumbnail?: string
}

const ImageDropzoneInput = ({
inputRef,
onImageAccepted,
onImageLoad,
thumbnail,
children,
}: React.PropsWithChildren<DropzoneProps>) => {
const t = useTranslator(CommonTranslations)
Expand Down Expand Up @@ -130,9 +142,11 @@ const ImageDropzoneInput = ({
>
{children}
<input {...getInputProps()} />
<ErrorMessage variant="body1" align="center" error={status.error}>
{status.message}
</ErrorMessage>
{!thumbnail && (
<ErrorMessage variant="body1" align="center" error={status.error}>
{status.message}
</ErrorMessage>
)}
</DropzoneContainer>
)
}
Expand Down
7 changes: 5 additions & 2 deletions frontend/components/Dashboard/ImagePreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { ButtonBase, Tooltip } from "@mui/material"
import { styled } from "@mui/material/styles"

import ContainedImage from "../Images/ContainedImage"
import { useTranslator } from "/hooks/useTranslator"
import CoursesTranslations from "/translations/courses"

const CloseButton = styled(ButtonBase)`
position: absolute;
Expand Down Expand Up @@ -60,6 +62,7 @@ const ImagePreview = ({
height,
...rest
}: ImagePreviewProps) => {
const t = useTranslator(CoursesTranslations)
if (!file) {
return null
}
Expand All @@ -68,11 +71,11 @@ const ImagePreview = ({
<ImagePreviewContainer {...rest}>
<ContainedImage
src={file}
alt={file.length > 64 ? "Image preview" : file} // don't spout gibberish if it's a base64
alt={file.length > 64 ? t("coursePhotoPreview") : file} // don't spout gibberish if it's a base64
fill
/>
{onImageRemove && (
<Tooltip title="Remove picture">
<Tooltip title={t("courseRemovePhoto")}>
<CloseButton onClick={onImageRemove}>&times;</CloseButton>
</Tooltip>
)}
Expand Down
2 changes: 2 additions & 0 deletions frontend/translations/courses/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
"courseModules": "Study modules",
"coursePhoto": "Photo",
"courseNewPhoto": "Upload new photo",
"courseRemovePhoto": "Remove photo",
"coursePhotoPreview": "Photo preview",
"courseTranslations": "Course translations",
"confirmationAreYouSure": "Are you sure?",
"confirmationRemoveTranslation": "Do you want to remove this translation?",
Expand Down
2 changes: 2 additions & 0 deletions frontend/translations/courses/fi.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
"courseModules": "Opintokokonaisuudet",
"coursePhoto": "Kuva",
"courseNewPhoto": "Lataa uusi kuva",
"courseRemovePhoto": "Poista kuva",
"coursePhotoPreview": "Esikatselu",
"courseTranslations": "Käännökset",
"confirmationAreYouSure": "Oletko varma?",
"confirmationRemoveTranslation": "Haluatko poistaa tämän käännöksen?",
Expand Down

0 comments on commit a9a548f

Please sign in to comment.