feat: modifed toast styles (#10)
* feat: support publish (#7) * fix: update pnpm-lock
This commit is contained in:
parent
3e9b0bead3
commit
d647387acd
17794
pnpm-lock.yaml
17794
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue