mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: Edit Row changes (#2564)
* chore: checklist value and popup * chore: properties side panel * chore: reorganize checklist field * chore: delete property promt * chore: delete property reorganize * fix: dnd bug of checklist field * fix: whitespace on empty fields * chore: new checklist item * fix: duplicate view * fix: named checklist bars * chore: checklist padding * fix: onclick * chore: change to nullish coalescing operator * chore: remove empty string from use translation * fix: add missing translations * chore: refactor select option and checklist field editors
This commit is contained in:
parent
9a213fa562
commit
6935653e15
@ -5,7 +5,7 @@ export const Button = ({
|
|||||||
children,
|
children,
|
||||||
onClick,
|
onClick,
|
||||||
}: {
|
}: {
|
||||||
size?: 'primary' | 'medium' | 'small' | 'box-small-transparent';
|
size?: 'primary' | 'medium' | 'small' | 'box-small-transparent' | 'medium-transparent';
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
onClick?: MouseEventHandler<HTMLButtonElement>;
|
onClick?: MouseEventHandler<HTMLButtonElement>;
|
||||||
}) => {
|
}) => {
|
||||||
@ -21,6 +21,11 @@ export const Button = ({
|
|||||||
case 'small':
|
case 'small':
|
||||||
setCls('w-[68px] h-[32px] flex items-center justify-center rounded-lg bg-main-accent text-white text-xs');
|
setCls('w-[68px] h-[32px] flex items-center justify-center rounded-lg bg-main-accent text-white text-xs');
|
||||||
break;
|
break;
|
||||||
|
case 'medium-transparent':
|
||||||
|
setCls(
|
||||||
|
'w-[170px] h-[48px] flex items-center justify-center rounded-lg border border-main-accent text-main-accent transition-colors duration-300 hover:bg-main-hovered hover:text-white'
|
||||||
|
);
|
||||||
|
break;
|
||||||
case 'box-small-transparent':
|
case 'box-small-transparent':
|
||||||
setCls('text-black hover:text-main-accent w-[24px] h-[24px]');
|
setCls('text-black hover:text-main-accent w-[24px] h-[24px]');
|
||||||
break;
|
break;
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
export const CheckListProgress = ({ completed, max }: { completed: number; max: number }) => {
|
||||||
|
return (
|
||||||
|
<div className={'flex w-full items-center gap-4 py-1'}>
|
||||||
|
{max > 0 && (
|
||||||
|
<>
|
||||||
|
<div className={'flex flex-1 gap-1'}>
|
||||||
|
{completed > 0 && filledCheckListBars({ amount: completed })}
|
||||||
|
{max - completed > 0 && emptyCheckListBars({ amount: max - completed })}
|
||||||
|
</div>
|
||||||
|
<div className={'text-xs text-shade-4'}>{((100 * completed) / max).toFixed(0)}%</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filledCheckListBars = ({ amount }: { amount: number }) => {
|
||||||
|
return Array(amount)
|
||||||
|
.fill(0)
|
||||||
|
.map((item, index) => <div key={index} className={'h-[4px] flex-1 flex-shrink-0 rounded bg-main-accent'}></div>);
|
||||||
|
};
|
||||||
|
|
||||||
|
const emptyCheckListBars = ({ amount }: { amount: number }) => {
|
||||||
|
return Array(amount)
|
||||||
|
.fill(0)
|
||||||
|
.map((item, index) => <div key={index} className={'h-[4px] flex-1 flex-shrink-0 rounded bg-tint-9'}></div>);
|
||||||
|
};
|
@ -1,156 +0,0 @@
|
|||||||
import { KeyboardEventHandler, useEffect, useRef, useState } from 'react';
|
|
||||||
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
|
|
||||||
import { useCell } from '$app/components/_shared/database-hooks/useCell';
|
|
||||||
import { CellCache } from '$app/stores/effects/database/cell/cell_cache';
|
|
||||||
import { FieldController } from '$app/stores/effects/database/field/field_controller';
|
|
||||||
import { SelectOptionCellDataPB, SelectOptionColorPB, SelectOptionPB } from '@/services/backend';
|
|
||||||
import { getBgColor } from '$app/components/_shared/getColor';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { Details2Svg } from '$app/components/_shared/svg/Details2Svg';
|
|
||||||
import { CheckmarkSvg } from '$app/components/_shared/svg/CheckmarkSvg';
|
|
||||||
import { CloseSvg } from '$app/components/_shared/svg/CloseSvg';
|
|
||||||
import { SelectOptionCellBackendService } from '$app/stores/effects/database/cell/select_option_bd_svc';
|
|
||||||
import { useAppSelector } from '$app/stores/store';
|
|
||||||
import { ISelectOption, ISelectOptionType } from '$app/stores/reducers/database/slice';
|
|
||||||
import { PopupWindow } from '$app/components/_shared/PopupWindow';
|
|
||||||
|
|
||||||
export const CellOptionsPopup = ({
|
|
||||||
top,
|
|
||||||
left,
|
|
||||||
cellIdentifier,
|
|
||||||
cellCache,
|
|
||||||
fieldController,
|
|
||||||
onOutsideClick,
|
|
||||||
openOptionDetail,
|
|
||||||
}: {
|
|
||||||
top: number;
|
|
||||||
left: number;
|
|
||||||
cellIdentifier: CellIdentifier;
|
|
||||||
cellCache: CellCache;
|
|
||||||
fieldController: FieldController;
|
|
||||||
onOutsideClick: () => void;
|
|
||||||
openOptionDetail: (_left: number, _top: number, _select_option: SelectOptionPB) => void;
|
|
||||||
}) => {
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
|
||||||
const { t } = useTranslation('');
|
|
||||||
const [value, setValue] = useState('');
|
|
||||||
const { data } = useCell(cellIdentifier, cellCache, fieldController);
|
|
||||||
const databaseStore = useAppSelector((state) => state.database);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (inputRef?.current) {
|
|
||||||
inputRef.current.focus();
|
|
||||||
}
|
|
||||||
}, [inputRef]);
|
|
||||||
|
|
||||||
const onKeyDown: KeyboardEventHandler = async (e) => {
|
|
||||||
if (e.key === 'Enter' && value.length > 0) {
|
|
||||||
await new SelectOptionCellBackendService(cellIdentifier).createOption({ name: value });
|
|
||||||
setValue('');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onUnselectOptionClick = async (option: SelectOptionPB) => {
|
|
||||||
await new SelectOptionCellBackendService(cellIdentifier).unselectOption([option.id]);
|
|
||||||
setValue('');
|
|
||||||
};
|
|
||||||
|
|
||||||
const onToggleOptionClick = async (option: SelectOptionPB) => {
|
|
||||||
if ((data as SelectOptionCellDataPB)?.select_options?.find((selectedOption) => selectedOption.id === option.id)) {
|
|
||||||
await new SelectOptionCellBackendService(cellIdentifier).unselectOption([option.id]);
|
|
||||||
} else {
|
|
||||||
await new SelectOptionCellBackendService(cellIdentifier).selectOption([option.id]);
|
|
||||||
}
|
|
||||||
setValue('');
|
|
||||||
};
|
|
||||||
|
|
||||||
const onKeyDownWrapper: KeyboardEventHandler = (e) => {
|
|
||||||
if (e.key === 'Escape') {
|
|
||||||
onOutsideClick();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onOptionDetailClick = (e: any, option: ISelectOption) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
let target = e.target as HTMLElement;
|
|
||||||
|
|
||||||
while (!(target instanceof HTMLButtonElement)) {
|
|
||||||
if (target.parentElement === null) return;
|
|
||||||
target = target.parentElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectOption = new SelectOptionPB({
|
|
||||||
id: option.selectOptionId,
|
|
||||||
name: option.title,
|
|
||||||
color: option.color || SelectOptionColorPB.Purple,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { right: _left, top: _top } = target.getBoundingClientRect();
|
|
||||||
openOptionDetail(_left, _top, selectOption);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PopupWindow className={'p-2 text-xs'} onOutsideClick={onOutsideClick} left={left} top={top}>
|
|
||||||
<div onKeyDown={onKeyDownWrapper} className={'flex flex-col gap-2 p-2'}>
|
|
||||||
<div className={'border-shades-3 flex flex-1 items-center gap-2 rounded border bg-main-selector px-2 '}>
|
|
||||||
<div className={'flex flex-wrap items-center gap-2 text-black'}>
|
|
||||||
{(data as SelectOptionCellDataPB)?.select_options?.map((option, index) => (
|
|
||||||
<div className={`${getBgColor(option.color)} flex items-center gap-0.5 rounded px-1 py-0.5`} key={index}>
|
|
||||||
<span>{option?.name || ''}</span>
|
|
||||||
<button onClick={() => onUnselectOptionClick(option)} className={'h-5 w-5 cursor-pointer'}>
|
|
||||||
<CloseSvg></CloseSvg>{' '}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)) || ''}
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
ref={inputRef}
|
|
||||||
className={'py-2'}
|
|
||||||
value={value}
|
|
||||||
onChange={(e) => setValue(e.target.value)}
|
|
||||||
placeholder={t('grid.selectOption.searchOption') || ''}
|
|
||||||
onKeyDown={onKeyDown}
|
|
||||||
/>
|
|
||||||
<div className={'font-mono text-shade-3'}>{value.length}/30</div>
|
|
||||||
</div>
|
|
||||||
<div className={'-mx-4 h-[1px] bg-shade-6'}></div>
|
|
||||||
<div className={'font-medium text-shade-3'}>{t('grid.selectOption.panelTitle') || ''}</div>
|
|
||||||
<div className={'flex flex-col gap-1'}>
|
|
||||||
{(databaseStore.fields[cellIdentifier.fieldId]?.fieldOptions as ISelectOptionType).selectOptions.map(
|
|
||||||
(option, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
onClick={() =>
|
|
||||||
onToggleOptionClick(
|
|
||||||
new SelectOptionPB({
|
|
||||||
id: option.selectOptionId,
|
|
||||||
name: option.title,
|
|
||||||
color: option.color || SelectOptionColorPB.Purple,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
className={
|
|
||||||
'flex cursor-pointer items-center justify-between rounded-lg px-2 py-1.5 hover:bg-main-secondary'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className={`${getBgColor(option.color)} rounded px-2 py-0.5`}>{option.title}</div>
|
|
||||||
<div className={'flex items-center'}>
|
|
||||||
{(data as SelectOptionCellDataPB)?.select_options?.find(
|
|
||||||
(selectedOption) => selectedOption.id === option.selectOptionId
|
|
||||||
) && (
|
|
||||||
<button className={'h-5 w-5 p-1'}>
|
|
||||||
<CheckmarkSvg></CheckmarkSvg>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button onClick={(e) => onOptionDetailClick(e, option)} className={'h-6 w-6 p-1'}>
|
|
||||||
<Details2Svg></Details2Svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</PopupWindow>
|
|
||||||
);
|
|
||||||
};
|
|
@ -0,0 +1,40 @@
|
|||||||
|
import { SelectOptionCellDataPB } from '@/services/backend';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import { ISelectOptionType } from '$app_reducers/database/slice';
|
||||||
|
import { useAppSelector } from '$app/stores/store';
|
||||||
|
import { CheckListProgress } from '$app/components/_shared/CheckListProgress';
|
||||||
|
|
||||||
|
export const CheckList = ({
|
||||||
|
data,
|
||||||
|
fieldId,
|
||||||
|
onEditClick,
|
||||||
|
}: {
|
||||||
|
data: SelectOptionCellDataPB | undefined;
|
||||||
|
fieldId: string;
|
||||||
|
onEditClick: (left: number, top: number) => void;
|
||||||
|
}) => {
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
const [allOptionsCount, setAllOptionsCount] = useState(0);
|
||||||
|
const [selectedOptionsCount, setSelectedOptionsCount] = useState(0);
|
||||||
|
const databaseStore = useAppSelector((state) => state.database);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setAllOptionsCount((databaseStore.fields[fieldId]?.fieldOptions as ISelectOptionType)?.selectOptions?.length ?? 0);
|
||||||
|
}, [databaseStore, fieldId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedOptionsCount((data as SelectOptionCellDataPB)?.select_options?.length ?? 0);
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const onClick = () => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
const { left, top } = ref.current.getBoundingClientRect();
|
||||||
|
onEditClick(left, top);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref} onClick={onClick} className={'flex w-full flex-wrap items-center gap-2 px-4 py-1 text-xs text-black'}>
|
||||||
|
<CheckListProgress completed={selectedOptionsCount} max={allOptionsCount} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,60 @@
|
|||||||
|
import { SelectOptionPB } from '@/services/backend';
|
||||||
|
import { EditorCheckSvg } from '$app/components/_shared/svg/EditorCheckSvg';
|
||||||
|
import { EditorUncheckSvg } from '$app/components/_shared/svg/EditorUncheckSvg';
|
||||||
|
import { Details2Svg } from '$app/components/_shared/svg/Details2Svg';
|
||||||
|
import { ISelectOption } from '$app_reducers/database/slice';
|
||||||
|
import { MouseEventHandler } from 'react';
|
||||||
|
|
||||||
|
export const CheckListOption = ({
|
||||||
|
option,
|
||||||
|
checked,
|
||||||
|
onToggleOptionClick,
|
||||||
|
openCheckListDetail,
|
||||||
|
}: {
|
||||||
|
option: ISelectOption;
|
||||||
|
checked: boolean;
|
||||||
|
onToggleOptionClick: (v: SelectOptionPB) => void;
|
||||||
|
openCheckListDetail: (left: number, top: number, option: SelectOptionPB) => void;
|
||||||
|
}) => {
|
||||||
|
const onCheckListDetailClick: MouseEventHandler = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
let target = e.target as HTMLElement;
|
||||||
|
|
||||||
|
while (!(target instanceof HTMLButtonElement)) {
|
||||||
|
if (target.parentElement === null) return;
|
||||||
|
target = target.parentElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectOption = new SelectOptionPB({
|
||||||
|
id: option.selectOptionId,
|
||||||
|
name: option.title,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { right: _left, top: _top } = target.getBoundingClientRect();
|
||||||
|
openCheckListDetail(_left, _top, selectOption);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={'flex cursor-pointer items-center justify-between rounded-lg px-2 py-1.5 hover:bg-main-secondary'}
|
||||||
|
onClick={() =>
|
||||||
|
onToggleOptionClick(
|
||||||
|
new SelectOptionPB({
|
||||||
|
id: option.selectOptionId,
|
||||||
|
name: option.title,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className={'h-5 w-5'}>
|
||||||
|
{checked ? <EditorCheckSvg></EditorCheckSvg> : <EditorUncheckSvg></EditorUncheckSvg>}
|
||||||
|
</div>
|
||||||
|
<div className={`flex-1 px-2 py-0.5`}>{option.title}</div>
|
||||||
|
<div className={'flex items-center'}>
|
||||||
|
<button onClick={onCheckListDetailClick} className={'h-6 w-6 p-1'}>
|
||||||
|
<Details2Svg></Details2Svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,97 @@
|
|||||||
|
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
|
||||||
|
import { SelectOptionCellDataPB, SelectOptionPB } from '@/services/backend';
|
||||||
|
import { PopupWindow } from '$app/components/_shared/PopupWindow';
|
||||||
|
import { ISelectOptionType } from '$app_reducers/database/slice';
|
||||||
|
import { useAppSelector } from '$app/stores/store';
|
||||||
|
import { useCell } from '$app/components/_shared/database-hooks/useCell';
|
||||||
|
import { CellCache } from '$app/stores/effects/database/cell/cell_cache';
|
||||||
|
import { FieldController } from '$app/stores/effects/database/field/field_controller';
|
||||||
|
import { SelectOptionCellBackendService } from '$app/stores/effects/database/cell/select_option_bd_svc';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { CheckListProgress } from '$app/components/_shared/CheckListProgress';
|
||||||
|
import { NewCheckListOption } from '$app/components/_shared/EditRow/CheckList/NewCheckListOption';
|
||||||
|
import { CheckListOption } from '$app/components/_shared/EditRow/CheckList/CheckListOption';
|
||||||
|
import { NewCheckListButton } from '$app/components/_shared/EditRow/CheckList/NewCheckListButton';
|
||||||
|
|
||||||
|
export const CheckListPopup = ({
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
cellIdentifier,
|
||||||
|
cellCache,
|
||||||
|
fieldController,
|
||||||
|
openCheckListDetail,
|
||||||
|
onOutsideClick,
|
||||||
|
}: {
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
cellIdentifier: CellIdentifier;
|
||||||
|
cellCache: CellCache;
|
||||||
|
fieldController: FieldController;
|
||||||
|
openCheckListDetail: (left: number, top: number, option: SelectOptionPB) => void;
|
||||||
|
onOutsideClick: () => void;
|
||||||
|
}) => {
|
||||||
|
const databaseStore = useAppSelector((state) => state.database);
|
||||||
|
const { data } = useCell(cellIdentifier, cellCache, fieldController);
|
||||||
|
|
||||||
|
const [allOptionsCount, setAllOptionsCount] = useState(0);
|
||||||
|
const [selectedOptionsCount, setSelectedOptionsCount] = useState(0);
|
||||||
|
const [newOptions, setNewOptions] = useState<string[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setAllOptionsCount(
|
||||||
|
(databaseStore.fields[cellIdentifier.fieldId]?.fieldOptions as ISelectOptionType)?.selectOptions?.length ?? 0
|
||||||
|
);
|
||||||
|
}, [databaseStore, cellIdentifier]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedOptionsCount((data as SelectOptionCellDataPB)?.select_options?.length ?? 0);
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const onToggleOptionClick = async (option: SelectOptionPB) => {
|
||||||
|
if ((data as SelectOptionCellDataPB)?.select_options?.find((selectedOption) => selectedOption.id === option.id)) {
|
||||||
|
await new SelectOptionCellBackendService(cellIdentifier).unselectOption([option.id]);
|
||||||
|
} else {
|
||||||
|
await new SelectOptionCellBackendService(cellIdentifier).selectOption([option.id]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PopupWindow className={'text-xs'} onOutsideClick={onOutsideClick} left={left} top={top}>
|
||||||
|
<div className={'min-w-[320px]'}>
|
||||||
|
<div className={'px-4 pt-8 pb-4'}>
|
||||||
|
<CheckListProgress completed={selectedOptionsCount} max={allOptionsCount} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={'flex flex-col p-2'}>
|
||||||
|
{(databaseStore.fields[cellIdentifier.fieldId]?.fieldOptions as ISelectOptionType).selectOptions.map(
|
||||||
|
(option, index) => (
|
||||||
|
<CheckListOption
|
||||||
|
key={index}
|
||||||
|
option={option}
|
||||||
|
checked={
|
||||||
|
!!(data as SelectOptionCellDataPB)?.select_options?.find((so) => so.id === option.selectOptionId)
|
||||||
|
}
|
||||||
|
onToggleOptionClick={onToggleOptionClick}
|
||||||
|
openCheckListDetail={openCheckListDetail}
|
||||||
|
></CheckListOption>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
{newOptions.map((option, index) => (
|
||||||
|
<NewCheckListOption
|
||||||
|
key={index}
|
||||||
|
index={index}
|
||||||
|
option={option}
|
||||||
|
newOptions={newOptions}
|
||||||
|
setNewOptions={setNewOptions}
|
||||||
|
cellIdentifier={cellIdentifier}
|
||||||
|
></NewCheckListOption>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className={'h-[1px] bg-shade-6'}></div>
|
||||||
|
<div className={'p-2'}>
|
||||||
|
<NewCheckListButton newOptions={newOptions} setNewOptions={setNewOptions}></NewCheckListButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopupWindow>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,94 @@
|
|||||||
|
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
|
||||||
|
import { KeyboardEventHandler, useEffect, useRef, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { SelectOptionPB } from '@/services/backend';
|
||||||
|
import { SelectOptionCellBackendService } from '$app/stores/effects/database/cell/select_option_bd_svc';
|
||||||
|
import { TrashSvg } from '$app/components/_shared/svg/TrashSvg';
|
||||||
|
import { PopupWindow } from '$app/components/_shared/PopupWindow';
|
||||||
|
|
||||||
|
export const EditCheckListPopup = ({
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
cellIdentifier,
|
||||||
|
editingSelectOption,
|
||||||
|
onOutsideClick,
|
||||||
|
}: {
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
cellIdentifier: CellIdentifier;
|
||||||
|
editingSelectOption: SelectOptionPB;
|
||||||
|
onOutsideClick: () => void;
|
||||||
|
}) => {
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [value, setValue] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setValue(editingSelectOption.name);
|
||||||
|
}, [editingSelectOption]);
|
||||||
|
|
||||||
|
const onKeyDown: KeyboardEventHandler = async (e) => {
|
||||||
|
if (e.key === 'Enter' && value.length > 0) {
|
||||||
|
await new SelectOptionCellBackendService(cellIdentifier).createOption({ name: value });
|
||||||
|
setValue('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onKeyDownWrapper: KeyboardEventHandler = (e) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
onOutsideClick();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onBlur = async () => {
|
||||||
|
const svc = new SelectOptionCellBackendService(cellIdentifier);
|
||||||
|
await svc.updateOption(
|
||||||
|
new SelectOptionPB({
|
||||||
|
id: editingSelectOption.id,
|
||||||
|
name: value,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDeleteOptionClick = async () => {
|
||||||
|
const svc = new SelectOptionCellBackendService(cellIdentifier);
|
||||||
|
await svc.deleteOption([editingSelectOption]);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PopupWindow
|
||||||
|
className={'p-2 text-xs'}
|
||||||
|
onOutsideClick={async () => {
|
||||||
|
await onBlur();
|
||||||
|
onOutsideClick();
|
||||||
|
}}
|
||||||
|
left={left}
|
||||||
|
top={top}
|
||||||
|
>
|
||||||
|
<div onKeyDown={onKeyDownWrapper} className={'flex flex-col gap-2 p-2'}>
|
||||||
|
<div className={'border-shades-3 flex flex-1 items-center gap-2 rounded border bg-main-selector px-2 '}>
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
className={'py-2'}
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => setValue(e.target.value)}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
onBlur={() => onBlur()}
|
||||||
|
/>
|
||||||
|
<div className={'font-mono text-shade-3'}>{value.length}/30</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => onDeleteOptionClick()}
|
||||||
|
className={
|
||||||
|
'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 text-main-alert hover:bg-main-secondary'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<i className={'h-5 w-5'}>
|
||||||
|
<TrashSvg></TrashSvg>
|
||||||
|
</i>
|
||||||
|
<span>{t('grid.selectOption.deleteTag')}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</PopupWindow>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,28 @@
|
|||||||
|
import AddSvg from '$app/components/_shared/svg/AddSvg';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export const NewCheckListButton = ({
|
||||||
|
newOptions,
|
||||||
|
setNewOptions,
|
||||||
|
}: {
|
||||||
|
newOptions: string[];
|
||||||
|
setNewOptions: (v: string[]) => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const newOptionClick = () => {
|
||||||
|
setNewOptions([...newOptions, '']);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={() => newOptionClick()}
|
||||||
|
className={'flex w-full items-center gap-2 rounded-lg px-2 py-2 hover:bg-shade-6'}
|
||||||
|
>
|
||||||
|
<i className={'h-5 w-5'}>
|
||||||
|
<AddSvg></AddSvg>
|
||||||
|
</i>
|
||||||
|
<span>{t('grid.field.addOption')}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,53 @@
|
|||||||
|
import { SelectOptionCellBackendService } from '$app/stores/effects/database/cell/select_option_bd_svc';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
|
||||||
|
|
||||||
|
export const NewCheckListOption = ({
|
||||||
|
index,
|
||||||
|
option,
|
||||||
|
newOptions,
|
||||||
|
setNewOptions,
|
||||||
|
cellIdentifier,
|
||||||
|
}: {
|
||||||
|
index: number;
|
||||||
|
option: string;
|
||||||
|
newOptions: string[];
|
||||||
|
setNewOptions: (v: string[]) => void;
|
||||||
|
cellIdentifier: CellIdentifier;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const updateNewOption = (value: string) => {
|
||||||
|
const newOptionsCopy = [...newOptions];
|
||||||
|
newOptionsCopy[index] = value;
|
||||||
|
setNewOptions(newOptionsCopy);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onNewOptionKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
void onSaveNewOptionClick();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSaveNewOptionClick = async () => {
|
||||||
|
await new SelectOptionCellBackendService(cellIdentifier).createOption({ name: newOptions[index] });
|
||||||
|
setNewOptions(newOptions.filter((_, i) => i !== index));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={'flex cursor-pointer items-center justify-between rounded-lg px-2 py-1.5 hover:bg-shade-6'}>
|
||||||
|
<input
|
||||||
|
onKeyDown={(e) => onNewOptionKeyDown(e as unknown as KeyboardEvent)}
|
||||||
|
className={'min-w-0 flex-1 pl-7'}
|
||||||
|
value={option}
|
||||||
|
onChange={(e) => updateNewOption(e.target.value)}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={() => onSaveNewOptionClick()}
|
||||||
|
className={'flex items-center gap-2 rounded-lg bg-main-accent px-4 py-2 text-white hover:bg-main-hovered'}
|
||||||
|
>
|
||||||
|
{t('grid.selectOption.create')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -4,10 +4,10 @@ import { PopupWindow } from '$app/components/_shared/PopupWindow';
|
|||||||
import { CheckmarkSvg } from '$app/components/_shared/svg/CheckmarkSvg';
|
import { CheckmarkSvg } from '$app/components/_shared/svg/CheckmarkSvg';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { DateFormatPB } from '@/services/backend';
|
import { DateFormatPB } from '@/services/backend';
|
||||||
import { useDateTimeFormat } from '$app/components/_shared/EditRow/DateTimeFormat.hooks';
|
import { useDateTimeFormat } from '$app/components/_shared/EditRow/Date/DateTimeFormat.hooks';
|
||||||
import { useAppSelector } from '$app/stores/store';
|
import { useAppSelector } from '$app/stores/store';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { IDateType } from '$app/stores/reducers/database/slice';
|
import { IDateType } from '$app_reducers/database/slice';
|
||||||
|
|
||||||
export const DateFormatPopup = ({
|
export const DateFormatPopup = ({
|
||||||
left,
|
left,
|
||||||
@ -22,7 +22,7 @@ export const DateFormatPopup = ({
|
|||||||
fieldController: FieldController;
|
fieldController: FieldController;
|
||||||
onOutsideClick: () => void;
|
onOutsideClick: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation('');
|
const { t } = useTranslation();
|
||||||
const { changeDateFormat } = useDateTimeFormat(cellIdentifier, fieldController);
|
const { changeDateFormat } = useDateTimeFormat(cellIdentifier, fieldController);
|
||||||
const databaseStore = useAppSelector((state) => state.database);
|
const databaseStore = useAppSelector((state) => state.database);
|
||||||
const [dateType, setDateType] = useState<IDateType | undefined>();
|
const [dateType, setDateType] = useState<IDateType | undefined>();
|
@ -8,7 +8,7 @@ import { useCell } from '$app/components/_shared/database-hooks/useCell';
|
|||||||
import { CalendarData } from '$app/stores/effects/database/cell/controller_builder';
|
import { CalendarData } from '$app/stores/effects/database/cell/controller_builder';
|
||||||
import { DateCellDataPB } from '@/services/backend';
|
import { DateCellDataPB } from '@/services/backend';
|
||||||
import { PopupWindow } from '$app/components/_shared/PopupWindow';
|
import { PopupWindow } from '$app/components/_shared/PopupWindow';
|
||||||
import { DateTypeOptions } from '$app/components/_shared/EditRow/DateTypeOptions';
|
import { DateTypeOptions } from '$app/components/_shared/EditRow/Date/DateTypeOptions';
|
||||||
|
|
||||||
export const DatePickerPopup = ({
|
export const DatePickerPopup = ({
|
||||||
left,
|
left,
|
@ -1,13 +1,13 @@
|
|||||||
import { DateFormatPopup } from '$app/components/_shared/EditRow/DateFormatPopup';
|
import { DateFormatPopup } from '$app/components/_shared/EditRow/Date/DateFormatPopup';
|
||||||
import { TimeFormatPopup } from '$app/components/_shared/EditRow/TimeFormatPopup';
|
import { TimeFormatPopup } from '$app/components/_shared/EditRow/Date/TimeFormatPopup';
|
||||||
import { MoreSvg } from '$app/components/_shared/svg/MoreSvg';
|
import { MoreSvg } from '$app/components/_shared/svg/MoreSvg';
|
||||||
import { EditorCheckSvg } from '$app/components/_shared/svg/EditorCheckSvg';
|
import { EditorCheckSvg } from '$app/components/_shared/svg/EditorCheckSvg';
|
||||||
import { EditorUncheckSvg } from '$app/components/_shared/svg/EditorUncheckSvg';
|
import { EditorUncheckSvg } from '$app/components/_shared/svg/EditorUncheckSvg';
|
||||||
import { MouseEventHandler, useEffect, useState } from 'react';
|
import { MouseEventHandler, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { IDateType } from '$app/stores/reducers/database/slice';
|
import { IDateType } from '$app_reducers/database/slice';
|
||||||
import { useAppSelector } from '$app/stores/store';
|
import { useAppSelector } from '$app/stores/store';
|
||||||
import { useDateTimeFormat } from '$app/components/_shared/EditRow/DateTimeFormat.hooks';
|
import { useDateTimeFormat } from '$app/components/_shared/EditRow/Date/DateTimeFormat.hooks';
|
||||||
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
|
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
|
||||||
import { FieldController } from '$app/stores/effects/database/field/field_controller';
|
import { FieldController } from '$app/stores/effects/database/field/field_controller';
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ export const DateTypeOptions = ({
|
|||||||
cellIdentifier: CellIdentifier;
|
cellIdentifier: CellIdentifier;
|
||||||
fieldController: FieldController;
|
fieldController: FieldController;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation('');
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [showDateFormatPopup, setShowDateFormatPopup] = useState(false);
|
const [showDateFormatPopup, setShowDateFormatPopup] = useState(false);
|
||||||
const [dateFormatTop, setDateFormatTop] = useState(0);
|
const [dateFormatTop, setDateFormatTop] = useState(0);
|
||||||
@ -105,9 +105,6 @@ export const DateTypeOptions = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className={'flex items-center gap-2'}>
|
<div className={'flex items-center gap-2'}>
|
||||||
{/*<i className={'h-4 w-4'}>
|
|
||||||
<ClockSvg></ClockSvg>
|
|
||||||
</i>*/}
|
|
||||||
<span>{t('grid.field.includeTime')}</span>
|
<span>{t('grid.field.includeTime')}</span>
|
||||||
</div>
|
</div>
|
||||||
{/*<i className={'h-5 w-5'}>*/}
|
{/*<i className={'h-5 w-5'}>*/}
|
@ -1,4 +1,4 @@
|
|||||||
import { useRef } from 'react';
|
import { MouseEventHandler, useRef } from 'react';
|
||||||
import { DateCellDataPB } from '@/services/backend';
|
import { DateCellDataPB } from '@/services/backend';
|
||||||
|
|
||||||
export const EditCellDate = ({
|
export const EditCellDate = ({
|
||||||
@ -10,15 +10,15 @@ export const EditCellDate = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const onClick = () => {
|
const onClick: MouseEventHandler = () => {
|
||||||
if (!ref.current) return;
|
if (!ref.current) return;
|
||||||
const { left, top } = ref.current.getBoundingClientRect();
|
const { left, top } = ref.current.getBoundingClientRect();
|
||||||
onEditClick(left, top);
|
onEditClick(left, top);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ref} onClick={() => onClick()} className={'w-full px-4 py-2'}>
|
<div ref={ref} onClick={onClick} className={'w-full px-4 py-1'}>
|
||||||
{data?.date || <> </>}
|
{data?.date}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
@ -3,10 +3,7 @@ import { FieldController } from '$app/stores/effects/database/field/field_contro
|
|||||||
import { FieldType, NumberFormatPB } from '@/services/backend';
|
import { FieldType, NumberFormatPB } from '@/services/backend';
|
||||||
import { TypeOptionController } from '$app/stores/effects/database/field/type_option/type_option_controller';
|
import { TypeOptionController } from '$app/stores/effects/database/field/type_option/type_option_controller';
|
||||||
import { Some } from 'ts-results';
|
import { Some } from 'ts-results';
|
||||||
import {
|
import { makeNumberTypeOptionContext } from '$app/stores/effects/database/field/type_option/type_option_context';
|
||||||
makeDateTypeOptionContext,
|
|
||||||
makeNumberTypeOptionContext,
|
|
||||||
} from '$app/stores/effects/database/field/type_option/type_option_context';
|
|
||||||
|
|
||||||
export const useNumberFormat = (cellIdentifier: CellIdentifier, fieldController: FieldController) => {
|
export const useNumberFormat = (cellIdentifier: CellIdentifier, fieldController: FieldController) => {
|
||||||
const changeNumberFormat = async (format: NumberFormatPB) => {
|
const changeNumberFormat = async (format: NumberFormatPB) => {
|
@ -1,12 +1,12 @@
|
|||||||
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
|
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
|
||||||
import { FieldController } from '$app/stores/effects/database/field/field_controller';
|
import { FieldController } from '$app/stores/effects/database/field/field_controller';
|
||||||
import { PopupWindow } from '$app/components/_shared/PopupWindow';
|
import { PopupWindow } from '$app/components/_shared/PopupWindow';
|
||||||
import { useNumberFormat } from '$app/components/_shared/EditRow/NumberFormat.hooks';
|
import { useNumberFormat } from '$app/components/_shared/EditRow/Date/NumberFormat.hooks';
|
||||||
import { NumberFormatPB } from '@/services/backend';
|
import { NumberFormatPB } from '@/services/backend';
|
||||||
import { CheckmarkSvg } from '$app/components/_shared/svg/CheckmarkSvg';
|
import { CheckmarkSvg } from '$app/components/_shared/svg/CheckmarkSvg';
|
||||||
import { useAppSelector } from '$app/stores/store';
|
import { useAppSelector } from '$app/stores/store';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { INumberType } from '$app/stores/reducers/database/slice';
|
import { INumberType } from '$app_reducers/database/slice';
|
||||||
|
|
||||||
const list = [
|
const list = [
|
||||||
{ format: NumberFormatPB.Num, title: 'Num' },
|
{ format: NumberFormatPB.Num, title: 'Num' },
|
@ -4,10 +4,10 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { PopupWindow } from '$app/components/_shared/PopupWindow';
|
import { PopupWindow } from '$app/components/_shared/PopupWindow';
|
||||||
import { TimeFormatPB } from '@/services/backend';
|
import { TimeFormatPB } from '@/services/backend';
|
||||||
import { CheckmarkSvg } from '$app/components/_shared/svg/CheckmarkSvg';
|
import { CheckmarkSvg } from '$app/components/_shared/svg/CheckmarkSvg';
|
||||||
import { useDateTimeFormat } from '$app/components/_shared/EditRow/DateTimeFormat.hooks';
|
import { useDateTimeFormat } from '$app/components/_shared/EditRow/Date/DateTimeFormat.hooks';
|
||||||
import { useAppSelector } from '$app/stores/store';
|
import { useAppSelector } from '$app/stores/store';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { IDateType } from '$app/stores/reducers/database/slice';
|
import { IDateType } from '$app_reducers/database/slice';
|
||||||
|
|
||||||
export const TimeFormatPopup = ({
|
export const TimeFormatPopup = ({
|
||||||
left,
|
left,
|
||||||
@ -22,7 +22,7 @@ export const TimeFormatPopup = ({
|
|||||||
fieldController: FieldController;
|
fieldController: FieldController;
|
||||||
onOutsideClick: () => void;
|
onOutsideClick: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation('');
|
const { t } = useTranslation();
|
||||||
const databaseStore = useAppSelector((state) => state.database);
|
const databaseStore = useAppSelector((state) => state.database);
|
||||||
const [dateType, setDateType] = useState<IDateType | undefined>();
|
const [dateType, setDateType] = useState<IDateType | undefined>();
|
||||||
|
|
@ -4,15 +4,17 @@ import { CellCache } from '$app/stores/effects/database/cell/cell_cache';
|
|||||||
import { FieldController } from '$app/stores/effects/database/field/field_controller';
|
import { FieldController } from '$app/stores/effects/database/field/field_controller';
|
||||||
import { DateCellDataPB, FieldType, SelectOptionCellDataPB, URLCellDataPB } from '@/services/backend';
|
import { DateCellDataPB, FieldType, SelectOptionCellDataPB, URLCellDataPB } from '@/services/backend';
|
||||||
import { useAppSelector } from '$app/stores/store';
|
import { useAppSelector } from '$app/stores/store';
|
||||||
import { EditCellText } from '$app/components/_shared/EditRow/EditCellText';
|
import { EditCellText } from '$app/components/_shared/EditRow/InlineEditFields/EditCellText';
|
||||||
import { FieldTypeIcon } from '$app/components/_shared/EditRow/FieldTypeIcon';
|
import { FieldTypeIcon } from '$app/components/_shared/EditRow/FieldTypeIcon';
|
||||||
import { EditCellDate } from '$app/components/_shared/EditRow/EditCellDate';
|
import { EditCellDate } from '$app/components/_shared/EditRow/Date/EditCellDate';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import { CellOptions } from '$app/components/_shared/EditRow/CellOptions';
|
import { CellOptions } from '$app/components/_shared/EditRow/Options/CellOptions';
|
||||||
import { EditCellNumber } from '$app/components/_shared/EditRow/EditCellNumber';
|
import { EditCellNumber } from '$app/components/_shared/EditRow/InlineEditFields/EditCellNumber';
|
||||||
import { EditCheckboxCell } from '$app/components/_shared/EditRow/EditCheckboxCell';
|
import { EditCheckboxCell } from '$app/components/_shared/EditRow/InlineEditFields/EditCheckboxCell';
|
||||||
import { EditCellUrl } from '$app/components/_shared/EditRow/EditCellUrl';
|
import { EditCellUrl } from '$app/components/_shared/EditRow/InlineEditFields/EditCellUrl';
|
||||||
import { Draggable } from 'react-beautiful-dnd';
|
import { Draggable } from 'react-beautiful-dnd';
|
||||||
|
import { DragElementSvg } from '$app/components/_shared/svg/DragElementSvg';
|
||||||
|
import { CheckList } from '$app/components/_shared/EditRow/CheckList/CheckList';
|
||||||
|
|
||||||
export const EditCellWrapper = ({
|
export const EditCellWrapper = ({
|
||||||
index,
|
index,
|
||||||
@ -22,6 +24,7 @@ export const EditCellWrapper = ({
|
|||||||
onEditFieldClick,
|
onEditFieldClick,
|
||||||
onEditOptionsClick,
|
onEditOptionsClick,
|
||||||
onEditDateClick,
|
onEditDateClick,
|
||||||
|
onEditCheckListClick,
|
||||||
}: {
|
}: {
|
||||||
index: number;
|
index: number;
|
||||||
cellIdentifier: CellIdentifier;
|
cellIdentifier: CellIdentifier;
|
||||||
@ -30,6 +33,7 @@ export const EditCellWrapper = ({
|
|||||||
onEditFieldClick: (cell: CellIdentifier, left: number, top: number) => void;
|
onEditFieldClick: (cell: CellIdentifier, left: number, top: number) => void;
|
||||||
onEditOptionsClick: (cell: CellIdentifier, left: number, top: number) => void;
|
onEditOptionsClick: (cell: CellIdentifier, left: number, top: number) => void;
|
||||||
onEditDateClick: (cell: CellIdentifier, left: number, top: number) => void;
|
onEditDateClick: (cell: CellIdentifier, left: number, top: number) => void;
|
||||||
|
onEditCheckListClick: (cell: CellIdentifier, left: number, top: number) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { data, cellController } = useCell(cellIdentifier, cellCache, fieldController);
|
const { data, cellController } = useCell(cellIdentifier, cellCache, fieldController);
|
||||||
const databaseStore = useAppSelector((state) => state.database);
|
const databaseStore = useAppSelector((state) => state.database);
|
||||||
@ -48,26 +52,28 @@ export const EditCellWrapper = ({
|
|||||||
ref={provided.innerRef}
|
ref={provided.innerRef}
|
||||||
{...provided.draggableProps}
|
{...provided.draggableProps}
|
||||||
{...provided.dragHandleProps}
|
{...provided.dragHandleProps}
|
||||||
className={'flex w-full items-center text-xs'}
|
className={'flex w-full flex-col items-start gap-2 text-xs'}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
ref={el}
|
|
||||||
onClick={() => onClick()}
|
|
||||||
className={
|
className={
|
||||||
'relative flex w-[180px] cursor-pointer items-center gap-2 rounded-lg px-3 py-1.5 hover:bg-shade-6'
|
'relative flex cursor-pointer items-center gap-2 rounded-lg text-white transition-colors duration-200 hover:text-shade-3'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className={'flex h-5 w-5 flex-shrink-0 items-center justify-center'}>
|
<div ref={el} onClick={() => onClick()} className={'flex h-5 w-5'}>
|
||||||
|
<DragElementSvg></DragElementSvg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={'flex h-5 w-5 flex-shrink-0 items-center justify-center text-shade-3'}>
|
||||||
<FieldTypeIcon fieldType={cellIdentifier.fieldType}></FieldTypeIcon>
|
<FieldTypeIcon fieldType={cellIdentifier.fieldType}></FieldTypeIcon>
|
||||||
</div>
|
</div>
|
||||||
<span className={'overflow-hidden text-ellipsis whitespace-nowrap'}>
|
<span className={'overflow-hidden text-ellipsis whitespace-nowrap text-shade-3'}>
|
||||||
{databaseStore.fields[cellIdentifier.fieldId]?.title || ''}
|
{databaseStore.fields[cellIdentifier.fieldId]?.title ?? ''}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={'flex-1 cursor-pointer rounded-lg hover:bg-shade-6'}>
|
|
||||||
|
<div className={'w-full cursor-pointer rounded-lg pl-3 text-sm hover:bg-shade-6'}>
|
||||||
{(cellIdentifier.fieldType === FieldType.SingleSelect ||
|
{(cellIdentifier.fieldType === FieldType.SingleSelect ||
|
||||||
cellIdentifier.fieldType === FieldType.MultiSelect ||
|
cellIdentifier.fieldType === FieldType.MultiSelect) &&
|
||||||
cellIdentifier.fieldType === FieldType.Checklist) &&
|
|
||||||
cellController && (
|
cellController && (
|
||||||
<CellOptions
|
<CellOptions
|
||||||
data={data as SelectOptionCellDataPB}
|
data={data as SelectOptionCellDataPB}
|
||||||
@ -75,6 +81,14 @@ export const EditCellWrapper = ({
|
|||||||
></CellOptions>
|
></CellOptions>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{cellIdentifier.fieldType === FieldType.Checklist && cellController && (
|
||||||
|
<CheckList
|
||||||
|
data={data as SelectOptionCellDataPB}
|
||||||
|
fieldId={cellIdentifier.fieldId}
|
||||||
|
onEditClick={(left, top) => onEditCheckListClick(cellIdentifier, left, top)}
|
||||||
|
></CheckList>
|
||||||
|
)}
|
||||||
|
|
||||||
{cellIdentifier.fieldType === FieldType.Checkbox && cellController && (
|
{cellIdentifier.fieldType === FieldType.Checkbox && cellController && (
|
||||||
<EditCheckboxCell
|
<EditCheckboxCell
|
||||||
data={data as 'Yes' | 'No' | undefined}
|
data={data as 'Yes' | 'No' | undefined}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { MouseEventHandler, useEffect, useRef, useState } from 'react';
|
import { MouseEventHandler, useEffect, useRef, useState } from 'react';
|
||||||
import { TrashSvg } from '$app/components/_shared/svg/TrashSvg';
|
|
||||||
import { FieldTypeIcon } from '$app/components/_shared/EditRow/FieldTypeIcon';
|
import { FieldTypeIcon } from '$app/components/_shared/EditRow/FieldTypeIcon';
|
||||||
import { FieldTypeName } from '$app/components/_shared/EditRow/FieldTypeName';
|
import { FieldTypeName } from '$app/components/_shared/EditRow/FieldTypeName';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -11,7 +10,7 @@ import { useAppSelector } from '$app/stores/store';
|
|||||||
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
|
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
|
||||||
import { PopupWindow } from '$app/components/_shared/PopupWindow';
|
import { PopupWindow } from '$app/components/_shared/PopupWindow';
|
||||||
import { FieldType } from '@/services/backend';
|
import { FieldType } from '@/services/backend';
|
||||||
import { DateTypeOptions } from '$app/components/_shared/EditRow/DateTypeOptions';
|
import { DateTypeOptions } from '$app/components/_shared/EditRow/Date/DateTypeOptions';
|
||||||
|
|
||||||
export const EditFieldPopup = ({
|
export const EditFieldPopup = ({
|
||||||
top,
|
top,
|
||||||
@ -35,7 +34,7 @@ export const EditFieldPopup = ({
|
|||||||
onNumberFormat?: (buttonLeft: number, buttonTop: number) => void;
|
onNumberFormat?: (buttonLeft: number, buttonTop: number) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const databaseStore = useAppSelector((state) => state.database);
|
const databaseStore = useAppSelector((state) => state.database);
|
||||||
const { t } = useTranslation('');
|
const { t } = useTranslation();
|
||||||
const changeTypeButtonRef = useRef<HTMLDivElement>(null);
|
const changeTypeButtonRef = useRef<HTMLDivElement>(null);
|
||||||
const [name, setName] = useState('');
|
const [name, setName] = useState('');
|
||||||
|
|
||||||
@ -56,15 +55,6 @@ export const EditFieldPopup = ({
|
|||||||
changeFieldTypeClick(buttonTop, buttonRight);
|
changeFieldTypeClick(buttonTop, buttonRight);
|
||||||
};
|
};
|
||||||
|
|
||||||
// this is causing an error right now
|
|
||||||
const onDeleteFieldClick = async () => {
|
|
||||||
if (!fieldInfo) return;
|
|
||||||
const controller = new TypeOptionController(viewId, Some(fieldInfo));
|
|
||||||
await controller.initialize();
|
|
||||||
await controller.deleteField();
|
|
||||||
onOutsideClick();
|
|
||||||
};
|
|
||||||
|
|
||||||
const onNumberFormatClick: MouseEventHandler = (e) => {
|
const onNumberFormatClick: MouseEventHandler = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
let target = e.target as HTMLElement;
|
let target = e.target as HTMLElement;
|
||||||
@ -96,18 +86,6 @@ export const EditFieldPopup = ({
|
|||||||
className={'border-shades-3 flex-1 rounded border bg-main-selector px-2 py-2'}
|
className={'border-shades-3 flex-1 rounded border bg-main-selector px-2 py-2'}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={() => onDeleteFieldClick()}
|
|
||||||
className={'flex cursor-pointer items-center gap-2 rounded-lg py-2 text-main-alert hover:bg-main-secondary'}
|
|
||||||
>
|
|
||||||
<span className={'flex items-center gap-2 pl-2'}>
|
|
||||||
<i className={'block h-5 w-5'}>
|
|
||||||
<TrashSvg></TrashSvg>
|
|
||||||
</i>
|
|
||||||
<span>{t('grid.field.delete')}</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
ref={changeTypeButtonRef}
|
ref={changeTypeButtonRef}
|
||||||
onClick={() => onChangeFieldTypeClick()}
|
onClick={() => onChangeFieldTypeClick()}
|
||||||
|
@ -12,11 +12,16 @@ import { ChangeFieldTypePopup } from '$app/components/_shared/EditRow/ChangeFiel
|
|||||||
import { TypeOptionController } from '$app/stores/effects/database/field/type_option/type_option_controller';
|
import { TypeOptionController } from '$app/stores/effects/database/field/type_option/type_option_controller';
|
||||||
import { Some } from 'ts-results';
|
import { Some } from 'ts-results';
|
||||||
import { FieldType, SelectOptionPB } from '@/services/backend';
|
import { FieldType, SelectOptionPB } from '@/services/backend';
|
||||||
import { CellOptionsPopup } from '$app/components/_shared/EditRow/CellOptionsPopup';
|
import { CellOptionsPopup } from '$app/components/_shared/EditRow/Options/CellOptionsPopup';
|
||||||
import { DatePickerPopup } from '$app/components/_shared/EditRow/DatePickerPopup';
|
import { DatePickerPopup } from '$app/components/_shared/EditRow/Date/DatePickerPopup';
|
||||||
import { DragDropContext, Droppable, OnDragEndResponder } from 'react-beautiful-dnd';
|
import { DragDropContext, Droppable, OnDragEndResponder } from 'react-beautiful-dnd';
|
||||||
import { EditCellOptionPopup } from '$app/components/_shared/EditRow/EditCellOptionPopup';
|
import { EditCellOptionPopup } from '$app/components/_shared/EditRow/Options/EditCellOptionPopup';
|
||||||
import { NumberFormatPopup } from '$app/components/_shared/EditRow/NumberFormatPopup';
|
import { NumberFormatPopup } from '$app/components/_shared/EditRow/Date/NumberFormatPopup';
|
||||||
|
import { CheckListPopup } from '$app/components/_shared/EditRow/CheckList/CheckListPopup';
|
||||||
|
import { EditCheckListPopup } from '$app/components/_shared/EditRow/CheckList/EditCheckListPopup';
|
||||||
|
import { PropertiesPanel } from '$app/components/_shared/EditRow/PropertiesPanel';
|
||||||
|
import { ImageSvg } from '$app/components/_shared/svg/ImageSvg';
|
||||||
|
import { PromptWindow } from '$app/components/_shared/PromptWindow';
|
||||||
|
|
||||||
export const EditRow = ({
|
export const EditRow = ({
|
||||||
onClose,
|
onClose,
|
||||||
@ -30,7 +35,7 @@ export const EditRow = ({
|
|||||||
rowInfo: RowInfo;
|
rowInfo: RowInfo;
|
||||||
}) => {
|
}) => {
|
||||||
const { cells, onNewColumnClick } = useRow(viewId, controller, rowInfo);
|
const { cells, onNewColumnClick } = useRow(viewId, controller, rowInfo);
|
||||||
const { t } = useTranslation('');
|
const { t } = useTranslation();
|
||||||
const [unveil, setUnveil] = useState(false);
|
const [unveil, setUnveil] = useState(false);
|
||||||
|
|
||||||
const [editingCell, setEditingCell] = useState<CellIdentifier | null>(null);
|
const [editingCell, setEditingCell] = useState<CellIdentifier | null>(null);
|
||||||
@ -56,10 +61,21 @@ export const EditRow = ({
|
|||||||
|
|
||||||
const [editingSelectOption, setEditingSelectOption] = useState<SelectOptionPB | undefined>();
|
const [editingSelectOption, setEditingSelectOption] = useState<SelectOptionPB | undefined>();
|
||||||
|
|
||||||
|
const [showEditCheckList, setShowEditCheckList] = useState(false);
|
||||||
|
const [editCheckListTop, setEditCheckListTop] = useState(0);
|
||||||
|
const [editCheckListLeft, setEditCheckListLeft] = useState(0);
|
||||||
|
|
||||||
const [showNumberFormatPopup, setShowNumberFormatPopup] = useState(false);
|
const [showNumberFormatPopup, setShowNumberFormatPopup] = useState(false);
|
||||||
const [numberFormatTop, setNumberFormatTop] = useState(0);
|
const [numberFormatTop, setNumberFormatTop] = useState(0);
|
||||||
const [numberFormatLeft, setNumberFormatLeft] = useState(0);
|
const [numberFormatLeft, setNumberFormatLeft] = useState(0);
|
||||||
|
|
||||||
|
const [showCheckListPopup, setShowCheckListPopup] = useState(false);
|
||||||
|
const [checkListPopupTop, setCheckListPopupTop] = useState(0);
|
||||||
|
const [checkListPopupLeft, setCheckListPopupLeft] = useState(0);
|
||||||
|
|
||||||
|
const [deletingPropertyId, setDeletingPropertyId] = useState<string | null>(null);
|
||||||
|
const [showDeletePropertyPrompt, setShowDeletePropertyPrompt] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setUnveil(true);
|
setUnveil(true);
|
||||||
}, []);
|
}, []);
|
||||||
@ -125,12 +141,26 @@ export const EditRow = ({
|
|||||||
setEditCellOptionTop(_top);
|
setEditCellOptionTop(_top);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onOpenCheckListDetailClick = (_left: number, _top: number, _select_option: SelectOptionPB) => {
|
||||||
|
setEditingSelectOption(_select_option);
|
||||||
|
setShowEditCheckList(true);
|
||||||
|
setEditCheckListLeft(_left + 10);
|
||||||
|
setEditCheckListTop(_top);
|
||||||
|
};
|
||||||
|
|
||||||
const onNumberFormat = (_left: number, _top: number) => {
|
const onNumberFormat = (_left: number, _top: number) => {
|
||||||
setShowNumberFormatPopup(true);
|
setShowNumberFormatPopup(true);
|
||||||
setNumberFormatLeft(_left + 10);
|
setNumberFormatLeft(_left + 10);
|
||||||
setNumberFormatTop(_top);
|
setNumberFormatTop(_top);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onEditCheckListClick = (cellIdentifier: CellIdentifier, left: number, top: number) => {
|
||||||
|
setEditingCell(cellIdentifier);
|
||||||
|
setShowCheckListPopup(true);
|
||||||
|
setCheckListPopupLeft(left);
|
||||||
|
setCheckListPopupTop(top + 40);
|
||||||
|
};
|
||||||
|
|
||||||
const onDragEnd: OnDragEndResponder = (result) => {
|
const onDragEnd: OnDragEndResponder = (result) => {
|
||||||
if (!result.destination?.index) return;
|
if (!result.destination?.index) return;
|
||||||
void controller.moveField({
|
void controller.moveField({
|
||||||
@ -140,129 +170,193 @@ export const EditRow = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onDeletePropertyClick = (fieldId: string) => {
|
||||||
|
setDeletingPropertyId(fieldId);
|
||||||
|
setShowDeletePropertyPrompt(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDelete = async () => {
|
||||||
|
if (!deletingPropertyId) return;
|
||||||
|
const fieldInfo = controller.fieldController.getField(deletingPropertyId);
|
||||||
|
if (!fieldInfo) return;
|
||||||
|
const typeController = new TypeOptionController(viewId, Some(fieldInfo));
|
||||||
|
await typeController.initialize();
|
||||||
|
await typeController.deleteField();
|
||||||
|
setShowDeletePropertyPrompt(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
className={`fixed inset-0 z-10 flex items-center justify-center bg-black/30 backdrop-blur-sm transition-opacity duration-300 ${
|
|
||||||
unveil ? 'opacity-100' : 'opacity-0'
|
|
||||||
}`}
|
|
||||||
onClick={() => onCloseClick()}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
onClick={(e) => {
|
className={`fixed inset-0 z-10 flex items-center justify-center bg-black/30 backdrop-blur-sm transition-opacity duration-300 ${
|
||||||
e.stopPropagation();
|
unveil ? 'opacity-100' : 'opacity-0'
|
||||||
}}
|
}`}
|
||||||
className={`relative flex h-[90%] w-[70%] flex-col gap-8 rounded-xl bg-white px-8 pb-4 pt-12`}
|
onClick={() => onCloseClick()}
|
||||||
>
|
>
|
||||||
<div onClick={() => onCloseClick()} className={'absolute top-4 right-4'}>
|
<div
|
||||||
<button className={'block h-8 w-8 rounded-lg text-shade-2 hover:bg-main-secondary'}>
|
onClick={(e) => {
|
||||||
<CloseSvg></CloseSvg>
|
e.stopPropagation();
|
||||||
</button>
|
}}
|
||||||
</div>
|
className={`relative flex h-[90%] w-[70%] flex-col gap-8 rounded-xl bg-white `}
|
||||||
|
>
|
||||||
|
<div onClick={() => onCloseClick()} className={'absolute top-1 right-1'}>
|
||||||
|
<button className={'block h-8 w-8 rounded-lg text-shade-2 hover:bg-main-secondary'}>
|
||||||
|
<CloseSvg></CloseSvg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
<div className={'flex h-full'}>
|
||||||
<Droppable droppableId={'field-list'}>
|
<div className={'flex h-full flex-1 flex-col border-r border-shade-6 pb-4 pt-6'}>
|
||||||
{(provided) => (
|
<div className={'pl-12 pb-4'}>
|
||||||
<div
|
<button className={'flex items-center gap-2 p-4'}>
|
||||||
{...provided.droppableProps}
|
<i className={'h-5 w-5'}>
|
||||||
ref={provided.innerRef}
|
<ImageSvg></ImageSvg>
|
||||||
className={`flex flex-1 flex-col gap-2 ${
|
</i>
|
||||||
showFieldEditor || showChangeOptionsPopup || showDatePicker ? 'overflow-hidden' : 'overflow-auto'
|
<span className={'text-xs'}>Add Cover</span>
|
||||||
}`}
|
</button>
|
||||||
>
|
|
||||||
{cells.map((cell, cellIndex) => (
|
|
||||||
<EditCellWrapper
|
|
||||||
index={cellIndex}
|
|
||||||
key={cellIndex}
|
|
||||||
cellIdentifier={cell.cellIdentifier}
|
|
||||||
cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
|
|
||||||
fieldController={controller.fieldController}
|
|
||||||
onEditFieldClick={onEditFieldClick}
|
|
||||||
onEditOptionsClick={onEditOptionsClick}
|
|
||||||
onEditDateClick={onEditDateClick}
|
|
||||||
></EditCellWrapper>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</Droppable>
|
|
||||||
</DragDropContext>
|
|
||||||
|
|
||||||
<div className={'border-t border-shade-6 pt-2'}>
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
<button
|
<Droppable droppableId={'field-list'}>
|
||||||
onClick={() => onNewColumnClick()}
|
{(provided) => (
|
||||||
className={'flex w-full items-center gap-2 rounded-lg px-4 py-2 hover:bg-shade-6'}
|
<div
|
||||||
>
|
{...provided.droppableProps}
|
||||||
<i className={'h-5 w-5'}>
|
ref={provided.innerRef}
|
||||||
<AddSvg></AddSvg>
|
className={`flex flex-1 flex-col gap-8 px-8 ${
|
||||||
</i>
|
showFieldEditor || showChangeOptionsPopup || showDatePicker ? 'overflow-hidden' : 'overflow-auto'
|
||||||
<span>{t('grid.field.newColumn')}</span>
|
}`}
|
||||||
</button>
|
>
|
||||||
|
{cells.map((cell, cellIndex) => (
|
||||||
|
<EditCellWrapper
|
||||||
|
index={cellIndex}
|
||||||
|
key={cellIndex}
|
||||||
|
cellIdentifier={cell.cellIdentifier}
|
||||||
|
cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
|
||||||
|
fieldController={controller.fieldController}
|
||||||
|
onEditFieldClick={onEditFieldClick}
|
||||||
|
onEditOptionsClick={onEditOptionsClick}
|
||||||
|
onEditDateClick={onEditDateClick}
|
||||||
|
onEditCheckListClick={onEditCheckListClick}
|
||||||
|
></EditCellWrapper>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Droppable>
|
||||||
|
</DragDropContext>
|
||||||
|
|
||||||
|
<div className={'border-t border-shade-6 px-8 pt-2'}>
|
||||||
|
<button
|
||||||
|
onClick={() => onNewColumnClick()}
|
||||||
|
className={'flex w-full items-center gap-2 rounded-lg px-4 py-2 hover:bg-shade-6'}
|
||||||
|
>
|
||||||
|
<i className={'h-5 w-5'}>
|
||||||
|
<AddSvg></AddSvg>
|
||||||
|
</i>
|
||||||
|
<span>{t('grid.field.newProperty')}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<PropertiesPanel
|
||||||
|
viewId={viewId}
|
||||||
|
controller={controller}
|
||||||
|
rowInfo={rowInfo}
|
||||||
|
onDeletePropertyClick={onDeletePropertyClick}
|
||||||
|
></PropertiesPanel>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showFieldEditor && editingCell && (
|
||||||
|
<EditFieldPopup
|
||||||
|
top={editFieldTop}
|
||||||
|
left={editFieldLeft}
|
||||||
|
cellIdentifier={editingCell}
|
||||||
|
viewId={viewId}
|
||||||
|
onOutsideClick={onOutsideEditFieldClick}
|
||||||
|
fieldInfo={controller.fieldController.getField(editingCell.fieldId)}
|
||||||
|
fieldController={controller.fieldController}
|
||||||
|
changeFieldTypeClick={onChangeFieldTypeClick}
|
||||||
|
onNumberFormat={onNumberFormat}
|
||||||
|
></EditFieldPopup>
|
||||||
|
)}
|
||||||
|
{showChangeFieldTypePopup && (
|
||||||
|
<ChangeFieldTypePopup
|
||||||
|
top={changeFieldTypeTop}
|
||||||
|
left={changeFieldTypeLeft}
|
||||||
|
onClick={(newType) => changeFieldType(newType)}
|
||||||
|
onOutsideClick={() => setShowChangeFieldTypePopup(false)}
|
||||||
|
></ChangeFieldTypePopup>
|
||||||
|
)}
|
||||||
|
{showChangeOptionsPopup && editingCell && (
|
||||||
|
<CellOptionsPopup
|
||||||
|
top={changeOptionsTop}
|
||||||
|
left={changeOptionsLeft}
|
||||||
|
cellIdentifier={editingCell}
|
||||||
|
cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
|
||||||
|
fieldController={controller.fieldController}
|
||||||
|
onOutsideClick={() => setShowChangeOptionsPopup(false)}
|
||||||
|
openOptionDetail={onOpenOptionDetailClick}
|
||||||
|
></CellOptionsPopup>
|
||||||
|
)}
|
||||||
|
{showDatePicker && editingCell && (
|
||||||
|
<DatePickerPopup
|
||||||
|
top={datePickerTop}
|
||||||
|
left={datePickerLeft}
|
||||||
|
cellIdentifier={editingCell}
|
||||||
|
cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
|
||||||
|
fieldController={controller.fieldController}
|
||||||
|
onOutsideClick={() => setShowDatePicker(false)}
|
||||||
|
></DatePickerPopup>
|
||||||
|
)}
|
||||||
|
{showEditCellOption && editingCell && editingSelectOption && (
|
||||||
|
<EditCellOptionPopup
|
||||||
|
top={editCellOptionTop}
|
||||||
|
left={editCellOptionLeft}
|
||||||
|
cellIdentifier={editingCell}
|
||||||
|
editingSelectOption={editingSelectOption}
|
||||||
|
onOutsideClick={() => {
|
||||||
|
setShowEditCellOption(false);
|
||||||
|
}}
|
||||||
|
></EditCellOptionPopup>
|
||||||
|
)}
|
||||||
|
{showNumberFormatPopup && editingCell && (
|
||||||
|
<NumberFormatPopup
|
||||||
|
top={numberFormatTop}
|
||||||
|
left={numberFormatLeft}
|
||||||
|
cellIdentifier={editingCell}
|
||||||
|
fieldController={controller.fieldController}
|
||||||
|
onOutsideClick={() => {
|
||||||
|
setShowNumberFormatPopup(false);
|
||||||
|
}}
|
||||||
|
></NumberFormatPopup>
|
||||||
|
)}
|
||||||
|
{showCheckListPopup && editingCell && (
|
||||||
|
<CheckListPopup
|
||||||
|
top={checkListPopupTop}
|
||||||
|
left={checkListPopupLeft}
|
||||||
|
cellIdentifier={editingCell}
|
||||||
|
cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
|
||||||
|
fieldController={controller.fieldController}
|
||||||
|
onOutsideClick={() => setShowCheckListPopup(false)}
|
||||||
|
openCheckListDetail={onOpenCheckListDetailClick}
|
||||||
|
></CheckListPopup>
|
||||||
|
)}
|
||||||
|
{showEditCheckList && editingCell && editingSelectOption && (
|
||||||
|
<EditCheckListPopup
|
||||||
|
top={editCheckListTop}
|
||||||
|
left={editCheckListLeft}
|
||||||
|
cellIdentifier={editingCell}
|
||||||
|
editingSelectOption={editingSelectOption}
|
||||||
|
onOutsideClick={() => setShowEditCheckList(false)}
|
||||||
|
></EditCheckListPopup>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showFieldEditor && editingCell && (
|
|
||||||
<EditFieldPopup
|
|
||||||
top={editFieldTop}
|
|
||||||
left={editFieldLeft}
|
|
||||||
cellIdentifier={editingCell}
|
|
||||||
viewId={viewId}
|
|
||||||
onOutsideClick={onOutsideEditFieldClick}
|
|
||||||
fieldInfo={controller.fieldController.getField(editingCell.fieldId)}
|
|
||||||
fieldController={controller.fieldController}
|
|
||||||
changeFieldTypeClick={onChangeFieldTypeClick}
|
|
||||||
onNumberFormat={onNumberFormat}
|
|
||||||
></EditFieldPopup>
|
|
||||||
)}
|
|
||||||
{showChangeFieldTypePopup && (
|
|
||||||
<ChangeFieldTypePopup
|
|
||||||
top={changeFieldTypeTop}
|
|
||||||
left={changeFieldTypeLeft}
|
|
||||||
onClick={(newType) => changeFieldType(newType)}
|
|
||||||
onOutsideClick={() => setShowChangeFieldTypePopup(false)}
|
|
||||||
></ChangeFieldTypePopup>
|
|
||||||
)}
|
|
||||||
{showChangeOptionsPopup && editingCell && (
|
|
||||||
<CellOptionsPopup
|
|
||||||
top={changeOptionsTop}
|
|
||||||
left={changeOptionsLeft}
|
|
||||||
cellIdentifier={editingCell}
|
|
||||||
cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
|
|
||||||
fieldController={controller.fieldController}
|
|
||||||
onOutsideClick={() => setShowChangeOptionsPopup(false)}
|
|
||||||
openOptionDetail={onOpenOptionDetailClick}
|
|
||||||
></CellOptionsPopup>
|
|
||||||
)}
|
|
||||||
{showDatePicker && editingCell && (
|
|
||||||
<DatePickerPopup
|
|
||||||
top={datePickerTop}
|
|
||||||
left={datePickerLeft}
|
|
||||||
cellIdentifier={editingCell}
|
|
||||||
cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
|
|
||||||
fieldController={controller.fieldController}
|
|
||||||
onOutsideClick={() => setShowDatePicker(false)}
|
|
||||||
></DatePickerPopup>
|
|
||||||
)}
|
|
||||||
{showEditCellOption && editingCell && editingSelectOption && (
|
|
||||||
<EditCellOptionPopup
|
|
||||||
top={editCellOptionTop}
|
|
||||||
left={editCellOptionLeft}
|
|
||||||
cellIdentifier={editingCell}
|
|
||||||
editingSelectOption={editingSelectOption}
|
|
||||||
onOutsideClick={() => {
|
|
||||||
setShowEditCellOption(false);
|
|
||||||
}}
|
|
||||||
></EditCellOptionPopup>
|
|
||||||
)}
|
|
||||||
{showNumberFormatPopup && editingCell && (
|
|
||||||
<NumberFormatPopup
|
|
||||||
top={numberFormatTop}
|
|
||||||
left={numberFormatLeft}
|
|
||||||
cellIdentifier={editingCell}
|
|
||||||
fieldController={controller.fieldController}
|
|
||||||
onOutsideClick={() => {
|
|
||||||
setShowNumberFormatPopup(false);
|
|
||||||
}}
|
|
||||||
></NumberFormatPopup>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{showDeletePropertyPrompt && (
|
||||||
|
<PromptWindow
|
||||||
|
msg={'Are you sure you want to delete this property?'}
|
||||||
|
onYes={() => onDelete()}
|
||||||
|
onCancel={() => setShowDeletePropertyPrompt(false)}
|
||||||
|
></PromptWindow>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,7 @@ import { FieldType } from '@/services/backend';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export const FieldTypeName = ({ fieldType }: { fieldType: FieldType }) => {
|
export const FieldTypeName = ({ fieldType }: { fieldType: FieldType }) => {
|
||||||
const { t } = useTranslation('');
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{fieldType === FieldType.RichText && t('grid.field.textFieldName')}
|
{fieldType === FieldType.RichText && t('grid.field.textFieldName')}
|
||||||
|
@ -11,7 +11,7 @@ export const EditCellNumber = ({
|
|||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setValue(data || '');
|
setValue(data ?? '');
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
const save = async () => {
|
const save = async () => {
|
||||||
@ -23,7 +23,7 @@ export const EditCellNumber = ({
|
|||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => setValue(e.target.value)}
|
onChange={(e) => setValue(e.target.value)}
|
||||||
onBlur={() => save()}
|
onBlur={() => save()}
|
||||||
className={'w-full px-4 py-2'}
|
className={'w-full px-4 py-1'}
|
||||||
></input>
|
></input>
|
||||||
);
|
);
|
||||||
};
|
};
|
@ -12,12 +12,12 @@ export const EditCellText = ({
|
|||||||
const [contentRows, setContentRows] = useState(1);
|
const [contentRows, setContentRows] = useState(1);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setValue(data || '');
|
setValue(data ?? '');
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!value?.length) return;
|
if (!value?.length) return;
|
||||||
setContentRows(Math.max(1, (value || '').split('\n').length));
|
setContentRows(Math.max(1, (value ?? '').split('\n').length));
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
const onTextFieldChange = async (v: string) => {
|
const onTextFieldChange = async (v: string) => {
|
||||||
@ -29,9 +29,9 @@ export const EditCellText = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={''}>
|
<div>
|
||||||
<textarea
|
<textarea
|
||||||
className={'mt-0.5 h-full w-full resize-none px-4 py-2'}
|
className={'mt-0.5 h-full w-full resize-none px-4 py-1'}
|
||||||
rows={contentRows}
|
rows={contentRows}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => onTextFieldChange(e.target.value)}
|
onChange={(e) => onTextFieldChange(e.target.value)}
|
@ -13,7 +13,7 @@ export const EditCellUrl = ({
|
|||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setValue((data as URLCellDataPB)?.url || '');
|
setValue((data as URLCellDataPB)?.url ?? '');
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
const save = async () => {
|
const save = async () => {
|
||||||
@ -25,7 +25,7 @@ export const EditCellUrl = ({
|
|||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => setValue(e.target.value)}
|
onChange={(e) => setValue(e.target.value)}
|
||||||
onBlur={() => save()}
|
onBlur={() => save()}
|
||||||
className={'w-full px-4 py-2'}
|
className={'w-full px-4 py-1'}
|
||||||
></input>
|
></input>
|
||||||
);
|
);
|
||||||
};
|
};
|
@ -18,7 +18,7 @@ export const EditCheckboxCell = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onClick={() => toggleValue()} className={'block px-4 py-2'}>
|
<div onClick={() => toggleValue()} className={'block px-4 py-1'}>
|
||||||
<button className={'h-5 w-5'}>
|
<button className={'h-5 w-5'}>
|
||||||
{data === 'Yes' ? <EditorCheckSvg></EditorCheckSvg> : <EditorUncheckSvg></EditorUncheckSvg>}
|
{data === 'Yes' ? <EditorCheckSvg></EditorCheckSvg> : <EditorUncheckSvg></EditorUncheckSvg>}
|
||||||
</button>
|
</button>
|
@ -0,0 +1,69 @@
|
|||||||
|
import { SelectOptionColorPB, SelectOptionPB } from '@/services/backend';
|
||||||
|
import { getBgColor } from '$app/components/_shared/getColor';
|
||||||
|
import { CheckmarkSvg } from '$app/components/_shared/svg/CheckmarkSvg';
|
||||||
|
import { Details2Svg } from '$app/components/_shared/svg/Details2Svg';
|
||||||
|
import { ISelectOption } from '$app_reducers/database/slice';
|
||||||
|
import { SelectOptionCellBackendService } from '$app/stores/effects/database/cell/select_option_bd_svc';
|
||||||
|
import { MouseEventHandler } from 'react';
|
||||||
|
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
|
||||||
|
|
||||||
|
export const CellOption = ({
|
||||||
|
option,
|
||||||
|
checked,
|
||||||
|
cellIdentifier,
|
||||||
|
openOptionDetail,
|
||||||
|
clearValue,
|
||||||
|
}: {
|
||||||
|
option: ISelectOption;
|
||||||
|
checked: boolean;
|
||||||
|
cellIdentifier: CellIdentifier;
|
||||||
|
openOptionDetail: (_left: number, _top: number, _select_option: SelectOptionPB) => void;
|
||||||
|
clearValue: () => void;
|
||||||
|
}) => {
|
||||||
|
const onOptionDetailClick: MouseEventHandler = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
let target = e.target as HTMLElement;
|
||||||
|
|
||||||
|
while (!(target instanceof HTMLButtonElement)) {
|
||||||
|
if (target.parentElement === null) return;
|
||||||
|
target = target.parentElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectOption = new SelectOptionPB({
|
||||||
|
id: option.selectOptionId,
|
||||||
|
name: option.title,
|
||||||
|
color: option.color ?? SelectOptionColorPB.Purple,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { right: _left, top: _top } = target.getBoundingClientRect();
|
||||||
|
openOptionDetail(_left, _top, selectOption);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onToggleOptionClick: MouseEventHandler = async () => {
|
||||||
|
if (checked) {
|
||||||
|
await new SelectOptionCellBackendService(cellIdentifier).unselectOption([option.selectOptionId]);
|
||||||
|
} else {
|
||||||
|
await new SelectOptionCellBackendService(cellIdentifier).selectOption([option.selectOptionId]);
|
||||||
|
}
|
||||||
|
clearValue();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={onToggleOptionClick}
|
||||||
|
className={'flex cursor-pointer items-center justify-between rounded-lg px-2 py-1.5 hover:bg-main-secondary'}
|
||||||
|
>
|
||||||
|
<div className={`${getBgColor(option.color)} rounded px-2 py-0.5`}>{option.title}</div>
|
||||||
|
<div className={'flex items-center'}>
|
||||||
|
{checked && (
|
||||||
|
<button className={'h-5 w-5 p-1'}>
|
||||||
|
<CheckmarkSvg></CheckmarkSvg>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button onClick={onOptionDetailClick} className={'h-6 w-6 p-1'}>
|
||||||
|
<Details2Svg></Details2Svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -1,6 +1,6 @@
|
|||||||
import { SelectOptionCellDataPB } from '@/services/backend';
|
import { SelectOptionCellDataPB } from '@/services/backend';
|
||||||
import { getBgColor } from '$app/components/_shared/getColor';
|
import { getBgColor } from '$app/components/_shared/getColor';
|
||||||
import { useRef } from 'react';
|
import { MouseEventHandler, useRef } from 'react';
|
||||||
|
|
||||||
export const CellOptions = ({
|
export const CellOptions = ({
|
||||||
data,
|
data,
|
||||||
@ -11,23 +11,19 @@ export const CellOptions = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const onClick = () => {
|
const onClick: MouseEventHandler = () => {
|
||||||
if (!ref.current) return;
|
if (!ref.current) return;
|
||||||
const { left, top } = ref.current.getBoundingClientRect();
|
const { left, top } = ref.current.getBoundingClientRect();
|
||||||
onEditClick(left, top);
|
onEditClick(left, top);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div ref={ref} onClick={onClick} className={'flex w-full flex-wrap items-center gap-2 px-4 py-1 text-xs text-black'}>
|
||||||
ref={ref}
|
|
||||||
onClick={() => onClick()}
|
|
||||||
className={'flex w-full flex-wrap items-center gap-2 px-4 py-2 text-xs text-black'}
|
|
||||||
>
|
|
||||||
{data?.select_options?.map((option, index) => (
|
{data?.select_options?.map((option, index) => (
|
||||||
<div className={`${getBgColor(option.color)} rounded px-2 py-0.5`} key={index}>
|
<div className={`${getBgColor(option.color)} rounded px-2 py-0.5`} key={index}>
|
||||||
{option?.name || ''}
|
{option?.name ?? ''}
|
||||||
</div>
|
</div>
|
||||||
)) || ''}
|
))}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
@ -0,0 +1,104 @@
|
|||||||
|
import { KeyboardEventHandler, useEffect, useRef, useState } from 'react';
|
||||||
|
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
|
||||||
|
import { useCell } from '$app/components/_shared/database-hooks/useCell';
|
||||||
|
import { CellCache } from '$app/stores/effects/database/cell/cell_cache';
|
||||||
|
import { FieldController } from '$app/stores/effects/database/field/field_controller';
|
||||||
|
import { SelectOptionCellDataPB, SelectOptionPB } from '@/services/backend';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { SelectOptionCellBackendService } from '$app/stores/effects/database/cell/select_option_bd_svc';
|
||||||
|
import { useAppSelector } from '$app/stores/store';
|
||||||
|
import { ISelectOptionType } from '$app_reducers/database/slice';
|
||||||
|
import { PopupWindow } from '$app/components/_shared/PopupWindow';
|
||||||
|
import { CellOption } from '$app/components/_shared/EditRow/Options/CellOption';
|
||||||
|
import { SelectedOption } from '$app/components/_shared/EditRow/Options/SelectedOption';
|
||||||
|
|
||||||
|
export const CellOptionsPopup = ({
|
||||||
|
top,
|
||||||
|
left,
|
||||||
|
cellIdentifier,
|
||||||
|
cellCache,
|
||||||
|
fieldController,
|
||||||
|
onOutsideClick,
|
||||||
|
openOptionDetail,
|
||||||
|
}: {
|
||||||
|
top: number;
|
||||||
|
left: number;
|
||||||
|
cellIdentifier: CellIdentifier;
|
||||||
|
cellCache: CellCache;
|
||||||
|
fieldController: FieldController;
|
||||||
|
onOutsideClick: () => void;
|
||||||
|
openOptionDetail: (_left: number, _top: number, _select_option: SelectOptionPB) => void;
|
||||||
|
}) => {
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [value, setValue] = useState('');
|
||||||
|
const { data } = useCell(cellIdentifier, cellCache, fieldController);
|
||||||
|
const databaseStore = useAppSelector((state) => state.database);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (inputRef?.current) {
|
||||||
|
inputRef.current.focus();
|
||||||
|
}
|
||||||
|
}, [inputRef]);
|
||||||
|
|
||||||
|
const onKeyDown: KeyboardEventHandler = async (e) => {
|
||||||
|
if (e.key === 'Enter' && value.length > 0) {
|
||||||
|
await new SelectOptionCellBackendService(cellIdentifier).createOption({ name: value });
|
||||||
|
setValue('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onKeyDownWrapper: KeyboardEventHandler = (e) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
onOutsideClick();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PopupWindow className={'p-2 text-xs'} onOutsideClick={onOutsideClick} left={left} top={top}>
|
||||||
|
<div onKeyDown={onKeyDownWrapper} className={'flex flex-col gap-2 p-2'}>
|
||||||
|
<div className={'border-shades-3 flex flex-1 items-center gap-2 rounded border bg-main-selector px-2 '}>
|
||||||
|
<div className={'flex flex-wrap items-center gap-2 text-black'}>
|
||||||
|
{(data as SelectOptionCellDataPB)?.select_options?.map((option, index) => (
|
||||||
|
<SelectedOption
|
||||||
|
option={option}
|
||||||
|
key={index}
|
||||||
|
cellIdentifier={cellIdentifier}
|
||||||
|
clearValue={() => setValue('')}
|
||||||
|
></SelectedOption>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
className={'py-2'}
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => setValue(e.target.value)}
|
||||||
|
placeholder={t('grid.selectOption.searchOption') ?? ''}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
/>
|
||||||
|
<div className={'font-mono text-shade-3'}>{value.length}/30</div>
|
||||||
|
</div>
|
||||||
|
<div className={'-mx-4 h-[1px] bg-shade-6'}></div>
|
||||||
|
<div className={'font-medium text-shade-3'}>{t('grid.selectOption.panelTitle') ?? ''}</div>
|
||||||
|
<div className={'flex flex-col gap-1'}>
|
||||||
|
{(databaseStore.fields[cellIdentifier.fieldId]?.fieldOptions as ISelectOptionType).selectOptions.map(
|
||||||
|
(option, index) => (
|
||||||
|
<CellOption
|
||||||
|
key={index}
|
||||||
|
option={option}
|
||||||
|
checked={
|
||||||
|
!!(data as SelectOptionCellDataPB)?.select_options?.find(
|
||||||
|
(selectedOption) => selectedOption.id === option.selectOptionId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
cellIdentifier={cellIdentifier}
|
||||||
|
openOptionDetail={openOptionDetail}
|
||||||
|
clearValue={() => setValue('')}
|
||||||
|
></CellOption>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopupWindow>
|
||||||
|
);
|
||||||
|
};
|
@ -22,7 +22,7 @@ export const EditCellOptionPopup = ({
|
|||||||
onOutsideClick: () => void;
|
onOutsideClick: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const { t } = useTranslation('');
|
const { t } = useTranslation();
|
||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
@ -0,0 +1,30 @@
|
|||||||
|
import { getBgColor } from '$app/components/_shared/getColor';
|
||||||
|
import { CloseSvg } from '$app/components/_shared/svg/CloseSvg';
|
||||||
|
import { SelectOptionPB } from '@/services/backend';
|
||||||
|
import { SelectOptionCellBackendService } from '$app/stores/effects/database/cell/select_option_bd_svc';
|
||||||
|
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
|
||||||
|
import { MouseEventHandler } from 'react';
|
||||||
|
|
||||||
|
export const SelectedOption = ({
|
||||||
|
option,
|
||||||
|
cellIdentifier,
|
||||||
|
clearValue,
|
||||||
|
}: {
|
||||||
|
option: SelectOptionPB;
|
||||||
|
cellIdentifier: CellIdentifier;
|
||||||
|
clearValue: () => void;
|
||||||
|
}) => {
|
||||||
|
const onUnselectOptionClick: MouseEventHandler = async () => {
|
||||||
|
await new SelectOptionCellBackendService(cellIdentifier).unselectOption([option.id]);
|
||||||
|
clearValue();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`${getBgColor(option.color)} flex items-center gap-0.5 rounded px-1 py-0.5`}>
|
||||||
|
<span>{option?.name ?? ''}</span>
|
||||||
|
<button onClick={onUnselectOptionClick} className={'h-5 w-5 cursor-pointer'}>
|
||||||
|
<CloseSvg></CloseSvg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,169 @@
|
|||||||
|
import { DropDownShowSvg } from '$app/components/_shared/svg/DropDownShowSvg';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useRow } from '$app/components/_shared/database-hooks/useRow';
|
||||||
|
import { DatabaseController } from '$app/stores/effects/database/database_controller';
|
||||||
|
import { RowInfo } from '$app/stores/effects/database/row/row_cache';
|
||||||
|
import { FieldTypeIcon } from '$app/components/_shared/EditRow/FieldTypeIcon';
|
||||||
|
import { useAppSelector } from '$app/stores/store';
|
||||||
|
import { Switch } from '$app/components/_shared/Switch';
|
||||||
|
import { FieldType } from '@/services/backend';
|
||||||
|
import { FieldTypeName } from '$app/components/_shared/EditRow/FieldTypeName';
|
||||||
|
import { TrashSvg } from '$app/components/_shared/svg/TrashSvg';
|
||||||
|
import { MultiSelectTypeSvg } from '$app/components/_shared/svg/MultiSelectTypeSvg';
|
||||||
|
import { DocumentSvg } from '$app/components/_shared/svg/DocumentSvg';
|
||||||
|
import { SingleSelectTypeSvg } from '$app/components/_shared/svg/SingleSelectTypeSvg';
|
||||||
|
|
||||||
|
const typesOrder: FieldType[] = [
|
||||||
|
FieldType.RichText,
|
||||||
|
FieldType.Number,
|
||||||
|
FieldType.DateTime,
|
||||||
|
FieldType.SingleSelect,
|
||||||
|
FieldType.MultiSelect,
|
||||||
|
FieldType.Checkbox,
|
||||||
|
FieldType.URL,
|
||||||
|
FieldType.Checklist,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const PropertiesPanel = ({
|
||||||
|
viewId,
|
||||||
|
controller,
|
||||||
|
rowInfo,
|
||||||
|
onDeletePropertyClick,
|
||||||
|
}: {
|
||||||
|
viewId: string;
|
||||||
|
controller: DatabaseController;
|
||||||
|
rowInfo: RowInfo;
|
||||||
|
onDeletePropertyClick: (fieldId: string) => void;
|
||||||
|
}) => {
|
||||||
|
const { cells } = useRow(viewId, controller, rowInfo);
|
||||||
|
const databaseStore = useAppSelector((state) => state.database);
|
||||||
|
|
||||||
|
const [showAddedProperties, setShowAddedProperties] = useState(true);
|
||||||
|
const [showBasicProperties, setShowBasicProperties] = useState(false);
|
||||||
|
const [showAdvancedProperties, setShowAdvancedProperties] = useState(false);
|
||||||
|
|
||||||
|
const [hoveredPropertyIndex, setHoveredPropertyIndex] = useState(-1);
|
||||||
|
const [hiddenProperties, setHiddenProperties] = useState<boolean[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setHiddenProperties(cells.map(() => false));
|
||||||
|
}, [cells]);
|
||||||
|
|
||||||
|
const toggleHideProperty = (v: boolean, index: number) => {
|
||||||
|
setHiddenProperties(hiddenProperties.map((h, i) => (i === index ? !v : h)));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={'flex flex-col gap-2 overflow-auto py-12 px-4'}>
|
||||||
|
<div
|
||||||
|
onClick={() => setShowAddedProperties(!showAddedProperties)}
|
||||||
|
className={'flex cursor-pointer items-center justify-between gap-8 rounded-lg px-2 py-2 hover:bg-shade-6'}
|
||||||
|
>
|
||||||
|
<div className={'text-sm'}>Added Properties</div>
|
||||||
|
<i className={`h-5 w-5 transition-transform duration-500 ${showAddedProperties && 'rotate-180'}`}>
|
||||||
|
<DropDownShowSvg></DropDownShowSvg>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div className={'flex flex-col text-xs'} onMouseLeave={() => setHoveredPropertyIndex(-1)}>
|
||||||
|
{showAddedProperties &&
|
||||||
|
cells.map((cell, cellIndex) => (
|
||||||
|
<div
|
||||||
|
key={cellIndex}
|
||||||
|
onMouseEnter={() => setHoveredPropertyIndex(cellIndex)}
|
||||||
|
className={
|
||||||
|
'flex cursor-pointer items-center justify-between gap-4 rounded-lg px-2 py-1 hover:bg-main-secondary'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className={'flex items-center gap-2 text-black'}>
|
||||||
|
<div className={'flex h-5 w-5 flex-shrink-0 items-center justify-center'}>
|
||||||
|
<FieldTypeIcon fieldType={cell.cellIdentifier.fieldType}></FieldTypeIcon>
|
||||||
|
</div>
|
||||||
|
<span className={'overflow-hidden text-ellipsis whitespace-nowrap'}>
|
||||||
|
{databaseStore.fields[cell.cellIdentifier.fieldId]?.title ?? ''}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className={'flex items-center'}>
|
||||||
|
<i
|
||||||
|
onClick={() => onDeletePropertyClick(cell.cellIdentifier.fieldId)}
|
||||||
|
className={`h-[16px] w-[16px] text-black transition-opacity duration-300 ${
|
||||||
|
hoveredPropertyIndex === cellIndex ? 'opacity-100' : 'opacity-0'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<TrashSvg></TrashSvg>
|
||||||
|
</i>
|
||||||
|
<Switch value={!hiddenProperties[cellIndex]} setValue={(v) => toggleHideProperty(v, cellIndex)}></Switch>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
onClick={() => setShowBasicProperties(!showBasicProperties)}
|
||||||
|
className={'flex cursor-pointer items-center justify-between gap-8 rounded-lg px-2 py-2 hover:bg-shade-6'}
|
||||||
|
>
|
||||||
|
<div className={'text-sm'}>Basic Properties</div>
|
||||||
|
<i className={`h-5 w-5 transition-transform duration-500 ${showBasicProperties && 'rotate-180'}`}>
|
||||||
|
<DropDownShowSvg></DropDownShowSvg>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div className={'flex flex-col gap-2 text-xs'}>
|
||||||
|
{showBasicProperties && (
|
||||||
|
<div className={'flex flex-col'}>
|
||||||
|
{typesOrder.map((t, i) => (
|
||||||
|
<button
|
||||||
|
onClick={() => console.log('type clicked')}
|
||||||
|
key={i}
|
||||||
|
className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 pr-8 hover:bg-main-secondary'}
|
||||||
|
>
|
||||||
|
<i className={'h-5 w-5'}>
|
||||||
|
<FieldTypeIcon fieldType={t}></FieldTypeIcon>
|
||||||
|
</i>
|
||||||
|
<span>
|
||||||
|
<FieldTypeName fieldType={t}></FieldTypeName>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
onClick={() => setShowAdvancedProperties(!showAdvancedProperties)}
|
||||||
|
className={'flex cursor-pointer items-center justify-between gap-8 rounded-lg px-2 py-2 hover:bg-shade-6'}
|
||||||
|
>
|
||||||
|
<div className={'text-sm'}>Advanced Properties</div>
|
||||||
|
<i className={`h-5 w-5 transition-transform duration-500 ${showAdvancedProperties && 'rotate-180'}`}>
|
||||||
|
<DropDownShowSvg></DropDownShowSvg>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div className={'flex flex-col gap-2 text-xs'}>
|
||||||
|
{showAdvancedProperties && (
|
||||||
|
<div className={'flex flex-col'}>
|
||||||
|
<button
|
||||||
|
className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 pr-8 hover:bg-main-secondary'}
|
||||||
|
>
|
||||||
|
<i className={'h-5 w-5'}>
|
||||||
|
<MultiSelectTypeSvg></MultiSelectTypeSvg>
|
||||||
|
</i>
|
||||||
|
<span>Last edited time</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 pr-8 hover:bg-main-secondary'}
|
||||||
|
>
|
||||||
|
<i className={'h-5 w-5'}>
|
||||||
|
<DocumentSvg></DocumentSvg>
|
||||||
|
</i>
|
||||||
|
<span>Document</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 pr-8 hover:bg-main-secondary'}
|
||||||
|
>
|
||||||
|
<i className={'h-5 w-5'}>
|
||||||
|
<SingleSelectTypeSvg></SingleSelectTypeSvg>
|
||||||
|
</i>
|
||||||
|
<span>Relation to</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -9,7 +9,7 @@ export const PopupWindow = ({
|
|||||||
top,
|
top,
|
||||||
}: {
|
}: {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
className: string;
|
className?: string;
|
||||||
onOutsideClick: () => void;
|
onOutsideClick: () => void;
|
||||||
left: number;
|
left: number;
|
||||||
top: number;
|
top: number;
|
||||||
@ -33,7 +33,7 @@ export const PopupWindow = ({
|
|||||||
} else {
|
} else {
|
||||||
setAdjustedLeft(left);
|
setAdjustedLeft(left);
|
||||||
}
|
}
|
||||||
}, [ref, left, top, window]);
|
}, [ref, left, top]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -41,7 +41,7 @@ export const PopupWindow = ({
|
|||||||
className={
|
className={
|
||||||
'fixed z-10 rounded-lg bg-white shadow-md transition-opacity duration-300 ' +
|
'fixed z-10 rounded-lg bg-white shadow-md transition-opacity duration-300 ' +
|
||||||
(adjustedTop === -100 && adjustedLeft === -100 ? 'opacity-0 ' : 'opacity-100 ') +
|
(adjustedTop === -100 && adjustedLeft === -100 ? 'opacity-0 ' : 'opacity-100 ') +
|
||||||
(className || '')
|
(className ?? '')
|
||||||
}
|
}
|
||||||
style={{ top: `${adjustedTop}px`, left: `${adjustedLeft}px` }}
|
style={{ top: `${adjustedTop}px`, left: `${adjustedLeft}px` }}
|
||||||
>
|
>
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
import { Button } from '$app/components/_shared/Button';
|
||||||
|
|
||||||
|
export const PromptWindow = ({ msg, onYes, onCancel }: { msg: string; onYes: () => void; onCancel: () => void }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className='fixed inset-0 z-20 flex items-center justify-center bg-black/30 backdrop-blur-sm'
|
||||||
|
onClick={() => onCancel()}
|
||||||
|
>
|
||||||
|
<div className={'rounded-xl bg-white p-16'} onClick={(e) => e.stopPropagation()}>
|
||||||
|
<div className={'flex flex-col items-center justify-center gap-8'}>
|
||||||
|
<div className={'text-black'}>{msg}</div>
|
||||||
|
<div className={'flex items-center justify-around gap-4'}>
|
||||||
|
<Button onClick={() => onCancel()} size={'medium-transparent'}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => onYes()} size={'medium'}>
|
||||||
|
Yes
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,8 @@
|
|||||||
|
export const Switch = ({ value, setValue }: { value: boolean; setValue: (v: boolean) => void }) => {
|
||||||
|
return (
|
||||||
|
<label className='form-switch' style={{ transform: 'scale(0.5)', marginRight: '-16px' }}>
|
||||||
|
<input type='checkbox' checked={value} onChange={() => setValue(!value)} />
|
||||||
|
<i></i>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,12 @@
|
|||||||
|
export const DragElementSvg = () => {
|
||||||
|
return (
|
||||||
|
<svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||||
|
<rect x='9' y='3' width='2' height='2' rx='0.5' fill='currentColor' />
|
||||||
|
<rect x='5' y='3' width='2' height='2' rx='0.5' fill='currentColor' />
|
||||||
|
<rect x='9' y='7' width='2' height='2' rx='0.5' fill='currentColor' />
|
||||||
|
<rect x='5' y='7' width='2' height='2' rx='0.5' fill='currentColor' />
|
||||||
|
<rect x='9' y='11' width='2' height='2' rx='0.5' fill='currentColor' />
|
||||||
|
<rect x='5' y='11' width='2' height='2' rx='0.5' fill='currentColor' />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,9 @@
|
|||||||
|
export const ImageSvg = () => {
|
||||||
|
return (
|
||||||
|
<svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||||
|
<rect x='1.5' y='3' width='13' height='10' rx='1.5' stroke='currentColor' />
|
||||||
|
<circle cx='5.5' cy='6.5' r='1' stroke='currentColor' />
|
||||||
|
<path d='M5 13L10.112 8.45603C10.4211 8.18126 10.8674 8.12513 11.235 8.31482L14.5 10' stroke='currentColor' />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
@ -9,11 +9,10 @@ import { EarthSvg } from '../../_shared/svg/EarthSvg';
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { LanguageSelectPopup } from '../../_shared/LanguageSelectPopup';
|
import { LanguageSelectPopup } from '../../_shared/LanguageSelectPopup';
|
||||||
|
|
||||||
|
|
||||||
export const Login = () => {
|
export const Login = () => {
|
||||||
const { showPassword, onTogglePassword, onSignInClick, email, setEmail, password, setPassword, authError } =
|
const { showPassword, onTogglePassword, onSignInClick, email, setEmail, password, setPassword, authError } =
|
||||||
useLogin();
|
useLogin();
|
||||||
const { t } = useTranslation('');
|
const { t } = useTranslation();
|
||||||
const [showLanguagePopup, setShowLanguagePopup] = useState(false);
|
const [showLanguagePopup, setShowLanguagePopup] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -34,17 +33,17 @@ export const Login = () => {
|
|||||||
<input
|
<input
|
||||||
type='text'
|
type='text'
|
||||||
className={`input w-full ${authError && 'error'}`}
|
className={`input w-full ${authError && 'error'}`}
|
||||||
placeholder={t('signIn.emailHint') || ''}
|
placeholder={t('signIn.emailHint') ?? ''}
|
||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<div className='relative w-full'>
|
<div className='relative w-full'>
|
||||||
{/* Password input field */}
|
{/* Password input field */}
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type={showPassword ? 'text' : 'password'}
|
type={showPassword ? 'text' : 'password'}
|
||||||
className={`input w-full !pr-10 ${authError && 'error'}`}
|
className={`input w-full !pr-10 ${authError && 'error'}`}
|
||||||
placeholder={t('signIn.passwordHint') || ''}
|
placeholder={t('signIn.passwordHint') ?? ''}
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
@ -27,7 +27,7 @@ export const SignUp = () => {
|
|||||||
setRepeatedPassword,
|
setRepeatedPassword,
|
||||||
authError,
|
authError,
|
||||||
} = useSignUp();
|
} = useSignUp();
|
||||||
const { t } = useTranslation('');
|
const { t } = useTranslation();
|
||||||
const [showLanguagePopup, setShowLanguagePopup] = useState(false);
|
const [showLanguagePopup, setShowLanguagePopup] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -45,7 +45,7 @@ export const SignUp = () => {
|
|||||||
<input
|
<input
|
||||||
type='text'
|
type='text'
|
||||||
className={`input w-full ${authError && 'error'}`}
|
className={`input w-full ${authError && 'error'}`}
|
||||||
placeholder={t('signUp.emailHint') || ''}
|
placeholder={t('signUp.emailHint') ?? ''}
|
||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
/>
|
/>
|
||||||
@ -61,7 +61,7 @@ export const SignUp = () => {
|
|||||||
<input
|
<input
|
||||||
type={showPassword ? 'text' : 'password'}
|
type={showPassword ? 'text' : 'password'}
|
||||||
className={`input w-full !pr-10 ${authError && 'error'}`}
|
className={`input w-full !pr-10 ${authError && 'error'}`}
|
||||||
placeholder={t('signUp.passwordHint') || ''}
|
placeholder={t('signUp.passwordHint') ?? ''}
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
/>
|
/>
|
||||||
@ -79,7 +79,7 @@ export const SignUp = () => {
|
|||||||
<input
|
<input
|
||||||
type={showConfirmPassword ? 'text' : 'password'}
|
type={showConfirmPassword ? 'text' : 'password'}
|
||||||
className={`input w-full !pr-10 ${authError && 'error'}`}
|
className={`input w-full !pr-10 ${authError && 'error'}`}
|
||||||
placeholder={t('signUp.repeatPasswordHint') || ''}
|
placeholder={t('signUp.repeatPasswordHint') ?? ''}
|
||||||
value={repeatedPassword}
|
value={repeatedPassword}
|
||||||
onChange={(e) => setRepeatedPassword(e.target.value)}
|
onChange={(e) => setRepeatedPassword(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { SearchInput } from '../_shared/SearchInput';
|
import { SearchInput } from '../_shared/SearchInput';
|
||||||
import { BoardGroup } from './BoardGroup';
|
import { BoardGroup } from './BoardGroup';
|
||||||
import { NewBoardBlock } from './NewBoardBlock';
|
|
||||||
import { useDatabase } from '../_shared/database-hooks/useDatabase';
|
import { useDatabase } from '../_shared/database-hooks/useDatabase';
|
||||||
import { ViewLayoutPB } from '@/services/backend';
|
import { ViewLayoutPB } from '@/services/backend';
|
||||||
import { DragDropContext } from 'react-beautiful-dnd';
|
import { DragDropContext } from 'react-beautiful-dnd';
|
||||||
@ -45,7 +44,6 @@ export const Board = ({ viewId, title }: { viewId: string; title: string }) => {
|
|||||||
onOpenRow={onOpenRow}
|
onOpenRow={onOpenRow}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<NewBoardBlock onClick={() => console.log('new block')}></NewBoardBlock>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DragDropContext>
|
</DragDropContext>
|
||||||
|
@ -8,6 +8,7 @@ import { MouseEventHandler, useState } from 'react';
|
|||||||
import { PopupWindow } from '$app/components/_shared/PopupWindow';
|
import { PopupWindow } from '$app/components/_shared/PopupWindow';
|
||||||
import { TrashSvg } from '$app/components/_shared/svg/TrashSvg';
|
import { TrashSvg } from '$app/components/_shared/svg/TrashSvg';
|
||||||
import { RowBackendService } from '$app/stores/effects/database/row/row_bd_svc';
|
import { RowBackendService } from '$app/stores/effects/database/row/row_bd_svc';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export const BoardCard = ({
|
export const BoardCard = ({
|
||||||
index,
|
index,
|
||||||
@ -24,6 +25,8 @@ export const BoardCard = ({
|
|||||||
groupByFieldId: string;
|
groupByFieldId: string;
|
||||||
onOpenRow: (rowId: RowInfo) => void;
|
onOpenRow: (rowId: RowInfo) => void;
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { cells } = useRow(viewId, controller, rowInfo);
|
const { cells } = useRow(viewId, controller, rowInfo);
|
||||||
|
|
||||||
const [showCardPopup, setShowCardPopup] = useState(false);
|
const [showCardPopup, setShowCardPopup] = useState(false);
|
||||||
@ -95,7 +98,7 @@ export const BoardCard = ({
|
|||||||
<i className={'h-5 w-5'}>
|
<i className={'h-5 w-5'}>
|
||||||
<TrashSvg></TrashSvg>
|
<TrashSvg></TrashSvg>
|
||||||
</i>
|
</i>
|
||||||
<span className={'flex-shrink-0'}>Delete</span>
|
<span className={'flex-shrink-0'}>{t('grid.row.delete')}</span>
|
||||||
</button>
|
</button>
|
||||||
</PopupWindow>
|
</PopupWindow>
|
||||||
)}
|
)}
|
||||||
|
@ -7,6 +7,7 @@ import { BoardDateCell } from './BoardDateCell';
|
|||||||
import { BoardTextCell } from './BoardTextCell';
|
import { BoardTextCell } from './BoardTextCell';
|
||||||
import { BoardUrlCell } from '$app/components/board/BoardUrlCell';
|
import { BoardUrlCell } from '$app/components/board/BoardUrlCell';
|
||||||
import { BoardCheckboxCell } from '$app/components/board/BoardCheckboxCell';
|
import { BoardCheckboxCell } from '$app/components/board/BoardCheckboxCell';
|
||||||
|
import { BoardCheckListCell } from '$app/components/board/BoardCheckListCell';
|
||||||
|
|
||||||
export const BoardCell = ({
|
export const BoardCell = ({
|
||||||
cellIdentifier,
|
cellIdentifier,
|
||||||
@ -19,14 +20,18 @@ export const BoardCell = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{cellIdentifier.fieldType === FieldType.SingleSelect ||
|
{cellIdentifier.fieldType === FieldType.SingleSelect || cellIdentifier.fieldType === FieldType.MultiSelect ? (
|
||||||
cellIdentifier.fieldType === FieldType.MultiSelect ||
|
|
||||||
cellIdentifier.fieldType === FieldType.Checklist ? (
|
|
||||||
<BoardOptionsCell
|
<BoardOptionsCell
|
||||||
cellIdentifier={cellIdentifier}
|
cellIdentifier={cellIdentifier}
|
||||||
cellCache={cellCache}
|
cellCache={cellCache}
|
||||||
fieldController={fieldController}
|
fieldController={fieldController}
|
||||||
></BoardOptionsCell>
|
></BoardOptionsCell>
|
||||||
|
) : cellIdentifier.fieldType === FieldType.Checklist ? (
|
||||||
|
<BoardCheckListCell
|
||||||
|
cellIdentifier={cellIdentifier}
|
||||||
|
cellCache={cellCache}
|
||||||
|
fieldController={fieldController}
|
||||||
|
></BoardCheckListCell>
|
||||||
) : cellIdentifier.fieldType === FieldType.DateTime ? (
|
) : cellIdentifier.fieldType === FieldType.DateTime ? (
|
||||||
<BoardDateCell
|
<BoardDateCell
|
||||||
cellIdentifier={cellIdentifier}
|
cellIdentifier={cellIdentifier}
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
|
||||||
|
import { CellCache } from '$app/stores/effects/database/cell/cell_cache';
|
||||||
|
import { FieldController } from '$app/stores/effects/database/field/field_controller';
|
||||||
|
import { useCell } from '$app/components/_shared/database-hooks/useCell';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { ISelectOptionType } from '$app_reducers/database/slice';
|
||||||
|
import { SelectOptionCellDataPB } from '@/services/backend';
|
||||||
|
import { useAppSelector } from '$app/stores/store';
|
||||||
|
import { CheckListProgress } from '$app/components/_shared/CheckListProgress';
|
||||||
|
|
||||||
|
export const BoardCheckListCell = ({
|
||||||
|
cellIdentifier,
|
||||||
|
cellCache,
|
||||||
|
fieldController,
|
||||||
|
}: {
|
||||||
|
cellIdentifier: CellIdentifier;
|
||||||
|
cellCache: CellCache;
|
||||||
|
fieldController: FieldController;
|
||||||
|
}) => {
|
||||||
|
const { data } = useCell(cellIdentifier, cellCache, fieldController);
|
||||||
|
|
||||||
|
const databaseStore = useAppSelector((state) => state.database);
|
||||||
|
const [allOptionsCount, setAllOptionsCount] = useState(0);
|
||||||
|
const [selectedOptionsCount, setSelectedOptionsCount] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setAllOptionsCount(
|
||||||
|
(databaseStore.fields[cellIdentifier.fieldId]?.fieldOptions as ISelectOptionType)?.selectOptions?.length ?? 0
|
||||||
|
);
|
||||||
|
}, [databaseStore, cellIdentifier]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedOptionsCount((data as SelectOptionCellDataPB)?.select_options?.length ?? 0);
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
return <CheckListProgress completed={selectedOptionsCount} max={allOptionsCount} />;
|
||||||
|
};
|
@ -14,5 +14,5 @@ export const BoardDateCell = ({
|
|||||||
fieldController: FieldController;
|
fieldController: FieldController;
|
||||||
}) => {
|
}) => {
|
||||||
const { data } = useCell(cellIdentifier, cellCache, fieldController);
|
const { data } = useCell(cellIdentifier, cellCache, fieldController);
|
||||||
return <div>{(data as DateCellDataPB | undefined)?.date || ''}</div>;
|
return <div>{(data as DateCellDataPB | undefined)?.date ?? ''} </div>;
|
||||||
};
|
};
|
||||||
|
@ -5,6 +5,7 @@ import { RowInfo } from '$app/stores/effects/database/row/row_cache';
|
|||||||
import { DatabaseController } from '$app/stores/effects/database/database_controller';
|
import { DatabaseController } from '$app/stores/effects/database/database_controller';
|
||||||
import { Droppable } from 'react-beautiful-dnd';
|
import { Droppable } from 'react-beautiful-dnd';
|
||||||
import { DatabaseGroupController } from '$app/stores/effects/database/group/group_controller';
|
import { DatabaseGroupController } from '$app/stores/effects/database/group/group_controller';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export const BoardGroup = ({
|
export const BoardGroup = ({
|
||||||
viewId,
|
viewId,
|
||||||
@ -23,6 +24,8 @@ export const BoardGroup = ({
|
|||||||
onOpenRow: (rowId: RowInfo) => void;
|
onOpenRow: (rowId: RowInfo) => void;
|
||||||
group: DatabaseGroupController;
|
group: DatabaseGroupController;
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'flex h-full w-[250px] flex-col rounded-lg bg-surface-1'}>
|
<div className={'flex h-full w-[250px] flex-col rounded-lg bg-surface-1'}>
|
||||||
<div className={'flex items-center justify-between p-4'}>
|
<div className={'flex items-center justify-between p-4'}>
|
||||||
@ -73,7 +76,7 @@ export const BoardGroup = ({
|
|||||||
<span className={'h-5 w-5'}>
|
<span className={'h-5 w-5'}>
|
||||||
<AddSvg></AddSvg>
|
<AddSvg></AddSvg>
|
||||||
</span>
|
</span>
|
||||||
<span>New</span>
|
<span>{t('board.column.create_new_card')}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -20,9 +20,9 @@ export const BoardOptionsCell = ({
|
|||||||
<div className={'flex flex-wrap items-center gap-2 py-2 text-xs text-black'}>
|
<div className={'flex flex-wrap items-center gap-2 py-2 text-xs text-black'}>
|
||||||
{(data as SelectOptionCellDataPB)?.select_options?.map((option, index) => (
|
{(data as SelectOptionCellDataPB)?.select_options?.map((option, index) => (
|
||||||
<div className={`${getBgColor(option.color)} rounded px-2 py-0.5`} key={index}>
|
<div className={`${getBgColor(option.color)} rounded px-2 py-0.5`} key={index}>
|
||||||
{option?.name || ''}
|
{option?.name ?? ''}
|
||||||
</div>
|
</div>
|
||||||
)) || ''}
|
))}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -14,7 +14,7 @@ export const BoardSettingsPopup = ({
|
|||||||
onGroupClick: () => void;
|
onGroupClick: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [settingsItems, setSettingsItems] = useState<IPopupItem[]>([]);
|
const [settingsItems, setSettingsItems] = useState<IPopupItem[]>([]);
|
||||||
const { t } = useTranslation('');
|
const { t } = useTranslation();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSettingsItems([
|
setSettingsItems([
|
||||||
{
|
{
|
||||||
|
@ -16,9 +16,10 @@ export const BoardTextCell = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{((data as string | undefined) || '').split('\n').map((line, index) => (
|
{((data as string | undefined) ?? '').split('\n').map((line, index) => (
|
||||||
<div key={index}>{line}</div>
|
<div key={index}>{line}</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -17,8 +17,8 @@ export const BoardUrlCell = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<a className={'text-main-accent hover:underline'} href={(data as URLCellDataPB)?.url || ''} target={'_blank'}>
|
<a className={'text-main-accent hover:underline'} href={(data as URLCellDataPB)?.url ?? ''} target={'_blank'}>
|
||||||
{(data as URLCellDataPB)?.content || ''}
|
{(data as URLCellDataPB)?.content ?? ''}
|
||||||
</a>
|
</a>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
import AddSvg from '../_shared/svg/AddSvg';
|
|
||||||
|
|
||||||
export const NewBoardBlock = ({ onClick }: { onClick: () => void }) => {
|
|
||||||
return (
|
|
||||||
<div className={'w-[250px]'}>
|
|
||||||
<button onClick={onClick} className={'flex w-full items-center gap-2 rounded-lg px-4 py-2 hover:bg-surface-2'}>
|
|
||||||
<span className={'h-5 w-5'}>
|
|
||||||
<AddSvg></AddSvg>
|
|
||||||
</span>
|
|
||||||
<span>Add Block</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,7 +1,7 @@
|
|||||||
import { CellIdentifier } from '@/appflowy_app/stores/effects/database/cell/cell_bd_svc';
|
import { CellIdentifier } from '@/appflowy_app/stores/effects/database/cell/cell_bd_svc';
|
||||||
import { CellCache } from '@/appflowy_app/stores/effects/database/cell/cell_cache';
|
import { CellCache } from '@/appflowy_app/stores/effects/database/cell/cell_cache';
|
||||||
import { FieldController } from '@/appflowy_app/stores/effects/database/field/field_controller';
|
import { FieldController } from '@/appflowy_app/stores/effects/database/field/field_controller';
|
||||||
import { EditCheckboxCell } from '../../_shared/EditRow/EditCheckboxCell';
|
import { EditCheckboxCell } from '../../_shared/EditRow/InlineEditFields/EditCheckboxCell';
|
||||||
import { useCell } from '../../_shared/database-hooks/useCell';
|
import { useCell } from '../../_shared/database-hooks/useCell';
|
||||||
|
|
||||||
export const GridCheckBox = ({
|
export const GridCheckBox = ({
|
||||||
|
@ -3,9 +3,9 @@ import { CellCache } from '@/appflowy_app/stores/effects/database/cell/cell_cach
|
|||||||
import { FieldController } from '@/appflowy_app/stores/effects/database/field/field_controller';
|
import { FieldController } from '@/appflowy_app/stores/effects/database/field/field_controller';
|
||||||
import { useCell } from '../../_shared/database-hooks/useCell';
|
import { useCell } from '../../_shared/database-hooks/useCell';
|
||||||
import { DateCellDataPB } from '@/services/backend';
|
import { DateCellDataPB } from '@/services/backend';
|
||||||
import { EditCellDate } from '../../_shared/EditRow/EditCellDate';
|
import { EditCellDate } from '../../_shared/EditRow/Date/EditCellDate';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { DatePickerPopup } from '../../_shared/EditRow/DatePickerPopup';
|
import { DatePickerPopup } from '../../_shared/EditRow/Date/DatePickerPopup';
|
||||||
|
|
||||||
export const GridDate = ({
|
export const GridDate = ({
|
||||||
cellIdentifier,
|
cellIdentifier,
|
||||||
|
@ -2,7 +2,7 @@ import { CellIdentifier } from '@/appflowy_app/stores/effects/database/cell/cell
|
|||||||
import { CellCache } from '@/appflowy_app/stores/effects/database/cell/cell_cache';
|
import { CellCache } from '@/appflowy_app/stores/effects/database/cell/cell_cache';
|
||||||
import { FieldController } from '@/appflowy_app/stores/effects/database/field/field_controller';
|
import { FieldController } from '@/appflowy_app/stores/effects/database/field/field_controller';
|
||||||
import { useCell } from '../../_shared/database-hooks/useCell';
|
import { useCell } from '../../_shared/database-hooks/useCell';
|
||||||
import { EditCellNumber } from '../../_shared/EditRow/EditCellNumber';
|
import { EditCellNumber } from '../../_shared/EditRow/InlineEditFields/EditCellNumber';
|
||||||
|
|
||||||
export const GridNumberCell = ({
|
export const GridNumberCell = ({
|
||||||
cellIdentifier,
|
cellIdentifier,
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { CellOptions } from '$app/components/_shared/EditRow/CellOptions';
|
import { CellOptions } from '$app/components/_shared/EditRow/Options/CellOptions';
|
||||||
import { CellIdentifier } from '@/appflowy_app/stores/effects/database/cell/cell_bd_svc';
|
import { CellIdentifier } from '@/appflowy_app/stores/effects/database/cell/cell_bd_svc';
|
||||||
import { CellCache } from '@/appflowy_app/stores/effects/database/cell/cell_cache';
|
import { CellCache } from '@/appflowy_app/stores/effects/database/cell/cell_cache';
|
||||||
import { FieldController } from '@/appflowy_app/stores/effects/database/field/field_controller';
|
import { FieldController } from '@/appflowy_app/stores/effects/database/field/field_controller';
|
||||||
import { useCell } from '$app/components/_shared/database-hooks/useCell';
|
import { useCell } from '$app/components/_shared/database-hooks/useCell';
|
||||||
import { CellOptionsPopup } from '$app/components/_shared/EditRow/CellOptionsPopup';
|
import { CellOptionsPopup } from '$app/components/_shared/EditRow/Options/CellOptionsPopup';
|
||||||
import { EditCellOptionPopup } from '$app/components/_shared/EditRow/EditCellOptionPopup';
|
import { EditCellOptionPopup } from '$app/components/_shared/EditRow/Options/EditCellOptionPopup';
|
||||||
import { SelectOptionCellDataPB, SelectOptionPB } from '@/services/backend';
|
import { SelectOptionCellDataPB, SelectOptionPB } from '@/services/backend';
|
||||||
|
|
||||||
export default function GridSingleSelectOptions({
|
export default function GridSingleSelectOptions({
|
||||||
|
@ -2,7 +2,7 @@ import { CellIdentifier } from '@/appflowy_app/stores/effects/database/cell/cell
|
|||||||
import { CellCache } from '@/appflowy_app/stores/effects/database/cell/cell_cache';
|
import { CellCache } from '@/appflowy_app/stores/effects/database/cell/cell_cache';
|
||||||
import { FieldController } from '@/appflowy_app/stores/effects/database/field/field_controller';
|
import { FieldController } from '@/appflowy_app/stores/effects/database/field/field_controller';
|
||||||
import { useCell } from '../../_shared/database-hooks/useCell';
|
import { useCell } from '../../_shared/database-hooks/useCell';
|
||||||
import { EditCellText } from '../../_shared/EditRow/EditCellText';
|
import { EditCellText } from '../../_shared/EditRow/InlineEditFields/EditCellText';
|
||||||
|
|
||||||
export default function GridTextCell({
|
export default function GridTextCell({
|
||||||
cellIdentifier,
|
cellIdentifier,
|
||||||
|
@ -2,7 +2,7 @@ import { CellIdentifier } from '@/appflowy_app/stores/effects/database/cell/cell
|
|||||||
import { CellCache } from '@/appflowy_app/stores/effects/database/cell/cell_cache';
|
import { CellCache } from '@/appflowy_app/stores/effects/database/cell/cell_cache';
|
||||||
import { FieldController } from '@/appflowy_app/stores/effects/database/field/field_controller';
|
import { FieldController } from '@/appflowy_app/stores/effects/database/field/field_controller';
|
||||||
import { useCell } from '../../_shared/database-hooks/useCell';
|
import { useCell } from '../../_shared/database-hooks/useCell';
|
||||||
import { EditCellUrl } from '../../_shared/EditRow/EditCellUrl';
|
import { EditCellUrl } from '../../_shared/EditRow/InlineEditFields/EditCellUrl';
|
||||||
import { URLCellDataPB } from '@/services/backend';
|
import { URLCellDataPB } from '@/services/backend';
|
||||||
|
|
||||||
export const GridUrl = ({
|
export const GridUrl = ({
|
||||||
|
@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
export const GridTableHeader = ({ controller }: { controller: DatabaseController }) => {
|
export const GridTableHeader = ({ controller }: { controller: DatabaseController }) => {
|
||||||
const { fields, onAddField } = useGridTableHeaderHooks(controller);
|
const { fields, onAddField } = useGridTableHeaderHooks(controller);
|
||||||
const { t } = useTranslation('');
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -4,7 +4,7 @@ import { useGridAddRow } from './GridAddRow.hooks';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
export const GridAddRow = ({ controller }: { controller: DatabaseController }) => {
|
export const GridAddRow = ({ controller }: { controller: DatabaseController }) => {
|
||||||
const { addRow } = useGridAddRow(controller);
|
const { addRow } = useGridAddRow(controller);
|
||||||
const { t } = useTranslation('');
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -21,8 +21,8 @@ export const Breadcrumbs = ({ menuHidden, onShowMenuClick }: { menuHidden: boole
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const page = pagesStore.find((p) => p.id === activePageId);
|
const page = pagesStore.find((p) => p.id === activePageId);
|
||||||
const folder = foldersStore.find((f) => f.id === page?.folderId);
|
const folder = foldersStore.find((f) => f.id === page?.folderId);
|
||||||
setFolderName(folder?.title || '');
|
setFolderName(folder?.title ?? '');
|
||||||
setPageName(page?.title || '');
|
setPageName(page?.title ?? '');
|
||||||
}, [pagesStore, foldersStore, activePageId]);
|
}, [pagesStore, foldersStore, activePageId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { foldersActions, IFolder } from '../../../stores/reducers/folders/slice';
|
import { foldersActions, IFolder } from '$app_reducers/folders/slice';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useAppDispatch, useAppSelector } from '../../../stores/store';
|
import { useAppDispatch, useAppSelector } from '$app/stores/store';
|
||||||
import { IPage, pagesActions } from '../../../stores/reducers/pages/slice';
|
import { IPage, pagesActions } from '$app_reducers/pages/slice';
|
||||||
import { ViewLayoutPB } from '@/services/backend';
|
import { ViewLayoutPB } from '@/services/backend';
|
||||||
import { AppBackendService } from '../../../stores/effects/folder/app/app_bd_svc';
|
import { AppBackendService } from '$app/stores/effects/folder/app/app_bd_svc';
|
||||||
import { WorkspaceBackendService } from '../../../stores/effects/folder/workspace/workspace_bd_svc';
|
import { WorkspaceBackendService } from '$app/stores/effects/folder/workspace/workspace_bd_svc';
|
||||||
|
|
||||||
import { AppObserver } from '../../../stores/effects/folder/app/app_observer';
|
import { AppObserver } from '$app/stores/effects/folder/app/app_observer';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { INITIAL_FOLDER_HEIGHT, PAGE_ITEM_HEIGHT } from '../../_shared/constants';
|
import { INITIAL_FOLDER_HEIGHT, PAGE_ITEM_HEIGHT } from '../../_shared/constants';
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
|
|||||||
|
|
||||||
// Backend services
|
// Backend services
|
||||||
const appBackendService = new AppBackendService(folder.id);
|
const appBackendService = new AppBackendService(folder.id);
|
||||||
const workspaceBackendService = new WorkspaceBackendService(workspace.id || '');
|
const workspaceBackendService = new WorkspaceBackendService(workspace.id ?? '');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void appObserver.subscribe({
|
void appObserver.subscribe({
|
||||||
|
@ -2,9 +2,9 @@ import { Details2Svg } from '../../_shared/svg/Details2Svg';
|
|||||||
import AddSvg from '../../_shared/svg/AddSvg';
|
import AddSvg from '../../_shared/svg/AddSvg';
|
||||||
import { NavItemOptionsPopup } from './NavItemOptionsPopup';
|
import { NavItemOptionsPopup } from './NavItemOptionsPopup';
|
||||||
import { NewPagePopup } from './NewPagePopup';
|
import { NewPagePopup } from './NewPagePopup';
|
||||||
import { IFolder } from '../../../stores/reducers/folders/slice';
|
import { IFolder } from '$app_reducers/folders/slice';
|
||||||
import { useFolderEvents } from './FolderItem.hooks';
|
import { useFolderEvents } from './FolderItem.hooks';
|
||||||
import { IPage } from '../../../stores/reducers/pages/slice';
|
import { IPage } from '$app_reducers/pages/slice';
|
||||||
import { PageItem } from './PageItem';
|
import { PageItem } from './PageItem';
|
||||||
import { Button } from '../../_shared/Button';
|
import { Button } from '../../_shared/Button';
|
||||||
import { RenamePopup } from './RenamePopup';
|
import { RenamePopup } from './RenamePopup';
|
||||||
|
@ -5,7 +5,7 @@ import { WorkspaceBackendService } from '../../../stores/effects/folder/workspac
|
|||||||
export const useNewFolder = () => {
|
export const useNewFolder = () => {
|
||||||
const appDispatch = useAppDispatch();
|
const appDispatch = useAppDispatch();
|
||||||
const workspace = useAppSelector((state) => state.workspace);
|
const workspace = useAppSelector((state) => state.workspace);
|
||||||
const workspaceBackendService = new WorkspaceBackendService(workspace.id || '');
|
const workspaceBackendService = new WorkspaceBackendService(workspace.id ?? '');
|
||||||
|
|
||||||
const onNewFolder = async () => {
|
const onNewFolder = async () => {
|
||||||
const newApp = await workspaceBackendService.createApp({
|
const newApp = await workspaceBackendService.createApp({
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { IPage, pagesActions } from '../../../stores/reducers/pages/slice';
|
import { IPage, pagesActions } from '$app_reducers/pages/slice';
|
||||||
import { useAppDispatch } from '../../../stores/store';
|
import { useAppDispatch } from '$app/stores/store';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { ViewBackendService } from '../../../stores/effects/folder/view/view_bd_svc';
|
import { ViewBackendService } from '$app/stores/effects/folder/view/view_bd_svc';
|
||||||
import { useError } from '../../error/Error.hooks';
|
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import { ViewPB } from '@/services/backend';
|
||||||
|
|
||||||
export const usePageEvents = (page: IPage) => {
|
export const usePageEvents = (page: IPage) => {
|
||||||
const appDispatch = useAppDispatch();
|
const appDispatch = useAppDispatch();
|
||||||
@ -41,8 +41,9 @@ export const usePageEvents = (page: IPage) => {
|
|||||||
appDispatch(pagesActions.deletePage({ id: page.id }));
|
appDispatch(pagesActions.deletePage({ id: page.id }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const duplicatePage = () => {
|
const duplicatePage = async () => {
|
||||||
closePopup();
|
closePopup();
|
||||||
|
await viewBackendService.duplicate(ViewPB.fromObject({}));
|
||||||
appDispatch(
|
appDispatch(
|
||||||
pagesActions.addPage({ id: nanoid(8), pageType: page.pageType, title: page.title, folderId: page.folderId })
|
pagesActions.addPage({ id: nanoid(8), pageType: page.pageType, title: page.title, folderId: page.folderId })
|
||||||
);
|
);
|
||||||
|
@ -9,7 +9,7 @@ export const useWorkspace = () => {
|
|||||||
|
|
||||||
const appDispatch = useAppDispatch();
|
const appDispatch = useAppDispatch();
|
||||||
|
|
||||||
const userBackendService: UserBackendService = new UserBackendService(currentUser.id || 0);
|
const userBackendService: UserBackendService = new UserBackendService(currentUser.id ?? 0);
|
||||||
|
|
||||||
const loadWorkspaceItems = async () => {
|
const loadWorkspaceItems = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -37,6 +37,8 @@ import { SortSvg } from '$app/components/_shared/svg/SortSvg';
|
|||||||
import { TextTypeSvg } from '$app/components/_shared/svg/TextTypeSvg';
|
import { TextTypeSvg } from '$app/components/_shared/svg/TextTypeSvg';
|
||||||
import { TrashSvg } from '$app/components/_shared/svg/TrashSvg';
|
import { TrashSvg } from '$app/components/_shared/svg/TrashSvg';
|
||||||
import { UrlTypeSvg } from '$app/components/_shared/svg/UrlTypeSvg';
|
import { UrlTypeSvg } from '$app/components/_shared/svg/UrlTypeSvg';
|
||||||
|
import { DragElementSvg } from '$app/components/_shared/svg/DragElementSvg';
|
||||||
|
import { ImageSvg } from '$app/components/_shared/svg/ImageSvg';
|
||||||
|
|
||||||
export const AllIcons = () => {
|
export const AllIcons = () => {
|
||||||
return (
|
return (
|
||||||
@ -83,6 +85,9 @@ export const AllIcons = () => {
|
|||||||
<i className={'h-5 w-5'} title={'DocumentSvg'}>
|
<i className={'h-5 w-5'} title={'DocumentSvg'}>
|
||||||
<DocumentSvg></DocumentSvg>
|
<DocumentSvg></DocumentSvg>
|
||||||
</i>
|
</i>
|
||||||
|
<i className={'h-5 w-5'} title={'DragElementSvg'}>
|
||||||
|
<DragElementSvg></DragElementSvg>
|
||||||
|
</i>
|
||||||
<i className={'h-5 w-5'} title={'DropDownShowSvg'}>
|
<i className={'h-5 w-5'} title={'DropDownShowSvg'}>
|
||||||
<DropDownShowSvg></DropDownShowSvg>
|
<DropDownShowSvg></DropDownShowSvg>
|
||||||
</i>
|
</i>
|
||||||
@ -116,6 +121,9 @@ export const AllIcons = () => {
|
|||||||
<i className={'h-5 w-5'} title={'HideMenuSvg'}>
|
<i className={'h-5 w-5'} title={'HideMenuSvg'}>
|
||||||
<HideMenuSvg></HideMenuSvg>
|
<HideMenuSvg></HideMenuSvg>
|
||||||
</i>
|
</i>
|
||||||
|
<i className={'h-5 w-5'} title={'ImageSvg'}>
|
||||||
|
<ImageSvg></ImageSvg>
|
||||||
|
</i>
|
||||||
<i className={'h-5 w-5'} title={'InformationSvg'}>
|
<i className={'h-5 w-5'} title={'InformationSvg'}>
|
||||||
<InformationSvg></InformationSvg>
|
<InformationSvg></InformationSvg>
|
||||||
</i>
|
</i>
|
||||||
|
@ -5,5 +5,6 @@ import './styles/tailwind.css';
|
|||||||
import './styles/font.css';
|
import './styles/font.css';
|
||||||
import './styles/template.css';
|
import './styles/template.css';
|
||||||
import './styles/Calendar.css';
|
import './styles/Calendar.css';
|
||||||
|
import './styles/switch.css';
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />);
|
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />);
|
||||||
|
58
frontend/appflowy_tauri/src/styles/switch.css
Normal file
58
frontend/appflowy_tauri/src/styles/switch.css
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
.form-switch {
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
.form-switch i {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
width: 46px;
|
||||||
|
height: 26px;
|
||||||
|
@apply bg-shade-6;
|
||||||
|
border-radius: 23px;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
transition: all 0.3s linear;
|
||||||
|
}
|
||||||
|
.form-switch i::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
width: 42px;
|
||||||
|
height: 22px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 11px;
|
||||||
|
transform: translate3d(2px, 2px, 0) scale3d(1, 1, 1);
|
||||||
|
transition: all 0.25s linear;
|
||||||
|
}
|
||||||
|
.form-switch i::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 11px;
|
||||||
|
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.24);
|
||||||
|
transform: translate3d(2px, 2px, 0);
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
.form-switch:active i::after {
|
||||||
|
width: 28px;
|
||||||
|
transform: translate3d(2px, 2px, 0);
|
||||||
|
}
|
||||||
|
.form-switch:active input:checked + i::after {
|
||||||
|
transform: translate3d(16px, 2px, 0);
|
||||||
|
}
|
||||||
|
.form-switch input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.form-switch input:checked + i {
|
||||||
|
@apply bg-main-accent;
|
||||||
|
}
|
||||||
|
.form-switch input:checked + i::before {
|
||||||
|
transform: translate3d(18px, 2px, 0) scale3d(0, 0, 0);
|
||||||
|
}
|
||||||
|
.form-switch input:checked + i::after {
|
||||||
|
transform: translate3d(22px, 2px, 0);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user