mirror of
https://github.com/docmost/docmost.git
synced 2026-06-16 06:57:01 +08:00
feat: implement space and workspace icons (#1558)
* feat: implement space and workspace icons - Create reusable AvatarUploader component supporting avatars, space icons, and workspace icons - Add Sharp package for server-side image resizing and optimization - Create reusable AvatarUploader component supporting avatars, space icons, and workspace icons - Support removing icons * add workspace logo support - add upload loader - add white background to transparent image - other fixes and enhancements * dark mode * fixes * cleanup
This commit is contained in:
@@ -1,11 +1,23 @@
|
||||
import React from 'react';
|
||||
import { useSpaceQuery } from '@/features/space/queries/space-query.ts';
|
||||
import { EditSpaceForm } from '@/features/space/components/edit-space-form.tsx';
|
||||
import { Button, Divider, Group, Text } from '@mantine/core';
|
||||
import DeleteSpaceModal from './delete-space-modal';
|
||||
import React, { useState } from "react";
|
||||
import { useSpaceQuery } from "@/features/space/queries/space-query.ts";
|
||||
import { EditSpaceForm } from "@/features/space/components/edit-space-form.tsx";
|
||||
import { Button, Divider, Text } from "@mantine/core";
|
||||
import DeleteSpaceModal from "./delete-space-modal";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import ExportModal from "@/components/common/export-modal.tsx";
|
||||
import AvatarUploader from "@/components/common/avatar-uploader.tsx";
|
||||
import {
|
||||
uploadSpaceIcon,
|
||||
removeSpaceIcon,
|
||||
} from "@/features/attachments/services/attachment-service.ts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { AvatarIconType } from "@/features/attachments/types/attachment.types.ts";
|
||||
import { queryClient } from "@/main.tsx";
|
||||
import {
|
||||
ResponsiveSettingsContent,
|
||||
ResponsiveSettingsControl,
|
||||
ResponsiveSettingsRow,
|
||||
} from "@/components/ui/responsive-settings-row.tsx";
|
||||
|
||||
interface SpaceDetailsProps {
|
||||
spaceId: string;
|
||||
@@ -13,9 +25,40 @@ interface SpaceDetailsProps {
|
||||
}
|
||||
export default function SpaceDetails({ spaceId, readOnly }: SpaceDetailsProps) {
|
||||
const { t } = useTranslation();
|
||||
const { data: space, isLoading } = useSpaceQuery(spaceId);
|
||||
const { data: space, isLoading, refetch } = useSpaceQuery(spaceId);
|
||||
const [exportOpened, { open: openExportModal, close: closeExportModal }] =
|
||||
useDisclosure(false);
|
||||
const [isIconUploading, setIsIconUploading] = useState(false);
|
||||
|
||||
const handleIconUpload = async (file: File) => {
|
||||
setIsIconUploading(true);
|
||||
try {
|
||||
await uploadSpaceIcon(file, spaceId);
|
||||
await refetch();
|
||||
await queryClient.invalidateQueries({
|
||||
predicate: (item) => ["spaces"].includes(item.queryKey[0] as string),
|
||||
});
|
||||
} catch (err) {
|
||||
// skip
|
||||
} finally {
|
||||
setIsIconUploading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleIconRemove = async () => {
|
||||
setIsIconUploading(true);
|
||||
try {
|
||||
await removeSpaceIcon(spaceId);
|
||||
await refetch();
|
||||
await queryClient.invalidateQueries({
|
||||
predicate: (item) => ["spaces"].includes(item.queryKey[0] as string),
|
||||
});
|
||||
} catch (err) {
|
||||
// skip
|
||||
} finally {
|
||||
setIsIconUploading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -24,38 +67,56 @@ export default function SpaceDetails({ spaceId, readOnly }: SpaceDetailsProps) {
|
||||
<Text my="md" fw={600}>
|
||||
{t("Details")}
|
||||
</Text>
|
||||
|
||||
<div style={{ marginBottom: "20px" }}>
|
||||
<Text size="sm" fw={500} mb="xs">
|
||||
{t("Icon")}
|
||||
</Text>
|
||||
<AvatarUploader
|
||||
currentImageUrl={space.logo}
|
||||
fallbackName={space.name}
|
||||
size={"60px"}
|
||||
variant="filled"
|
||||
|
||||
type={AvatarIconType.SPACE_ICON}
|
||||
onUpload={handleIconUpload}
|
||||
onRemove={handleIconRemove}
|
||||
isLoading={isIconUploading}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<EditSpaceForm space={space} readOnly={readOnly} />
|
||||
|
||||
{!readOnly && (
|
||||
<>
|
||||
|
||||
<Divider my="lg" />
|
||||
|
||||
<Group justify="space-between" wrap="nowrap" gap="xl">
|
||||
<div>
|
||||
<ResponsiveSettingsRow>
|
||||
<ResponsiveSettingsContent>
|
||||
<Text size="md">{t("Export space")}</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t("Export all pages and attachments in this space.")}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<Button onClick={openExportModal}>
|
||||
{t("Export")}
|
||||
</Button>
|
||||
</Group>
|
||||
</ResponsiveSettingsContent>
|
||||
<ResponsiveSettingsControl>
|
||||
<Button onClick={openExportModal}>{t("Export")}</Button>
|
||||
</ResponsiveSettingsControl>
|
||||
</ResponsiveSettingsRow>
|
||||
|
||||
<Divider my="lg" />
|
||||
|
||||
<Group justify="space-between" wrap="nowrap" gap="xl">
|
||||
<div>
|
||||
<ResponsiveSettingsRow>
|
||||
<ResponsiveSettingsContent>
|
||||
<Text size="md">{t("Delete space")}</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t("Delete this space with all its pages and data.")}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<DeleteSpaceModal space={space} />
|
||||
</Group>
|
||||
</ResponsiveSettingsContent>
|
||||
<ResponsiveSettingsControl>
|
||||
<DeleteSpaceModal space={space} />
|
||||
</ResponsiveSettingsControl>
|
||||
</ResponsiveSettingsRow>
|
||||
|
||||
<ExportModal
|
||||
type="space"
|
||||
|
||||
Reference in New Issue
Block a user