feat: tauri kanban fixes (#2273)

* chore: group cards count

* chore: delete board card

* chore: date time format read and update

* fix: move field

* fix: dnd fields

* chore: number format popup

* chore: refactor date options

* chore: replace button in DateFormatPopup with PopupItem

---------

Co-authored-by: qinluhe <qinluhe.twodog@gmail.com>
This commit is contained in:
Askarbek Zadauly 2023-04-30 15:01:25 +06:00 committed by GitHub
parent 2838cd5e0c
commit 55cb7acc7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 675 additions and 85 deletions

View File

@ -640,6 +640,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=935868#93586873d1982d3b4ab96993a39810e4bb4d1993"
dependencies = [
"anyhow",
"bytes",
@ -657,6 +658,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=935868#93586873d1982d3b4ab96993a39810e4bb4d1993"
dependencies = [
"anyhow",
"chrono",
@ -678,6 +680,7 @@ dependencies = [
[[package]]
name = "collab-derive"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=935868#93586873d1982d3b4ab96993a39810e4bb4d1993"
dependencies = [
"proc-macro2",
"quote",
@ -689,6 +692,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=935868#93586873d1982d3b4ab96993a39810e4bb4d1993"
dependencies = [
"anyhow",
"collab",
@ -705,6 +709,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=935868#93586873d1982d3b4ab96993a39810e4bb4d1993"
dependencies = [
"anyhow",
"collab",
@ -722,6 +727,7 @@ dependencies = [
[[package]]
name = "collab-persistence"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=935868#93586873d1982d3b4ab96993a39810e4bb4d1993"
dependencies = [
"bincode",
"chrono",

View File

@ -0,0 +1,96 @@
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
import { FieldController } from '$app/stores/effects/database/field/field_controller';
import { PopupWindow } from '$app/components/_shared/PopupWindow';
import { CheckmarkSvg } from '$app/components/_shared/svg/CheckmarkSvg';
import { useTranslation } from 'react-i18next';
import { DateFormatPB } from '@/services/backend';
import { useDateTimeFormat } from '$app/components/_shared/EditRow/DateTimeFormat.hooks';
import { useAppSelector } from '$app/stores/store';
import { useEffect, useState } from 'react';
import { IDateType } from '$app/stores/reducers/database/slice';
export const DateFormatPopup = ({
left,
top,
cellIdentifier,
fieldController,
onOutsideClick,
}: {
left: number;
top: number;
cellIdentifier: CellIdentifier;
fieldController: FieldController;
onOutsideClick: () => void;
}) => {
const { t } = useTranslation('');
const { changeDateFormat } = useDateTimeFormat(cellIdentifier, fieldController);
const databaseStore = useAppSelector((state) => state.database);
const [dateType, setDateType] = useState<IDateType | undefined>();
useEffect(() => {
setDateType(databaseStore.fields[cellIdentifier.fieldId]?.fieldOptions as IDateType);
}, [databaseStore]);
const changeFormat = async (format: DateFormatPB) => {
await changeDateFormat(format);
onOutsideClick();
};
return (
<PopupWindow className={'p-2 text-xs'} onOutsideClick={onOutsideClick} left={left} top={top}>
<PopupItem
changeFormat={changeFormat}
format={DateFormatPB.Friendly}
checked={dateType?.dateFormat === DateFormatPB.Friendly}
text={t('grid.field.dateFormatFriendly')}
/>
<PopupItem
changeFormat={changeFormat}
format={DateFormatPB.ISO}
checked={dateType?.dateFormat === DateFormatPB.ISO}
text={t('grid.field.dateFormatISO')}
/>
<PopupItem
changeFormat={changeFormat}
format={DateFormatPB.Local}
checked={dateType?.dateFormat === DateFormatPB.Local}
text={t('grid.field.dateFormatLocal')}
/>
<PopupItem
changeFormat={changeFormat}
format={DateFormatPB.US}
checked={dateType?.dateFormat === DateFormatPB.US}
text={t('grid.field.dateFormatUS')}
/>
</PopupWindow>
);
};
function PopupItem({
format,
text,
changeFormat,
checked,
}: {
format: DateFormatPB;
text: string;
changeFormat: (_: DateFormatPB) => Promise<void>;
checked: boolean;
}) {
return (
<button
onClick={() => changeFormat(format)}
className={
'flex w-full cursor-pointer items-center justify-between rounded-lg px-2 py-1.5 hover:bg-main-secondary'
}
>
{text}
{checked && (
<div className={'ml-8 h-5 w-5 p-1'}>
<CheckmarkSvg></CheckmarkSvg>
</div>
)}
</button>
);
}

View File

@ -1,17 +1,14 @@
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
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 Calendar from 'react-calendar';
import dayjs from 'dayjs';
import { ClockSvg } from '$app/components/_shared/svg/ClockSvg';
import { MoreSvg } from '$app/components/_shared/svg/MoreSvg';
import { EditorUncheckSvg } from '$app/components/_shared/svg/EditorUncheckSvg';
import { useCell } from '$app/components/_shared/database-hooks/useCell';
import { CalendarData } from '$app/stores/effects/database/cell/controller_builder';
import { DateCellDataPB } from '@/services/backend';
import { PopupWindow } from '$app/components/_shared/PopupWindow';
import { DateTypeOptions } from '$app/components/_shared/EditRow/DateTypeOptions';
export const DatePickerPopup = ({
left,
@ -29,15 +26,13 @@ export const DatePickerPopup = ({
onOutsideClick: () => void;
}) => {
const { data, cellController } = useCell(cellIdentifier, cellCache, fieldController);
const { t } = useTranslation('');
const [selectedDate, setSelectedDate] = useState<Date>(new Date());
useEffect(() => {
const date_pb = data as DateCellDataPB | undefined;
if (!date_pb || !date_pb?.date.length) return;
// should be changed after we can modify date format
setSelectedDate(dayjs(date_pb.date, 'MMM DD, YYYY').toDate());
setSelectedDate(dayjs(date_pb.date).toDate());
}, [data]);
const onChange = async (v: Date | null | (Date | null)[]) => {
@ -50,30 +45,10 @@ export const DatePickerPopup = ({
return (
<PopupWindow className={'p-2 text-xs'} onOutsideClick={onOutsideClick} left={left} top={top}>
<div className={'px-2'}>
<div className={'px-2 pb-2'}>
<Calendar onChange={(d) => onChange(d)} value={selectedDate} />
</div>
<hr className={'-mx-2 my-4 border-shade-6'} />
<div className={'flex items-center justify-between px-4'}>
<div className={'flex items-center gap-2'}>
<i className={'h-4 w-4'}>
<ClockSvg></ClockSvg>
</i>
<span>{t('grid.field.includeTime')}</span>
</div>
<i className={'h-5 w-5'}>
<EditorUncheckSvg></EditorUncheckSvg>
</i>
</div>
<hr className={'-mx-2 my-4 border-shade-6'} />
<div className={'flex items-center justify-between px-4 pb-2'}>
<span>
{t('grid.field.dateFormat')} & {t('grid.field.timeFormat')}
</span>
<i className={'h-5 w-5'}>
<MoreSvg></MoreSvg>
</i>
</div>
<DateTypeOptions cellIdentifier={cellIdentifier} fieldController={fieldController}></DateTypeOptions>
</PopupWindow>
);
};

View File

@ -0,0 +1,35 @@
import { TypeOptionController } from '$app/stores/effects/database/field/type_option/type_option_controller';
import { Some } from 'ts-results';
import { DateFormatPB, DateTypeOptionPB, FieldType, TimeFormatPB } from '@/services/backend';
import { makeDateTypeOptionContext } from '$app/stores/effects/database/field/type_option/type_option_context';
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
import { FieldController } from '$app/stores/effects/database/field/field_controller';
export const useDateTimeFormat = (cellIdentifier: CellIdentifier, fieldController: FieldController) => {
const changeFormat = async (change: (option: DateTypeOptionPB) => void) => {
const fieldInfo = fieldController.getField(cellIdentifier.fieldId);
if (!fieldInfo) return;
const typeOptionController = new TypeOptionController(cellIdentifier.viewId, Some(fieldInfo), FieldType.DateTime);
await typeOptionController.initialize();
const dateTypeOptionContext = makeDateTypeOptionContext(typeOptionController);
const typeOption = await dateTypeOptionContext.getTypeOption().then((a) => a.unwrap());
change(typeOption);
await dateTypeOptionContext.setTypeOption(typeOption);
};
const changeDateFormat = async (format: DateFormatPB) => {
await changeFormat((option) => (option.date_format = format));
};
const changeTimeFormat = async (format: TimeFormatPB) => {
await changeFormat((option) => (option.time_format = format));
};
const includeTime = async (include: boolean) => {
await changeFormat((option) => (option.include_time = include));
};
return {
changeDateFormat,
changeTimeFormat,
includeTime,
};
};

View File

@ -0,0 +1,149 @@
import { DateFormatPopup } from '$app/components/_shared/EditRow/DateFormatPopup';
import { TimeFormatPopup } from '$app/components/_shared/EditRow/TimeFormatPopup';
import { MoreSvg } from '$app/components/_shared/svg/MoreSvg';
import { EditorCheckSvg } from '$app/components/_shared/svg/EditorCheckSvg';
import { EditorUncheckSvg } from '$app/components/_shared/svg/EditorUncheckSvg';
import { MouseEventHandler, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { IDateType } from '$app/stores/reducers/database/slice';
import { useAppSelector } from '$app/stores/store';
import { useDateTimeFormat } from '$app/components/_shared/EditRow/DateTimeFormat.hooks';
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
import { FieldController } from '$app/stores/effects/database/field/field_controller';
export const DateTypeOptions = ({
cellIdentifier,
fieldController,
}: {
cellIdentifier: CellIdentifier;
fieldController: FieldController;
}) => {
const { t } = useTranslation('');
const [showDateFormatPopup, setShowDateFormatPopup] = useState(false);
const [dateFormatTop, setDateFormatTop] = useState(0);
const [dateFormatLeft, setDateFormatLeft] = useState(0);
const [showTimeFormatPopup, setShowTimeFormatPopup] = useState(false);
const [timeFormatTop, setTimeFormatTop] = useState(0);
const [timeFormatLeft, setTimeFormatLeft] = useState(0);
const [dateType, setDateType] = useState<IDateType | undefined>();
const databaseStore = useAppSelector((state) => state.database);
const { includeTime } = useDateTimeFormat(cellIdentifier, fieldController);
useEffect(() => {
setDateType(databaseStore.fields[cellIdentifier.fieldId]?.fieldOptions as IDateType);
}, [databaseStore]);
const onDateFormatClick = (_left: number, _top: number) => {
setShowDateFormatPopup(true);
setDateFormatLeft(_left + 10);
setDateFormatTop(_top);
};
const onTimeFormatClick = (_left: number, _top: number) => {
setShowTimeFormatPopup(true);
setTimeFormatLeft(_left + 10);
setTimeFormatTop(_top);
};
const _onDateFormatClick: MouseEventHandler = (e) => {
e.stopPropagation();
let target = e.target as HTMLElement;
while (!(target instanceof HTMLButtonElement)) {
if (target.parentElement === null) return;
target = target.parentElement;
}
const { right: _left, top: _top } = target.getBoundingClientRect();
onDateFormatClick(_left, _top);
};
const _onTimeFormatClick: MouseEventHandler = (e) => {
e.stopPropagation();
let target = e.target as HTMLElement;
while (!(target instanceof HTMLButtonElement)) {
if (target.parentElement === null) return;
target = target.parentElement;
}
const { right: _left, top: _top } = target.getBoundingClientRect();
onTimeFormatClick(_left, _top);
};
const toggleIncludeTime = async () => {
if (dateType?.includeTime) {
await includeTime(false);
} else {
await includeTime(true);
}
};
return (
<div className={'flex flex-col'}>
<hr className={'-mx-2 my-2 border-shade-6'} />
<button
onClick={_onDateFormatClick}
className={
'flex w-full cursor-pointer items-center justify-between rounded-lg px-2 py-2 hover:bg-main-secondary'
}
>
<span>{t('grid.field.dateFormat')}</span>
<i className={'h-5 w-5'}>
<MoreSvg></MoreSvg>
</i>
</button>
<hr className={'-mx-2 my-2 border-shade-6'} />
<button
onClick={() => toggleIncludeTime()}
className={
'flex w-full cursor-pointer items-center justify-between rounded-lg px-2 py-2 hover:bg-main-secondary'
}
>
<div className={'flex items-center gap-2'}>
{/*<i className={'h-4 w-4'}>
<ClockSvg></ClockSvg>
</i>*/}
<span>{t('grid.field.includeTime')}</span>
</div>
<i className={'h-5 w-5'}>
{dateType?.includeTime ? <EditorCheckSvg></EditorCheckSvg> : <EditorUncheckSvg></EditorUncheckSvg>}
</i>
</button>
<button
onClick={_onTimeFormatClick}
className={
'flex w-full cursor-pointer items-center justify-between rounded-lg px-2 py-2 hover:bg-main-secondary'
}
>
<span>{t('grid.field.timeFormat')}</span>
<i className={'h-5 w-5'}>
<MoreSvg></MoreSvg>
</i>
</button>
{showDateFormatPopup && (
<DateFormatPopup
top={dateFormatTop}
left={dateFormatLeft}
cellIdentifier={cellIdentifier}
fieldController={fieldController}
onOutsideClick={() => setShowDateFormatPopup(false)}
></DateFormatPopup>
)}
{showTimeFormatPopup && (
<TimeFormatPopup
top={timeFormatTop}
left={timeFormatLeft}
cellIdentifier={cellIdentifier}
fieldController={fieldController}
onOutsideClick={() => setShowTimeFormatPopup(false)}
></TimeFormatPopup>
)}
</div>
);
};

View File

@ -1,5 +1,5 @@
import { CellController } from '$app/stores/effects/database/cell/cell_controller';
import { useEffect, useState, KeyboardEvent, useMemo } from 'react';
import { useEffect, useState } from 'react';
export const EditCellText = ({
data,
@ -16,6 +16,7 @@ export const EditCellText = ({
}, [data]);
useEffect(() => {
if (!value?.length) return;
setContentRows(Math.max(1, (value || '').split('\n').length));
}, [value]);

View File

@ -42,7 +42,7 @@ export const EditCellWrapper = ({
};
return (
<Draggable draggableId={cellIdentifier.fieldId} index={index}>
<Draggable draggableId={cellIdentifier.fieldId} index={index} key={cellIdentifier.fieldId}>
{(provided) => (
<div
ref={provided.innerRef}
@ -61,7 +61,7 @@ export const EditCellWrapper = ({
<FieldTypeIcon fieldType={cellIdentifier.fieldType}></FieldTypeIcon>
</div>
<span className={'overflow-hidden text-ellipsis whitespace-nowrap'}>
{databaseStore.fields[cellIdentifier.fieldId].title}
{databaseStore.fields[cellIdentifier.fieldId]?.title || ''}
</span>
</div>
<div className={'flex-1 cursor-pointer rounded-lg hover:bg-shade-6'}>

View File

@ -1,15 +1,17 @@
import { 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 { FieldTypeName } from '$app/components/_shared/EditRow/FieldTypeName';
import { useTranslation } from 'react-i18next';
import { TypeOptionController } from '$app/stores/effects/database/field/type_option/type_option_controller';
import { Some } from 'ts-results';
import { FieldInfo } from '$app/stores/effects/database/field/field_controller';
import { FieldController, FieldInfo } from '$app/stores/effects/database/field/field_controller';
import { MoreSvg } from '$app/components/_shared/svg/MoreSvg';
import { useAppSelector } from '$app/stores/store';
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
import { PopupWindow } from '$app/components/_shared/PopupWindow';
import { FieldType } from '@/services/backend';
import { DateTypeOptions } from '$app/components/_shared/EditRow/DateTypeOptions';
export const EditFieldPopup = ({
top,
@ -18,7 +20,9 @@ export const EditFieldPopup = ({
viewId,
onOutsideClick,
fieldInfo,
fieldController,
changeFieldTypeClick,
onNumberFormat,
}: {
top: number;
left: number;
@ -26,7 +30,9 @@ export const EditFieldPopup = ({
viewId: string;
onOutsideClick: () => void;
fieldInfo: FieldInfo | undefined;
fieldController?: FieldController;
changeFieldTypeClick: (buttonTop: number, buttonRight: number) => void;
onNumberFormat?: (buttonLeft: number, buttonTop: number) => void;
}) => {
const databaseStore = useAppSelector((state) => state.database);
const { t } = useTranslation('');
@ -59,6 +65,19 @@ export const EditFieldPopup = ({
onOutsideClick();
};
const onNumberFormatClick: MouseEventHandler = (e) => {
e.stopPropagation();
let target = e.target as HTMLElement;
while (!(target instanceof HTMLButtonElement)) {
if (target.parentElement === null) return;
target = target.parentElement;
}
const { right: _left, top: _top } = target.getBoundingClientRect();
onNumberFormat?.(_left, _top);
};
return (
<PopupWindow
className={'px-2 py-2 text-xs'}
@ -69,7 +88,7 @@ export const EditFieldPopup = ({
left={left}
top={top}
>
<div className={'flex flex-col gap-2 p-2'}>
<div className={'flex flex-col gap-2'}>
<input
value={name}
onChange={(e) => setName(e.target.value)}
@ -79,24 +98,24 @@ export const EditFieldPopup = ({
<button
onClick={() => onDeleteFieldClick()}
className={
'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2 text-main-alert hover:bg-main-secondary'
}
className={'flex cursor-pointer items-center gap-2 rounded-lg py-2 text-main-alert hover:bg-main-secondary'}
>
<i className={'h-5 w-5'}>
<TrashSvg></TrashSvg>
</i>
<span>{t('grid.field.delete')}</span>
<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
ref={changeTypeButtonRef}
onClick={() => onChangeFieldTypeClick()}
className={
'relative flex cursor-pointer items-center justify-between rounded-lg text-black hover:bg-main-secondary'
'relative flex cursor-pointer items-center justify-between rounded-lg py-2 text-black hover:bg-main-secondary'
}
>
<button className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2'}>
<button className={'flex cursor-pointer items-center gap-2 rounded-lg pl-2'}>
<i className={'h-5 w-5'}>
<FieldTypeIcon fieldType={cellIdentifier.fieldType}></FieldTypeIcon>
</i>
@ -104,10 +123,35 @@ export const EditFieldPopup = ({
<FieldTypeName fieldType={cellIdentifier.fieldType}></FieldTypeName>
</span>
</button>
<i className={'h-5 w-5'}>
<MoreSvg></MoreSvg>
</i>
<span className={'pr-2'}>
<i className={' block h-5 w-5'}>
<MoreSvg></MoreSvg>
</i>
</span>
</div>
{cellIdentifier.fieldType === FieldType.Number && (
<>
<hr className={'-mx-2 border-shade-6'} />
<button
onClick={onNumberFormatClick}
className={
'flex w-full cursor-pointer items-center justify-between rounded-lg py-2 hover:bg-main-secondary'
}
>
<span className={'pl-2'}>{t('grid.field.numberFormat')}</span>
<span className={'pr-2'}>
<i className={'block h-5 w-5'}>
<MoreSvg></MoreSvg>
</i>
</span>
</button>
</>
)}
{cellIdentifier.fieldType === FieldType.DateTime && fieldController && (
<DateTypeOptions cellIdentifier={cellIdentifier} fieldController={fieldController}></DateTypeOptions>
)}
</div>
</PopupWindow>
);

View File

@ -16,6 +16,7 @@ import { CellOptionsPopup } from '$app/components/_shared/EditRow/CellOptionsPop
import { DatePickerPopup } from '$app/components/_shared/EditRow/DatePickerPopup';
import { DragDropContext, Droppable, OnDragEndResponder } from 'react-beautiful-dnd';
import { EditCellOptionPopup } from '$app/components/_shared/EditRow/EditCellOptionPopup';
import { NumberFormatPopup } from '$app/components/_shared/EditRow/NumberFormatPopup';
export const EditRow = ({
onClose,
@ -55,6 +56,10 @@ export const EditRow = ({
const [editingSelectOption, setEditingSelectOption] = useState<SelectOptionPB | undefined>();
const [showNumberFormatPopup, setShowNumberFormatPopup] = useState(false);
const [numberFormatTop, setNumberFormatTop] = useState(0);
const [numberFormatLeft, setNumberFormatLeft] = useState(0);
useEffect(() => {
setUnveil(true);
}, []);
@ -120,10 +125,16 @@ export const EditRow = ({
setEditCellOptionTop(_top);
};
const onNumberFormat = (_left: number, _top: number) => {
setShowNumberFormatPopup(true);
setNumberFormatLeft(_left + 10);
setNumberFormatTop(_top);
};
const onDragEnd: OnDragEndResponder = (result) => {
if (!result.destination?.index) return;
void controller.moveField({
fieldId: result.source.droppableId,
fieldId: result.draggableId,
fromIndex: result.source.index,
toIndex: result.destination.index,
});
@ -195,7 +206,9 @@ export const EditRow = ({
viewId={viewId}
onOutsideClick={onOutsideEditFieldClick}
fieldInfo={controller.fieldController.getField(editingCell.fieldId)}
fieldController={controller.fieldController}
changeFieldTypeClick={onChangeFieldTypeClick}
onNumberFormat={onNumberFormat}
></EditFieldPopup>
)}
{showChangeFieldTypePopup && (
@ -238,6 +251,17 @@ export const EditRow = ({
}}
></EditCellOptionPopup>
)}
{showNumberFormatPopup && editingCell && (
<NumberFormatPopup
top={numberFormatTop}
left={numberFormatLeft}
cellIdentifier={editingCell}
fieldController={controller.fieldController}
onOutsideClick={() => {
setShowNumberFormatPopup(false);
}}
></NumberFormatPopup>
)}
</div>
</div>
);

View File

@ -0,0 +1,26 @@
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
import { FieldController } from '$app/stores/effects/database/field/field_controller';
import { FieldType, NumberFormatPB } from '@/services/backend';
import { TypeOptionController } from '$app/stores/effects/database/field/type_option/type_option_controller';
import { Some } from 'ts-results';
import {
makeDateTypeOptionContext,
makeNumberTypeOptionContext,
} from '$app/stores/effects/database/field/type_option/type_option_context';
export const useNumberFormat = (cellIdentifier: CellIdentifier, fieldController: FieldController) => {
const changeNumberFormat = async (format: NumberFormatPB) => {
const fieldInfo = fieldController.getField(cellIdentifier.fieldId);
if (!fieldInfo) return;
const typeOptionController = new TypeOptionController(cellIdentifier.viewId, Some(fieldInfo), FieldType.Number);
await typeOptionController.initialize();
const numberTypeOptionContext = makeNumberTypeOptionContext(typeOptionController);
const typeOption = await numberTypeOptionContext.getTypeOption().then((a) => a.unwrap());
typeOption.format = format;
await numberTypeOptionContext.setTypeOption(typeOption);
};
return {
changeNumberFormat,
};
};

View File

@ -0,0 +1,108 @@
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
import { FieldController } from '$app/stores/effects/database/field/field_controller';
import { PopupWindow } from '$app/components/_shared/PopupWindow';
import { useNumberFormat } from '$app/components/_shared/EditRow/NumberFormat.hooks';
import { NumberFormatPB } from '@/services/backend';
import { CheckmarkSvg } from '$app/components/_shared/svg/CheckmarkSvg';
import { useAppSelector } from '$app/stores/store';
import { useEffect, useState } from 'react';
import { INumberType } from '$app/stores/reducers/database/slice';
const list = [
{ format: NumberFormatPB.Num, title: 'Num' },
{ format: NumberFormatPB.USD, title: 'USD' },
{ format: NumberFormatPB.CanadianDollar, title: 'CanadianDollar' },
{ format: NumberFormatPB.EUR, title: 'EUR' },
{ format: NumberFormatPB.Pound, title: 'Pound' },
{ format: NumberFormatPB.Yen, title: 'Yen' },
{ format: NumberFormatPB.Ruble, title: 'Ruble' },
{ format: NumberFormatPB.Rupee, title: 'Rupee' },
{ format: NumberFormatPB.Won, title: 'Won' },
{ format: NumberFormatPB.Yuan, title: 'Yuan' },
{ format: NumberFormatPB.Real, title: 'Real' },
{ format: NumberFormatPB.Lira, title: 'Lira' },
{ format: NumberFormatPB.Rupiah, title: 'Rupiah' },
{ format: NumberFormatPB.Franc, title: 'Franc' },
{ format: NumberFormatPB.HongKongDollar, title: 'HongKongDollar' },
{ format: NumberFormatPB.NewZealandDollar, title: 'NewZealandDollar' },
{ format: NumberFormatPB.Krona, title: 'Krona' },
{ format: NumberFormatPB.NorwegianKrone, title: 'NorwegianKrone' },
{ format: NumberFormatPB.MexicanPeso, title: 'MexicanPeso' },
{ format: NumberFormatPB.Rand, title: 'Rand' },
{ format: NumberFormatPB.NewTaiwanDollar, title: 'NewTaiwanDollar' },
{ format: NumberFormatPB.DanishKrone, title: 'DanishKrone' },
{ format: NumberFormatPB.Baht, title: 'Baht' },
{ format: NumberFormatPB.Forint, title: 'Forint' },
{ format: NumberFormatPB.Koruna, title: 'Koruna' },
{ format: NumberFormatPB.Shekel, title: 'Shekel' },
{ format: NumberFormatPB.ChileanPeso, title: 'ChileanPeso' },
{ format: NumberFormatPB.PhilippinePeso, title: 'PhilippinePeso' },
{ format: NumberFormatPB.Dirham, title: 'Dirham' },
{ format: NumberFormatPB.ColombianPeso, title: 'ColombianPeso' },
{ format: NumberFormatPB.Riyal, title: 'Riyal' },
{ format: NumberFormatPB.Ringgit, title: 'Ringgit' },
{ format: NumberFormatPB.Leu, title: 'Leu' },
{ format: NumberFormatPB.ArgentinePeso, title: 'ArgentinePeso' },
{ format: NumberFormatPB.UruguayanPeso, title: 'UruguayanPeso' },
{ format: NumberFormatPB.Percent, title: 'Percent' },
];
export const NumberFormatPopup = ({
left,
top,
cellIdentifier,
fieldController,
onOutsideClick,
}: {
left: number;
top: number;
cellIdentifier: CellIdentifier;
fieldController: FieldController;
onOutsideClick: () => void;
}) => {
const { changeNumberFormat } = useNumberFormat(cellIdentifier, fieldController);
const databaseStore = useAppSelector((state) => state.database);
const [numberType, setNumberType] = useState<INumberType | undefined>();
useEffect(() => {
setNumberType(databaseStore.fields[cellIdentifier.fieldId]?.fieldOptions as INumberType);
}, [databaseStore]);
const changeNumberFormatClick = async (format: NumberFormatPB) => {
await changeNumberFormat(format);
onOutsideClick();
};
return (
<PopupWindow className={'p-2 text-xs'} onOutsideClick={onOutsideClick} left={left} top={top}>
<div className={'h-[400px] overflow-auto'}>
{list.map((item, index) => (
<FormatButton
key={index}
title={item.title}
checked={numberType?.numberFormat === item.format}
onClick={() => changeNumberFormatClick(item.format)}
></FormatButton>
))}
</div>
</PopupWindow>
);
};
const FormatButton = ({ title, checked, onClick }: { title: string; checked: boolean; onClick: () => void }) => {
return (
<button
onClick={() => onClick()}
className={
'flex w-full cursor-pointer items-center justify-between rounded-lg py-1.5 px-2 hover:bg-main-secondary'
}
>
<span className={'block pr-8'}>{title}</span>
{checked && (
<div className={'h-5 w-5 p-1'}>
<CheckmarkSvg></CheckmarkSvg>
</div>
)}
</button>
);
};

View File

@ -0,0 +1,72 @@
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
import { FieldController } from '$app/stores/effects/database/field/field_controller';
import { useTranslation } from 'react-i18next';
import { PopupWindow } from '$app/components/_shared/PopupWindow';
import { TimeFormatPB } from '@/services/backend';
import { CheckmarkSvg } from '$app/components/_shared/svg/CheckmarkSvg';
import { useDateTimeFormat } from '$app/components/_shared/EditRow/DateTimeFormat.hooks';
import { useAppSelector } from '$app/stores/store';
import { useEffect, useState } from 'react';
import { IDateType } from '$app/stores/reducers/database/slice';
export const TimeFormatPopup = ({
left,
top,
cellIdentifier,
fieldController,
onOutsideClick,
}: {
left: number;
top: number;
cellIdentifier: CellIdentifier;
fieldController: FieldController;
onOutsideClick: () => void;
}) => {
const { t } = useTranslation('');
const databaseStore = useAppSelector((state) => state.database);
const [dateType, setDateType] = useState<IDateType | undefined>();
useEffect(() => {
setDateType(databaseStore.fields[cellIdentifier.fieldId]?.fieldOptions as IDateType);
}, [databaseStore]);
const { changeTimeFormat } = useDateTimeFormat(cellIdentifier, fieldController);
const changeFormat = async (format: TimeFormatPB) => {
await changeTimeFormat(format);
onOutsideClick();
};
return (
<PopupWindow className={'p-2 text-xs'} onOutsideClick={onOutsideClick} left={left} top={top}>
<button
onClick={() => changeFormat(TimeFormatPB.TwelveHour)}
className={
'flex w-full cursor-pointer items-center justify-between rounded-lg px-2 py-1.5 hover:bg-main-secondary'
}
>
{t('grid.field.timeFormatTwelveHour')}
{dateType?.timeFormat === TimeFormatPB.TwelveHour && (
<div className={'ml-8 h-5 w-5 p-1'}>
<CheckmarkSvg></CheckmarkSvg>
</div>
)}
</button>
<button
onClick={() => changeFormat(TimeFormatPB.TwentyFourHour)}
className={
'flex w-full cursor-pointer items-center justify-between rounded-lg px-2 py-1.5 hover:bg-main-secondary'
}
>
{t('grid.field.timeFormatTwentyFourHour')}
{dateType?.timeFormat === TimeFormatPB.TwentyFourHour && (
<div className={'ml-8 h-5 w-5 p-1'}>
<CheckmarkSvg></CheckmarkSvg>
</div>
)}
</button>
</PopupWindow>
);
};

View File

@ -70,7 +70,7 @@ export default async function (viewId: string, fieldInfo: FieldInfo, dispatch?:
title: field.name,
fieldType: field.field_type,
fieldOptions: {
NumberFormatPB: typeOption.format,
numberFormat: typeOption.format,
},
};
}
@ -82,8 +82,8 @@ export default async function (viewId: string, fieldInfo: FieldInfo, dispatch?:
title: field.name,
fieldType: field.field_type,
fieldOptions: {
DateFormatPB: typeOption.date_format,
TimeFormatPB: typeOption.time_format,
dateFormat: typeOption.date_format,
timeFormat: typeOption.time_format,
includeTime: typeOption.include_time,
},
};

View File

@ -25,9 +25,13 @@ export const useCell = (cellIdentifier: CellIdentifier, cellCache: CellCache, fi
});
void (async () => {
const cellData = await c.getCellData();
if (cellData.some) {
setData(cellData.unwrap());
try {
const cellData = await c.getCellData();
if (cellData.some) {
setData(cellData.unwrap());
}
} catch (e) {
// mute for now
}
})();

View File

@ -4,7 +4,10 @@ import { useRow } from '../_shared/database-hooks/useRow';
import { DatabaseController } from '$app/stores/effects/database/database_controller';
import { BoardCell } from './BoardCell';
import { Draggable } from 'react-beautiful-dnd';
import { MouseEventHandler } from 'react';
import { MouseEventHandler, useState } from 'react';
import { PopupWindow } from '$app/components/_shared/PopupWindow';
import { TrashSvg } from '$app/components/_shared/svg/TrashSvg';
import { RowBackendService } from '$app/stores/effects/database/row/row_bd_svc';
export const BoardCard = ({
index,
@ -23,38 +26,79 @@ export const BoardCard = ({
}) => {
const { cells } = useRow(viewId, controller, rowInfo);
const [showCardPopup, setShowCardPopup] = useState(false);
const [cardPopupLeft, setCardPopupLeft] = useState(0);
const [cardPopupTop, setCardPopupTop] = useState(0);
const onDetailClick: MouseEventHandler = (e) => {
e.stopPropagation();
// onOpenRow(rowInfo);
let target = e.target as HTMLElement;
while (!(target instanceof HTMLButtonElement)) {
if (target.parentElement === null) return;
target = target.parentElement;
}
const { right: left, top } = target.getBoundingClientRect();
setCardPopupLeft(left);
setCardPopupTop(top);
setShowCardPopup(true);
};
const onDeleteRowClick = async () => {
setShowCardPopup(false);
const svc = new RowBackendService(viewId);
await svc.deleteRow(rowInfo.row.id);
};
return (
<Draggable draggableId={rowInfo.row.id} index={index}>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
onClick={() => onOpenRow(rowInfo)}
className={`relative cursor-pointer select-none rounded-lg border border-shade-6 bg-white px-3 py-2 transition-transform duration-100 hover:bg-main-selector `}
>
<button onClick={onDetailClick} className={'absolute right-4 top-2.5 h-5 w-5 rounded hover:bg-surface-2'}>
<Details2Svg></Details2Svg>
</button>
<div className={'flex flex-col gap-3'}>
{cells
.filter((cell) => cell.fieldId !== groupByFieldId)
.map((cell, cellIndex) => (
<BoardCell
key={cellIndex}
cellIdentifier={cell.cellIdentifier}
cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
fieldController={controller.fieldController}
></BoardCell>
))}
<>
<Draggable draggableId={rowInfo.row.id} key={rowInfo.row.id} index={index}>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
onClick={() => onOpenRow(rowInfo)}
className={`relative cursor-pointer select-none rounded-lg border border-shade-6 bg-white px-3 py-2 transition-transform duration-100 hover:bg-main-selector `}
>
<button onClick={onDetailClick} className={'absolute right-4 top-2.5 h-5 w-5 rounded hover:bg-surface-2'}>
<Details2Svg></Details2Svg>
</button>
<div className={'flex flex-col gap-3'}>
{cells
.filter((cell) => cell.fieldId !== groupByFieldId)
.map((cell, cellIndex) => (
<BoardCell
key={cellIndex}
cellIdentifier={cell.cellIdentifier}
cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
fieldController={controller.fieldController}
></BoardCell>
))}
</div>
</div>
</div>
)}
</Draggable>
{showCardPopup && (
<PopupWindow
className={'p-2 text-xs'}
onOutsideClick={() => setShowCardPopup(false)}
left={cardPopupLeft}
top={cardPopupTop}
>
<button
key={index}
className={'flex w-full cursor-pointer items-center gap-2 rounded-lg px-2 py-2 hover:bg-main-secondary'}
onClick={() => onDeleteRowClick()}
>
<i className={'h-5 w-5'}>
<TrashSvg></TrashSvg>
</i>
<span className={'flex-shrink-0'}>Delete</span>
</button>
</PopupWindow>
)}
</Draggable>
</>
);
};

View File

@ -28,7 +28,7 @@ export const BoardGroup = ({
<div className={'flex items-center justify-between p-4'}>
<div className={'flex items-center gap-2'}>
<span>{group.name}</span>
<span className={'text-shade-4'}>()</span>
<span className={'text-shade-4'}>({group.rows.length})</span>
</div>
<div className={'flex items-center gap-2'}>
<button className={'h-5 w-5 rounded hover:bg-surface-2'}>

View File

@ -13,13 +13,13 @@ export interface ISelectOptionType {
}
export interface IDateType {
DateFormatPB: DateFormatPB;
TimeFormatPB: TimeFormatPB;
dateFormat: DateFormatPB;
timeFormat: TimeFormatPB;
includeTime: boolean;
}
export interface INumberType {
NumberFormatPB: NumberFormatPB;
numberFormat: NumberFormatPB;
}
export interface IDatabaseField {

View File

@ -544,6 +544,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=935868#93586873d1982d3b4ab96993a39810e4bb4d1993"
dependencies = [
"anyhow",
"bytes",
@ -561,6 +562,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=935868#93586873d1982d3b4ab96993a39810e4bb4d1993"
dependencies = [
"anyhow",
"chrono",
@ -582,6 +584,7 @@ dependencies = [
[[package]]
name = "collab-derive"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=935868#93586873d1982d3b4ab96993a39810e4bb4d1993"
dependencies = [
"proc-macro2",
"quote",
@ -593,6 +596,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=935868#93586873d1982d3b4ab96993a39810e4bb4d1993"
dependencies = [
"anyhow",
"collab",
@ -609,6 +613,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=935868#93586873d1982d3b4ab96993a39810e4bb4d1993"
dependencies = [
"anyhow",
"collab",
@ -626,6 +631,7 @@ dependencies = [
[[package]]
name = "collab-persistence"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=935868#93586873d1982d3b4ab96993a39810e4bb4d1993"
dependencies = [
"bincode",
"chrono",