feat: modifed toast styles (#10)

* feat: support publish (#7)

* fix: update pnpm-lock
This commit is contained in:
Kilu.He 2025-01-08 14:24:13 +08:00 committed by GitHub
parent 3e9b0bead3
commit d647387acd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 7903 additions and 10054 deletions

File diff suppressed because it is too large Load Diff

View File

@ -14,40 +14,45 @@ const CustomSnackbar = React.forwardRef<HTMLDivElement, CustomContentProps>((pro
const { closeSnackbar } = useSnackbar();
const icons = {
success: <TaskAltRounded className="w-6 h-6 text-green-500" />,
error: <HighlightOff className="w-6 h-6 text-red-500" />,
warning: <ErrorOutline className="w-6 h-6 text-yellow-500" />,
info: <PowerSettingsNew className="w-6 h-6 text-blue-500" />,
success: <TaskAltRounded className="w-5 h-5 text-green-500" />,
error: <HighlightOff className="w-5 h-5 text-red-500" />,
warning: <ErrorOutline className="w-5 h-5 text-yellow-500" />,
info: <PowerSettingsNew className="w-5 h-5 text-blue-500" />,
loading: null,
default: null,
};
const colors = {
success: 'bg-green-50 border-green-300',
error: 'bg-red-50',
warning: 'bg-yellow-50 border-yellow-300',
info: 'bg-blue-50 border-blue-300',
success: 'border-green-300 border bg-bg-body',
error: 'bg-bg-body border-red-300 border',
warning: 'bg-bg-body border-yellow-300 border',
info: 'bg-bg-body border-blue-300 border',
default: 'bg-bg-body border border-content-blue-400',
};
const [hovered, setHovered] = React.useState<boolean>(false);
return (
<SnackbarContent
ref={ref}
className={`${colors[variant]} rounded-lg shadow-lg`}
className={`${colors[variant]} rounded-lg shadow`}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
<div className="flex items-center justify-between w-full p-4">
<div className="flex items-center justify-between w-full p-3">
<div className="flex-shrink-0">
{icons[variant]}
</div>
<div className="ml-3 flex-1">
<p className="text-sm font-medium">{message}</p>
</div>
<IconButton
className={'mx-2'}
{hovered && <IconButton
className={'mx-2 rounded-full bg-fill-list-hover hover:opacity-100 opacity-60'}
onClick={() => closeSnackbar(id)}
>
<CloseIcon className="h-5 w-5 text-text-caption" />
</IconButton>
<CloseIcon className="h-3 w-3 text-text-title" />
</IconButton>}
</div>
</SnackbarContent>
);

View File

@ -15,6 +15,7 @@ import {
YjsEditorKey,
YSharedRoot,
} from '@/application/types';
import { notify } from '@/components/_shared/notify';
import { findAncestors, findView, findViewByLayout } from '@/components/_shared/outline/utils';
import RequestAccess from '@/components/app/landing-pages/RequestAccess';
import { AFConfigContext, useService } from '@/components/main/app.hooks';
@ -62,6 +63,8 @@ export interface AppContextType {
updateSpace?: (payload: UpdateSpacePayload) => Promise<void>;
uploadFile?: (viewId: string, file: File, onProgress?: (n: number) => void) => Promise<string>;
getSubscriptions?: () => Promise<Subscription[]>;
publish?: (view: View, publishName?: string) => Promise<void>;
unpublish?: (viewId: string) => Promise<void>;
}
const USER_NO_ACCESS_CODE = [1024, 1012];
@ -641,6 +644,24 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
}
}, [currentWorkspaceId, service]);
const publish = useCallback(async (view: View, publishName?: string) => {
if (!service || !currentWorkspaceId) return;
const isDatabase = [ViewLayout.Board, ViewLayout.Grid, ViewLayout.Calendar].includes(view.layout);
const viewId = view.view_id;
await service.publishView(currentWorkspaceId, viewId, {
publish_name: publishName,
visible_database_view_ids: isDatabase ? view.children?.map((v) => v.view_id) : undefined,
});
void loadOutline(currentWorkspaceId, false);
}, [currentWorkspaceId, loadOutline, service]);
const unpublish = useCallback(async (viewId: string) => {
if (!service || !currentWorkspaceId) return;
await service.unpublishView(currentWorkspaceId, viewId);
void loadOutline(currentWorkspaceId, false);
}, [currentWorkspaceId, loadOutline, service]);
return <AppContext.Provider
value={{
currentWorkspaceId,
@ -679,6 +700,8 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
updateSpace,
uploadFile,
getSubscriptions,
publish,
unpublish,
}}
>
{requestAccessOpened ? <RequestAccess /> : children}
@ -819,6 +842,8 @@ export function useAppHandlers () {
updateSpace: context.updateSpace,
uploadFile: context.uploadFile,
getSubscriptions: context.getSubscriptions,
publish: context.publish,
unpublish: context.unpublish,
};
}

