mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-09-25 16:30:03 +00:00
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:
parent
dda798752f
commit
4b0368d552
@ -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(() => {
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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) => {
|
||||
|
@ -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'} />
|
||||
|
@ -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}
|
||||
/>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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]}`,
|
||||
};
|
||||
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user