Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/application/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,7 @@ export interface ViewMetaProps {
extra?: ViewExtra | null;
readOnly?: boolean;
updatePage?: (viewId: string, data: UpdatePagePayload) => Promise<void>;
uploadFile?: (file: File) => Promise<string>;
onEnter?: (text: string) => void;
maxWidth?: number;
}
Expand Down
26 changes: 26 additions & 0 deletions src/components/_shared/LoadingDots.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export default function LoadingDots({
className,
colors = ['#00b5ff', '#e3006d', '#f7931e'],
}: {
className?: string;
colors?: [string, string, string];
}) {
return (
<div className={className}>
<div
style={{
width: `30px`,
aspectRatio: '2',
background: `
radial-gradient(circle closest-side, ${colors[0]} 90%, transparent) 0% 50%,
radial-gradient(circle closest-side, ${colors[1]} 90%, transparent) 50% 50%,
radial-gradient(circle closest-side, ${colors[2]} 90%, transparent) 100% 50%
`,
backgroundSize: 'calc(100%/3) 50%',
backgroundRepeat: 'no-repeat',
animation: 'dots-loading 1s infinite linear',
}}
/>
</div>
);
}
17 changes: 13 additions & 4 deletions src/components/_shared/image-upload/UploadImage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import FileDropzone from '@/components/_shared/file-dropzone/FileDropzone';
import LoadingDots from '@/components/_shared/LoadingDots';
import { notify } from '@/components/_shared/notify';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
Expand All @@ -10,24 +11,28 @@ export function UploadImage({ onDone, uploadAction }: {
uploadAction?: (file: File) => Promise<string>
}) {
const { t } = useTranslation();
const handleFileChange = useCallback(async (files: File[]) => {
const [loading, setLoading] = React.useState(false);
const handleFileChange = useCallback(async(files: File[]) => {
setLoading(true);
const file = files[0];

if (!file) return;
if(!file) return;

try {
const url = await uploadAction?.(file);

if (!url) {
if(!url) {
onDone?.(URL.createObjectURL(file));
return;
}

onDone?.(url);
// eslint-disable-next-line
} catch (e: any) {
} catch(e: any) {
notify.error(e.message);
onDone?.(URL.createObjectURL(file));
} finally {
setLoading(false);
}

}, [onDone, uploadAction]);
Expand All @@ -39,6 +44,10 @@ export function UploadImage({ onDone, uploadAction }: {
onChange={handleFileChange}
accept={ALLOWED_IMAGE_EXTENSIONS.join(',')}
/>
{loading &&
<div className={'absolute bg-bg-body z-10 opacity-90 flex items-center inset-0 justify-center w-full h-full'}>
<LoadingDots />
</div>}
</div>

);
Expand Down
37 changes: 35 additions & 2 deletions src/components/_shared/view-icon/ChangeIconPopover.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { ViewIconType } from '@/application/types';
import { EmojiPicker } from '@/components/_shared/emoji-picker';
import IconPicker from '@/components/_shared/icon-picker/IconPicker';
import { UploadImage } from '@/components/_shared/image-upload';
import { Popover } from '@/components/_shared/popover';
import { TabPanel, ViewTab, ViewTabs } from '@/components/_shared/tabs/ViewTabs';
import { Button } from '@mui/material';
import { PopoverProps } from '@mui/material/Popover';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';

function ChangeIconPopover ({
function ChangeIconPopover({
open,
anchorEl,
onClose,
Expand All @@ -20,16 +21,20 @@ function ChangeIconPopover ({
removeIcon,
anchorPosition,
hideRemove,
uploadEnabled,
onUploadFile,
}: {
open: boolean,
anchorEl?: HTMLElement | null,
anchorPosition?: PopoverProps['anchorPosition'],
onClose: () => void,
defaultType: 'emoji' | 'icon',
defaultType: 'emoji' | 'icon' | 'upload',
emojiEnabled?: boolean,
uploadEnabled?: boolean,
iconEnabled?: boolean,
popoverProps?: Partial<PopoverProps>,
onSelectIcon?: (icon: { ty: ViewIconType, value: string, color?: string, content?: string }) => void,
onUploadFile?: (file: File) => Promise<string>,
removeIcon?: () => void,
hideRemove?: boolean,
}) {
Expand Down Expand Up @@ -80,6 +85,16 @@ function ChangeIconPopover ({
/>
)
}
{
uploadEnabled && (
<ViewTab
className={'flex items-center flex-row justify-center gap-1.5'}
value={'upload'}
label={'Upload'}
data-testid="upload-tab"
/>
)
}

</ViewTabs>
{!hideRemove && <Button
Expand Down Expand Up @@ -126,6 +141,24 @@ function ChangeIconPopover ({
hideRemove
/>
</TabPanel>}
{uploadEnabled && <TabPanel
index={'upload'}
value={value}
>
<div className={'pt-4 relative pb-2'}>
<UploadImage
onDone={(url) => {
onSelectIcon?.({
ty: ViewIconType.URL,
value: url,
});
handleClose();
}}
uploadAction={onUploadFile}
/>
</div>

</TabPanel>}
</Popover>
);
}
Expand Down
13 changes: 7 additions & 6 deletions src/components/app/DatabaseView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@ import React, { Suspense, useCallback, useMemo } from 'react';
import { useSearchParams } from 'react-router-dom';
import ViewMetaPreview from 'src/components/view-meta/ViewMetaPreview';

function DatabaseView ({ viewMeta, ...props }: ViewComponentProps) {
function DatabaseView({ viewMeta, uploadFile, ...props }: ViewComponentProps) {
const [search, setSearch] = useSearchParams();
const outline = useAppOutline();
const iidIndex = viewMeta.viewId;
const view = useMemo(() => {
if (!outline || !iidIndex) return;
if(!outline || !iidIndex) return;
return findView(outline || [], iidIndex);
}, [outline, iidIndex]);

const visibleViewIds = useMemo(() => {
if (!view) return [];
if(!view) return [];
return [view.view_id, ...(view.children?.map(v => v.view_id) || [])];
}, [view]);

Expand Down Expand Up @@ -58,11 +58,11 @@ function DatabaseView ({ viewMeta, ...props }: ViewComponentProps) {
const doc = props.doc;
const database = doc?.getMap(YjsEditorKey.data_section)?.get(YjsEditorKey.database) as YDatabase;
const skeleton = useMemo(() => {
if (rowId) {
if(rowId) {
return <DocumentSkeleton />;
}

switch (viewMeta.layout) {
switch(viewMeta.layout) {
case ViewLayout.Grid:
return <GridSkeleton includeTitle={false} />;
case ViewLayout.Board:
Expand All @@ -74,7 +74,7 @@ function DatabaseView ({ viewMeta, ...props }: ViewComponentProps) {
}
}, [rowId, viewMeta.layout]);

if (!viewId || !doc || !database) return null;
if(!viewId || !doc || !database) return null;

return (
<div
Expand All @@ -87,6 +87,7 @@ function DatabaseView ({ viewMeta, ...props }: ViewComponentProps) {
{...viewMeta}
readOnly={props.readOnly}
updatePage={props.updatePage}
uploadFile={uploadFile}
/>}

<Suspense fallback={skeleton}>
Expand Down
9 changes: 8 additions & 1 deletion src/components/app/outline/ViewItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function ViewItem({ view, width, level = 0, renderExtra, expandIds, toggleExpand
const selectedViewId = useAppViewId();
const viewId = view.view_id;
const selected = selectedViewId === viewId;
const { updatePage } = useAppHandlers();
const { updatePage, uploadFile } = useAppHandlers();

const isExpanded = expandIds.includes(viewId);
const [hovered, setHovered] = React.useState<boolean>(false);
Expand All @@ -57,6 +57,11 @@ function ViewItem({ view, width, level = 0, renderExtra, expandIds, toggleExpand
/></span>;
}, [isExpanded, level, toggleExpand, viewId]);

const onUploadFile = useCallback(async(file: File) => {
if(!uploadFile) return Promise.reject();
return uploadFile(viewId, file);
}, [uploadFile, viewId]);

const renderItem = useMemo(() => {
if(!view) return null;

Expand Down Expand Up @@ -165,6 +170,8 @@ function ViewItem({ view, width, level = 0, renderExtra, expandIds, toggleExpand
onClose={() => {
setIconPopoverAnchorEl(null);
}}
uploadEnabled
onUploadFile={onUploadFile}
popoverProps={popoverProps}
onSelectIcon={(icon) => {
if(icon.ty === ViewIconType.Icon) {
Expand Down
35 changes: 26 additions & 9 deletions src/components/app/view-actions/MorePageActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,35 @@ function MorePageActions({ view, onClose }: {

const {
updatePage,
uploadFile,
} = useAppHandlers();
const { t } = useTranslation();

const handleChangeIcon = useCallback(async (icon: { ty: ViewIconType, value: string }) => {
const viewId = view.view_id;

const onUploadFile = useCallback(async(file: File) => {
if(!uploadFile) return Promise.reject();
return uploadFile(viewId, file);
}, [uploadFile, viewId]);

const handleChangeIcon = useCallback(async(icon: { ty: ViewIconType, value: string, color?: string }) => {
try {
await updatePage?.(view.view_id, {
icon: icon,
icon: icon.ty === ViewIconType.Icon ? {
ty: ViewIconType.Icon,
value: JSON.stringify({
color: icon.color,
groupName: icon.value.split('/')[0],
iconName: icon.value.split('/')[1],
}),
} : icon,
name: view.name,
extra: view.extra || {},
});
setIconPopoverAnchorEl(null);
onClose?.();
// eslint-disable-next-line
} catch (e: any) {
} catch(e: any) {
notify.error(e);
}
}, [onClose, updatePage, view.extra, view.name, view.view_id]);
Expand All @@ -63,14 +78,14 @@ function MorePageActions({ view, onClose }: {
const actions = useMemo(() => {
return [{
label: t('button.rename'),
icon: <EditIcon/>,
icon: <EditIcon />,
onClick: () => {
setRenameModalOpen(true);
onClose?.();
},
}, {
label: t('disclosureAction.changeIcon'),
icon: <ChangeIcon/>,
icon: <ChangeIcon />,
onClick: (e: React.MouseEvent<HTMLButtonElement>) => {
setIconPopoverAnchorEl(e.currentTarget);
},
Expand All @@ -96,32 +111,34 @@ function MorePageActions({ view, onClose }: {
viewId={view.view_id}
movePopoverOrigins={popoverProps}
/>
<Divider className={'w-full'}/>
<Divider className={'w-full'} />
<Button
size={'small'}

className={'px-3 py-1 justify-start'}
color={'inherit'}
onClick={() => {
if (!currentWorkspaceId) return;
if(!currentWorkspaceId) return;
onClose?.();
window.open(`/app/${currentWorkspaceId}/${view.view_id}`, '_blank');

}}
startIcon={<OpenInBrowserIcon className={'w-4 h-4'}/>}
startIcon={<OpenInBrowserIcon className={'w-4 h-4'} />}
>
{t('disclosureAction.openNewTab')}
</Button>
<Suspense fallback={null}>
<ChangeIconPopover
iconEnabled={false}
iconEnabled
defaultType={'emoji'}
open={openIconPopover}
anchorEl={iconPopoverAnchorEl}
onClose={() => {
onClose?.();
setIconPopoverAnchorEl(null);
}}
onUploadFile={onUploadFile}
uploadEnabled
popoverProps={popoverProps}
onSelectIcon={handleChangeIcon}
removeIcon={handleRemoveIcon}
Expand Down
2 changes: 2 additions & 0 deletions src/components/document/Document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const Document = (props: DocumentProps) => {
updatePage,
onRendered,
onEditorConnected,
uploadFile,
} = props;
const blockId = search.get('blockId') || undefined;

Expand Down Expand Up @@ -76,6 +77,7 @@ export const Document = (props: DocumentProps) => {
updatePage={updatePage}
onEnter={readOnly ? undefined : handleEnter}
maxWidth={988}
uploadFile={uploadFile}
/>
<Suspense fallback={<EditorSkeleton />}>
<div className={'flex justify-center w-full'}>
Expand Down
4 changes: 4 additions & 0 deletions src/components/view-meta/AddIconCover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ function AddIconCover({
setIconAnchorEl,
maxWidth,
visible,
onUploadFile,
}: {
visible: boolean;
hasIcon: boolean;
Expand All @@ -24,6 +25,7 @@ function AddIconCover({
iconAnchorEl: HTMLElement | null;
setIconAnchorEl: (el: HTMLElement | null) => void;
maxWidth?: number;
onUploadFile: (file: File) => Promise<string>;
}) {
const { t } = useTranslation();

Expand Down Expand Up @@ -80,6 +82,8 @@ function AddIconCover({
setIconAnchorEl(null);
onUpdateIcon?.({ ty: ViewIconType.Emoji, value: '' });
}}
uploadEnabled
onUploadFile={onUploadFile}
/>
</>

Expand Down
Loading
Loading