From 50d33e7de4badd89a84973c336127ccf04666827 Mon Sep 17 00:00:00 2001 From: "Kilu.He" <108015703+qinluhe@users.noreply.github.com> Date: Mon, 13 Jan 2025 14:18:44 +0800 Subject: [PATCH] feat: supports publishing optional subviews of database views (#21) --- src/@types/translations/en.json | 4 +- src/components/app/app.hooks.tsx | 9 +- src/components/app/share/PublishPanel.tsx | 105 ++++++++++++++++++---- src/components/app/share/publish.hooks.ts | 27 +----- 4 files changed, 97 insertions(+), 48 deletions(-) diff --git a/src/@types/translations/en.json b/src/@types/translations/en.json index d2cda5e..edd7707 100644 --- a/src/@types/translations/en.json +++ b/src/@types/translations/en.json @@ -3054,5 +3054,7 @@ "searchResults": "Search results", "noSearchResults": "No search results", "AISearchPlaceholder": "Search or ask a question...", - "searchLabel": "Search" + "searchLabel": "Search", + "publishSelectedViews": "Publish {{count}} selected views", + "untitled": "Untitled" } diff --git a/src/components/app/app.hooks.tsx b/src/components/app/app.hooks.tsx index 36fc931..71759c7 100644 --- a/src/components/app/app.hooks.tsx +++ b/src/components/app/app.hooks.tsx @@ -62,7 +62,7 @@ export interface AppContextType { updateSpace?: (payload: UpdateSpacePayload) => Promise; uploadFile?: (viewId: string, file: File, onProgress?: (n: number) => void) => Promise; getSubscriptions?: () => Promise; - publish?: (view: View, publishName?: string) => Promise; + publish?: (view: View, publishName?: string, visibleViewIds?: string[]) => Promise; unpublish?: (viewId: string) => Promise; } @@ -645,16 +645,13 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { } }, [currentWorkspaceId, service]); - const publish = useCallback(async (view: View, publishName?: string) => { + const publish = useCallback(async (view: View, publishName?: string, visibleViewIds?: string[]) => { if (!service || !currentWorkspaceId) return; - const isDatabase = [ViewLayout.Board, ViewLayout.Grid, ViewLayout.Calendar].includes(view.layout); const viewId = view.view_id; - const children = view.children || []; - const visibleViewIds = [view.view_id, ...(children.map((v) => v.view_id))]; await service.publishView(currentWorkspaceId, viewId, { publish_name: publishName, - visible_database_view_ids: isDatabase ? visibleViewIds : undefined, + visible_database_view_ids: visibleViewIds, }); await loadOutline(currentWorkspaceId, false); }, [currentWorkspaceId, loadOutline, service]); diff --git a/src/components/app/share/PublishPanel.tsx b/src/components/app/share/PublishPanel.tsx index cb68e05..28ac6a5 100644 --- a/src/components/app/share/PublishPanel.tsx +++ b/src/components/app/share/PublishPanel.tsx @@ -1,11 +1,15 @@ +import { ViewLayout } from '@/application/types'; import { notify } from '@/components/_shared/notify'; +import PageIcon from '@/components/_shared/view-icon/PageIcon'; import { useAppHandlers } from '@/components/app/app.hooks'; import { useLoadPublishInfo } from '@/components/app/share/publish.hooks'; import PublishLinkPreview from '@/components/app/share/PublishLinkPreview'; -import { Button, CircularProgress, Typography } from '@mui/material'; +import { Button, CircularProgress, Divider, Typography } from '@mui/material'; import React, { useCallback, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { ReactComponent as PublishIcon } from '@/assets/publish.svg'; +import { ReactComponent as CheckboxCheckSvg } from '@/assets/check_filled.svg'; +import { ReactComponent as CheckboxUncheckSvg } from '@/assets/uncheck.svg'; function PublishPanel ({ viewId, opened, onClose }: { viewId: string; onClose: () => void; opened: boolean }) { const { t } = useTranslation(); @@ -24,6 +28,7 @@ function PublishPanel ({ viewId, opened, onClose }: { viewId: string; onClose: ( } = useLoadPublishInfo(viewId); const [unpublishLoading, setUnpublishLoading] = React.useState(false); const [publishLoading, setPublishLoading] = React.useState(false); + const [visibleViewId, setVisibleViewId] = React.useState(undefined); useEffect(() => { if (opened) { @@ -35,8 +40,10 @@ function PublishPanel ({ viewId, opened, onClose }: { viewId: string; onClose: ( if (!publish || !view) return; setPublishLoading(true); + const newPublishName = publishName || publishInfo?.publishName || undefined; + try { - await publish(view, publishName || publishInfo?.publishName); + await publish(view, newPublishName, visibleViewId); await loadPublishInfo(); notify.success(t('publish.publishSuccessfully')); // eslint-disable-next-line @@ -45,7 +52,7 @@ function PublishPanel ({ viewId, opened, onClose }: { viewId: string; onClose: ( } finally { setPublishLoading(false); } - }, [loadPublishInfo, publish, t, view, publishInfo]); + }, [loadPublishInfo, publish, t, view, publishInfo, visibleViewId]); const handleUnpublish = useCallback(async () => { if (!view || !unpublish) return; @@ -103,22 +110,84 @@ function PublishPanel ({ viewId, opened, onClose }: { viewId: string; onClose: ( ; }, [handlePublish, handleUnpublish, isOwner, isPublisher, onClose, publishInfo, t, unpublishLoading, url, view]); + const layout = view?.layout; + const isDatabase = layout !== undefined ? [ViewLayout.Grid, ViewLayout.Board, ViewLayout.Calendar].includes(layout) : false; + const hasPublished = view?.is_published; + + useEffect(() => { + if (!hasPublished && isDatabase && view) { + const childIds = [view.view_id, ...view.children.map((child) => child.view_id)]; + + setVisibleViewId(childIds); + } else { + setVisibleViewId(undefined); + } + }, [hasPublished, isDatabase, view]); + const renderUnpublished = useCallback(() => { - return ; - }, [handlePublish, publishLoading, t]); + if (!view) return null; + const list = [view, ...view.children]; + + return
+ {isDatabase && +
+
{t('publishSelectedViews', { + count: visibleViewId?.length || 0, + })}
+ +
+ {list.map((item) => { + const id = item.view_id; + const isCurrentView = view.view_id === item.view_id; + + const selected = visibleViewId?.includes(item.view_id); + + return ; + })} +
+ +
} +
; + }, [handlePublish, isDatabase, publishLoading, t, view, visibleViewId]); return (
diff --git a/src/components/app/share/publish.hooks.ts b/src/components/app/share/publish.hooks.ts index 7f2e9ea..1f750bb 100644 --- a/src/components/app/share/publish.hooks.ts +++ b/src/components/app/share/publish.hooks.ts @@ -1,11 +1,9 @@ -import { View } from '@/application/types'; -import { useCurrentWorkspaceId, useUserWorkspaceInfo } from '@/components/app/app.hooks'; +import { useAppView, useUserWorkspaceInfo } from '@/components/app/app.hooks'; import { useCurrentUser, useService } from '@/components/main/app.hooks'; import React, { useCallback, useEffect, useMemo } from 'react'; export function useLoadPublishInfo (viewId: string) { - const [view, setView] = React.useState(); - const currentWorkspaceId = useCurrentWorkspaceId(); + const view = useAppView(viewId); const [publishInfo, setPublishInfo] = React.useState<{ namespace: string, publishName: string, @@ -18,29 +16,12 @@ export function useLoadPublishInfo (viewId: string) { const currentUser = useCurrentUser(); const isOwner = userWorkspaceInfo?.selectedWorkspace?.owner?.uid.toString() === currentUser?.uid.toString(); const isPublisher = publishInfo?.publisherEmail === currentUser?.email; - const loadView = useCallback(async () => { - if (!viewId || !service || !currentWorkspaceId) return; - try { - const view = await service.getAppView(currentWorkspaceId, viewId); - - setView(view); - return view; - // eslint-disable-next-line - } catch (e: any) { - // do nothing - console.error(e); - } - }, [currentWorkspaceId, service, viewId]); const loadPublishInfo = useCallback(async () => { if (!service) return; setLoading(true); try { - const view = await loadView(); - - if (!view) return; - - const res = await service.getPublishInfo(view?.view_id); + const res = await service.getPublishInfo(viewId); setPublishInfo(res); @@ -50,7 +31,7 @@ export function useLoadPublishInfo (viewId: string) { } setLoading(false); - }, [loadView, service]); + }, [viewId, service]); useEffect(() => { void loadPublishInfo();