fix: display duplicate entry (#5853)

* fix: some issue

* fix: add colors

* fix: do not allow people who are not appflowy
This commit is contained in:
Kilu.He 2024-09-03 11:50:28 +08:00 committed by GitHub
parent dda798752f
commit 4b0368d552
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 160 additions and 68 deletions

View File

@ -59,17 +59,6 @@ function AsTemplate ({
await loadTemplate();
}
notify.info({
type: 'success',
title: t('template.uploadSuccess'),
message: t('template.uploadSuccessDescription'),
okText: t('template.viewTemplate'),
onOk: () => {
const url = import.meta.env.AF_BASE_URL?.includes('test') ? 'https://test.appflowy.io' : 'https://appflowy.io';
window.open(`${url}/templates/${selectedCategoryIds[0]}/${viewId}`, '_blank');
},
});
handleBack();
} catch (error) {
// eslint-disable-next-line
@ -77,8 +66,7 @@ function AsTemplate ({
notify.error(error.toString());
}
}, [service, selectedCreatorId, selectedCategoryIds, viewId, isNewTemplate, isFeatured, viewUrl, template, t, handleBack, loadTemplate]);
}, [service, selectedCreatorId, selectedCategoryIds, isNewTemplate, isFeatured, viewId, viewUrl, template, loadTemplate, handleBack]);
const submitRef = React.useRef<HTMLInputElement>(null);
useEffect(() => {

View File

@ -8,7 +8,58 @@ import { ReactComponent as CloudUploadIcon } from '@/assets/cloud_add.svg';
import { useTranslation } from 'react-i18next';
function CreatorAvatar ({ src, name, enableUpload, onChange, size }: {
const colorArray = [
'#5287D8',
'#6E9DE3',
'#8BB3ED',
'#A7C9F7',
'#979EB6',
'#A2A8BF',
'#ACB2C8',
'#C1C7DA',
'#E8AF53',
'#E6C25A',
'#E6D26F',
'#E6E288',
'#589599',
'#68AD8E',
'#79C47F',
'#8CDB6A',
'#AA94DC',
'#C49EEB',
'#BAACEE',
'#D5C4FB',
'#F597D2',
'#FCB2E3',
'#FDC5E8',
'#F8D2E1',
'#D1D269',
'#C7C98D',
'#CED09B',
'#DAD9B6',
'#DDD2C6',
'#DDD6C7',
'#EADED3',
'#FED5C4',
'#72A7D8',
'#8FCAE3',
'#64B3DA',
'#52B2D4',
'#90A4FF',
'#A8BEF4',
'#AEBDFF',
'#C2CDFF',
'#86C1B7',
'#A6D8D0',
'#A7D7A8',
'#C8E4C9',
'#FF9494',
'#FFBDBD',
'#DCA8A8',
'#E3C4C4',
];
function CreatorAvatar({ src, name, enableUpload, onChange, size }: {
src: string;
name: string;
enableUpload?: boolean;
@ -20,7 +71,7 @@ function CreatorAvatar ({ src, name, enableUpload, onChange, size }: {
const [tab, setTab] = React.useState(0);
const avatarProps = useMemo(() => {
return stringAvatar(name || '');
return stringAvatar(name || '', colorArray);
}, [name]);
const [openModal, setOpenModal] = React.useState(false);
@ -46,15 +97,21 @@ function CreatorAvatar ({ src, name, enableUpload, onChange, size }: {
e.stopPropagation();
}}
>
<Avatar src={src} className={'w-full h-full object-cover p-2'} {...avatarProps} sx={{
...avatarProps?.sx,
bgcolor: imageUrl ? 'var(--bg-body)' : avatarProps?.sx.bgcolor,
width: size || undefined,
height: size || undefined,
}}
<Avatar
src={src}
className={'w-full h-full object-cover p-2'} {...avatarProps}
sx={{
...avatarProps?.sx,
bgcolor: imageUrl ? 'var(--bg-body)' : avatarProps?.sx.bgcolor,
width: size || undefined,
height: size || undefined,
}}
/>
{enableUpload && showUpload && (
<Tooltip title={t('template.creator.uploadAvatar')} arrow>
<Tooltip
title={t('template.creator.uploadAvatar')}
arrow
>
<Button
component="label"
role={undefined}
@ -79,24 +136,38 @@ function CreatorAvatar ({ src, name, enableUpload, onChange, size }: {
disabled: !imageUrl,
}}
onOk={() => {
if (!imageUrl) return;
if(!imageUrl) return;
onChange?.(imageUrl);
setOpenModal(false);
}} title={t('template.uploadAvatar')}
}}
title={t('template.uploadAvatar')}
onCancel={() => setOpenModal(false)}
onClose={() => setOpenModal(false)} open={openModal}
onClose={() => setOpenModal(false)}
open={openModal}
>
<div className={'min-w-[400px] flex flex-col gap-4'}>
<ViewTabs value={tab} onChange={(_, newValue) => {
setTab(newValue);
setImageUrl(src);
}}
<ViewTabs
value={tab}
onChange={(_, newValue) => {
setTab(newValue);
setImageUrl(src);
}}
>
<ViewTab value={0} label={t('document.imageBlock.embedLink.label')} />
<ViewTab value={1} label={t('button.upload')} />
<ViewTab
value={0}
label={t('document.imageBlock.embedLink.label')}
/>
<ViewTab
value={1}
label={t('button.upload')}
/>
</ViewTabs>
<TabPanel className={'w-full'} value={tab} index={0}>
<TabPanel
className={'w-full'}
value={tab}
index={0}
>
<OutlinedInput
size={'small'}
value={imageUrl}
@ -109,7 +180,11 @@ function CreatorAvatar ({ src, name, enableUpload, onChange, size }: {
placeholder={t('document.imageBlock.embedLink.placeholder')}
/>
</TabPanel>
<TabPanel className={'w-full flex flex-col gap-2'} value={tab} index={1}>
<TabPanel
className={'w-full flex flex-col gap-2'}
value={tab}
index={1}
>
<UploadAvatar onChange={setImageUrl} />
</TabPanel>

View File

@ -2,9 +2,7 @@ import { TemplateSummary, TemplateCategory } from '@/application/template.type';
import CreatorAvatar from '@/components/as-template/creator/CreatorAvatar';
import React, { useMemo } from 'react';
const url = import.meta.env.AF_BASE_URL?.includes('test') ? 'https://test.appflowy.io' : 'https://appflowy.io';
function TemplateItem ({ template, category }: { template: TemplateSummary; category: TemplateCategory }) {
function TemplateItem({ template, category }: { template: TemplateSummary; category: TemplateCategory }) {
const iframeUrl = useMemo(() => {
const url = new URL(template.view_url);
@ -17,20 +15,26 @@ function TemplateItem ({ template, category }: { template: TemplateSummary; cate
return (
<>
<a
href={`${url}/templates/${category.id}/${template.view_id}`}
<div
className={'relative rounded-[16px] pt-4 px-4 h-[230px] w-full overflow-hidden'}
target={'_blank'}
style={{
backgroundColor: category?.bg_color,
}}
>
<iframe loading={'lazy'} className={'w-full h-full'} src={iframeUrl} />
</a>
<iframe
loading={'lazy'}
className={'w-full h-full'}
src={iframeUrl}
/>
</div>
<div className={'template-info'}>
<div className={'template-creator'}>
<div className={'avatar'}>
<CreatorAvatar size={40} src={template.creator.avatar_url} name={template.creator.name} />
<CreatorAvatar
size={40}
src={template.creator.avatar_url}
name={template.creator.name}
/>
</div>
<div className={'right-info'}>
<div className={'template-name'}>{template.name}</div>

View File

@ -5,7 +5,7 @@ import CardField from '@/components/database/components/field/CardField';
import React from 'react';
import { EventWrapperProps } from 'react-big-calendar';
export function Event({ event }: EventWrapperProps<CalendarEvent>) {
export function Event ({ event }: EventWrapperProps<CalendarEvent>) {
const { id } = event;
const [rowId] = id.split(':');
const showFields = useFieldsSelector();
@ -15,7 +15,7 @@ export function Event({ event }: EventWrapperProps<CalendarEvent>) {
return (
<div className={'px-1 py-0.5'}>
<RichTooltip content={<EventPaper rowId={rowId} />} open={open} placement='right' onClose={() => setOpen(false)}>
<RichTooltip content={<EventPaper rowId={rowId} />} open={open} placement="right" onClose={() => setOpen(false)}>
<div
onClick={() => {
if (window.innerWidth < 768) {
@ -25,7 +25,7 @@ export function Event({ event }: EventWrapperProps<CalendarEvent>) {
}
}}
className={
'flex min-h-[24px] cursor-pointer flex-col gap-2 rounded-md border border-line-border bg-bg-body p-2 text-xs text-xs shadow-sm hover:bg-fill-list-active hover:shadow'
'flex min-h-[24px] cursor-pointer flex-col gap-2 rounded-md border border-line-divider bg-bg-body p-2 text-xs shadow-sm hover:bg-fill-list-active hover:shadow'
}
>
{showFields.map((field) => {

View File

@ -1,4 +1,5 @@
import { usePublishContext } from '@/application/publish';
import { useCurrentUser } from '@/components/app/app.hooks';
import { openOrDownload } from '@/components/publish/header/utils';
import { createHotkey, HOT_KEY_NAME } from '@/utils/hotkeys';
import { Divider, IconButton, Tooltip } from '@mui/material';
@ -10,11 +11,11 @@ import Breadcrumb from './Breadcrumb';
import { ReactComponent as Logo } from '@/assets/logo.svg';
import MoreActions from './MoreActions';
import { ReactComponent as SideOutlined } from '@/assets/side_outlined.svg';
// import { Duplicate } from './duplicate';
import { Duplicate } from './duplicate';
export const HEADER_HEIGHT = 48;
export function PublishViewHeader ({ onOpenDrawer, openDrawer }: { onOpenDrawer: () => void; openDrawer: boolean }) {
export function PublishViewHeader({ onOpenDrawer, openDrawer }: { onOpenDrawer: () => void; openDrawer: boolean }) {
const { t } = useTranslation();
const viewMeta = usePublishContext()?.viewMeta;
const crumbs = useMemo(() => {
@ -27,7 +28,7 @@ export function PublishViewHeader ({ onOpenDrawer, openDrawer }: { onOpenDrawer:
const extra = ancestor?.extra ? JSON.parse(ancestor.extra) : {};
icon = extra.icon?.value || ancestor.icon?.value;
} catch (e) {
} catch(e) {
// ignore
}
@ -49,7 +50,7 @@ export function PublishViewHeader ({ onOpenDrawer, openDrawer }: { onOpenDrawer:
}, []);
const onKeyDown = useCallback((e: KeyboardEvent) => {
switch (true) {
switch(true) {
case createHotkey(HOT_KEY_NAME.TOGGLE_SIDEBAR)(e):
e.preventDefault();
// setOpen((prev) => !prev);
@ -68,13 +69,17 @@ export function PublishViewHeader ({ onOpenDrawer, openDrawer }: { onOpenDrawer:
const handleOpenPopover = useCallback(() => {
debounceClosePopover.cancel();
if (openDrawer) {
if(openDrawer) {
return;
}
setOpenPopover(true);
}, [openDrawer, debounceClosePopover]);
const currentUser = useCurrentUser();
const isAppFlowyUser = currentUser?.email?.endsWith('@appflowy.io');
return (
<div
style={{
@ -113,8 +118,12 @@ export function PublishViewHeader ({ onOpenDrawer, openDrawer }: { onOpenDrawer:
<div className={'flex items-center gap-2'}>
<MoreActions />
{/*<Duplicate />*/}
<Divider orientation={'vertical'} className={'mx-2'} flexItem />
{isAppFlowyUser && <Duplicate />}
<Divider
orientation={'vertical'}
className={'mx-2'}
flexItem
/>
<Tooltip title={t('publish.downloadApp')}>
<button onClick={openOrDownload}>
<Logo className={'h-6 w-6'} />

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();
@ -19,11 +19,23 @@ export function Duplicate () {
return (
<>
<Button onClick={handleClick} size={'small'} variant={'outlined'} color={'inherit'}>
<Button
onClick={handleClick}
size={'small'}
variant={'outlined'}
color={'inherit'}
>
{t('publish.saveThisPage')}
</Button>
<LoginModal redirectTo={url} open={loginOpen} onClose={handleLoginClose} />
{duplicateOpen && <DuplicateModal open={duplicateOpen} onClose={handleDuplicateClose} />}
<LoginModal
redirectTo={url}
open={loginOpen}
onClose={handleLoginClose}
/>
{duplicateOpen && <DuplicateModal
open={duplicateOpen}
onClose={handleDuplicateClose}
/>}
</>
);
}

View File

@ -44,12 +44,12 @@ export const gradientMap = {
// Convert ARGB to RGBA
// Flutter uses ARGB, but CSS uses RGBA
function argbToRgba (color: string): string {
function argbToRgba(color: string): string {
const hex = color.replace(/^#|0x/, '');
const hasAlpha = hex.length === 8;
if (!hasAlpha) {
if(!hasAlpha) {
return color.replace('0x', '#');
}
@ -61,30 +61,34 @@ function argbToRgba (color: string): string {
return `rgba(${r}, ${g}, ${b}, ${a})`;
}
export function renderColor (color: string) {
if (colorMap[color as ColorEnum]) {
export function renderColor(color: string) {
if(colorMap[color as ColorEnum]) {
return colorMap[color as ColorEnum];
}
if (gradientMap[color as GradientEnum]) {
if(gradientMap[color as GradientEnum]) {
return gradientMap[color as GradientEnum];
}
return argbToRgba(color);
}
export function stringToColor (string: string) {
export function stringToColor(string: string, colorArray?: string[]) {
let hash = 0;
let i;
/* eslint-disable no-bitwise */
for (i = 0; i < string.length; i += 1) {
for(i = 0; i < string.length; i += 1) {
hash = string.charCodeAt(i) + ((hash << 5) - hash);
}
if(colorArray) {
return colorArray[string.slice(0, 1).charCodeAt(0) % colorArray.length];
}
let color = '#';
for (i = 0; i < 3; i += 1) {
for(i = 0; i < 3; i += 1) {
const value = (hash >> (i * 8)) & 0xff;
color += `00${value.toString(16)}`.slice(-2);
@ -94,14 +98,14 @@ export function stringToColor (string: string) {
return color;
}
export function stringAvatar (name: string) {
if (!name) {
export function stringAvatar(name: string, colorArray?: string[]) {
if(!name) {
return null;
}
return {
sx: {
bgcolor: stringToColor(name),
bgcolor: stringToColor(name, colorArray),
},
children: `${name.split('')[0]}`,
};

View File

@ -2358,7 +2358,7 @@
"mustSelectPrimaryDatabase": "The primary view must be selected",
"noDatabaseSelected": "No database selected, please select at least one database.",
"unableToDeselectPrimaryDatabase": "Unable to deselect primary database",
"saveThisPage": "Save this page",
"saveThisPage": "Start with this template",
"duplicateTitle": "Where would you like to add",
"selectWorkspace": "Select a workspace",
"addTo": "Add to",