View File

@ -1,4 +1,4 @@
import { SubscriptionPlan, View, ViewLayout } from '@/application/types';
import { SubscriptionPlan, View } from '@/application/types';
import { notify } from '@/components/_shared/notify';
import { flattenViews } from '@/components/_shared/outline/utils';
import { useAppHandlers, useUserWorkspaceInfo } from '@/components/app/app.hooks';
@ -129,36 +129,34 @@ export function PublishManage ({
}, [isOwner, service, t, workspaceId]);
const currentWorkspaceId = userWorkspaceInfo?.selectedWorkspace?.id;
const {
publish,
unpublish,
} = useAppHandlers();
const handlePublish = useCallback(async (view: View, publishName: string) => {
if (!service || !currentWorkspaceId) return;
const isDatabase = [ViewLayout.Board, ViewLayout.Grid, ViewLayout.Calendar].includes(view.layout);
const viewId = view.view_id;
if (!publish) return;
try {
await service.publishView(currentWorkspaceId, viewId, {
publish_name: publishName,
visible_database_view_ids: isDatabase ? view.children?.map((v) => v.view_id) : undefined,
});
await publish(view, publishName);
notify.success(t('publish.publishSuccessfully'));
// eslint-disable-next-line
} catch (e: any) {
notify.error(e.message);
}
}, [currentWorkspaceId, service, t]);
}, [publish, t]);
const handleUnpublish = useCallback(async (viewId: string) => {
if (!service || !currentWorkspaceId) return;
if (!unpublish) return;
try {
await service.unpublishView(currentWorkspaceId, viewId);
await unpublish(viewId);
void loadPublishPages();
notify.success(t('publish.unpublishSuccessfully'));
// eslint-disable-next-line
} catch (e: any) {
notify.error(e.message);
}
}, [currentWorkspaceId, loadPublishPages, service, t]);
}, [loadPublishPages, t, unpublish]);
const {
getSubscriptions,
@ -252,7 +250,7 @@ export function PublishManage ({
if (!isOwner || activeSubscription === SubscriptionPlan.Free) {
return;
}
e.currentTarget.blur();
setUpdateOpen(true);
}}

View File

@ -5,7 +5,7 @@ import { CircularProgress, IconButton, InputBase, Tooltip } from '@mui/material'
import { ReactComponent as LinkIcon } from '@/assets/link.svg';
import { ReactComponent as DownIcon } from '@/assets/chevron_down.svg';
import { ReactComponent as CheckIcon } from '@/assets/check.svg';
import React from 'react';
import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
function PublishLinkPreview ({
@ -15,6 +15,7 @@ function PublishLinkPreview ({
url,
isOwner,
isPublisher,
onClose,
}: {
publishInfo: { namespace: string, publishName: string };
onUnPublish: () => Promise<void>;
@ -22,6 +23,7 @@ function PublishLinkPreview ({
url: string;
isOwner: boolean;
isPublisher: boolean;
onClose?: () => void;
}) {
const [siteOpen, setSiteOpen] = React.useState<boolean>(false);
const [renameOpen, setRenameOpen] = React.useState<boolean>(false);
@ -30,6 +32,10 @@ function PublishLinkPreview ({
const [focused, setFocused] = React.useState<boolean>(false);
const [loading, setLoading] = React.useState<boolean>(false);
useEffect(() => {
setPublishName(publishInfo.publishName);
}, [publishInfo.publishName]);
const handlePublish = async () => {
if (loading) return;
if (publishName === publishInfo.publishName) return;
@ -62,6 +68,7 @@ function PublishLinkPreview ({
size={'small'}
onClick={() => {
setSiteOpen(true);
onClose?.();
}}
>
<DownIcon className={'w-4 h-4'} />
@ -111,6 +118,7 @@ function PublishLinkPreview ({
}
setRenameOpen(true);
onClose?.();
}}
>
{loading ? <CircularProgress size={14} /> :
@ -142,7 +150,7 @@ function PublishLinkPreview ({
onPublish={onPublish}
url={url}
/>}
{siteOpen && <NormalModal
<NormalModal
okButtonProps={{
className: 'hidden',
}}
@ -168,7 +176,7 @@ function PublishLinkPreview ({
/>
</div>
</NormalModal>}
</NormalModal>
</div>
</>

View File

@ -1,17 +1,18 @@
import { ViewLayout } from '@/application/types';
import { notify } from '@/components/_shared/notify';
import { useCurrentWorkspaceId } from '@/components/app/app.hooks';
import { useAppHandlers } from '@/components/app/app.hooks';
import { useLoadPublishInfo } from '@/components/app/share/publish.hooks';
import PublishLinkPreview from '@/components/app/share/PublishLinkPreview';
import { useService } from '@/components/main/app.hooks';
import { Button, CircularProgress, Typography } from '@mui/material';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { ReactComponent as PublishIcon } from '@/assets/publish.svg';
function PublishPanel ({ viewId }: { viewId: string }) {
const currentWorkspaceId = useCurrentWorkspaceId();
function PublishPanel ({ viewId, onClose }: { viewId: string; onClose: () => void }) {
const { t } = useTranslation();
const {
publish,
unpublish,
} = useAppHandlers();
const {
url,
loadPublishInfo,
@ -22,40 +23,35 @@ function PublishPanel ({ viewId }: { viewId: string }) {
isPublisher,
} = useLoadPublishInfo(viewId);
const service = useService();
const handlePublish = useCallback(async (publishName?: string) => {
if (!service || !currentWorkspaceId || !view) return;
const isDatabase = [ViewLayout.Board, ViewLayout.Grid, ViewLayout.Calendar].includes(view.layout);
if (!publish || !view) return;
try {
await service.publishView(currentWorkspaceId, viewId, {
publish_name: publishName,
visible_database_view_ids: isDatabase ? view.children?.map((v) => v.view_id) : undefined,
});
await loadPublishInfo();
await publish(view, publishName);
void loadPublishInfo();
notify.success(t('publish.publishSuccessfully'));
// eslint-disable-next-line
} catch (e: any) {
notify.error(e.message);
}
}, [currentWorkspaceId, loadPublishInfo, service, t, view, viewId]);
}, [loadPublishInfo, publish, t, view]);
const handleUnpublish = useCallback(async () => {
if (!service || !currentWorkspaceId || !view) return;
if (!view || !unpublish) return;
if (!isOwner && !isPublisher) {
notify.error(t('settings.sites.error.publishPermissionDenied'));
return;
}
try {
await service.unpublishView(currentWorkspaceId, viewId);
await unpublish(viewId);
await loadPublishInfo();
notify.success(t('publish.unpublishSuccessfully'));
// eslint-disable-next-line
} catch (e: any) {
notify.error(e.message);
}
}, [currentWorkspaceId, isOwner, isPublisher, loadPublishInfo, service, t, view, viewId]);
}, [isOwner, isPublisher, loadPublishInfo, t, unpublish, view, viewId]);
const renderPublished = useCallback(() => {
if (!publishInfo || !view) return null;
@ -67,6 +63,7 @@ function PublishPanel ({ viewId }: { viewId: string }) {
onUnPublish={handleUnpublish}
isOwner={isOwner}
isPublisher={isPublisher}
onClose={onClose}
/>
<div className={'flex items-center gap-4 justify-end w-full'}>
<Button
@ -86,7 +83,7 @@ function PublishPanel ({ viewId }: { viewId: string }) {
>{t('shareAction.visitSite')}</Button>
</div>
</div>;
}, [handlePublish, handleUnpublish, isOwner, isPublisher, publishInfo, t, url, view]);
}, [handlePublish, handleUnpublish, isOwner, isPublisher, onClose, publishInfo, t, url, view]);
const renderUnpublished = useCallback(() => {
return <Button

View File

@ -22,7 +22,8 @@ export function ShareButton ({ viewId }: { viewId: string }) {
variant={'contained'}
color={'primary'}
>{t('shareAction.buttonText')}</Button>
{opened && <Popover
<Popover
keepMounted
open={opened}
anchorEl={ref.current}
onClose={() => setOpened(false)}
@ -41,9 +42,13 @@ export function ShareButton ({ viewId }: { viewId: string }) {
}}
>
<div className={'flex flex-col gap-2 w-fit p-2'}>
<ShareTabs viewId={viewId} />
<ShareTabs
opened={opened}
viewId={viewId}
onClose={() => setOpened(false)}
/>
</div>
</Popover>}
</Popover>
</>
);
}

View File

@ -15,7 +15,7 @@ enum TabKey {
TEMPLATE = 'template',
}
function ShareTabs({ viewId }: { viewId: string }) {
function ShareTabs ({ opened, viewId, onClose }: { opened: boolean, viewId: string; onClose: () => void }) {
const { t } = useTranslation();
const view = useAppView(viewId);
const [value, setValue] = React.useState<TabKey>(TabKey.SHARE);
@ -29,18 +29,18 @@ function ShareTabs({ viewId }: { viewId: string }) {
}, {
value: TabKey.PUBLISH,
label: t('shareAction.publish'),
icon: view?.is_published ? <PublishedWithChanges className={'w-4 h-4 text-function-success mb-0'}/> : undefined,
icon: view?.is_published ? <PublishedWithChanges className={'w-4 h-4 text-function-success mb-0'} /> : undefined,
Panel: PublishPanel,
}, currentUser?.email?.endsWith('appflowy.io') && view?.is_published && {
value: TabKey.TEMPLATE,
label: t('template.asTemplate'),
icon: <Templates className={'w-4 h-4 mb-0'}/>,
icon: <Templates className={'w-4 h-4 mb-0'} />,
Panel: TemplatePanel,
}].filter(Boolean) as {
value: TabKey;
label: string;
icon?: React.JSX.Element;
Panel: React.FC<{ viewId: string }>
Panel: React.FC<{ viewId: string; onClose: () => void }>
}[];
}, [currentUser?.email, t, view?.is_published]);
@ -56,7 +56,7 @@ function ShareTabs({ viewId }: { viewId: string }) {
onChange={onChange}
value={value}
>
{options.map((option) => (
{opened && options.map((option) => (
<ViewTab
className={'flex items-center flex-row justify-center gap-1.5'}
key={option.value}
@ -74,7 +74,10 @@ function ShareTabs({ viewId }: { viewId: string }) {
index={option.value}
value={value}
>
<option.Panel viewId={viewId}/>
<option.Panel
viewId={viewId}
onClose={onClose}
/>
</TabPanel>
))}
</div>

View File

@ -19,8 +19,8 @@ const StyledSnackbarProvider = styled(SnackbarProvider)`
`;
export default function withAppWrapper(Component: React.FC): React.FC {
return function AppWrapper(): JSX.Element {
export default function withAppWrapper (Component: React.FC): React.FC {
return function AppWrapper (): JSX.Element {
return (
<AppTheme>
<ErrorBoundary FallbackComponent={ErrorHandlerPage}>
@ -41,7 +41,7 @@ export default function withAppWrapper(Component: React.FC): React.FC {
>
<AppConfig>
<Suspense>
<Component/>
<Component />
</Suspense>
</AppConfig>
</StyledSnackbarProvider>