mirror of
https://github.com/AppFlowy-IO/AppFlowy-Web.git
synced 2026-03-13 10:00:26 +08:00
chore: fix web repo issues (#82)
* chore: fix the row height incorrect on firefox * chore: add back button to row modal * chore: remove sticky header on open view
This commit is contained in:
@@ -70,6 +70,7 @@ export interface DatabaseContextState {
|
||||
// Calendar view type map: viewId -> CalendarViewType
|
||||
calendarViewTypeMap?: Map<string, CalendarViewType>;
|
||||
setCalendarViewType?: (viewId: string, viewType: CalendarViewType) => void;
|
||||
openPageModalViewId?: string;
|
||||
}
|
||||
|
||||
export const DatabaseContext = createContext<DatabaseContextState | null>(null);
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { ThemeModeContext } from '@/components/main/useAppThemeMode';
|
||||
import { renderColor } from '@/utils/color';
|
||||
import { getIcon } from '@/utils/emoji';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { useContext, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { ReactComponent as SpaceIcon1 } from '@/assets/space_icon/space_icon_1.svg';
|
||||
import { ReactComponent as SpaceIcon10 } from '@/assets/space_icon/space_icon_10.svg';
|
||||
import { ReactComponent as SpaceIcon11 } from '@/assets/space_icon/space_icon_11.svg';
|
||||
import { ReactComponent as SpaceIcon12 } from '@/assets/space_icon/space_icon_12.svg';
|
||||
import { ReactComponent as SpaceIcon13 } from '@/assets/space_icon/space_icon_13.svg';
|
||||
import { ReactComponent as SpaceIcon14 } from '@/assets/space_icon/space_icon_14.svg';
|
||||
import { ReactComponent as SpaceIcon15 } from '@/assets/space_icon/space_icon_15.svg';
|
||||
import { ReactComponent as SpaceIcon2 } from '@/assets/space_icon/space_icon_2.svg';
|
||||
import { ReactComponent as SpaceIcon3 } from '@/assets/space_icon/space_icon_3.svg';
|
||||
import { ReactComponent as SpaceIcon4 } from '@/assets/space_icon/space_icon_4.svg';
|
||||
@@ -11,13 +16,9 @@ import { ReactComponent as SpaceIcon6 } from '@/assets/space_icon/space_icon_6.s
|
||||
import { ReactComponent as SpaceIcon7 } from '@/assets/space_icon/space_icon_7.svg';
|
||||
import { ReactComponent as SpaceIcon8 } from '@/assets/space_icon/space_icon_8.svg';
|
||||
import { ReactComponent as SpaceIcon9 } from '@/assets/space_icon/space_icon_9.svg';
|
||||
import { ReactComponent as SpaceIcon10 } from '@/assets/space_icon/space_icon_10.svg';
|
||||
import { ReactComponent as SpaceIcon11 } from '@/assets/space_icon/space_icon_11.svg';
|
||||
import { ReactComponent as SpaceIcon12 } from '@/assets/space_icon/space_icon_12.svg';
|
||||
import { ReactComponent as SpaceIcon13 } from '@/assets/space_icon/space_icon_13.svg';
|
||||
import { ReactComponent as SpaceIcon14 } from '@/assets/space_icon/space_icon_14.svg';
|
||||
import { ReactComponent as SpaceIcon15 } from '@/assets/space_icon/space_icon_15.svg';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { ThemeModeContext } from '@/components/main/useAppThemeMode';
|
||||
import { renderColor } from '@/utils/color';
|
||||
import { getIcon } from '@/utils/emoji';
|
||||
|
||||
export const getIconComponent = (icon: string) => {
|
||||
switch (icon) {
|
||||
@@ -58,11 +59,16 @@ export const getIconComponent = (icon: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
function SpaceIcon({ value, char, bgColor, className: classNameProp }: {
|
||||
value: string,
|
||||
char?: string,
|
||||
bgColor?: string,
|
||||
className?: string
|
||||
function SpaceIcon({
|
||||
value,
|
||||
char,
|
||||
bgColor,
|
||||
className: classNameProp,
|
||||
}: {
|
||||
value: string;
|
||||
char?: string;
|
||||
bgColor?: string;
|
||||
className?: string;
|
||||
}) {
|
||||
const IconComponent = getIconComponent(value);
|
||||
const isDark = useContext(ThemeModeContext)?.isDark || false;
|
||||
@@ -70,7 +76,7 @@ function SpaceIcon({ value, char, bgColor, className: classNameProp }: {
|
||||
|
||||
useEffect(() => {
|
||||
if (value) {
|
||||
void getIcon(value).then(icon => {
|
||||
void getIcon(value).then((icon) => {
|
||||
setCustomIconContent(icon?.content || '');
|
||||
});
|
||||
}
|
||||
@@ -78,22 +84,31 @@ function SpaceIcon({ value, char, bgColor, className: classNameProp }: {
|
||||
|
||||
const customIcon = useMemo(() => {
|
||||
if (customIconContent) {
|
||||
const cleanSvg = DOMPurify.sanitize(customIconContent.replaceAll('black', isDark ? 'black' : 'white').replace('<svg', '<svg width="100%" height="100%"'), {
|
||||
USE_PROFILES: { svg: true, svgFilters: true },
|
||||
});
|
||||
const cleanSvg = DOMPurify.sanitize(
|
||||
customIconContent
|
||||
.replaceAll('black', isDark ? 'black' : 'white')
|
||||
.replace('<svg', '<svg width="100%" height="100%"'),
|
||||
{
|
||||
USE_PROFILES: { svg: true, svgFilters: true },
|
||||
}
|
||||
);
|
||||
|
||||
return <span className={'flex p-[0.2em] items-center justify-center'}><span dangerouslySetInnerHTML={{
|
||||
__html: cleanSvg,
|
||||
}}/></span>;
|
||||
return (
|
||||
<span className={'flex items-center justify-center p-[0.2em]'}>
|
||||
<span
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: cleanSvg,
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}, [customIconContent, isDark]);
|
||||
|
||||
const content = useMemo(() => {
|
||||
if (char) {
|
||||
return (
|
||||
<span className={'text-content-on-fill font-medium h-full w-full flex items-center justify-center'}>
|
||||
{char}
|
||||
</span>
|
||||
<span className={'flex h-full w-full items-center justify-center font-medium text-content-on-fill'}>{char}</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -101,11 +116,18 @@ function SpaceIcon({ value, char, bgColor, className: classNameProp }: {
|
||||
return customIcon;
|
||||
}
|
||||
|
||||
return <IconComponent className={'h-full w-full'}/>;
|
||||
return <IconComponent className={'h-full w-full'} />;
|
||||
}, [IconComponent, char, customIcon]);
|
||||
|
||||
const className = useMemo(() => {
|
||||
const classList = ['icon', 'h-[1.2em]', 'w-[1.2em]', 'shrink-0', 'rounded-[4px]', 'p-[0.1em]'];
|
||||
const classList = [
|
||||
'icon',
|
||||
'h-[1.2em] min-h-[1.2em]',
|
||||
'w-[1.2em] min-w-[1.2em]',
|
||||
'shrink-0',
|
||||
'rounded-[4px]',
|
||||
'p-[0.1em]',
|
||||
];
|
||||
|
||||
if (classNameProp) {
|
||||
classList.push(classNameProp);
|
||||
@@ -114,12 +136,16 @@ function SpaceIcon({ value, char, bgColor, className: classNameProp }: {
|
||||
return classList.join(' ');
|
||||
}, [classNameProp]);
|
||||
|
||||
return <span
|
||||
className={className}
|
||||
style={{
|
||||
backgroundColor: bgColor ? renderColor(bgColor) : 'rgb(163, 74, 253)',
|
||||
}}
|
||||
>{content}</span>;
|
||||
return (
|
||||
<span
|
||||
className={className}
|
||||
style={{
|
||||
backgroundColor: bgColor ? renderColor(bgColor) : 'rgb(163, 74, 253)',
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export default SpaceIcon;
|
||||
|
||||
@@ -2,14 +2,14 @@ import { Suspense, useCallback, useMemo } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
import { ViewComponentProps, ViewLayout, YDatabase, YjsEditorKey } from '@/application/types';
|
||||
import { useAppOutline } from '@/components/app/app.hooks';
|
||||
import { Database } from '@/components/database';
|
||||
import { findView } from '@/components/_shared/outline/utils';
|
||||
import ComponentLoading from '@/components/_shared/progress/ComponentLoading';
|
||||
import CalendarSkeleton from '@/components/_shared/skeleton/CalendarSkeleton';
|
||||
import DocumentSkeleton from '@/components/_shared/skeleton/DocumentSkeleton';
|
||||
import GridSkeleton from '@/components/_shared/skeleton/GridSkeleton';
|
||||
import KanbanSkeleton from '@/components/_shared/skeleton/KanbanSkeleton';
|
||||
import { useAppOutline } from '@/components/app/app.hooks';
|
||||
import { Database } from '@/components/database';
|
||||
|
||||
import ViewMetaPreview from 'src/components/view-meta/ViewMetaPreview';
|
||||
|
||||
@@ -53,6 +53,7 @@ function DatabaseView(props: ViewComponentProps) {
|
||||
);
|
||||
|
||||
const rowId = search.get('r') || undefined;
|
||||
const modalRowId = search.get('r-modal') || undefined;
|
||||
const doc = props.doc;
|
||||
const database = doc?.getMap(YjsEditorKey.data_section)?.get(YjsEditorKey.database) as YDatabase;
|
||||
const skeleton = useMemo(() => {
|
||||
@@ -103,6 +104,7 @@ function DatabaseView(props: ViewComponentProps) {
|
||||
visibleViewIds={visibleViewIds}
|
||||
onChangeView={handleChangeView}
|
||||
onOpenRowPage={handleNavigateToRow}
|
||||
modalRowId={modalRowId}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { useOutlineDrawer } from '@/components/_shared/outline/outline.hooks';
|
||||
import { AFScroller } from '@/components/_shared/scroller';
|
||||
import { useAIChatContext } from '@/components/ai-chat/AIChatProvider';
|
||||
import { useViewErrorStatus } from '@/components/app/app.hooks';
|
||||
import { useAppHandlers, useViewErrorStatus } from '@/components/app/app.hooks';
|
||||
import { ConnectBanner } from '@/components/app/ConnectBanner';
|
||||
import { AppHeader } from '@/components/app/header';
|
||||
import Main from '@/components/app/Main';
|
||||
@@ -17,6 +17,7 @@ function MainLayout() {
|
||||
const { drawerOpened, drawerWidth, setDrawerWidth, toggleOpenDrawer } = useOutlineDrawer();
|
||||
const { drawerOpen: chatViewDrawerOpen, drawerWidth: openViewDrawerWidth } = useAIChatContext();
|
||||
|
||||
const { openPageModalViewId } = useAppHandlers();
|
||||
const { notFound, deleted } = useViewErrorStatus();
|
||||
|
||||
const main = useMemo(() => {
|
||||
@@ -65,17 +66,19 @@ function MainLayout() {
|
||||
/>
|
||||
<ConnectBanner />
|
||||
|
||||
<div
|
||||
className={'sticky-header-overlay'}
|
||||
style={{
|
||||
width: '100%',
|
||||
position: 'sticky',
|
||||
top: 48,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: 50,
|
||||
}}
|
||||
/>
|
||||
{!openPageModalViewId && (
|
||||
<div
|
||||
className={'sticky-header-overlay'}
|
||||
style={{
|
||||
width: '100%',
|
||||
position: 'sticky',
|
||||
top: 48,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: 50,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ErrorBoundary FallbackComponent={SomethingError}>{main}</ErrorBoundary>
|
||||
</AFScroller>
|
||||
|
||||
@@ -7,6 +7,8 @@ import { UIVariant, ViewComponentProps, ViewLayout, ViewMetaProps, YDoc } from '
|
||||
import { ReactComponent as ArrowDownIcon } from '@/assets/icons/alt_arrow_down.svg';
|
||||
import { ReactComponent as CloseIcon } from '@/assets/icons/close.svg';
|
||||
import { ReactComponent as ExpandIcon } from '@/assets/icons/expand.svg';
|
||||
import { findAncestors, findView } from '@/components/_shared/outline/utils';
|
||||
import SpaceIcon from '@/components/_shared/view-icon/SpaceIcon';
|
||||
import { useAppHandlers, useAppOutline, useCurrentWorkspaceId } from '@/components/app/app.hooks';
|
||||
import DatabaseView from '@/components/app/DatabaseView';
|
||||
import MoreActions from '@/components/app/header/MoreActions';
|
||||
@@ -14,8 +16,6 @@ import MovePagePopover from '@/components/app/view-actions/MovePagePopover';
|
||||
import { Document } from '@/components/document';
|
||||
import RecordNotFound from '@/components/error/RecordNotFound';
|
||||
import { useService } from '@/components/main/app.hooks';
|
||||
import { findAncestors, findView } from '@/components/_shared/outline/utils';
|
||||
import SpaceIcon from '@/components/_shared/view-icon/SpaceIcon';
|
||||
|
||||
import ShareButton from 'src/components/app/share/ShareButton';
|
||||
|
||||
@@ -144,9 +144,6 @@ function ViewModal({ viewId, open, onClose }: { viewId?: string; open: boolean;
|
||||
{space && ref.current && (
|
||||
<MovePagePopover
|
||||
viewId={viewId}
|
||||
popoverContentProps={{
|
||||
container: ref.current,
|
||||
}}
|
||||
open={movePageOpen}
|
||||
onOpenChange={setMovePageOpen}
|
||||
onMoved={() => {
|
||||
|
||||
@@ -247,6 +247,7 @@ export function useAppHandlers() {
|
||||
onRendered: context.onRendered,
|
||||
addPage: context.addPage,
|
||||
openPageModal: context.openPageModal,
|
||||
openPageModalViewId: context.openPageModalViewId,
|
||||
deletePage: context.deletePage,
|
||||
deleteTrash: context.deleteTrash,
|
||||
restorePage: context.restorePage,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { CustomIconPopover } from '@/components/_shared/cutsom-icon';
|
||||
import SpaceIcon from '@/components/_shared/view-icon/SpaceIcon';
|
||||
import { Avatar } from '@mui/material';
|
||||
import React from 'react';
|
||||
import { ReactComponent as EditIcon } from '@/assets/icons/edit.svg';
|
||||
|
||||
function SpaceIconButton ({
|
||||
import { ReactComponent as EditIcon } from '@/assets/icons/edit.svg';
|
||||
import { CustomIconPopover } from '@/components/_shared/cutsom-icon';
|
||||
import SpaceIcon from '@/components/_shared/view-icon/SpaceIcon';
|
||||
|
||||
function SpaceIconButton({
|
||||
spaceIcon,
|
||||
spaceIconColor,
|
||||
spaceName,
|
||||
@@ -39,29 +40,33 @@ function SpaceIconButton ({
|
||||
>
|
||||
<Avatar
|
||||
variant={'rounded'}
|
||||
className={`${size ? `w-[${size}px] h-[${size}px]` : 'w-10 h-10'} rounded-[30%] bg-transparent`}
|
||||
className={`aspect-square h-10 w-10 rounded-[30%] bg-transparent`}
|
||||
onMouseEnter={() => setSpaceIconEditing(true)}
|
||||
onMouseLeave={() => setSpaceIconEditing(false)}
|
||||
onClick={() => {
|
||||
setSpaceIconEditing(false);
|
||||
}}
|
||||
style={{
|
||||
minWidth: size ? `${size}px` : undefined,
|
||||
minHeight: size ? `${size}px` : '100%',
|
||||
}}
|
||||
>
|
||||
<SpaceIcon
|
||||
bgColor={spaceIconColor}
|
||||
value={spaceIcon || ''}
|
||||
className={'w-full h-full p-0.5'}
|
||||
className={'h-full w-full !p-2'}
|
||||
char={spaceIcon ? undefined : spaceName.slice(0, 1)}
|
||||
/>
|
||||
{spaceIconEditing &&
|
||||
<div className={'absolute cursor-pointer inset-0 bg-black bg-opacity-30 rounded-[8px]'}>
|
||||
<div className={'flex items-center text-white justify-center w-full h-full'}>
|
||||
{spaceIconEditing && (
|
||||
<div className={'absolute inset-0 cursor-pointer rounded-[8px] bg-black bg-opacity-30'}>
|
||||
<div className={'flex h-full w-full items-center justify-center text-white'}>
|
||||
<EditIcon />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
</Avatar>
|
||||
</CustomIconPopover>
|
||||
);
|
||||
}
|
||||
|
||||
export default SpaceIconButton;
|
||||
export default SpaceIconButton;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { Button, Divider } from '@mui/material';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { SpacePermission } from '@/application/types';
|
||||
import { ReactComponent as ArrowDownIcon } from '@/assets/icons/alt_arrow_down.svg';
|
||||
import { ReactComponent as LockIcon } from '@/assets/icons/lock.svg';
|
||||
import { ReactComponent as PublicIcon } from '@/assets/icons/public.svg';
|
||||
import { ReactComponent as TickIcon } from '@/assets/icons/tick.svg';
|
||||
import { Popover } from '@/components/_shared/popover';
|
||||
import { Button, Divider } from '@mui/material';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function SpacePermissionButton({
|
||||
onSelected,
|
||||
|
||||
@@ -33,6 +33,7 @@ export interface Database2Props {
|
||||
viewId: string;
|
||||
iidName: string;
|
||||
rowId?: string;
|
||||
modalRowId?: string;
|
||||
appendBreadcrumb?: AppendBreadcrumb;
|
||||
onChangeView: (viewId: string) => void;
|
||||
onOpenRowPage?: (rowId: string) => void;
|
||||
@@ -62,6 +63,7 @@ function Database(props: Database2Props) {
|
||||
readOnly = true,
|
||||
loadView,
|
||||
navigateToView,
|
||||
modalRowId,
|
||||
} = props;
|
||||
const database = doc.getMap(YjsEditorKey.data_section)?.get(YjsEditorKey.database) as YDatabase | null;
|
||||
const view = database?.get(YjsDatabaseKey.views)?.get(iidIndex);
|
||||
@@ -136,8 +138,8 @@ function Database(props: Database2Props) {
|
||||
};
|
||||
}, [handleUpdateRowDocMap, rowOrders]);
|
||||
|
||||
const [openModalRowId, setOpenModalRowId] = useState<string | null>(null);
|
||||
const [openModalViewId, setOpenModalViewId] = useState<string | null>(null);
|
||||
const [openModalRowId, setOpenModalRowId] = useState<string | null>(() => modalRowId || null);
|
||||
const [openModalViewId, setOpenModalViewId] = useState<string | null>(() => (modalRowId ? viewId : null));
|
||||
const [openModalRowDatabaseDoc, setOpenModalRowDatabaseDoc] = useState<YDoc | null>(null);
|
||||
const [openModalRowDocMap, setOpenModalRowDocMap] = useState<Record<RowId, YDoc> | null>(null);
|
||||
|
||||
|
||||
@@ -3,11 +3,14 @@ import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { useDatabaseContext } from '@/application/database-yjs';
|
||||
import { useDeleteRowDispatch, useDuplicateRowDispatch } from '@/application/database-yjs/dispatch';
|
||||
import { ReactComponent as ArrowLeftIcon } from '@/assets/icons/arrow_left.svg';
|
||||
import { ReactComponent as DeleteIcon } from '@/assets/icons/delete.svg';
|
||||
import { ReactComponent as DuplicateIcon } from '@/assets/icons/duplicate.svg';
|
||||
import { ReactComponent as ExpandIcon } from '@/assets/icons/expand.svg';
|
||||
import { ReactComponent as MoreIcon } from '@/assets/icons/more.svg';
|
||||
import { AFScroller } from '@/components/_shared/scroller';
|
||||
import { DatabaseRow } from '@/components/database/DatabaseRow';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
@@ -19,7 +22,6 @@ import {
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { AFScroller } from '@/components/_shared/scroller';
|
||||
|
||||
function DatabaseRowModal({
|
||||
open,
|
||||
@@ -32,7 +34,7 @@ function DatabaseRowModal({
|
||||
onOpenChange: (open: boolean) => void;
|
||||
openPage?: (rowId: string) => void;
|
||||
}) {
|
||||
// const {} = useDatabaseContext();
|
||||
const { openPageModalViewId } = useDatabaseContext();
|
||||
const { t } = useTranslation();
|
||||
const duplicateRow = useDuplicateRowDispatch();
|
||||
const deleteRow = useDeleteRowDispatch();
|
||||
@@ -49,12 +51,32 @@ function DatabaseRowModal({
|
||||
disableAutoFocus={false}
|
||||
disableEnforceFocus={false}
|
||||
disableRestoreFocus={true}
|
||||
hideBackdrop={!!openPageModalViewId}
|
||||
PaperProps={{
|
||||
className: `max-w-[70vw] relative w-[1188px] h-[80vh] overflow-hidden flex flex-col`,
|
||||
}}
|
||||
>
|
||||
<DialogContent className={'flex h-full w-full flex-col px-0 py-0'}>
|
||||
<DialogTitle className={'flex max-h-[48px] flex-1 items-center justify-end gap-2 px-2'}>
|
||||
<div className='flex flex-1 items-center'>
|
||||
{openPageModalViewId && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size={'icon'}
|
||||
variant='ghost'
|
||||
onClick={() => {
|
||||
onOpenChange(false);
|
||||
}}
|
||||
>
|
||||
<ArrowLeftIcon className='h-5 w-5' />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Go back</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
|
||||
@@ -10,7 +10,6 @@ import { RenderRow, RenderRowType } from '@/components/database/components/grid/
|
||||
import { useGridContext } from '@/components/database/grid/useGridContext';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const MIN_HEIGHT = 36;
|
||||
|
||||
function GridVirtualColumn({
|
||||
data,
|
||||
@@ -87,7 +86,6 @@ function GridVirtualColumn({
|
||||
readOnly && rowType === RenderRowType.CalculateRow ? 'border-t border-border-primary' : ''
|
||||
)}
|
||||
style={{
|
||||
height: rowIndex === 0 ? MIN_HEIGHT : row.size,
|
||||
width: columnData.width,
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user