Skip to content

Commit a55b454

Browse files
adriangohjwkarrui
andauthored
isom-1659 internal links for folders (#865)
* make standalone UI work * integrate to handle onChange * update getAncestry to getAncestryWithSelf * show existing link location on open * lint LinkEditorModal * refactor - remove moveItem (use ResourceItem instead) * refactor: reuse ResourceSelector * add gap * use Resource.type for checking instead * make isHighlighted compulsory * ensure api returns desired type * better typecheck of values from trpc.resource * update to use onlyShowFolders instead * fix - display full permalink * update from div to Box * replace to use import from generatedEnums * refactor - use single source of truth getIcon * update schema name to getAncestryWithSelfSchema to be consistent * fix mock - getAncestry to getAncestryWithSelf * remove "undefined" from "type" * remove "null" from acceptable type value * refactor - do a 1-for-1 abstraction to useResourceStack * abstract away more stuff + usememo/usecallback for perf * add missing dependencies in addToStack * refactor - move logic into ResourceItem * more explicit about the props * remove unused import * return flatten item * pass in fullPermalink instead * undo absttracting away into useResourceStack * add gap * exclude index page from showing up in search * fix - full width issue * add searchbar skeleton * update resourceItem UI * refactor headers out * abstract logic away * add "results count" state * option for additional padding based on whether user is using search * set fixed height to 280px * fix text alignment issue * fix "back to home" padding * refactor into new file + add "NoItemsInFolderResult" * refactor - move all logic into ResourceSelector to reduce number of props passed * rename to ResourceSelectorContent * clear searchbar on clicking clear search button * allow passing in of resourcetypes into search query * extract into useSearchQuery * use useSearchQuery instead * reduce API call by passing in siteId * move useSearchQuery out of useResourceStack * add loading state * remove unused import * prep: abstract logic into useResourceStack for easier refactoring * refactor into getAncestryWithSelf + add getBatchAncestryWithSelf endpoint * fix - use handleOnClick directly * fix - make resourceTypes optional for search endpoint * fix test * standardize types into 1 * hide logic in hook + fix search * add comment * set higher height for Skeleton * remove unused type export * fix nested use hooks issue * fix navigation * fix padding on "load more" button * fix - overwrite returned functions for search by query * extract lastResourceItemInAncestryStack into standalone utils file * usecallback everything + pass out clearSearchValue * clearSearchValue() onChange current * fix typo in query * fix test * fix - incorrect header shown when using query * fix - remove conditional for hook * remove accidental .only commit * enforce using output schema instead * fix stories * add stories * fix lint * slight refactoring - move isResourceItemDisabled into hook * add getNestedFolderChildrenOf endpoint * update isResourceItemDisabled to return true for nested children * lint + use useMemo to improve perf * allow passing in of file explorer height + update skeleton height and full width * Update apps/studio/src/server/modules/resource/resource.service.ts Co-authored-by: Kar Rui Lau <[email protected]> * chore: refactor and fix based on comments * chore: fix query * fix - missing comma * add output schema * add output schema * replace [0] * replace with Error * rename to matchedResources * fix - use the correct resource * fix - suspend header and content for better UX * break useResourceStack into useResourceSelector hook * split into useResourceQuery * update preview-tw * remove compute to DB * fix lint * replace with recursive CTE * fix suspense (searcbar should not suspense) * use "interactionType" * fix getBatchAncestryWithSelfQuery * early returns * replace logic with sql * cast to text * add unit test * move types out * fix - link editor link not showing if existing link * fix (move modal should show parent children instead) + rename for clarity * refactor: improve type safety for ResourceSelector components * fix - double flash * fix: move to root * refactor: extract prop types for ResourceSelector components * lint and format * feat: add resource type to move resource action * invalidate queries on moved * chore: clean up unused Tailwind CSS classes * fix: add descriptive error message for cross-site resource move * fix: prevent moving resource to its current parent folder * fix: allow clearing resource selection in ResourceSelector * fix: improve resource move destination selection handling * fix: handle null resourceId in link editor reference link generation * fix: add descriptive error message for cross-site resource move * test: add test for preventing resource move to the same folder * refactor: extract user permissions setup into reusable function * test: add permission checks for moving resources to root page * fix: ensure async user permissions setup is properly awaited * refactor(tests): simplify editor permissions setup in folder router tests * test(resource): remove hardcoded homepage from ancestry stack * fix(resource): prepend leading slash to fullPermalink * fix(stories): update getAncestryStack handler to return empty array * fix double slash * remove flashing * fix(component): prevent event propagation in ResourceSelectorHeader --------- Co-authored-by: Kar Rui Lau <[email protected]>
1 parent b384d04 commit a55b454

40 files changed

+2051
-736
lines changed

apps/studio/src/components/ErrorBoundary/DefaultTrpcError.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
import type { FallbackProps } from "react-error-boundary"
12
import { useRouter } from "next/router"
23
import { type TRPC_ERROR_CODE_KEY } from "@trpc/server/rpc"
3-
import { FallbackProps } from "react-error-boundary"
44

55
import { trpc } from "~/utils/trpc"
66
import { FullscreenSpinner } from "../FullscreenSpinner"

apps/studio/src/components/PageEditor/LinkEditorModal.tsx

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
ModalFooter,
99
ModalHeader,
1010
ModalOverlay,
11-
Text,
1211
} from "@chakra-ui/react"
1312
import {
1413
Button,
@@ -33,7 +32,6 @@ import {
3332
import { useQueryParse } from "~/hooks/useQueryParse"
3433
import { useZodForm } from "~/lib/form"
3534
import { getReferenceLink, getResourceIdFromReferenceLink } from "~/utils/link"
36-
import { trpc } from "~/utils/trpc"
3735
import { ResourceSelector } from "../ResourceSelector"
3836
import { FileAttachment } from "./FileAttachment"
3937

@@ -48,31 +46,21 @@ interface PageLinkElementProps {
4846

4947
const PageLinkElement = ({ value, onChange }: PageLinkElementProps) => {
5048
const { siteId } = useQueryParse(editSiteSchema)
51-
52-
const selectedResourceId = getResourceIdFromReferenceLink(value)
53-
54-
const { data: resource } = trpc.resource.getWithFullPermalink.useQuery({
55-
resourceId: selectedResourceId,
56-
})
57-
5849
return (
59-
<>
60-
<ResourceSelector
61-
siteId={String(siteId)}
62-
onChange={(resourceId) =>
63-
onChange(getReferenceLink({ siteId: String(siteId), resourceId }))
64-
}
65-
selectedResourceId={selectedResourceId}
66-
/>
67-
68-
{!!resource && (
69-
<Box bg="utility.feedback.info-subtle" p="0.75rem" w="full" mt="0.5rem">
70-
<Text textStyle="caption-1">
71-
You selected /{resource.fullPermalink}
72-
</Text>
73-
</Box>
74-
)}
75-
</>
50+
<ResourceSelector
51+
interactionType="link"
52+
siteId={siteId}
53+
onChange={(resourceId) =>
54+
onChange(
55+
getReferenceLink({
56+
siteId: String(siteId),
57+
resourceId: resourceId ?? "",
58+
}),
59+
)
60+
}
61+
selectedResourceId={getResourceIdFromReferenceLink(value)}
62+
fileExplorerHeight={12}
63+
/>
7664
)
7765
}
7866

Lines changed: 57 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,109 +1,77 @@
1-
import type { IconType } from "react-icons"
2-
import { Suspense, useMemo } from "react"
3-
import { Box, HStack, Icon, Skeleton, Text } from "@chakra-ui/react"
1+
import type { ButtonProps } from "@opengovsg/design-system-react"
2+
import { Icon, Skeleton, Text, VStack } from "@chakra-ui/react"
43
import { dataAttr } from "@chakra-ui/utils"
54
import { Button } from "@opengovsg/design-system-react"
6-
import { QueryErrorResetBoundary } from "@tanstack/react-query"
7-
import { ResourceType } from "~prisma/generated/generatedEnums"
8-
import { ErrorBoundary } from "react-error-boundary"
9-
import { BiData, BiFile, BiFolder, BiLink, BiLockAlt } from "react-icons/bi"
105

11-
import type { RouterOutput } from "~/utils/trpc"
6+
import type { ResourceItemContent } from "~/schemas/resource"
7+
import { getIcon } from "~/utils/resources"
128

13-
type ResourceItemProps = Pick<
14-
RouterOutput["resource"]["getChildrenOf"]["items"][number],
15-
"permalink" | "type"
16-
> & {
17-
isSelected: boolean
18-
isDisabled: boolean
19-
onResourceItemSelect: () => void
9+
interface ResourceItemProps {
10+
item: ResourceItemContent
11+
isDisabled?: boolean
12+
isHighlighted?: boolean
13+
handleOnClick?: () => void
14+
hasAdditionalLeftPadding?: boolean
15+
isLoading?: boolean
2016
}
2117

22-
const SuspendableResourceItem = ({
23-
permalink,
24-
type,
25-
isSelected,
26-
isDisabled,
27-
onResourceItemSelect,
28-
}: ResourceItemProps) => {
29-
const icon: IconType = useMemo(() => {
30-
switch (type) {
31-
case ResourceType.CollectionLink:
32-
return BiLink
33-
case ResourceType.Folder:
34-
return BiFolder
35-
case ResourceType.CollectionPage:
36-
case ResourceType.Page:
37-
case ResourceType.IndexPage:
38-
return BiFile
39-
case ResourceType.Collection:
40-
return BiData
41-
}
42-
}, [type])
43-
18+
const ResourceItemContainer = (props: ButtonProps) => {
4419
return (
4520
<Button
4621
variant="clear"
4722
w="full"
4823
justifyContent="flex-start"
49-
color="base.content.default"
50-
isDisabled={isDisabled}
51-
data-selected={dataAttr(isSelected)}
24+
color={"base.content.default"}
25+
height="fit-content"
26+
alignItems="flex-start"
27+
gap="0.25rem"
28+
{...props}
29+
/>
30+
)
31+
}
32+
33+
export const ResourceItemSkeleton = () => {
34+
return (
35+
<ResourceItemContainer isDisabled>
36+
<VStack alignItems="flex-start" textAlign="left" gap="0.25rem">
37+
<Skeleton width="12rem" height="1.125rem" variant="pulse" />
38+
<Skeleton width="18rem" height="1.125rem" variant="pulse" />
39+
</VStack>
40+
</ResourceItemContainer>
41+
)
42+
}
43+
44+
export const ResourceItem = ({
45+
item,
46+
isDisabled,
47+
isHighlighted = false,
48+
handleOnClick,
49+
hasAdditionalLeftPadding = false,
50+
}: ResourceItemProps) => {
51+
return (
52+
<ResourceItemContainer
53+
data-selected={dataAttr(isHighlighted)}
5254
_selected={{
5355
color: "interaction.main.default",
54-
bgColor: "interaction.muted.main.active",
55-
_hover: {
56-
bgColor: "unset",
57-
},
58-
}}
59-
_active={{
60-
bgColor: "interaction.tinted.main.active",
61-
}}
62-
_disabled={{
63-
color: "interaction.support.disabled-content",
64-
bgColor: "unset",
65-
cursor: "not-allowed",
56+
bg: "interaction.muted.main.active",
6657
_hover: {
67-
bgColor: "unset",
58+
color: "interaction.main.default",
59+
bg: "interaction.muted.main.active",
6860
},
6961
}}
70-
_hover={{
71-
bgColor: "interaction.muted.main.hover",
72-
}}
73-
pl="2.25rem"
74-
py="0.375rem"
75-
borderRadius="0.25rem"
76-
onClick={onResourceItemSelect}
77-
leftIcon={<Icon as={icon} />}
62+
{...(hasAdditionalLeftPadding && { pl: "2.25rem" })}
63+
onClick={handleOnClick}
64+
leftIcon={<Icon as={getIcon(item.type)} />}
65+
isDisabled={isDisabled}
7866
>
79-
<HStack align="start" w="full" justify="space-between">
80-
<Text textStyle="caption-1" noOfLines={1}>
81-
/{permalink}
67+
<VStack alignItems="flex-start" textAlign="left" gap="0.25rem">
68+
<Text noOfLines={1} textStyle="caption-1">
69+
{item.title}
8270
</Text>
83-
{isDisabled && <Icon as={BiLockAlt} fontSize="0.75rem" />}
84-
</HStack>
85-
</Button>
86-
)
87-
}
88-
89-
export const ResourceItem = (props: ResourceItemProps) => {
90-
return (
91-
<QueryErrorResetBoundary>
92-
{({ reset }) => (
93-
<ErrorBoundary
94-
onReset={reset}
95-
fallbackRender={({ resetErrorBoundary }) => (
96-
<Box>
97-
There was an error!
98-
<Button onClick={() => resetErrorBoundary()}>Try again</Button>
99-
</Box>
100-
)}
101-
>
102-
<Suspense fallback={<Skeleton />}>
103-
<SuspendableResourceItem {...props} />
104-
</Suspense>
105-
</ErrorBoundary>
106-
)}
107-
</QueryErrorResetBoundary>
71+
<Text noOfLines={1} textStyle="caption-2">
72+
{`/${item.permalink}`}
73+
</Text>
74+
</VStack>
75+
</ResourceItemContainer>
10876
)
10977
}

0 commit comments

Comments
 (0)