-
-
-
- For {mediaRoom} other than
- {mediaRoom === "images"
- ? ` 'png', 'jpg', '.jpeg', 'gif', 'tif', '.tiff', 'bmp', 'ico', 'svg'`
- : ` 'pdf'`}
- , please use
-
- https://go.gov.sg
-
- to upload and link them to your Isomer site.
-
-
-
+
{queryParams.mediaDirectoryName
? queryParams.mediaDirectoryName.split("%2F").map((dir, idx) => (
{
- e.preventDefault()
- setQueryParams((prevState) => ({
- ...prevState,
- mediaDirectoryName: getMediaDirectoryName(
- queryParams.mediaDirectoryName,
- { end: idx + 1 }
- ),
- }))
- }}
- />
+ >
+ {
+ e.preventDefault()
+ setQueryParams((prevState) => ({
+ ...prevState,
+ mediaDirectoryName: getMediaDirectoryName(
+ queryParams.mediaDirectoryName,
+ { end: idx + 1 }
+ ),
+ }))
+ }}
+ >
+ {deslugifyDirectory(dir)}
+
+
))
: null}
-
+
@@ -205,24 +230,28 @@ const MediasSelectModal = ({
isLoaded={!isListMediaFilesLoading}
>
- {files
- .filter(({ data }) => filteredMedias.includes(data?.name))
- .map(({ data, isLoading }, mediaItemIndex) => (
-
- onMediaSelect(data)}
- isSelected={data.name === watch("selectedMedia")?.name}
- />
-
- ))}
+ {files &&
+ files
+ .filter(({ data }) => filteredMedias.includes(data?.name))
+ .map(({ data, isLoading }, mediaItemIndex) => (
+
+ onMediaSelect(data)}
+ isSelected={
+ data.name === watch("selectedMedia")?.name
+ }
+ showSettings={false}
+ />
+
+ ))}
From 83a1adc8677a491741e049be3de884fcaa7b2cf5 Mon Sep 17 00:00:00 2001
From: Hsu Zhong Jun <27919917+dcshzj@users.noreply.github.com>
Date: Tue, 14 Nov 2023 11:33:51 +0800
Subject: [PATCH 07/13] fix(preview): use site colours for headings in preview
(#1663)
* fix(preview): use site colours for headings in preview
* fix(preview): use secondary colours for quotes as well
---
src/utils/siteColorUtils.js | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/src/utils/siteColorUtils.js b/src/utils/siteColorUtils.js
index ad92358df..369f04be7 100644
--- a/src/utils/siteColorUtils.js
+++ b/src/utils/siteColorUtils.js
@@ -106,6 +106,41 @@ const createPageStyleSheet = (repoName, primaryColor, secondaryColor) => {
`.content h5 strong { color: ${secondaryColor} !important;}`,
0
)
+ customStyleSheet.insertRule(
+ `.content h1 { color: ${secondaryColor} !important;}`,
+ 0
+ )
+ customStyleSheet.insertRule(
+ `.content h2 { color: ${secondaryColor} !important;}`,
+ 0
+ )
+ customStyleSheet.insertRule(
+ `.content h3 { color: ${secondaryColor} !important;}`,
+ 0
+ )
+ customStyleSheet.insertRule(
+ `.content h4 { color: ${secondaryColor} !important;}`,
+ 0
+ )
+ customStyleSheet.insertRule(
+ `.content h5 { color: ${secondaryColor} !important;}`,
+ 0
+ )
+
+ // EditPage: Blockquotes
+ customStyleSheet.insertRule(
+ `.content blockquote { border-left-color: ${secondaryColor} !important;}`,
+ 0
+ )
+ customStyleSheet.insertRule(
+ `.content blockquote > p { color: ${secondaryColor} !important;}`,
+ 0
+ )
+ customStyleSheet.insertRule(
+ `.content blockquote > ul { color: ${secondaryColor} !important;}`,
+ 0
+ )
+
customStyleSheet.insertRule(
`.has-text-secondary { color: ${secondaryColor} !important;}`,
0
From b0a1e980686bad8e66e9aa02990f78578e144389 Mon Sep 17 00:00:00 2001
From: Kishore <42832651+kishore03109@users.noreply.github.com>
Date: Tue, 14 Nov 2023 14:02:10 +0800
Subject: [PATCH 08/13] Fix/buildStatusBadgeAPI (#1673)
* fix(statusBadge): rm lingering api call
* fix(stagingStatus): rm unused imports
* fix(status Badge): fix storybook not rendering
* fix(badgeStatus): add in qnmark
* fix(badgeStatus): fix styling issues
---
src/components/Header.jsx | 32 +++++++++----------
src/components/Header/StatusBadge.tsx | 20 ++++++------
src/constants/featureFlags.ts | 1 +
src/hooks/useGetStagingStatus.ts | 2 --
.../layouts/SiteEditLayout/SiteEditHeader.tsx | 6 ++--
.../layouts/SiteViewLayout/SiteViewHeader.tsx | 4 +--
src/utils/growthbook.ts | 17 +++++++++-
7 files changed, 47 insertions(+), 35 deletions(-)
diff --git a/src/components/Header.jsx b/src/components/Header.jsx
index c1565f54a..0274a1774 100644
--- a/src/components/Header.jsx
+++ b/src/components/Header.jsx
@@ -1,12 +1,4 @@
-import {
- Box,
- Flex,
- Icon,
- Text,
- HStack,
- useDisclosure,
- Skeleton,
-} from "@chakra-ui/react"
+import { Box, Flex, Icon, Text, HStack, useDisclosure } from "@chakra-ui/react"
import { Button, IconButton } from "@opengovsg/design-system-react"
import axios from "axios"
import PropTypes from "prop-types"
@@ -19,17 +11,20 @@ import { StatusBadge } from "components/Header/StatusBadge"
import { ViewStagingSiteModal } from "components/ViewStagingSiteModal"
import { WarningModal } from "components/WarningModal"
+import { FEATURE_FLAGS } from "constants/featureFlags"
+
import { useLoginContext } from "contexts/LoginContext"
import {
useGetReviewRequests,
useGetStagingUrl,
} from "hooks/siteDashboardHooks"
-import { useGetStagingStatus } from "hooks/useGetStagingStatus"
import useRedirectHook from "hooks/useRedirectHook"
import { ReviewRequestModal } from "layouts/ReviewRequest"
+import { useIsIsomerFeatureOn } from "utils/growthbook"
+
import { getBackButton } from "utils"
// axios settings
@@ -88,10 +83,9 @@ const Header = ({
if (isEditPage && !shouldAllowEditPageBackNav) onWarningModalOpen()
else toggleBackNav()
}
- const {
- data: getStagingStatusData,
- isLoading: isGetStagingStatusLoading,
- } = useGetStagingStatus(siteName)
+ const isShowStagingBuildStatusEnabled = useIsIsomerFeatureOn(
+ FEATURE_FLAGS.IS_SHOW_STAGING_BUILD_STATUS_ENABLED
+ )
return (
<>
@@ -139,9 +133,13 @@ const Header = ({
) : null}
-
- {getStagingStatusData && }
-
+ (
+ {isShowStagingBuildStatusEnabled && (
+
+
+
+ )}
+ )
{
switch (status) {
case "READY":
headingText = `All saved edits are on staging`
- bodyText = "Click on 'Open staging' to take a look."
+ bodyText = "Click 'Open staging' to see a preview of your site."
biLoaderIcon = (props: IconBaseProps) => (
@@ -40,18 +40,16 @@ const StagingPopoverContent = ({ status }: StagingPopoverContentProps) => {
break
case "PENDING":
- headingText = `Staging site is building`
- bodyText =
- "We detected a change in your site. We'll let you know when the staging site is ready."
+ headingText = `Your staging site is being built`
+ bodyText = "This status indicator will change once it's ready."
biLoaderIcon = (props: IconBaseProps) => (
)
break
case "ERROR":
- headingText =
- "We had some trouble updating the staging site since the latest save. "
+ headingText = "Your staging site may not show the latest changes"
bodyText =
- "Don't worry, your production site isn't affected. Try saving your page again. If the issue persists, please contact support@isomer.gov.sg."
+ "Your live site isn't affected. Please save the page again before clicking 'Open Staging'. If changes still don't show, email support@isomer.gov.sg"
biLoaderIcon = (props: IconBaseProps) =>
break
@@ -110,7 +108,7 @@ export const StatusBadgeComponent = ({
return (
-
+
{displayText}
+
diff --git a/src/constants/featureFlags.ts b/src/constants/featureFlags.ts
index da4bfff4c..c49cb8259 100644
--- a/src/constants/featureFlags.ts
+++ b/src/constants/featureFlags.ts
@@ -7,6 +7,7 @@ export const FEATURE_FLAGS = {
IS_SHOW_STAGING_BUILD_STATUS_ENABLED: "is_show_staging_build_status_enabled",
} as const
+export type FeatureFlagsType = typeof FEATURE_FLAGS[keyof typeof FEATURE_FLAGS]
// NOTE: Only have 4 default blocks:
// hero/infobar/infopic/resources
export const NUM_DEFAULT_HOMEPAGE_BLOCKS = 4
diff --git a/src/hooks/useGetStagingStatus.ts b/src/hooks/useGetStagingStatus.ts
index 259b7893d..4e47877ea 100644
--- a/src/hooks/useGetStagingStatus.ts
+++ b/src/hooks/useGetStagingStatus.ts
@@ -1,7 +1,5 @@
-import { useFeatureIsOn } from "@growthbook/growthbook-react"
import { useQuery, UseQueryResult } from "react-query"
-import { FEATURE_FLAGS } from "constants/featureFlags"
import { GET_STAGING_BUILD_STATUS_KEY } from "constants/queryKeys"
import { getStagingBuildStatus } from "services/StagingBuildService"
diff --git a/src/layouts/layouts/SiteEditLayout/SiteEditHeader.tsx b/src/layouts/layouts/SiteEditLayout/SiteEditHeader.tsx
index 93060199a..f80fe898d 100644
--- a/src/layouts/layouts/SiteEditLayout/SiteEditHeader.tsx
+++ b/src/layouts/layouts/SiteEditLayout/SiteEditHeader.tsx
@@ -6,7 +6,6 @@ import {
HStack,
useDisclosure,
} from "@chakra-ui/react"
-import { useFeatureIsOn } from "@growthbook/growthbook-react"
import { Button, IconButton } from "@opengovsg/design-system-react"
import { BiArrowBack, BiCheckCircle } from "react-icons/bi"
import { useParams, useHistory } from "react-router-dom"
@@ -26,10 +25,9 @@ import { useGetStagingUrl } from "hooks/siteDashboardHooks"
import { ReviewRequestModal } from "layouts/ReviewRequest"
+import { useIsIsomerFeatureOn } from "utils/growthbook"
import { doesOpenReviewRequestExist } from "utils/reviewRequests"
-import { FeatureFlags } from "types/featureFlags"
-
export const SiteEditHeader = (): JSX.Element => {
const { isOpen, onOpen, onClose } = useDisclosure()
const {
@@ -45,7 +43,7 @@ export const SiteEditHeader = (): JSX.Element => {
} = useDisclosure()
const { siteName } = useParams<{ siteName: string }>()
const { data: stagingUrl, isLoading } = useGetStagingUrl(siteName)
- const isShowStagingBuildStatusEnabled = useFeatureIsOn(
+ const isShowStagingBuildStatusEnabled = useIsIsomerFeatureOn(
FEATURE_FLAGS.IS_SHOW_STAGING_BUILD_STATUS_ENABLED
)
diff --git a/src/layouts/layouts/SiteViewLayout/SiteViewHeader.tsx b/src/layouts/layouts/SiteViewLayout/SiteViewHeader.tsx
index c7f24b96a..9c20d9e98 100644
--- a/src/layouts/layouts/SiteViewLayout/SiteViewHeader.tsx
+++ b/src/layouts/layouts/SiteViewLayout/SiteViewHeader.tsx
@@ -22,14 +22,14 @@ import { FEATURE_FLAGS } from "constants/featureFlags"
import { useLoginContext } from "contexts/LoginContext"
-import { FeatureFlags } from "types/featureFlags"
+import { useIsIsomerFeatureOn } from "utils/growthbook"
export const SiteViewHeader = (): JSX.Element => {
const { displayedName } = useLoginContext()
const { pathname } = useLocation()
const isAtSiteDashboard = pathname.endsWith("dashboard")
const { siteName } = useParams<{ siteName: string }>()
- const isShowStagingBuildStatusEnabled = useFeatureIsOn(
+ const isShowStagingBuildStatusEnabled = useIsIsomerFeatureOn(
FEATURE_FLAGS.IS_SHOW_STAGING_BUILD_STATUS_ENABLED
)
diff --git a/src/utils/growthbook.ts b/src/utils/growthbook.ts
index 1095567a3..7c3a99e2a 100644
--- a/src/utils/growthbook.ts
+++ b/src/utils/growthbook.ts
@@ -1,4 +1,9 @@
-import { GrowthBook } from "@growthbook/growthbook-react"
+import { GrowthBook, useFeatureIsOn } from "@growthbook/growthbook-react"
+import { useParams } from "react-router-dom"
+
+import { FeatureFlagsType } from "constants/featureFlags"
+
+import { FeatureFlags } from "types/featureFlags"
const GROWTHBOOK_API_HOST = "https://cdn.growthbook.io"
@@ -20,3 +25,13 @@ export const getSiteNameAttributeFromPath = (path: string) => {
}
return ""
}
+
+export const useIsIsomerFeatureOn = (
+ featureName: FeatureFlagsType
+): boolean => {
+ const { siteName } = useParams<{ siteName: string }>()
+ if (siteName === "storybook") return true
+
+ const isFeatureEnabled = useFeatureIsOn(featureName)
+ return isFeatureEnabled
+}
From ab12c0c6889846db5f8520a46e919f14bb553537 Mon Sep 17 00:00:00 2001
From: Hsu Zhong Jun <27919917+dcshzj@users.noreply.github.com>
Date: Tue, 14 Nov 2023 14:26:32 +0800
Subject: [PATCH 09/13] feat(media): allow create album with selected media
(#1666)
* chore(media): move utility method outside
* feat(hooks): add new hook to create and move together
* feat(media): add new create media folder modal
* feat(media): add new modal to Media layout
* styles(media): adjustments from design feedback
* feat(media): add pagination support for modal
* fix(media): add methods as dependency of useEffect
* fix(media): standardise to use onTouched for validation
* fix(media): adapt strings to mediatype and plurals
* fix(media): omit selected pages when skipping selection
* fix(media): avoid prop drilling siteName and isWriteDisabled
* style(media): add padding to allow outline to show
---
.../Attachment/AttachmentDropzone.tsx | 2 +-
.../CreateMediaFolderModal.stories.tsx | 96 +++++
.../CreateMediaFolderModal.tsx | 373 ++++++++++++++++++
.../CreateMediaFolderModal/index.ts | 1 +
.../DeleteMediaModal/DeleteMediaModal.tsx | 2 +-
.../DirectorySettingsSchema.jsx | 1 +
.../ImagePreviewCard/ImagePreviewCard.tsx | 29 +-
.../MediaCreationModal/MediaCreationModal.tsx | 75 ++--
.../MoveMediaModal/MoveMediaModal.tsx | 2 +-
.../useCreateDirectoryAndMoveFilesHook.ts | 101 +++++
.../moveHooks/useMoveMultipleMediaHook.tsx | 2 +-
src/layouts/Media/Media.tsx | 128 ++++--
.../Media/components/FilePreviewCard.tsx | 157 +++++---
src/types/media.ts | 5 +
src/utils/media.ts | 14 +-
15 files changed, 872 insertions(+), 116 deletions(-)
create mode 100644 src/components/CreateMediaFolderModal/CreateMediaFolderModal.stories.tsx
create mode 100644 src/components/CreateMediaFolderModal/CreateMediaFolderModal.tsx
create mode 100644 src/components/CreateMediaFolderModal/index.ts
create mode 100644 src/hooks/directoryHooks/useCreateDirectoryAndMoveFilesHook.ts
diff --git a/src/components/Attachment/AttachmentDropzone.tsx b/src/components/Attachment/AttachmentDropzone.tsx
index 1a06034e5..0811942e4 100644
--- a/src/components/Attachment/AttachmentDropzone.tsx
+++ b/src/components/Attachment/AttachmentDropzone.tsx
@@ -27,7 +27,7 @@ export const AttachmentDropzone = ({
Choose files
-
+ {" "}
or drag and drop here
diff --git a/src/components/CreateMediaFolderModal/CreateMediaFolderModal.stories.tsx b/src/components/CreateMediaFolderModal/CreateMediaFolderModal.stories.tsx
new file mode 100644
index 000000000..9ed7964d8
--- /dev/null
+++ b/src/components/CreateMediaFolderModal/CreateMediaFolderModal.stories.tsx
@@ -0,0 +1,96 @@
+import { useDisclosure } from "@chakra-ui/react"
+import { Button } from "@opengovsg/design-system-react"
+import type { Meta, StoryFn } from "@storybook/react"
+import { MemoryRouter, Route } from "react-router-dom"
+
+import { getMediaLabels } from "utils/media"
+
+import { MOCK_MEDIA_ITEM_DATA, MOCK_MEDIA_ITEM_ONE } from "mocks/constants"
+import { handlers } from "mocks/handlers"
+import { buildMediaFileData, buildMediaFolderFilesData } from "mocks/utils"
+import { useSuccessToast } from "utils"
+
+import { CreateMediaFolderModal } from "./CreateMediaFolderModal"
+
+const createMediaFolderModalMeta = {
+ title: "Components/Create Media Folder Modal",
+ component: CreateMediaFolderModal,
+ decorators: [
+ (Story) => {
+ return (
+
+
+
+
+
+ )
+ },
+ ],
+} as Meta
+
+const createMediaFolderModalTemplate: StoryFn<
+ typeof CreateMediaFolderModal
+> = ({ originalSelectedMedia }) => {
+ const { isOpen, onOpen, onClose } = useDisclosure({ defaultIsOpen: true })
+ const successToast = useSuccessToast()
+ const onProceed = async (result: any) => {
+ console.log(result)
+ successToast({
+ id: "storybook-create-media-folder-success",
+ description: "STORYBOOK: Media folder has been successfully created",
+ })
+ onClose()
+ }
+
+ return (
+ <>
+ Open create media folder modal
+
+ >
+ )
+}
+
+export const Default = createMediaFolderModalTemplate.bind({})
+Default.args = {
+ originalSelectedMedia: [],
+}
+Default.parameters = {
+ msw: {
+ handlers: [
+ ...handlers,
+ buildMediaFolderFilesData(MOCK_MEDIA_ITEM_DATA),
+ buildMediaFileData(MOCK_MEDIA_ITEM_ONE),
+ ],
+ },
+}
+
+export const OneSelected = createMediaFolderModalTemplate.bind({})
+OneSelected.args = {
+ originalSelectedMedia: [
+ { filePath: "/images/hero-banner.png", size: 1234, sha: "sha1234" },
+ ],
+}
+
+export const MultipleSelected = createMediaFolderModalTemplate.bind({})
+MultipleSelected.args = {
+ originalSelectedMedia: [
+ { filePath: "/images/hero-banner.png", size: 1234, sha: "sha1234" },
+ { filePath: "/images/hero-banner2.png", size: 2345, sha: "sha1234" },
+ ],
+}
+
+export default createMediaFolderModalMeta
diff --git a/src/components/CreateMediaFolderModal/CreateMediaFolderModal.tsx b/src/components/CreateMediaFolderModal/CreateMediaFolderModal.tsx
new file mode 100644
index 000000000..8e756ce3b
--- /dev/null
+++ b/src/components/CreateMediaFolderModal/CreateMediaFolderModal.tsx
@@ -0,0 +1,373 @@
+import {
+ Box,
+ Center,
+ FormControl,
+ HStack,
+ Modal,
+ ModalBody,
+ ModalContent,
+ ModalFooter,
+ ModalHeader,
+ ModalOverlay,
+ SimpleGrid,
+ Skeleton,
+ Text,
+} from "@chakra-ui/react"
+import { yupResolver } from "@hookform/resolvers/yup"
+import {
+ Button,
+ FormErrorMessage,
+ FormLabel,
+ Input,
+ ModalCloseButton,
+ Pagination,
+} from "@opengovsg/design-system-react"
+import { AxiosError } from "axios"
+import _ from "lodash"
+import { useEffect } from "react"
+import { FormProvider, useForm } from "react-hook-form"
+import { UseMutateAsyncFunction } from "react-query"
+import { useRouteMatch } from "react-router-dom"
+
+import { DirectorySettingsSchema } from "components/DirectorySettingsModal"
+import { ImagePreviewCard } from "components/ImagePreviewCard"
+
+import { MEDIA_PAGINATION_SIZE } from "constants/media"
+
+import { useListMediaFolderFiles } from "hooks/directoryHooks"
+import { useGetAllMediaFiles } from "hooks/directoryHooks/useGetAllMediaFiles"
+import { usePaginate } from "hooks/usePaginate"
+
+import { FilePreviewCard } from "layouts/Media/components"
+
+import { getSelectedMediaDto } from "utils/media"
+import { isWriteActionsDisabled } from "utils/reviewRequests"
+
+import { GetMediaSubdirectoriesDto, MediaData } from "types/directory"
+import { MiddlewareError } from "types/error"
+import {
+ MediaFolderCreationInfo,
+ MediaFolderTypes,
+ MediaLabels,
+ SelectedMediaDto,
+} from "types/media"
+
+interface CreateMediaFolderModalProps {
+ originalSelectedMedia: SelectedMediaDto[]
+ mediaLabels: MediaLabels
+ mediaType: MediaFolderTypes
+ subDirectories: GetMediaSubdirectoriesDto | undefined
+ mediaDirectoryName: string
+ isOpen: boolean
+ isLoading: boolean
+ onClose: () => void
+ onProceed: UseMutateAsyncFunction<
+ void,
+ AxiosError,
+ MediaFolderCreationInfo,
+ unknown
+ >
+}
+
+const getButtonLabel = (
+ originalSelectedMedia: SelectedMediaDto[],
+ selectedMedia: SelectedMediaDto[],
+ mediaLabels: MediaLabels
+) => {
+ const {
+ singularDirectoryLabel,
+ singularMediaLabel,
+ pluralMediaLabel,
+ } = mediaLabels
+
+ if (originalSelectedMedia.length > 0) {
+ return `Create ${singularDirectoryLabel}`
+ }
+
+ if (selectedMedia.length > 1) {
+ return `Add ${selectedMedia.length} ${pluralMediaLabel} to ${singularDirectoryLabel}`
+ }
+
+ if (selectedMedia.length === 1) {
+ return `Add ${singularMediaLabel} to ${singularDirectoryLabel}`
+ }
+
+ return `Add ${pluralMediaLabel} to ${singularDirectoryLabel}`
+}
+
+export const CreateMediaFolderModal = ({
+ originalSelectedMedia,
+ mediaLabels,
+ mediaType,
+ subDirectories,
+ mediaDirectoryName,
+ isOpen,
+ isLoading,
+ onClose,
+ onProceed,
+}: CreateMediaFolderModalProps): JSX.Element => {
+ const { params } = useRouteMatch<{
+ siteName: string
+ }>()
+ const { siteName } = params
+ const [curPage, setCurPage] = usePaginate()
+ const isWriteDisabled = isWriteActionsDisabled(siteName)
+ const {
+ singularMediaLabel,
+ pluralMediaLabel,
+ singularDirectoryLabel,
+ } = mediaLabels
+
+ const existingTitles = subDirectories?.directories.map(
+ (directory) => directory.name
+ )
+
+ const {
+ data: mediaFolderFiles,
+ isLoading: isListMediaFilesLoading,
+ } = useListMediaFolderFiles({
+ siteName,
+ mediaDirectoryName,
+ // NOTE: Subtracting 1 here because `usePaginate`
+ // returns an index with 1 offset
+ curPage: curPage - 1,
+ limit: MEDIA_PAGINATION_SIZE,
+ })
+
+ const files = useGetAllMediaFiles(
+ mediaFolderFiles?.files || [],
+ siteName,
+ mediaDirectoryName
+ )
+
+ const methods = useForm({
+ mode: "onTouched",
+ resolver: yupResolver(DirectorySettingsSchema(existingTitles)),
+ context: {
+ type: "mediaDirectoryName",
+ },
+ defaultValues: {
+ newDirectoryName: "",
+ selectedPages: originalSelectedMedia,
+ },
+ })
+
+ const handleSelect = (fileData: MediaData) => {
+ if (
+ methods
+ .getValues("selectedPages")
+ .some((selectedData) => selectedData.filePath === fileData.mediaPath)
+ ) {
+ methods.setValue(
+ "selectedPages",
+ methods
+ .getValues("selectedPages")
+ .filter(
+ (selectedData) => selectedData.filePath !== fileData.mediaPath
+ )
+ )
+ } else {
+ const selectedData = getSelectedMediaDto(fileData)
+ methods.setValue("selectedPages", [
+ ...methods.getValues("selectedPages"),
+ selectedData,
+ ])
+ }
+ }
+
+ useEffect(() => {
+ methods.setValue("selectedPages", originalSelectedMedia)
+ }, [methods, originalSelectedMedia])
+
+ const onModalClose = () => {
+ methods.reset()
+ onClose()
+ }
+
+ const onSubmit = async (data: MediaFolderCreationInfo) => {
+ await onProceed(data)
+ methods.reset()
+ }
+
+ return (
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/CreateMediaFolderModal/index.ts b/src/components/CreateMediaFolderModal/index.ts
new file mode 100644
index 000000000..3bba53832
--- /dev/null
+++ b/src/components/CreateMediaFolderModal/index.ts
@@ -0,0 +1 @@
+export * from "./CreateMediaFolderModal"
diff --git a/src/components/DeleteMediaModal/DeleteMediaModal.tsx b/src/components/DeleteMediaModal/DeleteMediaModal.tsx
index f4ffd0642..320ba4c1c 100644
--- a/src/components/DeleteMediaModal/DeleteMediaModal.tsx
+++ b/src/components/DeleteMediaModal/DeleteMediaModal.tsx
@@ -64,7 +64,7 @@ export const DeleteMediaModal = ({
{selectedMedia.length === 1 && (
<>
-
+
Delete {selectedMedia[0].filePath.split("/").pop()}?
diff --git a/src/components/DirectorySettingsModal/DirectorySettingsSchema.jsx b/src/components/DirectorySettingsModal/DirectorySettingsSchema.jsx
index b5740fc14..ffd3099e1 100644
--- a/src/components/DirectorySettingsModal/DirectorySettingsSchema.jsx
+++ b/src/components/DirectorySettingsModal/DirectorySettingsSchema.jsx
@@ -78,4 +78,5 @@ export const DirectorySettingsSchema = (existingTitlesArray = []) =>
() => false
)
}),
+ selectedPages: Yup.array().ensure(),
})
diff --git a/src/components/ImagePreviewCard/ImagePreviewCard.tsx b/src/components/ImagePreviewCard/ImagePreviewCard.tsx
index 3a10facbe..bb8774356 100644
--- a/src/components/ImagePreviewCard/ImagePreviewCard.tsx
+++ b/src/components/ImagePreviewCard/ImagePreviewCard.tsx
@@ -5,12 +5,13 @@ import {
Grid,
GridItem,
Image,
+ ImageProps,
Text,
useMultiStyleConfig,
VStack,
} from "@chakra-ui/react"
import { Checkbox } from "@opengovsg/design-system-react"
-import { BiEditAlt, BiTrash } from "react-icons/bi"
+import { BiEditAlt, BiFolder, BiTrash } from "react-icons/bi"
import { Link as RouterLink, useRouteMatch } from "react-router-dom"
import { ContextMenu } from "components/ContextMenu"
@@ -25,11 +26,14 @@ export interface ImagePreviewCardProps {
name: string
addedTime: number
mediaUrl: string
+ imageHeight?: ImageProps["height"]
isSelected: boolean
isMenuNeeded?: boolean
onOpen?: () => void
+ onClick?: () => void
onCheck?: () => void
onDelete?: () => void
+ onMove?: () => void
}
// Note: This is written as a separate component as the current Card API is not
@@ -38,11 +42,14 @@ export const ImagePreviewCard = ({
name,
addedTime,
mediaUrl,
+ imageHeight = "15rem",
isSelected,
isMenuNeeded = true,
onOpen,
+ onClick,
onCheck,
onDelete,
+ onMove,
}: ImagePreviewCardProps): JSX.Element => {
const { url } = useRouteMatch()
const { setRedirectToPage } = useRedirectHook()
@@ -91,9 +98,18 @@ export const ImagePreviewCard = ({
// Note: Outline is required to avoid the card from shifting when selected
outline={isSelected ? "solid 2px" : "solid 1px"}
outlineColor={isSelected ? "base.divider.brand" : "base.divider.medium"}
- onClick={() =>
- setRedirectToPage(`${url}/editMediaSettings/${encodedName}`)
- }
+ onClick={(e) => {
+ // For some weird reason, the onClick event is treated as a submit event
+ // We can safely disable the default behaviour here since we define the
+ // onClick behaviour ourselves
+ e.preventDefault()
+
+ if (onClick) {
+ onClick()
+ } else {
+ setRedirectToPage(`${url}/editMediaSettings/${encodedName}`)
+ }
+ }}
>
@@ -113,7 +129,7 @@ export const ImagePreviewCard = ({
Rename image
+ } onClick={onMove}>
+ Move to
+
}
color="interaction.critical.default"
diff --git a/src/components/MediaCreationModal/MediaCreationModal.tsx b/src/components/MediaCreationModal/MediaCreationModal.tsx
index b3725ed10..cd7ef8a1a 100644
--- a/src/components/MediaCreationModal/MediaCreationModal.tsx
+++ b/src/components/MediaCreationModal/MediaCreationModal.tsx
@@ -17,6 +17,7 @@ import {
Flex,
} from "@chakra-ui/react"
import { Button, Infobox } from "@opengovsg/design-system-react"
+import _ from "lodash"
import { useEffect, useState } from "react"
import { FileRejection } from "react-dropzone"
import { BiCheckCircle, BiSolidErrorCircle } from "react-icons/bi"
@@ -26,6 +27,8 @@ import { Attachment } from "components/Attachment"
import { useCreateMultipleMedia } from "hooks/mediaHooks/useCreateMultipleMedia"
+import { getMediaLabels } from "utils/media"
+
import { MediaDirectoryParams } from "types/folders"
import { MediaFolderTypes } from "types/media"
import { MEDIA_FILE_MAX_SIZE } from "utils"
@@ -67,15 +70,16 @@ const MediaDropzone = ({
mediaType,
}: MediaDropzoneProps) => {
const { onClose } = useModalContext()
+ const { singularMediaLabel, pluralMediaLabel } = getMediaLabels(mediaType)
return (
<>
- Upload files
+ Upload {pluralMediaLabel}
- You can upload more than 1 file at once. Having too many files can
- slow down the site loading time, so we recommend only uploading
- necessary files to your site.
+ You can upload more than 1 {singularMediaLabel} at once. Having too
+ many {pluralMediaLabel} can slow down the site loading time, so we
+ recommend only uploading necessary {pluralMediaLabel} to your site.
Cancel
- onUpload(uploadedFiles)}
- >{`Upload ${uploadedFiles.length} ${
- uploadedFiles.length > 1 ? "files" : "file"
- }`}
+ onUpload(uploadedFiles)}>
+ {`Upload ${uploadedFiles.length} ${
+ uploadedFiles.length === 1 ? singularMediaLabel : pluralMediaLabel
+ }`}
+
>
)
@@ -112,17 +115,20 @@ const MediaDropzone = ({
interface UploadProgressIndicatorProps {
cur: number
total: number
+ mediaType: MediaFolderTypes
}
const UploadProgressIndicator = ({
cur,
total,
+ mediaType,
}: UploadProgressIndicatorProps) => {
const { onClose } = useModalContext()
+ const { pluralMediaLabel } = getMediaLabels(mediaType)
return (
<>
- Upload files
+ Upload {pluralMediaLabel}
{`Uploading ${cur} of ${total} files`}
+ >{`Uploading ${cur} of ${total} ${pluralMediaLabel}`}
Do not close this screen or navigate away
@@ -152,16 +158,26 @@ const UploadProgressIndicator = ({
interface MediaUploadSuccessDropzoneProps {
numMedia: number
errorMessages: string[]
+ mediaType: MediaFolderTypes
}
const MediaUploadSuccessDropzone = ({
numMedia,
errorMessages,
+ mediaType,
}: MediaUploadSuccessDropzoneProps) => {
const { onClose } = useModalContext()
+ const {
+ singularMediaLabel,
+ pluralMediaLabel,
+ singularDirectoryLabel,
+ } = getMediaLabels(mediaType)
return (
<>
- Files uploaded!
+
+ {_.upperFirst(numMedia === 1 ? singularMediaLabel : pluralMediaLabel)}{" "}
+ uploaded!
+
- {`Successfully uploaded ${numMedia} files`}
+
+ {`Successfully uploaded ${numMedia} ${
+ numMedia === 1 ? singularMediaLabel : pluralMediaLabel
+ }`}
+
{errorMessages.length > 0 && (
<>
@@ -187,7 +204,7 @@ const MediaUploadSuccessDropzone = ({
mr="0.5rem"
/>
- {`${errorMessages.length} files failed to upload`}
+ {`${errorMessages.length} ${pluralMediaLabel} failed to upload`}
@@ -203,7 +220,7 @@ const MediaUploadSuccessDropzone = ({
)}
- Return to album
+ Return to {singularDirectoryLabel}
>
)
@@ -211,11 +228,18 @@ const MediaUploadSuccessDropzone = ({
interface MediaUploadFailedDropzoneProps {
errorMessages: string[]
+ mediaType: MediaFolderTypes
}
const MediaUploadFailedDropzone = ({
errorMessages,
+ mediaType,
}: MediaUploadFailedDropzoneProps) => {
const { onClose } = useModalContext()
+ const {
+ singularMediaLabel,
+ pluralMediaLabel,
+ singularDirectoryLabel,
+ } = getMediaLabels(mediaType)
return (
<>
@@ -225,7 +249,9 @@ const MediaUploadFailedDropzone = ({
{`${errorMessages.length} ${
- errorMessages.length > 1 ? "files" : "file"
+ errorMessages.length === 1
+ ? singularMediaLabel
+ : pluralMediaLabel
} failed to upload`}
@@ -247,7 +273,7 @@ const MediaUploadFailedDropzone = ({