fix: some duplicate issues (#6171)

This commit is contained in:
Kilu.He 2024-09-03 12:49:56 +08:00 committed by GitHub
parent 4b0368d552
commit 94c033bba8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 141 additions and 84 deletions

View file

@ -212,7 +212,6 @@ export class AFClientService implements AFService {
async getCurrentUser () {
const data = await APIService.getCurrentUser();
await APIService.getWorkspaces();
return data;
}

View file

@ -1,3 +1,4 @@
import { notify } from '@/components/_shared/notify';
import { loadEmojiData } from '@/utils/emoji';
import { EmojiMartData } from '@emoji-mart/data';
import { PopoverProps } from '@mui/material/Popover';
@ -16,8 +17,10 @@ interface Emoji {
native: string;
}
export function useLoadEmojiData({ onEmojiSelect }: { onEmojiSelect: (emoji: string) => void }) {
export function useLoadEmojiData ({ onEmojiSelect }: { onEmojiSelect: (emoji: string) => void }) {
const [searchValue, setSearchValue] = useState('');
const [loading, setLoading] = useState(false);
const [isEmpty, setIsEmpty] = useState(false);
const [emojiCategories, setEmojiCategories] = useState<EmojiCategory[]>([]);
const [skin, setSkin] = useState<number>(() => {
return Number(localStorage.getItem('emoji-mart.skin')) || 0;
@ -30,40 +33,52 @@ export function useLoadEmojiData({ onEmojiSelect }: { onEmojiSelect: (emoji: str
const searchEmojiData = useCallback(
async (searchVal?: string) => {
const emojiData = await loadEmojiData();
setLoading(true);
setIsEmpty(false);
try {
const emojiData = await loadEmojiData();
const { emojis, categories } = emojiData as EmojiMartData;
const { emojis, categories } = emojiData as EmojiMartData;
const filteredCategories = categories
.map((category) => {
const { id, emojis: categoryEmojis } = category;
const filteredCategories = categories
.map((category) => {
const { id, emojis: categoryEmojis } = category;
return {
id,
emojis: categoryEmojis
.filter((emojiId) => {
const emoji = emojis[emojiId];
return {
id,
emojis: categoryEmojis
.filter((emojiId) => {
const emoji = emojis[emojiId];
if (!searchVal) return true;
return filterSearchValue(emoji, searchVal);
})
.map((emojiId) => {
const emoji = emojis[emojiId];
const { name, skins } = emoji;
if (!searchVal) return true;
return filterSearchValue(emoji, searchVal);
})
.map((emojiId) => {
const emoji = emojis[emojiId];
const { name, skins } = emoji;
return {
id: emojiId,
name,
native: skins[skin] ? skins[skin].native : skins[0].native,
};
}),
};
})
.filter((category) => category.emojis.length > 0);
return {
id: emojiId,
name,
native: skins[skin] ? skins[skin].native : skins[0].native,
};
}),
};
})
.filter((category) => category.emojis.length > 0);
if (filteredCategories.length === 0) {
setIsEmpty(true);
}
setEmojiCategories(filteredCategories);
setEmojiCategories(filteredCategories);
} catch (_e) {
notify.error('Failed to load emoji data');
setIsEmpty(true);
}
setLoading(false);
},
[skin]
[skin],
);
useEffect(() => {
@ -80,7 +95,7 @@ export function useLoadEmojiData({ onEmojiSelect }: { onEmojiSelect: (emoji: str
async (native: string) => {
onEmojiSelect(native);
},
[onEmojiSelect]
[onEmojiSelect],
);
return {
@ -90,10 +105,12 @@ export function useLoadEmojiData({ onEmojiSelect }: { onEmojiSelect: (emoji: str
onSelect,
onSkinChange,
skin,
loading,
isEmpty,
};
}
export function useSelectSkinPopoverProps(): PopoverProps & {
export function useSelectSkinPopoverProps (): PopoverProps & {
onOpen: (event: React.MouseEvent<HTMLButtonElement>) => void;
onClose: () => void;
} {
@ -118,12 +135,12 @@ export function useSelectSkinPopoverProps(): PopoverProps & {
};
}
function filterSearchValue(
function filterSearchValue (
emoji: {
name: string;
keywords?: string[];
},
searchValue: string
searchValue: string,
) {
const { name, keywords } = emoji;
const searchValueLowerCase = searchValue.toLowerCase();
@ -134,7 +151,7 @@ function filterSearchValue(
);
}
export function getRowsWithCategories(emojiCategories: EmojiCategory[], rowSize: number) {
export function getRowsWithCategories (emojiCategories: EmojiCategory[], rowSize: number) {
const rows: {
id: string;
type: 'category' | 'emojis';
@ -157,4 +174,4 @@ export function getRowsWithCategories(emojiCategories: EmojiCategory[], rowSize:
});
});
return rows;
}
}

View file

@ -1,8 +1,10 @@
import CircularProgress from '@mui/material/CircularProgress';
import React from 'react';
import { useLoadEmojiData } from './EmojiPicker.hooks';
import EmojiPickerHeader from './EmojiPickerHeader';
import EmojiPickerCategories from './EmojiPickerCategories';
import emptyImageSrc from '@/assets/images/empty.png';
interface Props {
onEmojiSelect: (emoji: string) => void;
@ -11,8 +13,9 @@ interface Props {
hideRemove?: boolean;
}
export function EmojiPicker({ defaultEmoji, onEscape, ...props }: Props) {
const { skin, onSkinChange, emojiCategories, setSearchValue, searchValue, onSelect } = useLoadEmojiData(props);
export function EmojiPicker ({ defaultEmoji, onEscape, ...props }: Props) {
const { skin, onSkinChange, emojiCategories, setSearchValue, searchValue, onSelect, loading, isEmpty } =
useLoadEmojiData(props);
return (
<div tabIndex={0} className={'emoji-picker flex h-[360px] max-h-[70vh] flex-col p-4 pt-2'}>
@ -24,14 +27,22 @@ export function EmojiPicker({ defaultEmoji, onEscape, ...props }: Props) {
searchValue={searchValue}
onSearchChange={setSearchValue}
/>
<EmojiPickerCategories
defaultEmoji={defaultEmoji}
onEscape={onEscape}
onEmojiSelect={onSelect}
emojiCategories={emojiCategories}
/>
{loading ? (
<div className={'flex h-full items-center justify-center'}>
<CircularProgress />
</div>
) : isEmpty ? (
<img src={emptyImageSrc} alt={'No data found'} className={'mx-auto h-[200px]'} />
) : (
<EmojiPickerCategories
defaultEmoji={defaultEmoji}
onEscape={onEscape}
onEmojiSelect={onSelect}
emojiCategories={emojiCategories}
/>
)}
</div>
);
}
export default EmojiPicker;
export default EmojiPicker;

View file

@ -13,6 +13,8 @@ import { useTranslation } from 'react-i18next';
import { ReactComponent as CloseIcon } from '@/assets/close.svg';
import { ReactComponent as DeleteIcon } from '@/assets/trash.svg';
import './template.scss';
import { slugify } from '@/components/as-template/utils';
import { ReactComponent as WebsiteIcon } from '@/assets/website.svg';
function AsTemplate ({
viewName,
@ -56,9 +58,10 @@ function AsTemplate ({
await service?.updateTemplate(template.view_id, formData);
} else {
await service?.createTemplate(formData);
await loadTemplate();
}
await loadTemplate();
handleBack();
} catch (error) {
// eslint-disable-next-line
@ -108,6 +111,15 @@ function AsTemplate ({
>
{t('button.cancel')}
</Button>
{template && <Button
startIcon={<WebsiteIcon />}
variant={'text'}
onClick={() => {
const url = import.meta.env.AF_BASE_URL?.includes('test') ? 'https://test.appflowy.io' : 'https://appflowy.io';
window.open(`${url}/templates/${slugify(template.categories[0].name)}/${template.view_id}`);
}} color={'primary'}
>{t('template.viewTemplate')}</Button>}
<div className={'flex items-center gap-2'}>
{template && <Button
startIcon={<DeleteIcon />}

View file

@ -0,0 +1,9 @@
export function slugify (text: string) {
return text
.toString() // ensure the text is a string
.toLowerCase() // make the text lowercase
.trim() // remove leading and trailing whitespaces
.replace(/\s+/g, '-') // replace all whitespaces with '-'
.replace(/[^\w-]+/g, '') // remove all non-word characters
.replace(/--+/g, '-'); // replace multiple '-' with single '-'
}

View file

@ -6,7 +6,7 @@ import { useSearchParams } from 'react-router-dom';
import { useDuplicate } from '@/components/publish/header/duplicate/useDuplicate';
import DuplicateModal from '@/components/publish/header/duplicate/DuplicateModal';
export function Duplicate() {
export function Duplicate () {
const { t } = useTranslation();
const { loginOpen, duplicateOpen, handleDuplicateClose, handleLoginClose, url } = useDuplicate();
const [, setSearch] = useSearchParams();
@ -32,10 +32,10 @@ export function Duplicate() {
open={loginOpen}
onClose={handleLoginClose}
/>
{duplicateOpen && <DuplicateModal
<DuplicateModal
open={duplicateOpen}
onClose={handleDuplicateClose}
/>}
/>
</>
);
}

View file

@ -10,7 +10,7 @@ import { PublishContext } from '@/application/publish';
import { CollabType, ViewLayout } from '@/application/collab.type';
import { notify } from '@/components/_shared/notify';
function getCollabTypeFromViewLayout(layout: ViewLayout) {
function getCollabTypeFromViewLayout (layout: ViewLayout) {
switch (layout) {
case ViewLayout.Document:
return CollabType.Document;
@ -23,7 +23,7 @@ function getCollabTypeFromViewLayout(layout: ViewLayout) {
}
}
function DuplicateModal({ open, onClose }: { open: boolean; onClose: () => void }) {
function DuplicateModal ({ open, onClose }: { open: boolean; onClose: () => void }) {
const { t } = useTranslation();
const service = useContext(AFConfigContext)?.service;
const viewMeta = useContext(PublishContext)?.viewMeta;
@ -40,6 +40,8 @@ function DuplicateModal({ open, onClose }: { open: boolean; onClose: () => void
selectedSpaceId,
workspaceLoading,
spaceLoading,
loadWorkspaces,
loadSpaces,
} = useLoadWorkspaces();
useEffect(() => {
@ -49,6 +51,18 @@ function DuplicateModal({ open, onClose }: { open: boolean; onClose: () => void
}
}, [open, setSelectedSpaceId, setSelectedWorkspaceId, workspaceList]);
useEffect(() => {
if (open) {
void loadWorkspaces();
}
}, [loadWorkspaces, open]);
useEffect(() => {
if (selectedWorkspaceId) {
void loadSpaces(selectedWorkspaceId);
}
}, [loadSpaces, selectedWorkspaceId]);
const handleDuplicate = useCallback(async () => {
if (!viewId) return;
const collabType = getCollabTypeFromViewLayout(layout);
@ -126,4 +140,4 @@ function DuplicateModal({ open, onClose }: { open: boolean; onClose: () => void
);
}
export default DuplicateModal;
export default DuplicateModal;

View file

@ -4,7 +4,7 @@ import { useSearchParams } from 'react-router-dom';
import { SpaceView, Workspace } from '@/application/types';
import { notify } from '@/components/_shared/notify';
export function useDuplicate() {
export function useDuplicate () {
const isAuthenticated = useContext(AFConfigContext)?.isAuthenticated || false;
const [search, setSearch] = useSearchParams();
const [loginOpen, setLoginOpen] = React.useState(false);
@ -46,11 +46,11 @@ export function useDuplicate() {
};
}
export function useLoadWorkspaces() {
export function useLoadWorkspaces () {
const [spaceLoading, setSpaceLoading] = useState<boolean>(false);
const [workspaceLoading, setWorkspaceLoading] = useState<boolean>(false);
const [selectedWorkspaceId, setSelectedWorkspaceId] = useState<string>('1');
const [selectedSpaceId, setSelectedSpaceId] = useState<string>('1');
const [selectedWorkspaceId, setSelectedWorkspaceId] = useState<string>('');
const [selectedSpaceId, setSelectedSpaceId] = useState<string>('');
const [workspaceList, setWorkspaceList] = useState<Workspace[]>([]);
@ -58,36 +58,28 @@ export function useLoadWorkspaces() {
const service = useContext(AFConfigContext)?.service;
useEffect(() => {
void (async () => {
setWorkspaceLoading(true);
try {
const workspaces = await service?.getWorkspaces();
const loadWorkspaces = useCallback(async () => {
setWorkspaceLoading(true);
try {
const workspaces = await service?.getWorkspaces();
if (workspaces) {
setWorkspaceList(workspaces);
setSelectedWorkspaceId(workspaces[0].id);
} else {
setWorkspaceList([]);
setSelectedWorkspaceId('');
}
} catch (e) {
notify.error('Failed to load workspaces');
} finally {
setWorkspaceLoading(false);
if (workspaces) {
setWorkspaceList(workspaces);
setSelectedWorkspaceId(workspaces[0].id);
} else {
setWorkspaceList([]);
setSelectedWorkspaceId('');
}
})();
} catch (e) {
notify.error('Failed to load workspaces');
} finally {
setWorkspaceLoading(false);
}
}, [service]);
useEffect(() => {
if (workspaceList.length === 0 || !selectedWorkspaceId || workspaceLoading) {
setSpaceList([]);
setSelectedSpaceId('');
return;
}
setSpaceLoading(true);
void (async () => {
const loadSpaces = useCallback(
async (selectedWorkspaceId: string) => {
setSpaceLoading(true);
try {
const folder = await service?.getWorkspaceFolder(selectedWorkspaceId);
@ -115,8 +107,9 @@ export function useLoadWorkspaces() {
setSelectedSpaceId('');
setSpaceLoading(false);
}
})();
}, [selectedWorkspaceId, service, workspaceList.length, workspaceLoading]);
},
[service],
);
return {
workspaceList,
@ -127,5 +120,7 @@ export function useLoadWorkspaces() {
setSelectedSpaceId,
workspaceLoading,
spaceLoading,
loadWorkspaces,
loadSpaces,
};
}
}