mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: move kanban blocks (#2022)
* chore: add edit / create field test * chore: add delete field test * chore: change log class arguments * chore: delete/create row * chore: set tracing log to debug level * fix: filter notification with id * chore: add get single select type option data * fix: high cpu usage * chore: format code * chore: update tokio version * chore: config tokio runtime subscriber * chore: add profiling feature * chore: setup auto login * chore: fix tauri build * chore: (unstable) using controllers * fix: initially authenticated and serializable fix * fix: ci warning * ci: compile error * fix: new folder trash overflow * fix: min width for nav panel * fix: nav panel and main panel animation on hide menu * fix: highlight active page * fix: post merge fixes * fix: post merge fix * fix: remove warnings * fix: change IDatabaseField fix eslint errors * chore: create cell component for each field type * chore: move cell hook into custom cell component * chore: refactor row hook * chore: add tauri clean * chore: add tauri clean * chore: save offset top of nav items * chore: move constants * fix: nav item popup overflow * fix: page rename position * chore: remove offset top * chore: remove floating menu functions * chore: scroll down to new page * chore: smooth scroll and scroll to new folder * fix: breadcrumbs * chore: back and forward buttons nav scroll fix * chore: get board groups and rows * chore: set log level & remove empty line * fix: create kanban board row * fix: appflowy session name * chore: import beautiful dnd * bug: kanban new row * chore: update refs * fix: dispose group controller * fix: dispose cell controller * chore: move rows in group * chore: move row into other block * fix: groups observer dispose * chore: dnd reordering * chore: fix import references * chore: initial edit board modal * fix: kanban board rendering * chore: add column and edit text cell * chore: column rename * chore: edit row components reorganize * chore: don't show group by field * wip: edit cell type * chore: fade in, out * chore: change field type * chore: update editing cell * chore: fade in change * chore: cell options layout * fix: padding fixes for cell wrapper * fix: cell options positions * chore: cell options write to backend * fix: select options for new row * chore: edit url cell * chore: language button * fix: close popup on lang select * fix: save url cell * chore: date picker * chore: small code cleanups * chore: options in board * chore: move fields dnd --------- Co-authored-by: nathan <nathan@appflowy.io> Co-authored-by: Nathan.fooo <86001920+appflowy@users.noreply.github.com> Co-authored-by: appflowy <annie@appflowy.io>
This commit is contained in:
parent
9fff00f731
commit
fe524dbc78
@ -23,6 +23,7 @@
|
|||||||
"@slate-yjs/core": "^0.3.1",
|
"@slate-yjs/core": "^0.3.1",
|
||||||
"@tanstack/react-virtual": "3.0.0-beta.54",
|
"@tanstack/react-virtual": "3.0.0-beta.54",
|
||||||
"@tauri-apps/api": "^1.2.0",
|
"@tauri-apps/api": "^1.2.0",
|
||||||
|
"dayjs": "^1.11.7",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
"google-protobuf": "^3.21.2",
|
"google-protobuf": "^3.21.2",
|
||||||
"i18next": "^22.4.10",
|
"i18next": "^22.4.10",
|
||||||
@ -32,6 +33,8 @@
|
|||||||
"nanoid": "^4.0.0",
|
"nanoid": "^4.0.0",
|
||||||
"protoc-gen-ts": "^0.8.5",
|
"protoc-gen-ts": "^0.8.5",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
|
"react-calendar": "^4.1.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-error-boundary": "^3.1.4",
|
"react-error-boundary": "^3.1.4",
|
||||||
"react-i18next": "^12.2.0",
|
"react-i18next": "^12.2.0",
|
||||||
@ -44,8 +47,8 @@
|
|||||||
"slate-react": "^0.91.9",
|
"slate-react": "^0.91.9",
|
||||||
"ts-results": "^3.3.0",
|
"ts-results": "^3.3.0",
|
||||||
"utf8": "^3.0.0",
|
"utf8": "^3.0.0",
|
||||||
"yjs": "^13.5.51",
|
"y-indexeddb": "^9.0.9",
|
||||||
"y-indexeddb": "^9.0.9"
|
"yjs": "^13.5.51"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^1.2.2",
|
"@tauri-apps/cli": "^1.2.2",
|
||||||
@ -53,6 +56,7 @@
|
|||||||
"@types/is-hotkey": "^0.1.7",
|
"@types/is-hotkey": "^0.1.7",
|
||||||
"@types/node": "^18.7.10",
|
"@types/node": "^18.7.10",
|
||||||
"@types/react": "^18.0.15",
|
"@types/react": "^18.0.15",
|
||||||
|
"@types/react-beautiful-dnd": "^13.1.3",
|
||||||
"@types/react-dom": "^18.0.6",
|
"@types/react-dom": "^18.0.6",
|
||||||
"@types/utf8": "^3.0.1",
|
"@types/utf8": "^3.0.1",
|
||||||
"@types/uuid": "^9.0.1",
|
"@types/uuid": "^9.0.1",
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
import { SelectOptionCellDataPB } from '@/services/backend';
|
||||||
|
import { getBgColor } from '$app/components/_shared/getColor';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
|
||||||
|
export const CellOptions = ({
|
||||||
|
data,
|
||||||
|
onEditClick,
|
||||||
|
}: {
|
||||||
|
data: SelectOptionCellDataPB | undefined;
|
||||||
|
onEditClick: (left: number, top: number) => void;
|
||||||
|
}) => {
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const onClick = () => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
const { left, top } = ref.current.getBoundingClientRect();
|
||||||
|
onEditClick(left, top);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
onClick={() => onClick()}
|
||||||
|
className={'flex flex-wrap items-center gap-2 px-4 py-2 text-xs text-black'}
|
||||||
|
>
|
||||||
|
{data?.select_options?.map((option, index) => (
|
||||||
|
<div className={`${getBgColor(option.color)} rounded px-2 py-0.5`} key={index}>
|
||||||
|
{option?.name || ''}
|
||||||
|
</div>
|
||||||
|
)) || ''}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,152 @@
|
|||||||
|
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 useOutsideClick from '$app/components/_shared/useOutsideClick';
|
||||||
|
import { SelectOptionCellBackendService } from '$app/stores/effects/database/cell/select_option_bd_svc';
|
||||||
|
import { useAppSelector } from '$app/stores/store';
|
||||||
|
import { ISelectOptionType } from '$app/stores/reducers/database/slice';
|
||||||
|
|
||||||
|
export const CellOptionsPopup = ({
|
||||||
|
top,
|
||||||
|
left,
|
||||||
|
cellIdentifier,
|
||||||
|
cellCache,
|
||||||
|
fieldController,
|
||||||
|
onOutsideClick,
|
||||||
|
}: {
|
||||||
|
top: number;
|
||||||
|
left: number;
|
||||||
|
cellIdentifier: CellIdentifier;
|
||||||
|
cellCache: CellCache;
|
||||||
|
fieldController: FieldController;
|
||||||
|
onOutsideClick: () => void;
|
||||||
|
}) => {
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
const { t } = useTranslation('');
|
||||||
|
const [adjustedTop, setAdjustedTop] = useState(-100);
|
||||||
|
const [value, setValue] = useState('');
|
||||||
|
const { data, cellController } = useCell(cellIdentifier, cellCache, fieldController);
|
||||||
|
const databaseStore = useAppSelector((state) => state.database);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
const { height } = ref.current.getBoundingClientRect();
|
||||||
|
if (top + height + 40 > window.innerHeight) {
|
||||||
|
setAdjustedTop(window.innerHeight - height - 40);
|
||||||
|
} else {
|
||||||
|
setAdjustedTop(top);
|
||||||
|
}
|
||||||
|
}, [ref, window, top, left]);
|
||||||
|
|
||||||
|
useOutsideClick(ref, async () => {
|
||||||
|
onOutsideClick();
|
||||||
|
});
|
||||||
|
|
||||||
|
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 | undefined)?.select_options?.find(
|
||||||
|
(selectedOption) => selectedOption.id === option.id
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
await new SelectOptionCellBackendService(cellIdentifier).unselectOption([option.id]);
|
||||||
|
} else {
|
||||||
|
await new SelectOptionCellBackendService(cellIdentifier).selectOption([option.id]);
|
||||||
|
}
|
||||||
|
setValue('');
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('loaded data: ', data);
|
||||||
|
console.log('have stored ', databaseStore.fields[cellIdentifier.fieldId]);
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={`fixed z-10 rounded-lg bg-white px-2 py-2 text-xs shadow-md transition-opacity duration-300 ${
|
||||||
|
adjustedTop === -100 ? 'opacity-0' : 'opacity-100'
|
||||||
|
}`}
|
||||||
|
style={{ top: `${adjustedTop + 40}px`, left: `${left}px` }}
|
||||||
|
>
|
||||||
|
<div 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 | undefined)?.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
|
||||||
|
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-semibold 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 | undefined)?.select_options?.find(
|
||||||
|
(selectedOption) => selectedOption.id === option.selectOptionId
|
||||||
|
) && (
|
||||||
|
<button className={'h-5 w-5 p-1'}>
|
||||||
|
<CheckmarkSvg></CheckmarkSvg>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button className={'h-6 w-6 p-1'}>
|
||||||
|
<Details2Svg></Details2Svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,71 @@
|
|||||||
|
import { FieldType } from '@/services/backend';
|
||||||
|
import { FieldTypeIcon } from '$app/components/_shared/EditRow/FieldTypeIcon';
|
||||||
|
import { FieldTypeName } from '$app/components/_shared/EditRow/FieldTypeName';
|
||||||
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import useOutsideClick from '$app/components/_shared/useOutsideClick';
|
||||||
|
|
||||||
|
const typesOrder: FieldType[] = [
|
||||||
|
FieldType.RichText,
|
||||||
|
FieldType.Number,
|
||||||
|
FieldType.DateTime,
|
||||||
|
FieldType.SingleSelect,
|
||||||
|
FieldType.MultiSelect,
|
||||||
|
FieldType.Checkbox,
|
||||||
|
FieldType.URL,
|
||||||
|
FieldType.Checklist,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ChangeFieldTypePopup = ({
|
||||||
|
top,
|
||||||
|
right,
|
||||||
|
onClick,
|
||||||
|
onOutsideClick,
|
||||||
|
}: {
|
||||||
|
top: number;
|
||||||
|
right: number;
|
||||||
|
onClick: (newType: FieldType) => void;
|
||||||
|
onOutsideClick: () => void;
|
||||||
|
}) => {
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
const [adjustedTop, setAdjustedTop] = useState(-100);
|
||||||
|
useOutsideClick(ref, async () => {
|
||||||
|
onOutsideClick();
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
const { height } = ref.current.getBoundingClientRect();
|
||||||
|
if (top + height > window.innerHeight) {
|
||||||
|
setAdjustedTop(window.innerHeight - height);
|
||||||
|
} else {
|
||||||
|
setAdjustedTop(top);
|
||||||
|
}
|
||||||
|
}, [ref, window, top, right]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={`fixed z-10 rounded-lg bg-white p-2 text-xs shadow-md transition-opacity duration-300 ${
|
||||||
|
adjustedTop === -100 ? 'opacity-0' : 'opacity-100'
|
||||||
|
}`}
|
||||||
|
style={{ top: `${adjustedTop}px`, left: `${right + 30}px` }}
|
||||||
|
>
|
||||||
|
<div className={'flex flex-col'}>
|
||||||
|
{typesOrder.map((t, i) => (
|
||||||
|
<button
|
||||||
|
onClick={() => onClick(t)}
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,97 @@
|
|||||||
|
import { useEffect, useRef, 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 useOutsideClick from '$app/components/_shared/useOutsideClick';
|
||||||
|
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';
|
||||||
|
|
||||||
|
export const DatePickerPopup = ({
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
cellIdentifier,
|
||||||
|
cellCache,
|
||||||
|
fieldController,
|
||||||
|
onOutsideClick,
|
||||||
|
}: {
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
cellIdentifier: CellIdentifier;
|
||||||
|
cellCache: CellCache;
|
||||||
|
fieldController: FieldController;
|
||||||
|
onOutsideClick: () => void;
|
||||||
|
}) => {
|
||||||
|
const { data, cellController } = useCell(cellIdentifier, cellCache, fieldController);
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
const [adjustedTop, setAdjustedTop] = useState(-100);
|
||||||
|
// const [value, setValue] = useState();
|
||||||
|
const { t } = useTranslation('');
|
||||||
|
const [selectedDate, setSelectedDate] = useState<Date>(new Date());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
const { height } = ref.current.getBoundingClientRect();
|
||||||
|
if (top + height + 40 > window.innerHeight) {
|
||||||
|
setAdjustedTop(top - height - 40);
|
||||||
|
} else {
|
||||||
|
setAdjustedTop(top);
|
||||||
|
}
|
||||||
|
}, [ref, window, top, left]);
|
||||||
|
|
||||||
|
useOutsideClick(ref, async () => {
|
||||||
|
onOutsideClick();
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// console.log((data as DateCellDataPB).date);
|
||||||
|
// setSelectedDate(new Date((data as DateCellDataPB).date));
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const onChange = (v: Date | null | (Date | null)[]) => {
|
||||||
|
if (v instanceof Date) {
|
||||||
|
console.log(dayjs(v).format('YYYY-MM-DD'));
|
||||||
|
setSelectedDate(v);
|
||||||
|
// void cellController?.saveCellData(new DateCellDataPB({ date: dayjs(v).format('YYYY-MM-DD') }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={`fixed z-10 rounded-lg bg-white px-2 py-2 text-xs shadow-md transition-opacity duration-300 ${
|
||||||
|
adjustedTop === -100 ? 'opacity-0' : 'opacity-100'
|
||||||
|
}`}
|
||||||
|
style={{ top: `${adjustedTop + 40}px`, left: `${left}px` }}
|
||||||
|
>
|
||||||
|
<div className={'px-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>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,24 @@
|
|||||||
|
import { useRef } from 'react';
|
||||||
|
import { DateCellDataPB } from '@/services/backend';
|
||||||
|
|
||||||
|
export const EditCellDate = ({
|
||||||
|
data,
|
||||||
|
onEditClick,
|
||||||
|
}: {
|
||||||
|
data?: DateCellDataPB;
|
||||||
|
onEditClick: (left: number, top: number) => void;
|
||||||
|
}) => {
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const onClick = () => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
const { left, top } = ref.current.getBoundingClientRect();
|
||||||
|
onEditClick(left, top);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref} onClick={() => onClick()} className={'px-4 py-2'}>
|
||||||
|
{data?.date || <> </>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,29 @@
|
|||||||
|
import { CellController } from '$app/stores/effects/database/cell/cell_controller';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export const EditCellNumber = ({
|
||||||
|
data,
|
||||||
|
cellController,
|
||||||
|
}: {
|
||||||
|
data: string | undefined;
|
||||||
|
cellController: CellController<any, any>;
|
||||||
|
}) => {
|
||||||
|
const [value, setValue] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setValue(data || '');
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
await cellController?.saveCellData(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => setValue(e.target.value)}
|
||||||
|
onBlur={() => save()}
|
||||||
|
className={'w-full px-4 py-2'}
|
||||||
|
></input>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,41 @@
|
|||||||
|
import { CellController } from '$app/stores/effects/database/cell/cell_controller';
|
||||||
|
import { useEffect, useState, KeyboardEvent, useMemo } from 'react';
|
||||||
|
|
||||||
|
export const EditCellText = ({
|
||||||
|
data,
|
||||||
|
cellController,
|
||||||
|
}: {
|
||||||
|
data: string | undefined;
|
||||||
|
cellController: CellController<any, any>;
|
||||||
|
}) => {
|
||||||
|
const [value, setValue] = useState('');
|
||||||
|
const [contentRows, setContentRows] = useState(1);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setValue(data || '');
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setContentRows(Math.max(1, (value || '').split('\n').length));
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
const onTextFieldChange = async (v: string) => {
|
||||||
|
setValue(v);
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
await cellController?.saveCellData(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={''}>
|
||||||
|
<textarea
|
||||||
|
className={'mt-0.5 h-full w-full resize-none px-4 py-2'}
|
||||||
|
rows={contentRows}
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => onTextFieldChange(e.target.value)}
|
||||||
|
onBlur={() => save()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,31 @@
|
|||||||
|
import { URLCellDataPB } from '@/services/backend';
|
||||||
|
import { CellController } from '$app/stores/effects/database/cell/cell_controller';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { URLCellController } from '$app/stores/effects/database/cell/controller_builder';
|
||||||
|
|
||||||
|
export const EditCellUrl = ({
|
||||||
|
data,
|
||||||
|
cellController,
|
||||||
|
}: {
|
||||||
|
data: URLCellDataPB | undefined;
|
||||||
|
cellController: CellController<any, any>;
|
||||||
|
}) => {
|
||||||
|
const [value, setValue] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setValue((data as URLCellDataPB)?.url || '');
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
await (cellController as URLCellController)?.saveCellData(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => setValue(e.target.value)}
|
||||||
|
onBlur={() => save()}
|
||||||
|
className={'w-full px-4 py-2'}
|
||||||
|
></input>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,102 @@
|
|||||||
|
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 { DateCellDataPB, FieldType, SelectOptionCellDataPB, URLCellDataPB } from '@/services/backend';
|
||||||
|
import { useAppSelector } from '$app/stores/store';
|
||||||
|
import { EditCellText } from '$app/components/_shared/EditRow/EditCellText';
|
||||||
|
import { FieldTypeIcon } from '$app/components/_shared/EditRow/FieldTypeIcon';
|
||||||
|
import { EditCellDate } from '$app/components/_shared/EditRow/EditCellDate';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import { CellOptions } from '$app/components/_shared/EditRow/CellOptions';
|
||||||
|
import { EditCellNumber } from '$app/components/_shared/EditRow/EditCellNumber';
|
||||||
|
import { EditCheckboxCell } from '$app/components/_shared/EditRow/EditCheckboxCell';
|
||||||
|
import { EditCellUrl } from '$app/components/_shared/EditRow/EditCellUrl';
|
||||||
|
import { Draggable } from 'react-beautiful-dnd';
|
||||||
|
|
||||||
|
export const EditCellWrapper = ({
|
||||||
|
index,
|
||||||
|
cellIdentifier,
|
||||||
|
cellCache,
|
||||||
|
fieldController,
|
||||||
|
onEditFieldClick,
|
||||||
|
onEditOptionsClick,
|
||||||
|
onEditDateClick,
|
||||||
|
}: {
|
||||||
|
index: number;
|
||||||
|
cellIdentifier: CellIdentifier;
|
||||||
|
cellCache: CellCache;
|
||||||
|
fieldController: FieldController;
|
||||||
|
onEditFieldClick: (top: number, right: number) => void;
|
||||||
|
onEditOptionsClick: (left: number, top: number) => void;
|
||||||
|
onEditDateClick: (left: number, top: number) => void;
|
||||||
|
}) => {
|
||||||
|
const { data, cellController } = useCell(cellIdentifier, cellCache, fieldController);
|
||||||
|
const databaseStore = useAppSelector((state) => state.database);
|
||||||
|
const el = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const onClick = () => {
|
||||||
|
if (!el.current) return;
|
||||||
|
const { top, right } = el.current.getBoundingClientRect();
|
||||||
|
onEditFieldClick(top, right);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Draggable draggableId={cellIdentifier.fieldId} index={index}>
|
||||||
|
{(provided) => (
|
||||||
|
<div
|
||||||
|
ref={provided.innerRef}
|
||||||
|
{...provided.draggableProps}
|
||||||
|
{...provided.dragHandleProps}
|
||||||
|
className={'flex w-full items-center text-xs'}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
ref={el}
|
||||||
|
onClick={() => onClick()}
|
||||||
|
className={
|
||||||
|
'relative flex w-[180px] cursor-pointer items-center gap-2 rounded-lg px-3 py-1.5 hover:bg-shade-6'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className={'flex h-5 w-5 flex-shrink-0 items-center justify-center'}>
|
||||||
|
<FieldTypeIcon fieldType={cellIdentifier.fieldType}></FieldTypeIcon>
|
||||||
|
</div>
|
||||||
|
<span className={'overflow-hidden text-ellipsis whitespace-nowrap'}>
|
||||||
|
{databaseStore.fields[cellIdentifier.fieldId].title}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className={'flex-1 cursor-pointer rounded-lg hover:bg-shade-6'}>
|
||||||
|
{(cellIdentifier.fieldType === FieldType.SingleSelect ||
|
||||||
|
cellIdentifier.fieldType === FieldType.MultiSelect ||
|
||||||
|
cellIdentifier.fieldType === FieldType.Checklist) &&
|
||||||
|
cellController && (
|
||||||
|
<CellOptions
|
||||||
|
data={data as SelectOptionCellDataPB | undefined}
|
||||||
|
onEditClick={onEditOptionsClick}
|
||||||
|
></CellOptions>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{cellIdentifier.fieldType === FieldType.Checkbox && cellController && (
|
||||||
|
<EditCheckboxCell data={data as boolean | undefined} cellController={cellController}></EditCheckboxCell>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{cellIdentifier.fieldType === FieldType.DateTime && (
|
||||||
|
<EditCellDate data={data as DateCellDataPB | undefined} onEditClick={onEditDateClick}></EditCellDate>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{cellIdentifier.fieldType === FieldType.Number && cellController && (
|
||||||
|
<EditCellNumber data={data as string | undefined} cellController={cellController}></EditCellNumber>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{cellIdentifier.fieldType === FieldType.URL && cellController && (
|
||||||
|
<EditCellUrl data={data as URLCellDataPB | undefined} cellController={cellController}></EditCellUrl>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{cellIdentifier.fieldType === FieldType.RichText && cellController && (
|
||||||
|
<EditCellText data={data as string | undefined} cellController={cellController}></EditCellText>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Draggable>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,23 @@
|
|||||||
|
import { EditorCheckSvg } from '$app/components/_shared/svg/EditorCheckSvg';
|
||||||
|
import { EditorUncheckSvg } from '$app/components/_shared/svg/EditorUncheckSvg';
|
||||||
|
import { CellController } from '$app/stores/effects/database/cell/cell_controller';
|
||||||
|
|
||||||
|
export const EditCheckboxCell = ({
|
||||||
|
data,
|
||||||
|
cellController,
|
||||||
|
}: {
|
||||||
|
data: boolean | undefined;
|
||||||
|
cellController: CellController<any, any>;
|
||||||
|
}) => {
|
||||||
|
const toggleValue = async () => {
|
||||||
|
await cellController?.saveCellData(!data);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div onClick={() => toggleValue()} className={'block px-4 py-2'}>
|
||||||
|
<button className={'h-5 w-5'}>
|
||||||
|
{data ? <EditorCheckSvg></EditorCheckSvg> : <EditorUncheckSvg></EditorUncheckSvg>}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,130 @@
|
|||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import useOutsideClick from '$app/components/_shared/useOutsideClick';
|
||||||
|
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 { MoreSvg } from '$app/components/_shared/svg/MoreSvg';
|
||||||
|
import { useAppSelector } from '$app/stores/store';
|
||||||
|
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
|
||||||
|
|
||||||
|
export const EditFieldPopup = ({
|
||||||
|
top,
|
||||||
|
right,
|
||||||
|
cellIdentifier,
|
||||||
|
viewId,
|
||||||
|
onOutsideClick,
|
||||||
|
fieldInfo,
|
||||||
|
changeFieldTypeClick,
|
||||||
|
}: {
|
||||||
|
top: number;
|
||||||
|
right: number;
|
||||||
|
cellIdentifier: CellIdentifier;
|
||||||
|
viewId: string;
|
||||||
|
onOutsideClick: () => void;
|
||||||
|
fieldInfo: FieldInfo | undefined;
|
||||||
|
changeFieldTypeClick: (buttonTop: number, buttonRight: number) => void;
|
||||||
|
}) => {
|
||||||
|
const databaseStore = useAppSelector((state) => state.database);
|
||||||
|
const { t } = useTranslation('');
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
const changeTypeButtonRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [name, setName] = useState('');
|
||||||
|
|
||||||
|
const [adjustedTop, setAdjustedTop] = useState(-100);
|
||||||
|
|
||||||
|
useOutsideClick(ref, async () => {
|
||||||
|
await save();
|
||||||
|
onOutsideClick();
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setName(databaseStore.fields[cellIdentifier.fieldId].title);
|
||||||
|
}, [databaseStore, cellIdentifier]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
const { height } = ref.current.getBoundingClientRect();
|
||||||
|
if (top + height > window.innerHeight) {
|
||||||
|
setAdjustedTop(window.innerHeight - height);
|
||||||
|
} else {
|
||||||
|
setAdjustedTop(top);
|
||||||
|
}
|
||||||
|
}, [ref, window, top, right]);
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
if (!fieldInfo) return;
|
||||||
|
const controller = new TypeOptionController(viewId, Some(fieldInfo));
|
||||||
|
await controller.initialize();
|
||||||
|
await controller.setFieldName(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChangeFieldTypeClick = () => {
|
||||||
|
if (!changeTypeButtonRef.current) return;
|
||||||
|
const { top: buttonTop, right: buttonRight } = changeTypeButtonRef.current.getBoundingClientRect();
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={`fixed z-10 rounded-lg bg-white px-2 py-2 text-xs shadow-md transition-opacity duration-300 ${
|
||||||
|
adjustedTop === -100 ? 'opacity-0' : 'opacity-100'
|
||||||
|
}`}
|
||||||
|
style={{ top: `${adjustedTop}px`, left: `${right + 10}px` }}
|
||||||
|
>
|
||||||
|
<div className={'flex flex-col gap-2 p-2'}>
|
||||||
|
<input
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
onBlur={() => save()}
|
||||||
|
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 px-2 py-2 text-main-alert hover:bg-main-secondary'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<i className={'h-5 w-5'}>
|
||||||
|
<TrashSvg></TrashSvg>
|
||||||
|
</i>
|
||||||
|
<span>{t('grid.field.delete')}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div
|
||||||
|
ref={changeTypeButtonRef}
|
||||||
|
onClick={() => onChangeFieldTypeClick()}
|
||||||
|
className={
|
||||||
|
'relative flex cursor-pointer items-center justify-between rounded-lg text-black hover:bg-main-secondary'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<button className={'flex cursor-pointer items-center gap-2 rounded-lg px-2 py-2'}>
|
||||||
|
<i className={'h-5 w-5'}>
|
||||||
|
<FieldTypeIcon fieldType={cellIdentifier.fieldType}></FieldTypeIcon>
|
||||||
|
</i>
|
||||||
|
<span>
|
||||||
|
<FieldTypeName fieldType={cellIdentifier.fieldType}></FieldTypeName>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<i className={'h-5 w-5'}>
|
||||||
|
<MoreSvg></MoreSvg>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,210 @@
|
|||||||
|
import { CloseSvg } from '$app/components/_shared/svg/CloseSvg';
|
||||||
|
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 { EditCellWrapper } from '$app/components/_shared/EditRow/EditCellWrapper';
|
||||||
|
import AddSvg from '$app/components/_shared/svg/AddSvg';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { EditFieldPopup } from '$app/components/_shared/EditRow/EditFieldPopup';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
|
||||||
|
import { ChangeFieldTypePopup } from '$app/components/_shared/EditRow/ChangeFieldTypePopup';
|
||||||
|
import { TypeOptionController } from '$app/stores/effects/database/field/type_option/type_option_controller';
|
||||||
|
import { Some } from 'ts-results';
|
||||||
|
import { FieldType } from '@/services/backend';
|
||||||
|
import { CellOptionsPopup } from '$app/components/_shared/EditRow/CellOptionsPopup';
|
||||||
|
import { DatePickerPopup } from '$app/components/_shared/EditRow/DatePickerPopup';
|
||||||
|
import { DragDropContext, Droppable, OnDragEndResponder } from 'react-beautiful-dnd';
|
||||||
|
|
||||||
|
export const EditRow = ({
|
||||||
|
onClose,
|
||||||
|
viewId,
|
||||||
|
controller,
|
||||||
|
rowInfo,
|
||||||
|
}: {
|
||||||
|
onClose: () => void;
|
||||||
|
viewId: string;
|
||||||
|
controller: DatabaseController;
|
||||||
|
rowInfo: RowInfo;
|
||||||
|
}) => {
|
||||||
|
const { cells, onNewColumnClick } = useRow(viewId, controller, rowInfo);
|
||||||
|
const { t } = useTranslation('');
|
||||||
|
const [unveil, setUnveil] = useState(false);
|
||||||
|
|
||||||
|
const [editingCell, setEditingCell] = useState<CellIdentifier | null>(null);
|
||||||
|
const [showFieldEditor, setShowFieldEditor] = useState(false);
|
||||||
|
const [editFieldTop, setEditFieldTop] = useState(0);
|
||||||
|
const [editFieldRight, setEditFieldRight] = useState(0);
|
||||||
|
|
||||||
|
const [showChangeFieldTypePopup, setShowChangeFieldTypePopup] = useState(false);
|
||||||
|
const [changeFieldTypeTop, setChangeFieldTypeTop] = useState(0);
|
||||||
|
const [changeFieldTypeRight, setChangeFieldTypeRight] = useState(0);
|
||||||
|
|
||||||
|
const [showChangeOptionsPopup, setShowChangeOptionsPopup] = useState(false);
|
||||||
|
const [changeOptionsTop, setChangeOptionsTop] = useState(0);
|
||||||
|
const [changeOptionsLeft, setChangeOptionsLeft] = useState(0);
|
||||||
|
|
||||||
|
const [showDatePicker, setShowDatePicker] = useState(false);
|
||||||
|
const [datePickerTop, setDatePickerTop] = useState(0);
|
||||||
|
const [datePickerLeft, setDatePickerLeft] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setUnveil(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onCloseClick = () => {
|
||||||
|
setUnveil(false);
|
||||||
|
setTimeout(() => {
|
||||||
|
onClose();
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEditFieldClick = (cellIdentifier: CellIdentifier, top: number, right: number) => {
|
||||||
|
setEditingCell(cellIdentifier);
|
||||||
|
setEditFieldTop(top);
|
||||||
|
setEditFieldRight(right);
|
||||||
|
setShowFieldEditor(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onOutsideEditFieldClick = () => {
|
||||||
|
if (!showChangeFieldTypePopup) {
|
||||||
|
setShowFieldEditor(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChangeFieldTypeClick = (buttonTop: number, buttonRight: number) => {
|
||||||
|
setChangeFieldTypeTop(buttonTop);
|
||||||
|
setChangeFieldTypeRight(buttonRight);
|
||||||
|
setShowChangeFieldTypePopup(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeFieldType = async (newType: FieldType) => {
|
||||||
|
if (!editingCell) return;
|
||||||
|
|
||||||
|
const currentField = controller.fieldController.getField(editingCell.fieldId);
|
||||||
|
if (!currentField) return;
|
||||||
|
|
||||||
|
const typeOptionController = new TypeOptionController(viewId, Some(currentField));
|
||||||
|
await typeOptionController.switchToField(newType);
|
||||||
|
|
||||||
|
setEditingCell(new CellIdentifier(viewId, rowInfo.row.id, editingCell.fieldId, newType));
|
||||||
|
|
||||||
|
setShowChangeFieldTypePopup(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEditOptionsClick = async (cellIdentifier: CellIdentifier, left: number, top: number) => {
|
||||||
|
setEditingCell(cellIdentifier);
|
||||||
|
setChangeOptionsLeft(left);
|
||||||
|
setChangeOptionsTop(top);
|
||||||
|
setShowChangeOptionsPopup(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEditDateClick = async (cellIdentifier: CellIdentifier, left: number, top: number) => {
|
||||||
|
setEditingCell(cellIdentifier);
|
||||||
|
setDatePickerLeft(left);
|
||||||
|
setDatePickerTop(top);
|
||||||
|
setShowDatePicker(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDragEnd: OnDragEndResponder = (result) => {
|
||||||
|
if (!result.destination?.index) return;
|
||||||
|
void controller.moveField(result.source.droppableId, result.source.index, result.destination.index);
|
||||||
|
};
|
||||||
|
|
||||||
|
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'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className={`relative flex h-[90%] w-[70%] flex-col gap-8 rounded-xl bg-white px-8 pb-4 pt-12`}>
|
||||||
|
<div onClick={() => onCloseClick()} className={'absolute top-4 right-4'}>
|
||||||
|
<button className={'block h-8 w-8 rounded-lg text-shade-2 hover:bg-main-secondary'}>
|
||||||
|
<CloseSvg></CloseSvg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
|
<Droppable droppableId={'field-list'}>
|
||||||
|
{(provided) => (
|
||||||
|
<div
|
||||||
|
{...provided.droppableProps}
|
||||||
|
ref={provided.innerRef}
|
||||||
|
className={`flex flex-1 flex-col gap-2 ${
|
||||||
|
showFieldEditor || showChangeOptionsPopup || showDatePicker ? 'overflow-hidden' : 'overflow-auto'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{cells.map((cell, cellIndex) => (
|
||||||
|
<EditCellWrapper
|
||||||
|
index={cellIndex}
|
||||||
|
key={cellIndex}
|
||||||
|
cellIdentifier={cell.cellIdentifier}
|
||||||
|
cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
|
||||||
|
fieldController={controller.fieldController}
|
||||||
|
onEditFieldClick={(top: number, right: number) => onEditFieldClick(cell.cellIdentifier, top, right)}
|
||||||
|
onEditOptionsClick={(left: number, top: number) =>
|
||||||
|
onEditOptionsClick(cell.cellIdentifier, left, top)
|
||||||
|
}
|
||||||
|
onEditDateClick={(left: number, top: number) => onEditDateClick(cell.cellIdentifier, left, top)}
|
||||||
|
></EditCellWrapper>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Droppable>
|
||||||
|
</DragDropContext>
|
||||||
|
|
||||||
|
<div className={'border-t border-shade-6 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.newColumn')}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showFieldEditor && editingCell && (
|
||||||
|
<EditFieldPopup
|
||||||
|
top={editFieldTop}
|
||||||
|
right={editFieldRight}
|
||||||
|
cellIdentifier={editingCell}
|
||||||
|
viewId={viewId}
|
||||||
|
onOutsideClick={onOutsideEditFieldClick}
|
||||||
|
fieldInfo={controller.fieldController.getField(editingCell.fieldId)}
|
||||||
|
changeFieldTypeClick={onChangeFieldTypeClick}
|
||||||
|
></EditFieldPopup>
|
||||||
|
)}
|
||||||
|
{showChangeFieldTypePopup && (
|
||||||
|
<ChangeFieldTypePopup
|
||||||
|
top={changeFieldTypeTop}
|
||||||
|
right={changeFieldTypeRight}
|
||||||
|
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)}
|
||||||
|
></CellOptionsPopup>
|
||||||
|
)}
|
||||||
|
{showDatePicker && editingCell && (
|
||||||
|
<DatePickerPopup
|
||||||
|
top={datePickerTop}
|
||||||
|
left={datePickerLeft}
|
||||||
|
cellIdentifier={editingCell}
|
||||||
|
cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
|
||||||
|
fieldController={controller.fieldController}
|
||||||
|
onOutsideClick={() => setShowDatePicker(false)}
|
||||||
|
></DatePickerPopup>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,24 @@
|
|||||||
|
import { FieldType } from '@/services/backend';
|
||||||
|
import { TextTypeSvg } from '$app/components/_shared/svg/TextTypeSvg';
|
||||||
|
import { NumberTypeSvg } from '$app/components/_shared/svg/NumberTypeSvg';
|
||||||
|
import { DateTypeSvg } from '$app/components/_shared/svg/DateTypeSvg';
|
||||||
|
import { SingleSelectTypeSvg } from '$app/components/_shared/svg/SingleSelectTypeSvg';
|
||||||
|
import { MultiSelectTypeSvg } from '$app/components/_shared/svg/MultiSelectTypeSvg';
|
||||||
|
import { ChecklistTypeSvg } from '$app/components/_shared/svg/ChecklistTypeSvg';
|
||||||
|
import { UrlTypeSvg } from '$app/components/_shared/svg/UrlTypeSvg';
|
||||||
|
import { CheckboxSvg } from '$app/components/_shared/svg/CheckboxSvg';
|
||||||
|
|
||||||
|
export const FieldTypeIcon = ({ fieldType }: { fieldType: FieldType }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{fieldType === FieldType.RichText && <TextTypeSvg></TextTypeSvg>}
|
||||||
|
{fieldType === FieldType.Number && <NumberTypeSvg></NumberTypeSvg>}
|
||||||
|
{fieldType === FieldType.DateTime && <DateTypeSvg></DateTypeSvg>}
|
||||||
|
{fieldType === FieldType.SingleSelect && <SingleSelectTypeSvg></SingleSelectTypeSvg>}
|
||||||
|
{fieldType === FieldType.MultiSelect && <MultiSelectTypeSvg></MultiSelectTypeSvg>}
|
||||||
|
{fieldType === FieldType.Checklist && <ChecklistTypeSvg></ChecklistTypeSvg>}
|
||||||
|
{fieldType === FieldType.URL && <UrlTypeSvg></UrlTypeSvg>}
|
||||||
|
{fieldType === FieldType.Checkbox && <CheckboxSvg></CheckboxSvg>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,18 @@
|
|||||||
|
import { FieldType } from '@/services/backend';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export const FieldTypeName = ({ fieldType }: { fieldType: FieldType }) => {
|
||||||
|
const { t } = useTranslation('');
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{fieldType === FieldType.RichText && t('grid.field.textFieldName')}
|
||||||
|
{fieldType === FieldType.Number && t('grid.field.numberFieldName')}
|
||||||
|
{fieldType === FieldType.DateTime && t('grid.field.dateFieldName')}
|
||||||
|
{fieldType === FieldType.SingleSelect && t('grid.field.singleSelectFieldName')}
|
||||||
|
{fieldType === FieldType.MultiSelect && t('grid.field.multiSelectFieldName')}
|
||||||
|
{fieldType === FieldType.Checklist && t('grid.field.checklistFieldName')}
|
||||||
|
{fieldType === FieldType.URL && t('grid.field.urlFieldName')}
|
||||||
|
{fieldType === FieldType.Checkbox && t('grid.field.checkboxFieldName')}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -29,7 +29,10 @@ const supportedLanguages: { key: string; title: string }[] = [
|
|||||||
|
|
||||||
export const LanguageSelectPopup = ({ onClose }: { onClose: () => void }) => {
|
export const LanguageSelectPopup = ({ onClose }: { onClose: () => void }) => {
|
||||||
const items: IPopupItem[] = supportedLanguages.map<IPopupItem>((item) => ({
|
const items: IPopupItem[] = supportedLanguages.map<IPopupItem>((item) => ({
|
||||||
onClick: () => void i18n.changeLanguage(item.key),
|
onClick: () => {
|
||||||
|
void i18n.changeLanguage(item.key);
|
||||||
|
onClose();
|
||||||
|
},
|
||||||
title: item.title,
|
title: item.title,
|
||||||
icon: <></>,
|
icon: <></>,
|
||||||
}));
|
}));
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
NumberFormat,
|
NumberFormat,
|
||||||
SingleSelectTypeOptionPB,
|
SingleSelectTypeOptionPB,
|
||||||
TimeFormat,
|
TimeFormat,
|
||||||
} from '../../../../services/backend';
|
} from '@/services/backend';
|
||||||
import {
|
import {
|
||||||
makeChecklistTypeOptionContext,
|
makeChecklistTypeOptionContext,
|
||||||
makeDateTypeOptionContext,
|
makeDateTypeOptionContext,
|
||||||
|
@ -1,32 +1,43 @@
|
|||||||
import { CellIdentifier } from '../../../stores/effects/database/cell/cell_bd_svc';
|
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
|
||||||
import { CellCache } from '../../../stores/effects/database/cell/cell_cache';
|
import { CellCache } from '$app/stores/effects/database/cell/cell_cache';
|
||||||
import { FieldController } from '../../../stores/effects/database/field/field_controller';
|
import { FieldController } from '$app/stores/effects/database/field/field_controller';
|
||||||
import { CellControllerBuilder } from '../../../stores/effects/database/cell/controller_builder';
|
import { CellControllerBuilder } from '$app/stores/effects/database/cell/controller_builder';
|
||||||
import { DateCellDataPB, SelectOptionCellDataPB, URLCellDataPB } from '../../../../services/backend';
|
import { DateCellDataPB, SelectOptionCellDataPB, URLCellDataPB } from '$app/../services/backend';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { CellController } from '$app/stores/effects/database/cell/cell_controller';
|
||||||
|
|
||||||
export const useCell = (cellIdentifier: CellIdentifier, cellCache: CellCache, fieldController: FieldController) => {
|
export const useCell = (cellIdentifier: CellIdentifier, cellCache: CellCache, fieldController: FieldController) => {
|
||||||
const [data, setData] = useState<DateCellDataPB | URLCellDataPB | SelectOptionCellDataPB | string | undefined>();
|
const [data, setData] = useState<DateCellDataPB | URLCellDataPB | SelectOptionCellDataPB | string | undefined>();
|
||||||
|
const [cellController, setCellController] = useState<CellController<any, any>>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!cellIdentifier || !cellCache || !fieldController) return;
|
||||||
const builder = new CellControllerBuilder(cellIdentifier, cellCache, fieldController);
|
const builder = new CellControllerBuilder(cellIdentifier, cellCache, fieldController);
|
||||||
const cellController = builder.build();
|
const c = builder.build();
|
||||||
cellController.subscribeChanged({
|
setCellController(c);
|
||||||
onCellChanged: (value) => {
|
|
||||||
setData(value.unwrap());
|
c.subscribeChanged({
|
||||||
|
onCellChanged: (cellData) => {
|
||||||
|
if (cellData.some) {
|
||||||
|
setData(cellData.val);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// ignore the return value, because we are using the subscription
|
void (async () => {
|
||||||
void cellController.getCellData();
|
const cellData = await c.getCellData();
|
||||||
|
if (cellData.some) {
|
||||||
|
setData(cellData.unwrap());
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
// dispose is causing an error
|
void c.dispose();
|
||||||
// void cellController.dispose();
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, [cellIdentifier, cellCache, fieldController]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
cellController,
|
||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,26 +1,27 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { DatabaseController } from '../../../stores/effects/database/database_controller';
|
import { DatabaseController } from '$app/stores/effects/database/database_controller';
|
||||||
import { databaseActions, DatabaseFieldMap, IDatabaseColumn } from '../../../stores/reducers/database/slice';
|
import { databaseActions, DatabaseFieldMap, IDatabaseColumn } from '$app/stores/reducers/database/slice';
|
||||||
import { useAppDispatch } from '../../../stores/store';
|
import { useAppDispatch } from '$app/stores/store';
|
||||||
import loadField from './loadField';
|
import loadField from './loadField';
|
||||||
import { FieldInfo } from '../../../stores/effects/database/field/field_controller';
|
import { FieldInfo } from '$app/stores/effects/database/field/field_controller';
|
||||||
import { RowInfo } from '../../../stores/effects/database/row/row_cache';
|
import { RowInfo } from '$app/stores/effects/database/row/row_cache';
|
||||||
import { ViewLayoutTypePB } from '@/services/backend';
|
import { ViewLayoutTypePB } from '@/services/backend';
|
||||||
import { DatabaseGroupController } from '$app/stores/effects/database/group/group_controller';
|
import { DatabaseGroupController } from '$app/stores/effects/database/group/group_controller';
|
||||||
|
import { OnDragEndResponder } from 'react-beautiful-dnd';
|
||||||
|
|
||||||
export const useDatabase = (viewId: string, type?: ViewLayoutTypePB) => {
|
export const useDatabase = (viewId: string, type?: ViewLayoutTypePB) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const [controller, setController] = useState<DatabaseController>();
|
const [controller, setController] = useState<DatabaseController>();
|
||||||
const [rows, setRows] = useState<readonly RowInfo[]>([]);
|
const [rows, setRows] = useState<readonly RowInfo[]>([]);
|
||||||
const [groups, setGroups] = useState<readonly DatabaseGroupController[]>([]);
|
const [groups, setGroups] = useState<readonly DatabaseGroupController[]>([]);
|
||||||
|
const [groupByFieldId, setGroupByFieldId] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!viewId.length) return;
|
if (!viewId.length) return;
|
||||||
const c = new DatabaseController(viewId);
|
const c = new DatabaseController(viewId);
|
||||||
setController(c);
|
setController(c);
|
||||||
|
|
||||||
// dispose is causing an error
|
return () => void c.dispose();
|
||||||
// return () => void c.dispose();
|
|
||||||
}, [viewId]);
|
}, [viewId]);
|
||||||
|
|
||||||
const loadFields = async (fieldInfos: readonly FieldInfo[]) => {
|
const loadFields = async (fieldInfos: readonly FieldInfo[]) => {
|
||||||
@ -58,10 +59,37 @@ export const useDatabase = (viewId: string, type?: ViewLayoutTypePB) => {
|
|||||||
await controller.open();
|
await controller.open();
|
||||||
|
|
||||||
if (type === ViewLayoutTypePB.Board) {
|
if (type === ViewLayoutTypePB.Board) {
|
||||||
|
const fieldId = await controller.getGroupByFieldId();
|
||||||
|
setGroupByFieldId(fieldId.unwrap());
|
||||||
setGroups(controller.groups.value);
|
setGroups(controller.groups.value);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, [controller]);
|
}, [controller]);
|
||||||
|
|
||||||
return { loadFields, controller, rows, groups };
|
const onNewRowClick = async (index: number) => {
|
||||||
|
if (!groups) return;
|
||||||
|
if (!controller?.groups) return;
|
||||||
|
const group = groups[index];
|
||||||
|
await group.createRow();
|
||||||
|
|
||||||
|
setGroups([...controller.groups.value]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDragEnd: OnDragEndResponder = async (result) => {
|
||||||
|
if (!controller) return;
|
||||||
|
const { source, destination } = result;
|
||||||
|
const group = groups.find((g) => g.groupId === source.droppableId);
|
||||||
|
if (!group) return;
|
||||||
|
|
||||||
|
if (source.droppableId === destination?.droppableId) {
|
||||||
|
// move inside the block (group)
|
||||||
|
await controller.exchangeRow(group.rows[source.index].id, group.rows[destination.index].id);
|
||||||
|
} else {
|
||||||
|
// move to different block (group)
|
||||||
|
if (!destination?.droppableId) return;
|
||||||
|
await controller.moveRow(group.rows[source.index].id, destination.droppableId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { loadFields, controller, rows, groups, groupByFieldId, onNewRowClick, onDragEnd };
|
||||||
};
|
};
|
||||||
|
@ -1,14 +1,19 @@
|
|||||||
import { DatabaseController } from '../../../stores/effects/database/database_controller';
|
import { DatabaseController } from '$app/stores/effects/database/database_controller';
|
||||||
import { RowController } from '../../../stores/effects/database/row/row_controller';
|
import { RowController } from '$app/stores/effects/database/row/row_controller';
|
||||||
import { RowInfo } from '../../../stores/effects/database/row/row_cache';
|
import { RowInfo } from '$app/stores/effects/database/row/row_cache';
|
||||||
import { CellIdentifier } from '../../../stores/effects/database/cell/cell_bd_svc';
|
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { TypeOptionController } from '$app/stores/effects/database/field/type_option/type_option_controller';
|
||||||
|
import { None } from 'ts-results';
|
||||||
|
import { useAppSelector } from '$app/stores/store';
|
||||||
|
|
||||||
export const useRow = (viewId: string, databaseController: DatabaseController, rowInfo: RowInfo) => {
|
export const useRow = (viewId: string, databaseController: DatabaseController, rowInfo: RowInfo) => {
|
||||||
const [cells, setCells] = useState<{ fieldId: string; cellIdentifier: CellIdentifier }[]>([]);
|
const [cells, setCells] = useState<{ fieldId: string; cellIdentifier: CellIdentifier }[]>([]);
|
||||||
const [rowController, setRowController] = useState<RowController>();
|
const [rowController, setRowController] = useState<RowController>();
|
||||||
|
const databaseStore = useAppSelector((state) => state.database);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!databaseController || !rowInfo) return;
|
||||||
const rowCache = databaseController.databaseViewCache.getRowCache();
|
const rowCache = databaseController.databaseViewCache.getRowCache();
|
||||||
const fieldController = databaseController.fieldController;
|
const fieldController = databaseController.fieldController;
|
||||||
const c = new RowController(rowInfo, fieldController, rowCache);
|
const c = new RowController(rowInfo, fieldController, rowCache);
|
||||||
@ -17,7 +22,7 @@ export const useRow = (viewId: string, databaseController: DatabaseController, r
|
|||||||
return () => {
|
return () => {
|
||||||
// dispose row controller in future
|
// dispose row controller in future
|
||||||
};
|
};
|
||||||
}, []);
|
}, [databaseController, rowInfo]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!rowController) return;
|
if (!rowController) return;
|
||||||
@ -35,9 +40,16 @@ export const useRow = (viewId: string, databaseController: DatabaseController, r
|
|||||||
|
|
||||||
setCells(loadingCells);
|
setCells(loadingCells);
|
||||||
})();
|
})();
|
||||||
}, [rowController]);
|
}, [rowController, databaseStore.columns]);
|
||||||
|
|
||||||
|
const onNewColumnClick = async () => {
|
||||||
|
if (!databaseController) return;
|
||||||
|
const controller = new TypeOptionController(viewId, None);
|
||||||
|
await controller.initialize();
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cells: cells,
|
cells,
|
||||||
|
onNewColumnClick,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
export const ArrowLeftSvg = () => {
|
||||||
|
return (
|
||||||
|
<svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||||
|
<path d='M10 4L6 8L10 12' stroke='currentColor' strokeLinecap='round' strokeLinejoin='round' />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,7 @@
|
|||||||
|
export const ArrowRightSvg = () => {
|
||||||
|
return (
|
||||||
|
<svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||||
|
<path d='M6 4L10 8L6 12' stroke='currentColor' strokeLinecap='round' strokeLinejoin='round' />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,13 @@
|
|||||||
|
export const CheckboxSvg = () => {
|
||||||
|
return (
|
||||||
|
<svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||||
|
<path d='M6.5 8L8.11538 9.5L13.5 4.5' stroke='currentColor' strokeLinecap='round' strokeLinejoin='round' />
|
||||||
|
<path
|
||||||
|
d='M13 8.5V11.8889C13 12.1836 12.8829 12.4662 12.6746 12.6746C12.4662 12.8829 12.1836 13 11.8889 13H4.11111C3.81643 13 3.53381 12.8829 3.32544 12.6746C3.11706 12.4662 3 12.1836 3 11.8889V4.11111C3 3.81643 3.11706 3.53381 3.32544 3.32544C3.53381 3.11706 3.81643 3 4.11111 3H10.2222'
|
||||||
|
stroke='currentColor'
|
||||||
|
strokeLinecap='round'
|
||||||
|
strokeLinejoin='round'
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,7 @@
|
|||||||
|
export const CheckmarkSvg = () => {
|
||||||
|
return (
|
||||||
|
<svg width='100%' height='100%' viewBox='0 0 10 8' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||||
|
<path d='M1 5.2L2.84615 7L9 1' stroke='#00BCF0' strokeLinecap='round' strokeLinejoin='round' />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,15 @@
|
|||||||
|
export const ClockSvg = () => {
|
||||||
|
return (
|
||||||
|
<svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||||
|
<path
|
||||||
|
d='M8 13C10.7614 13 13 10.7614 13 8C13 5.23858 10.7614 3 8 3C5.23858 3 3 5.23858 3 8C3 10.7614 5.23858 13 8 13Z'
|
||||||
|
stroke='currentColor'
|
||||||
|
strokeLinecap='round'
|
||||||
|
strokeLinejoin='round'
|
||||||
|
/>
|
||||||
|
<path d='M8 5V8L10 9' stroke='currentColor' strokeLinecap='round' strokeLinejoin='round' />
|
||||||
|
<path d='M11.5 2.5L13.5 4.5' stroke='currentColor' strokeLinecap='round' />
|
||||||
|
<path d='M4.5 2.5L2.5 4.5' stroke='currentColor' strokeLinecap='round' />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,8 @@
|
|||||||
|
export const EditorCheckSvg = () => {
|
||||||
|
return (
|
||||||
|
<svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||||
|
<rect x='2' y='2' width='12' height='12' rx='4' fill='#00BCF0' />
|
||||||
|
<path d='M6 8L7.61538 9.5L10.5 6.5' stroke='white' strokeLinecap='round' strokeLinejoin='round' />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,7 @@
|
|||||||
|
export const EditorUncheckSvg = () => {
|
||||||
|
return (
|
||||||
|
<svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||||
|
<rect x='2.5' y='2.5' width='11' height='11' rx='3.5' stroke='#BDBDBD' />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,10 @@
|
|||||||
|
export const MoreSvg = () => {
|
||||||
|
return (
|
||||||
|
<svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||||
|
<path
|
||||||
|
d='M9.39568 7.6963L6.91032 5.56599C6.65085 5.34358 6.25 5.52795 6.25 5.86969L6.25 10.1303C6.25 10.4721 6.65085 10.6564 6.91032 10.434L9.39568 8.3037C9.58192 8.14406 9.58192 7.85594 9.39568 7.6963Z'
|
||||||
|
fill='currentColor'
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,9 @@
|
|||||||
|
export const SkipLeftSvg = () => {
|
||||||
|
return (
|
||||||
|
<svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||||
|
<path d='M3 11.7778L3 4' stroke='currentColor' strokeLinecap='round' strokeLinejoin='round' />
|
||||||
|
<path d='M9.5 4.5L6 8L9.5 11.5' stroke='currentColor' strokeLinecap='round' strokeLinejoin='round' />
|
||||||
|
<path d='M6 8L13 8' stroke='currentColor' strokeLinecap='round' strokeLinejoin='round' />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,9 @@
|
|||||||
|
export const SkipRightSvg = () => {
|
||||||
|
return (
|
||||||
|
<svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||||
|
<path d='M13 11.7778L13 4' stroke='currentColor' strokeLinecap='round' strokeLinejoin='round' />
|
||||||
|
<path d='M6.5 4.5L10 8L6.5 11.5' stroke='currentColor' strokeLinecap='round' strokeLinejoin='round' />
|
||||||
|
<path d='M10 8L3 8' stroke='currentColor' strokeLinecap='round' strokeLinejoin='round' />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
@ -4,9 +4,23 @@ import { BoardBlock } from './BoardBlock';
|
|||||||
import { NewBoardBlock } from './NewBoardBlock';
|
import { NewBoardBlock } from './NewBoardBlock';
|
||||||
import { useDatabase } from '../_shared/database-hooks/useDatabase';
|
import { useDatabase } from '../_shared/database-hooks/useDatabase';
|
||||||
import { ViewLayoutTypePB } from '@/services/backend';
|
import { ViewLayoutTypePB } from '@/services/backend';
|
||||||
|
import { DragDropContext } from 'react-beautiful-dnd';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { RowInfo } from '$app/stores/effects/database/row/row_cache';
|
||||||
|
import { EditRow } from '$app/components/_shared/EditRow/EditRow';
|
||||||
|
|
||||||
export const Board = ({ viewId }: { viewId: string }) => {
|
export const Board = ({ viewId }: { viewId: string }) => {
|
||||||
const { controller, rows, groups } = useDatabase(viewId, ViewLayoutTypePB.Board);
|
const { controller, rows, groups, groupByFieldId, onNewRowClick, onDragEnd } = useDatabase(
|
||||||
|
viewId,
|
||||||
|
ViewLayoutTypePB.Board
|
||||||
|
);
|
||||||
|
const [showBoardRow, setShowBoardRow] = useState(false);
|
||||||
|
const [boardRowInfo, setBoardRowInfo] = useState<RowInfo>();
|
||||||
|
|
||||||
|
const onOpenRow = (rowInfo: RowInfo) => {
|
||||||
|
setBoardRowInfo(rowInfo);
|
||||||
|
setShowBoardRow(true);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -22,24 +36,35 @@ export const Board = ({ viewId }: { viewId: string }) => {
|
|||||||
<SearchInput />
|
<SearchInput />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={'relative w-full flex-1 overflow-auto'}>
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
<div className={'absolute flex h-full flex-shrink-0 items-start justify-start gap-4'}>
|
<div className={'relative w-full flex-1 overflow-auto'}>
|
||||||
{controller &&
|
<div className={'absolute flex h-full flex-shrink-0 items-start justify-start gap-4'}>
|
||||||
groups &&
|
{controller &&
|
||||||
groups.map((group, index) => (
|
groups &&
|
||||||
<BoardBlock
|
groups.map((group, index) => (
|
||||||
key={index}
|
<BoardBlock
|
||||||
viewId={viewId}
|
key={group.groupId}
|
||||||
controller={controller}
|
viewId={viewId}
|
||||||
rows={group.rows}
|
controller={controller}
|
||||||
title={group.name}
|
group={group}
|
||||||
allRows={rows}
|
allRows={rows}
|
||||||
/>
|
groupByFieldId={groupByFieldId}
|
||||||
))}
|
onNewRowClick={() => onNewRowClick(index)}
|
||||||
|
onOpenRow={onOpenRow}
|
||||||
<NewBoardBlock onClick={() => console.log('new block')}></NewBoardBlock>
|
/>
|
||||||
|
))}
|
||||||
|
<NewBoardBlock onClick={() => console.log('new block')}></NewBoardBlock>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</DragDropContext>
|
||||||
|
{controller && showBoardRow && boardRowInfo && (
|
||||||
|
<EditRow
|
||||||
|
onClose={() => setShowBoardRow(false)}
|
||||||
|
viewId={viewId}
|
||||||
|
controller={controller}
|
||||||
|
rowInfo={boardRowInfo}
|
||||||
|
></EditRow>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -3,26 +3,31 @@ import AddSvg from '../_shared/svg/AddSvg';
|
|||||||
import { BoardCard } from './BoardCard';
|
import { BoardCard } from './BoardCard';
|
||||||
import { RowInfo } from '../../stores/effects/database/row/row_cache';
|
import { RowInfo } from '../../stores/effects/database/row/row_cache';
|
||||||
import { DatabaseController } from '../../stores/effects/database/database_controller';
|
import { DatabaseController } from '../../stores/effects/database/database_controller';
|
||||||
import { RowPB } from '@/services/backend';
|
import { Droppable } from 'react-beautiful-dnd';
|
||||||
|
import { DatabaseGroupController } from '$app/stores/effects/database/group/group_controller';
|
||||||
|
|
||||||
export const BoardBlock = ({
|
export const BoardBlock = ({
|
||||||
viewId,
|
viewId,
|
||||||
controller,
|
controller,
|
||||||
title,
|
|
||||||
rows,
|
|
||||||
allRows,
|
allRows,
|
||||||
|
groupByFieldId,
|
||||||
|
onNewRowClick,
|
||||||
|
onOpenRow,
|
||||||
|
group,
|
||||||
}: {
|
}: {
|
||||||
viewId: string;
|
viewId: string;
|
||||||
controller: DatabaseController;
|
controller: DatabaseController;
|
||||||
title: string;
|
|
||||||
rows: RowPB[];
|
|
||||||
allRows: readonly RowInfo[];
|
allRows: readonly RowInfo[];
|
||||||
|
groupByFieldId: string;
|
||||||
|
onNewRowClick: () => void;
|
||||||
|
onOpenRow: (rowId: RowInfo) => void;
|
||||||
|
group: DatabaseGroupController;
|
||||||
}) => {
|
}) => {
|
||||||
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'}>
|
||||||
<div className={'flex items-center gap-2'}>
|
<div className={'flex items-center gap-2'}>
|
||||||
<span>{title}</span>
|
<span>{group.name}</span>
|
||||||
<span className={'text-shade-4'}>()</span>
|
<span className={'text-shade-4'}>()</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={'flex items-center gap-2'}>
|
<div className={'flex items-center gap-2'}>
|
||||||
@ -34,18 +39,37 @@ export const BoardBlock = ({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={'flex flex-1 flex-col gap-1 overflow-auto px-2'}>
|
<Droppable droppableId={group.groupId}>
|
||||||
{rows.map((row_pb, index) => {
|
{(provided) => (
|
||||||
const row = allRows.find((r) => r.row.id === row_pb.id);
|
<div
|
||||||
return row ? (
|
className={'flex flex-1 flex-col gap-1 overflow-auto px-2'}
|
||||||
<BoardCard viewId={viewId} controller={controller} key={index} rowInfo={row}></BoardCard>
|
{...provided.droppableProps}
|
||||||
) : (
|
ref={provided.innerRef}
|
||||||
<span key={index}></span>
|
>
|
||||||
);
|
{group.rows.map((row_pb, index) => {
|
||||||
})}
|
const row = allRows.find((r) => r.row.id === row_pb.id);
|
||||||
</div>
|
return row ? (
|
||||||
|
<BoardCard
|
||||||
|
viewId={viewId}
|
||||||
|
controller={controller}
|
||||||
|
index={index}
|
||||||
|
key={row.row.id}
|
||||||
|
rowInfo={row}
|
||||||
|
groupByFieldId={groupByFieldId}
|
||||||
|
onOpenRow={onOpenRow}
|
||||||
|
></BoardCard>
|
||||||
|
) : (
|
||||||
|
<span key={index}></span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Droppable>
|
||||||
<div className={'p-2'}>
|
<div className={'p-2'}>
|
||||||
<button className={'flex w-full items-center gap-2 rounded-lg px-2 py-2 hover:bg-surface-2'}>
|
<button
|
||||||
|
onClick={onNewRowClick}
|
||||||
|
className={'flex w-full items-center gap-2 rounded-lg px-2 py-2 hover:bg-surface-2'}
|
||||||
|
>
|
||||||
<span className={'h-5 w-5'}>
|
<span className={'h-5 w-5'}>
|
||||||
<AddSvg></AddSvg>
|
<AddSvg></AddSvg>
|
||||||
</span>
|
</span>
|
||||||
|
@ -3,36 +3,52 @@ import { RowInfo } from '../../stores/effects/database/row/row_cache';
|
|||||||
import { useRow } from '../_shared/database-hooks/useRow';
|
import { useRow } from '../_shared/database-hooks/useRow';
|
||||||
import { DatabaseController } from '../../stores/effects/database/database_controller';
|
import { DatabaseController } from '../../stores/effects/database/database_controller';
|
||||||
import { BoardCell } from './BoardCell';
|
import { BoardCell } from './BoardCell';
|
||||||
|
import { Draggable } from 'react-beautiful-dnd';
|
||||||
|
|
||||||
export const BoardCard = ({
|
export const BoardCard = ({
|
||||||
|
index,
|
||||||
viewId,
|
viewId,
|
||||||
controller,
|
controller,
|
||||||
rowInfo,
|
rowInfo,
|
||||||
|
groupByFieldId,
|
||||||
|
onOpenRow,
|
||||||
}: {
|
}: {
|
||||||
|
index: number;
|
||||||
viewId: string;
|
viewId: string;
|
||||||
controller: DatabaseController;
|
controller: DatabaseController;
|
||||||
rowInfo: RowInfo;
|
rowInfo: RowInfo;
|
||||||
|
groupByFieldId: string;
|
||||||
|
onOpenRow: (rowId: RowInfo) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { cells } = useRow(viewId, controller, rowInfo);
|
const { cells } = useRow(viewId, controller, rowInfo);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Draggable draggableId={rowInfo.row.id} index={index}>
|
||||||
onClick={() => console.log('on click')}
|
{(provided) => (
|
||||||
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 `}
|
<div
|
||||||
>
|
ref={provided.innerRef}
|
||||||
<button className={'absolute right-4 top-2.5 h-5 w-5 rounded hover:bg-surface-2'}>
|
{...provided.draggableProps}
|
||||||
<Details2Svg></Details2Svg>
|
{...provided.dragHandleProps}
|
||||||
</button>
|
onClick={() => onOpenRow(rowInfo)}
|
||||||
<div className={'flex flex-col gap-3'}>
|
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 `}
|
||||||
{cells.map((cell, index) => (
|
>
|
||||||
<BoardCell
|
<button className={'absolute right-4 top-2.5 h-5 w-5 rounded hover:bg-surface-2'}>
|
||||||
key={index}
|
<Details2Svg></Details2Svg>
|
||||||
cellIdentifier={cell.cellIdentifier}
|
</button>
|
||||||
cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
|
<div className={'flex flex-col gap-3'}>
|
||||||
fieldController={controller.fieldController}
|
{cells
|
||||||
></BoardCell>
|
.filter((cell) => cell.fieldId !== groupByFieldId)
|
||||||
))}
|
.map((cell, cellIndex) => (
|
||||||
</div>
|
<BoardCell
|
||||||
</div>
|
key={cellIndex}
|
||||||
|
cellIdentifier={cell.cellIdentifier}
|
||||||
|
cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
|
||||||
|
fieldController={controller.fieldController}
|
||||||
|
></BoardCell>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Draggable>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -5,6 +5,7 @@ import { FieldType } from '../../../services/backend';
|
|||||||
import { BoardOptionsCell } from './BoardOptionsCell';
|
import { BoardOptionsCell } from './BoardOptionsCell';
|
||||||
import { BoardDateCell } from './BoardDateCell';
|
import { BoardDateCell } from './BoardDateCell';
|
||||||
import { BoardTextCell } from './BoardTextCell';
|
import { BoardTextCell } from './BoardTextCell';
|
||||||
|
import { BoardUrlCell } from '$app/components/board/BoardUrlCell';
|
||||||
|
|
||||||
export const BoardCell = ({
|
export const BoardCell = ({
|
||||||
cellIdentifier,
|
cellIdentifier,
|
||||||
@ -31,6 +32,12 @@ export const BoardCell = ({
|
|||||||
cellCache={cellCache}
|
cellCache={cellCache}
|
||||||
fieldController={fieldController}
|
fieldController={fieldController}
|
||||||
></BoardDateCell>
|
></BoardDateCell>
|
||||||
|
) : cellIdentifier.fieldType === FieldType.URL ? (
|
||||||
|
<BoardUrlCell
|
||||||
|
cellIdentifier={cellIdentifier}
|
||||||
|
cellCache={cellCache}
|
||||||
|
fieldController={fieldController}
|
||||||
|
></BoardUrlCell>
|
||||||
) : (
|
) : (
|
||||||
<BoardTextCell
|
<BoardTextCell
|
||||||
cellIdentifier={cellIdentifier}
|
cellIdentifier={cellIdentifier}
|
||||||
|
@ -3,6 +3,7 @@ import { useCell } from '../_shared/database-hooks/useCell';
|
|||||||
import { CellIdentifier } from '../../stores/effects/database/cell/cell_bd_svc';
|
import { CellIdentifier } from '../../stores/effects/database/cell/cell_bd_svc';
|
||||||
import { CellCache } from '../../stores/effects/database/cell/cell_cache';
|
import { CellCache } from '../../stores/effects/database/cell/cell_cache';
|
||||||
import { FieldController } from '../../stores/effects/database/field/field_controller';
|
import { FieldController } from '../../stores/effects/database/field/field_controller';
|
||||||
|
import { getBgColor } from '$app/components/_shared/getColor';
|
||||||
|
|
||||||
export const BoardOptionsCell = ({
|
export const BoardOptionsCell = ({
|
||||||
cellIdentifier,
|
cellIdentifier,
|
||||||
@ -16,10 +17,13 @@ export const BoardOptionsCell = ({
|
|||||||
const { data } = useCell(cellIdentifier, cellCache, fieldController);
|
const { data } = useCell(cellIdentifier, cellCache, fieldController);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className={'flex flex-wrap items-center gap-2 py-2 text-xs text-black'}>
|
||||||
{(data as SelectOptionCellDataPB | undefined)?.select_options?.map((option, index) => (
|
{(data as SelectOptionCellDataPB | undefined)?.select_options?.map((option, index) => (
|
||||||
<div key={index}>{option?.name || ''}</div>
|
<div className={`${getBgColor(option.color)} rounded px-2 py-0.5`} key={index}>
|
||||||
|
{option?.name || ''}
|
||||||
|
</div>
|
||||||
)) || ''}
|
)) || ''}
|
||||||
</>
|
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { CellIdentifier } from '../../stores/effects/database/cell/cell_bd_svc';
|
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
|
||||||
import { CellCache } from '../../stores/effects/database/cell/cell_cache';
|
import { CellCache } from '$app/stores/effects/database/cell/cell_cache';
|
||||||
import { FieldController } from '../../stores/effects/database/field/field_controller';
|
import { FieldController } from '$app/stores/effects/database/field/field_controller';
|
||||||
import { useCell } from '../_shared/database-hooks/useCell';
|
import { useCell } from '../_shared/database-hooks/useCell';
|
||||||
|
|
||||||
export const BoardTextCell = ({
|
export const BoardTextCell = ({
|
||||||
@ -14,5 +14,11 @@ export const BoardTextCell = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { data } = useCell(cellIdentifier, cellCache, fieldController);
|
const { data } = useCell(cellIdentifier, cellCache, fieldController);
|
||||||
|
|
||||||
return <div>{(data as string | undefined) || ''}</div>;
|
return (
|
||||||
|
<div>
|
||||||
|
{((data as string | undefined) || '').split('\n').map((line, index) => (
|
||||||
|
<div key={index}>{line}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
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 { URLCellDataPB } from '@/services/backend';
|
||||||
|
|
||||||
|
export const BoardUrlCell = ({
|
||||||
|
cellIdentifier,
|
||||||
|
cellCache,
|
||||||
|
fieldController,
|
||||||
|
}: {
|
||||||
|
cellIdentifier: CellIdentifier;
|
||||||
|
cellCache: CellCache;
|
||||||
|
fieldController: FieldController;
|
||||||
|
}) => {
|
||||||
|
const { data } = useCell(cellIdentifier, cellCache, fieldController);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<a
|
||||||
|
className={'text-main-accent hover:underline'}
|
||||||
|
href={(data as URLCellDataPB | undefined)?.url || ''}
|
||||||
|
target={'_blank'}
|
||||||
|
>
|
||||||
|
{(data as URLCellDataPB | undefined)?.content || ''}
|
||||||
|
</a>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -3,7 +3,7 @@ import { CloseSvg } from '../_shared/svg/CloseSvg';
|
|||||||
|
|
||||||
export const ErrorModal = ({ message, onClose }: { message: string; onClose: () => void }) => {
|
export const ErrorModal = ({ message, onClose }: { message: string; onClose: () => void }) => {
|
||||||
return (
|
return (
|
||||||
<div className={'fixed inset-0 z-20 flex items-center justify-center bg-white/30 backdrop-blur-sm'}>
|
<div className={'fixed inset-0 z-10 flex items-center justify-center bg-white/30 backdrop-blur-sm'}>
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
'relative flex flex-col items-center gap-8 rounded-xl border border-shade-5 bg-white px-16 py-8 shadow-md'
|
'relative flex flex-col items-center gap-8 rounded-xl border border-shade-5 bg-white px-16 py-8 shadow-md'
|
||||||
@ -11,7 +11,7 @@ export const ErrorModal = ({ message, onClose }: { message: string; onClose: ()
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
onClick={() => onClose()}
|
onClick={() => onClose()}
|
||||||
className={'absolute right-0 top-0 z-20 px-2 py-2 text-shade-5 hover:text-black'}
|
className={'absolute right-0 top-0 z-10 px-2 py-2 text-shade-5 hover:text-black'}
|
||||||
>
|
>
|
||||||
<i className={'block h-8 w-8'}>
|
<i className={'block h-8 w-8'}>
|
||||||
<CloseSvg></CloseSvg>
|
<CloseSvg></CloseSvg>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { FieldType } from '../../../../services/backend/models/flowy-database/field_entities';
|
import { FieldType } from '@/services/backend/models/flowy-database/field_entities';
|
||||||
import { gridActions } from '../../../stores/reducers/grid/slice';
|
import { gridActions } from '../../../stores/reducers/grid/slice';
|
||||||
import { useAppDispatch, useAppSelector } from '../../../stores/store';
|
import { useAppDispatch, useAppSelector } from '../../../stores/store';
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import { SingleSelectTypeSvg } from '../../_shared/svg/SingleSelectTypeSvg';
|
|||||||
import { MultiSelectTypeSvg } from '../../_shared/svg/MultiSelectTypeSvg';
|
import { MultiSelectTypeSvg } from '../../_shared/svg/MultiSelectTypeSvg';
|
||||||
import { ChecklistTypeSvg } from '../../_shared/svg/ChecklistTypeSvg';
|
import { ChecklistTypeSvg } from '../../_shared/svg/ChecklistTypeSvg';
|
||||||
import { UrlTypeSvg } from '../../_shared/svg/UrlTypeSvg';
|
import { UrlTypeSvg } from '../../_shared/svg/UrlTypeSvg';
|
||||||
import { FieldType } from '../../../../services/backend/models/flowy-database/field_entities';
|
import { FieldType } from '@/services/backend/models/flowy-database/field_entities';
|
||||||
|
|
||||||
export const GridTableHeader = () => {
|
export const GridTableHeader = () => {
|
||||||
const { fields, onAddField } = useGridTableHeaderHooks();
|
const { fields, onAddField } = useGridTableHeaderHooks();
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
import { EarthSvg } from '$app/components/_shared/svg/EarthSvg';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { LanguageSelectPopup } from '$app/components/_shared/LanguageSelectPopup';
|
||||||
|
|
||||||
|
export const LanguageButton = () => {
|
||||||
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button onClick={() => setShowPopup(!showPopup)} className={'h-5 w-5'}>
|
||||||
|
<EarthSvg></EarthSvg>
|
||||||
|
</button>
|
||||||
|
{showPopup && <LanguageSelectPopup onClose={() => setShowPopup(false)}></LanguageSelectPopup>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -2,6 +2,7 @@ import { Button } from '../../_shared/Button';
|
|||||||
import { Details2Svg } from '../../_shared/svg/Details2Svg';
|
import { Details2Svg } from '../../_shared/svg/Details2Svg';
|
||||||
import { usePageOptions } from './PageOptions.hooks';
|
import { usePageOptions } from './PageOptions.hooks';
|
||||||
import { OptionsPopup } from './OptionsPopup';
|
import { OptionsPopup } from './OptionsPopup';
|
||||||
|
import { LanguageButton } from '$app/components/layout/HeaderPanel/LanguageButton';
|
||||||
|
|
||||||
export const PageOptions = () => {
|
export const PageOptions = () => {
|
||||||
const { showOptionsPopup, onOptionsClick, onClose, onSignOutClick } = usePageOptions();
|
const { showOptionsPopup, onOptionsClick, onClose, onSignOutClick } = usePageOptions();
|
||||||
@ -13,7 +14,9 @@ export const PageOptions = () => {
|
|||||||
Share
|
Share
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<button id='option-button' className={'relative h-8 w-8'} onClick={onOptionsClick} >
|
<LanguageButton></LanguageButton>
|
||||||
|
|
||||||
|
<button id='option-button' className={'relative h-8 w-8'} onClick={onOptionsClick}>
|
||||||
<Details2Svg></Details2Svg>
|
<Details2Svg></Details2Svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@ import { foldersActions, IFolder } from '../../../stores/reducers/folders/slice'
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useAppDispatch, useAppSelector } from '../../../stores/store';
|
import { useAppDispatch, useAppSelector } from '../../../stores/store';
|
||||||
import { IPage, pagesActions } from '../../../stores/reducers/pages/slice';
|
import { IPage, pagesActions } from '../../../stores/reducers/pages/slice';
|
||||||
import { AppPB, ViewLayoutTypePB } from '../../../../services/backend';
|
import { AppPB, ViewLayoutTypePB } from '@/services/backend';
|
||||||
import { AppBackendService } from '../../../stores/effects/folder/app/app_bd_svc';
|
import { AppBackendService } from '../../../stores/effects/folder/app/app_bd_svc';
|
||||||
import { WorkspaceBackendService } from '../../../stores/effects/folder/workspace/workspace_bd_svc';
|
import { WorkspaceBackendService } from '../../../stores/effects/folder/workspace/workspace_bd_svc';
|
||||||
import { useError } from '../../error/Error.hooks';
|
import { useError } from '../../error/Error.hooks';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useAppSelector } from '../../../stores/store';
|
import { useAppSelector } from '../../../stores/store';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { IPage } from '../../../stores/reducers/pages/slice';
|
import { IPage } from '../../../stores/reducers/pages/slice';
|
||||||
import { ViewLayoutTypePB } from '../../../../services/backend';
|
import { ViewLayoutTypePB } from '@/services/backend';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
export const useNavigationPanelHooks = function () {
|
export const useNavigationPanelHooks = function () {
|
||||||
|
@ -7,7 +7,7 @@ import { IPage } from '../../../stores/reducers/pages/slice';
|
|||||||
import { Button } from '../../_shared/Button';
|
import { Button } from '../../_shared/Button';
|
||||||
import { usePageEvents } from './PageItem.hooks';
|
import { usePageEvents } from './PageItem.hooks';
|
||||||
import { RenamePopup } from './RenamePopup';
|
import { RenamePopup } from './RenamePopup';
|
||||||
import { ViewLayoutTypePB } from '../../../../services/backend';
|
import { ViewLayoutTypePB } from '@/services/backend';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { PAGE_ITEM_HEIGHT } from '../../_shared/constants';
|
import { PAGE_ITEM_HEIGHT } from '../../_shared/constants';
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { FlowyError, UserNotification } from '../../../../../services/backend';
|
import { FlowyError, UserNotification } from '@/services/backend';
|
||||||
import { NotificationParser, OnNotificationError } from '../../../../../services/backend/notifications';
|
import { NotificationParser, OnNotificationError } from '@/services/backend/notifications';
|
||||||
import { Result } from 'ts-results';
|
import { Result } from 'ts-results';
|
||||||
|
|
||||||
declare type UserNotificationCallback = (ty: UserNotification, payload: Result<Uint8Array, FlowyError>) => void;
|
declare type UserNotificationCallback = (ty: UserNotification, payload: Result<Uint8Array, FlowyError>) => void;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { FlowyError, UserNotification, UserProfilePB } from '../../../../../services/backend';
|
import { FlowyError, UserNotification, UserProfilePB } from '@/services/backend';
|
||||||
import { AFNotificationObserver, OnNotificationError } from '../../../../../services/backend/notifications';
|
import { AFNotificationObserver, OnNotificationError } from '@/services/backend/notifications';
|
||||||
import { UserNotificationParser } from './parser';
|
import { UserNotificationParser } from './parser';
|
||||||
import { Ok, Result } from 'ts-results';
|
import { Ok, Result } from 'ts-results';
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { DatabaseEventGetCell, DatabaseEventUpdateCell } from '../../../../../services/backend/events/flowy-database';
|
import { DatabaseEventGetCell, DatabaseEventUpdateCell } from '@/services/backend/events/flowy-database';
|
||||||
import { CellChangesetPB, CellIdPB } from '../../../../../services/backend/models/flowy-database/cell_entities';
|
import { CellChangesetPB, CellIdPB, FieldType } from '@/services/backend';
|
||||||
import { FieldType } from '../../../../../services/backend/models/flowy-database/field_entities';
|
|
||||||
|
|
||||||
class CellIdentifier {
|
class CellIdentifier {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -108,6 +108,7 @@ export class CellController<T, D> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
dispose = async () => {
|
dispose = async () => {
|
||||||
|
this.cellDataNotifier.unsubscribe();
|
||||||
await this.cellObserver.unsubscribe();
|
await this.cellObserver.unsubscribe();
|
||||||
await this.fieldNotifier.unsubscribe();
|
await this.fieldNotifier.unsubscribe();
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Ok, Result } from 'ts-results';
|
import { Ok, Result } from 'ts-results';
|
||||||
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
import { ChangeNotifier } from '$app/utils/change_notifier';
|
||||||
import { DatabaseNotificationObserver } from '../notifications/observer';
|
import { DatabaseNotificationObserver } from '../notifications/observer';
|
||||||
import { DatabaseNotification, FlowyError } from '../../../../../services/backend';
|
import { DatabaseNotification, FlowyError } from '@/services/backend';
|
||||||
|
|
||||||
type UpdateCellNotifiedValue = Result<void, FlowyError>;
|
type UpdateCellNotifiedValue = Result<void, FlowyError>;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { DateCellDataPB, FieldType, SelectOptionCellDataPB, URLCellDataPB } from '../../../../../services/backend';
|
import { DateCellDataPB, FieldType, SelectOptionCellDataPB, URLCellDataPB } from '@/services/backend';
|
||||||
import { CellIdentifier } from './cell_bd_svc';
|
import { CellIdentifier } from './cell_bd_svc';
|
||||||
import { CellController } from './cell_controller';
|
import { CellController } from './cell_controller';
|
||||||
import {
|
import {
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import utf8 from 'utf8';
|
import utf8 from 'utf8';
|
||||||
import { CellBackendService, CellIdentifier } from './cell_bd_svc';
|
import { CellBackendService, CellIdentifier } from './cell_bd_svc';
|
||||||
import { DateCellDataPB } from '../../../../../services/backend/models/flowy-database/date_type_option_entities';
|
import { SelectOptionCellDataPB, URLCellDataPB, DateCellDataPB } from '@/services/backend';
|
||||||
import { SelectOptionCellDataPB } from '../../../../../services/backend/models/flowy-database/select_type_option';
|
|
||||||
import { URLCellDataPB } from '../../../../../services/backend/models/flowy-database/url_type_option_entities';
|
|
||||||
import { Err, None, Ok, Option, Some } from 'ts-results';
|
import { Err, None, Ok, Option, Some } from 'ts-results';
|
||||||
import { Log } from '../../../../utils/log';
|
import { Log } from '$app/utils/log';
|
||||||
|
|
||||||
abstract class CellDataParser<T> {
|
abstract class CellDataParser<T> {
|
||||||
abstract parserData(data: Uint8Array): Option<T>;
|
abstract parserData(data: Uint8Array): Option<T>;
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import { Result } from 'ts-results';
|
import { Result } from 'ts-results';
|
||||||
import { FlowyError } from '../../../../../services/backend/models/flowy-error';
|
|
||||||
import { CellBackendService, CellIdentifier } from './cell_bd_svc';
|
import { CellBackendService, CellIdentifier } from './cell_bd_svc';
|
||||||
import { CalendarData } from './controller_builder';
|
import { CalendarData } from './controller_builder';
|
||||||
import { DateChangesetPB } from '../../../../../services/backend/models/flowy-database/date_type_option_entities';
|
import { DateChangesetPB, FlowyError, CellIdPB } from '@/services/backend';
|
||||||
import { CellIdPB } from '../../../../../services/backend/models/flowy-database/cell_entities';
|
import { DatabaseEventUpdateDateCell } from '@/services/backend/events/flowy-database';
|
||||||
import { DatabaseEventUpdateDateCell } from '../../../../../services/backend/events/flowy-database';
|
|
||||||
|
|
||||||
export abstract class CellDataPersistence<D> {
|
export abstract class CellDataPersistence<D> {
|
||||||
abstract save(data: D): Promise<Result<void, FlowyError>>;
|
abstract save(data: D): Promise<Result<void, FlowyError>>;
|
||||||
|
@ -5,13 +5,13 @@ import {
|
|||||||
SelectOptionCellChangesetPB,
|
SelectOptionCellChangesetPB,
|
||||||
SelectOptionChangesetPB,
|
SelectOptionChangesetPB,
|
||||||
SelectOptionPB,
|
SelectOptionPB,
|
||||||
} from '../../../../../services/backend';
|
} from '@/services/backend';
|
||||||
import {
|
import {
|
||||||
DatabaseEventCreateSelectOption,
|
DatabaseEventCreateSelectOption,
|
||||||
DatabaseEventGetSelectOptionCellData,
|
DatabaseEventGetSelectOptionCellData,
|
||||||
DatabaseEventUpdateSelectOption,
|
DatabaseEventUpdateSelectOption,
|
||||||
DatabaseEventUpdateSelectOptionCell,
|
DatabaseEventUpdateSelectOptionCell,
|
||||||
} from '../../../../../services/backend/events/flowy-database';
|
} from '@/services/backend/events/flowy-database';
|
||||||
|
|
||||||
export class SelectOptionBackendService {
|
export class SelectOptionBackendService {
|
||||||
constructor(public readonly viewId: string, public readonly fieldId: string) {}
|
constructor(public readonly viewId: string, public readonly fieldId: string) {}
|
||||||
|
@ -1,15 +1,20 @@
|
|||||||
import {
|
import {
|
||||||
DatabaseEventCreateRow,
|
DatabaseEventCreateRow,
|
||||||
DatabaseEventGetDatabase,
|
DatabaseEventGetDatabase,
|
||||||
|
DatabaseEventGetDatabaseSetting,
|
||||||
DatabaseEventGetFields,
|
DatabaseEventGetFields,
|
||||||
DatabaseEventGetGroup,
|
DatabaseEventGetGroup,
|
||||||
DatabaseEventGetGroups,
|
DatabaseEventGetGroups,
|
||||||
|
DatabaseEventMoveField,
|
||||||
DatabaseEventMoveGroup,
|
DatabaseEventMoveGroup,
|
||||||
DatabaseEventMoveGroupRow,
|
DatabaseEventMoveGroupRow,
|
||||||
|
DatabaseEventMoveRow,
|
||||||
DatabaseGroupIdPB,
|
DatabaseGroupIdPB,
|
||||||
|
MoveFieldPayloadPB,
|
||||||
MoveGroupPayloadPB,
|
MoveGroupPayloadPB,
|
||||||
MoveGroupRowPayloadPB,
|
MoveGroupRowPayloadPB,
|
||||||
} from '../../../../services/backend/events/flowy-database';
|
MoveRowPayloadPB,
|
||||||
|
} from '@/services/backend/events/flowy-database';
|
||||||
import {
|
import {
|
||||||
GetFieldPayloadPB,
|
GetFieldPayloadPB,
|
||||||
RepeatedFieldIdPB,
|
RepeatedFieldIdPB,
|
||||||
@ -17,9 +22,10 @@ import {
|
|||||||
DatabaseViewIdPB,
|
DatabaseViewIdPB,
|
||||||
CreateRowPayloadPB,
|
CreateRowPayloadPB,
|
||||||
ViewIdPB,
|
ViewIdPB,
|
||||||
} from '../../../../services/backend';
|
} from '@/services/backend';
|
||||||
import { FolderEventCloseView } from '../../../../services/backend/events/flowy-folder';
|
import { FolderEventCloseView } from '@/services/backend/events/flowy-folder';
|
||||||
|
|
||||||
|
/// A service that wraps the backend service
|
||||||
export class DatabaseBackendService {
|
export class DatabaseBackendService {
|
||||||
viewId: string;
|
viewId: string;
|
||||||
|
|
||||||
@ -27,6 +33,7 @@ export class DatabaseBackendService {
|
|||||||
this.viewId = viewId;
|
this.viewId = viewId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Open a database
|
||||||
openDatabase = async () => {
|
openDatabase = async () => {
|
||||||
const payload = DatabaseViewIdPB.fromObject({
|
const payload = DatabaseViewIdPB.fromObject({
|
||||||
value: this.viewId,
|
value: this.viewId,
|
||||||
@ -34,6 +41,7 @@ export class DatabaseBackendService {
|
|||||||
return DatabaseEventGetDatabase(payload);
|
return DatabaseEventGetDatabase(payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Close a database
|
||||||
closeDatabase = async () => {
|
closeDatabase = async () => {
|
||||||
const payload = ViewIdPB.fromObject({ value: this.viewId });
|
const payload = ViewIdPB.fromObject({ value: this.viewId });
|
||||||
return FolderEventCloseView(payload);
|
return FolderEventCloseView(payload);
|
||||||
@ -72,6 +80,15 @@ export class DatabaseBackendService {
|
|||||||
return DatabaseEventMoveGroupRow(payload);
|
return DatabaseEventMoveGroupRow(payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exchangeRow = (fromRowId: string, toRowId: string) => {
|
||||||
|
const payload = MoveRowPayloadPB.fromObject({
|
||||||
|
view_id: this.viewId,
|
||||||
|
from_row_id: fromRowId,
|
||||||
|
to_row_id: toRowId,
|
||||||
|
});
|
||||||
|
return DatabaseEventMoveRow(payload);
|
||||||
|
};
|
||||||
|
|
||||||
moveGroup = (fromGroupId: string, toGroupId: string) => {
|
moveGroup = (fromGroupId: string, toGroupId: string) => {
|
||||||
const payload = MoveGroupPayloadPB.fromObject({
|
const payload = MoveGroupPayloadPB.fromObject({
|
||||||
view_id: this.viewId,
|
view_id: this.viewId,
|
||||||
@ -81,6 +98,17 @@ export class DatabaseBackendService {
|
|||||||
return DatabaseEventMoveGroup(payload);
|
return DatabaseEventMoveGroup(payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
moveField = (fieldId: string, fromIndex: number, toIndex: number) => {
|
||||||
|
const payload = MoveFieldPayloadPB.fromObject({
|
||||||
|
view_id: this.viewId,
|
||||||
|
field_id: fieldId,
|
||||||
|
from_index: fromIndex,
|
||||||
|
to_index: toIndex,
|
||||||
|
});
|
||||||
|
return DatabaseEventMoveField(payload);
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Get all fields in database
|
||||||
getFields = async (fieldIds?: FieldIdPB[]) => {
|
getFields = async (fieldIds?: FieldIdPB[]) => {
|
||||||
const payload = GetFieldPayloadPB.fromObject({ view_id: this.viewId });
|
const payload = GetFieldPayloadPB.fromObject({ view_id: this.viewId });
|
||||||
|
|
||||||
@ -91,6 +119,7 @@ export class DatabaseBackendService {
|
|||||||
return DatabaseEventGetFields(payload).then((result) => result.map((value) => value.items));
|
return DatabaseEventGetFields(payload).then((result) => result.map((value) => value.items));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Get a group by id
|
||||||
getGroup = (groupId: string) => {
|
getGroup = (groupId: string) => {
|
||||||
const payload = DatabaseGroupIdPB.fromObject({ view_id: this.viewId, group_id: groupId });
|
const payload = DatabaseGroupIdPB.fromObject({ view_id: this.viewId, group_id: groupId });
|
||||||
return DatabaseEventGetGroup(payload);
|
return DatabaseEventGetGroup(payload);
|
||||||
@ -102,4 +131,9 @@ export class DatabaseBackendService {
|
|||||||
const payload = DatabaseViewIdPB.fromObject({ value: this.viewId });
|
const payload = DatabaseViewIdPB.fromObject({ value: this.viewId });
|
||||||
return DatabaseEventGetGroups(payload);
|
return DatabaseEventGetGroups(payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getSettings = () => {
|
||||||
|
const payload = DatabaseViewIdPB.fromObject({ value: this.viewId });
|
||||||
|
return DatabaseEventGetDatabaseSetting(payload);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { DatabaseBackendService } from './database_bd_svc';
|
import { DatabaseBackendService } from './database_bd_svc';
|
||||||
import { FieldController, FieldInfo } from './field/field_controller';
|
import { FieldController, FieldInfo } from './field/field_controller';
|
||||||
import { DatabaseViewCache } from './view/database_view_cache';
|
import { DatabaseViewCache } from './view/database_view_cache';
|
||||||
import { DatabasePB, GroupPB } from '../../../../services/backend';
|
import { DatabasePB, FlowyError, GroupPB } from '@/services/backend';
|
||||||
import { RowChangedReason, RowInfo } from './row/row_cache';
|
import { RowChangedReason, RowInfo } from './row/row_cache';
|
||||||
import { Err } from 'ts-results';
|
import { Err, Ok } from 'ts-results';
|
||||||
import { DatabaseGroupController } from './group/group_controller';
|
import { DatabaseGroupController } from './group/group_controller';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
import { DatabaseGroupObserver } from './group/group_observer';
|
import { DatabaseGroupObserver } from './group/group_observer';
|
||||||
import { Log } from '../../../utils/log';
|
import { Log } from '$app/utils/log';
|
||||||
|
|
||||||
export type DatabaseSubscriberCallbacks = {
|
export type DatabaseSubscriberCallbacks = {
|
||||||
onViewChanged?: (data: DatabasePB) => void;
|
onViewChanged?: (data: DatabasePB) => void;
|
||||||
@ -71,6 +71,20 @@ export class DatabaseController {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getGroupByFieldId = async () => {
|
||||||
|
const settingsResult = await this.backendService.getSettings();
|
||||||
|
if (settingsResult.ok) {
|
||||||
|
const settings = settingsResult.val;
|
||||||
|
const groupConfig = settings.group_configurations.items;
|
||||||
|
if (groupConfig.length === 0) {
|
||||||
|
return Err(new FlowyError({ msg: 'this database has no groups' }));
|
||||||
|
}
|
||||||
|
return Ok(settings.group_configurations.items[0].field_id);
|
||||||
|
} else {
|
||||||
|
return Err(settingsResult.val);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
createRow = () => {
|
createRow = () => {
|
||||||
return this.backendService.createRow();
|
return this.backendService.createRow();
|
||||||
};
|
};
|
||||||
@ -79,10 +93,19 @@ export class DatabaseController {
|
|||||||
return this.backendService.moveGroupRow(rowId, groupId);
|
return this.backendService.moveGroupRow(rowId, groupId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exchangeRow = async (fromRowId: string, toRowId: string) => {
|
||||||
|
await this.backendService.exchangeRow(fromRowId, toRowId);
|
||||||
|
await this.loadGroup();
|
||||||
|
};
|
||||||
|
|
||||||
moveGroup = (fromGroupId: string, toGroupId: string) => {
|
moveGroup = (fromGroupId: string, toGroupId: string) => {
|
||||||
return this.backendService.moveGroup(fromGroupId, toGroupId);
|
return this.backendService.moveGroup(fromGroupId, toGroupId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
moveField = (fieldId: string, fromIndex: number, toIndex: number) => {
|
||||||
|
return this.backendService.moveField(fieldId, fromIndex, toIndex);
|
||||||
|
};
|
||||||
|
|
||||||
private loadGroup = async () => {
|
private loadGroup = async () => {
|
||||||
const result = await this.backendService.loadGroups();
|
const result = await this.backendService.loadGroups();
|
||||||
if (result.ok) {
|
if (result.ok) {
|
||||||
@ -146,6 +169,10 @@ export class DatabaseController {
|
|||||||
};
|
};
|
||||||
|
|
||||||
dispose = async () => {
|
dispose = async () => {
|
||||||
|
this.groups.value.forEach((group) => {
|
||||||
|
void group.dispose();
|
||||||
|
});
|
||||||
|
await this.groupsObserver.unsubscribe();
|
||||||
await this.backendService.closeDatabase();
|
await this.backendService.closeDatabase();
|
||||||
await this.fieldController.dispose();
|
await this.fieldController.dispose();
|
||||||
await this.databaseViewCache.dispose();
|
await this.databaseViewCache.dispose();
|
||||||
|
@ -5,14 +5,14 @@ import {
|
|||||||
FieldType,
|
FieldType,
|
||||||
TypeOptionChangesetPB,
|
TypeOptionChangesetPB,
|
||||||
TypeOptionPathPB,
|
TypeOptionPathPB,
|
||||||
} from '../../../../../services/backend/models/flowy-database/field_entities';
|
} from '@/services/backend';
|
||||||
import {
|
import {
|
||||||
DatabaseEventDeleteField,
|
DatabaseEventDeleteField,
|
||||||
DatabaseEventDuplicateField,
|
DatabaseEventDuplicateField,
|
||||||
DatabaseEventGetTypeOption,
|
DatabaseEventGetTypeOption,
|
||||||
DatabaseEventUpdateField,
|
DatabaseEventUpdateField,
|
||||||
DatabaseEventUpdateFieldTypeOption,
|
DatabaseEventUpdateFieldTypeOption,
|
||||||
} from '../../../../../services/backend/events/flowy-database';
|
} from '@/services/backend/events/flowy-database';
|
||||||
|
|
||||||
export abstract class TypeOptionParser<T> {
|
export abstract class TypeOptionParser<T> {
|
||||||
abstract fromBuffer(buffer: Uint8Array): T;
|
abstract fromBuffer(buffer: Uint8Array): T;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Log } from '../../../../utils/log';
|
import { Log } from '$app/utils/log';
|
||||||
import { DatabaseBackendService } from '../database_bd_svc';
|
import { DatabaseBackendService } from '../database_bd_svc';
|
||||||
import { DatabaseFieldChangesetObserver } from './field_observer';
|
import { DatabaseFieldChangesetObserver } from './field_observer';
|
||||||
import { FieldIdPB, FieldPB, IndexFieldPB } from '../../../../../services/backend';
|
import { FieldIdPB, FieldPB, IndexFieldPB } from '@/services/backend';
|
||||||
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
import { ChangeNotifier } from '$app/utils/change_notifier';
|
||||||
|
|
||||||
export class FieldController {
|
export class FieldController {
|
||||||
private backendService: DatabaseBackendService;
|
private backendService: DatabaseBackendService;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Ok, Result } from 'ts-results';
|
import { Ok, Result } from 'ts-results';
|
||||||
import { DatabaseNotification, DatabaseFieldChangesetPB, FlowyError, FieldPB } from '../../../../../services/backend';
|
import { DatabaseNotification, DatabaseFieldChangesetPB, FlowyError, FieldPB } from '@/services/backend';
|
||||||
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
import { ChangeNotifier } from '$app/utils/change_notifier';
|
||||||
import { DatabaseNotificationObserver } from '../notifications/observer';
|
import { DatabaseNotificationObserver } from '../notifications/observer';
|
||||||
|
|
||||||
export type FieldChangesetSubscribeCallback = (value: Result<DatabaseFieldChangesetPB, FlowyError>) => void;
|
export type FieldChangesetSubscribeCallback = (value: Result<DatabaseFieldChangesetPB, FlowyError>) => void;
|
||||||
|
@ -3,12 +3,12 @@ import {
|
|||||||
FieldType,
|
FieldType,
|
||||||
TypeOptionPathPB,
|
TypeOptionPathPB,
|
||||||
UpdateFieldTypePayloadPB,
|
UpdateFieldTypePayloadPB,
|
||||||
} from '../../../../../../services/backend';
|
} from '@/services/backend';
|
||||||
import {
|
import {
|
||||||
DatabaseEventCreateTypeOption,
|
DatabaseEventCreateTypeOption,
|
||||||
DatabaseEventGetTypeOption,
|
DatabaseEventGetTypeOption,
|
||||||
DatabaseEventUpdateFieldType,
|
DatabaseEventUpdateFieldType,
|
||||||
} from '../../../../../../services/backend/events/flowy-database';
|
} from '@/services/backend/events/flowy-database';
|
||||||
|
|
||||||
export class TypeOptionBackendService {
|
export class TypeOptionBackendService {
|
||||||
constructor(public readonly viewId: string) {}
|
constructor(public readonly viewId: string) {}
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
NumberTypeOptionPB,
|
NumberTypeOptionPB,
|
||||||
SingleSelectTypeOptionPB,
|
SingleSelectTypeOptionPB,
|
||||||
URLTypeOptionPB,
|
URLTypeOptionPB,
|
||||||
} from '../../../../../../services/backend';
|
} from '@/services/backend';
|
||||||
import { utf8Decoder, utf8Encoder } from '../../cell/data_parser';
|
import { utf8Decoder, utf8Encoder } from '../../cell/data_parser';
|
||||||
import { DatabaseFieldObserver } from '../field_observer';
|
import { DatabaseFieldObserver } from '../field_observer';
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { FieldPB, FieldType, TypeOptionPB } from '../../../../../../services/backend';
|
import { FieldPB, FieldType, TypeOptionPB } from '@/services/backend';
|
||||||
import { ChangeNotifier } from '../../../../../utils/change_notifier';
|
import { ChangeNotifier } from '$app/utils/change_notifier';
|
||||||
import { FieldBackendService } from '../field_bd_svc';
|
import { FieldBackendService } from '../field_bd_svc';
|
||||||
import { Log } from '../../../../../utils/log';
|
import { Log } from '$app/utils/log';
|
||||||
import { None, Option, Some } from 'ts-results';
|
import { None, Option, Some } from 'ts-results';
|
||||||
import { FieldInfo } from '../field_controller';
|
import { FieldInfo } from '../field_controller';
|
||||||
import { TypeOptionBackendService } from './type_option_bd_svc';
|
import { TypeOptionBackendService } from './type_option_bd_svc';
|
||||||
|
@ -1,14 +1,8 @@
|
|||||||
import {
|
import { DatabaseNotification, FlowyError, GroupPB, GroupRowsNotificationPB, RowPB } from '@/services/backend';
|
||||||
DatabaseNotification,
|
import { ChangeNotifier } from '$app/utils/change_notifier';
|
||||||
FlowyError,
|
|
||||||
GroupPB,
|
|
||||||
GroupRowsNotificationPB,
|
|
||||||
RowPB,
|
|
||||||
} from '../../../../../services/backend';
|
|
||||||
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
|
||||||
import { None, Ok, Option, Result, Some } from 'ts-results';
|
import { None, Ok, Option, Result, Some } from 'ts-results';
|
||||||
import { DatabaseNotificationObserver } from '../notifications/observer';
|
import { DatabaseNotificationObserver } from '../notifications/observer';
|
||||||
import { Log } from '../../../../utils/log';
|
import { Log } from '$app/utils/log';
|
||||||
import { DatabaseBackendService } from '../database_bd_svc';
|
import { DatabaseBackendService } from '../database_bd_svc';
|
||||||
|
|
||||||
export type GroupDataCallbacks = {
|
export type GroupDataCallbacks = {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
import { ChangeNotifier } from '$app/utils/change_notifier';
|
||||||
import { Ok, Result } from 'ts-results';
|
import { Ok, Result } from 'ts-results';
|
||||||
import { DatabaseNotification, FlowyError, GroupChangesetPB, GroupPB } from '../../../../../services/backend';
|
import { DatabaseNotification, FlowyError, GroupChangesetPB, GroupPB } from '@/services/backend';
|
||||||
import { DatabaseNotificationObserver } from '../notifications/observer';
|
import { DatabaseNotificationObserver } from '../notifications/observer';
|
||||||
|
|
||||||
export type GroupByFieldCallback = (value: Result<GroupPB[], FlowyError>) => void;
|
export type GroupByFieldCallback = (value: Result<GroupPB[], FlowyError>) => void;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { DatabaseNotification, FlowyError } from '../../../../../services/backend';
|
import { DatabaseNotification, FlowyError } from '@/services/backend';
|
||||||
import { AFNotificationObserver } from '../../../../../services/backend/notifications';
|
import { AFNotificationObserver } from '@/services/backend/notifications';
|
||||||
import { DatabaseNotificationParser } from './parser';
|
import { DatabaseNotificationParser } from './parser';
|
||||||
import { Result } from 'ts-results';
|
import { Result } from 'ts-results';
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { DatabaseNotification, FlowyError } from '../../../../../services/backend';
|
import { DatabaseNotification, FlowyError } from '@/services/backend';
|
||||||
import { NotificationParser } from '../../../../../services/backend/notifications';
|
import { NotificationParser } from '@/services/backend/notifications';
|
||||||
import { Result } from 'ts-results';
|
import { Result } from 'ts-results';
|
||||||
|
|
||||||
declare type DatabaseNotificationCallback = (ty: DatabaseNotification, payload: Result<Uint8Array, FlowyError>) => void;
|
declare type DatabaseNotificationCallback = (ty: DatabaseNotification, payload: Result<Uint8Array, FlowyError>) => void;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { CreateRowPayloadPB, RowIdPB } from '../../../../../services/backend';
|
import { CreateRowPayloadPB, RowIdPB } from '@/services/backend';
|
||||||
import {
|
import {
|
||||||
DatabaseEventCreateRow,
|
DatabaseEventCreateRow,
|
||||||
DatabaseEventDeleteRow,
|
DatabaseEventDeleteRow,
|
||||||
DatabaseEventDuplicateRow,
|
DatabaseEventDuplicateRow,
|
||||||
DatabaseEventGetRow,
|
DatabaseEventGetRow,
|
||||||
} from '../../../../../services/backend/events/flowy-database';
|
} from '@/services/backend/events/flowy-database';
|
||||||
|
|
||||||
export class RowBackendService {
|
export class RowBackendService {
|
||||||
constructor(public readonly viewId: string) {}
|
constructor(public readonly viewId: string) {}
|
||||||
|
@ -7,14 +7,14 @@ import {
|
|||||||
RowsChangesetPB,
|
RowsChangesetPB,
|
||||||
RowsVisibilityChangesetPB,
|
RowsVisibilityChangesetPB,
|
||||||
ReorderSingleRowPB,
|
ReorderSingleRowPB,
|
||||||
} from '../../../../../services/backend';
|
} from '@/services/backend';
|
||||||
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
import { ChangeNotifier } from '$app/utils/change_notifier';
|
||||||
import { FieldInfo } from '../field/field_controller';
|
import { FieldInfo } from '../field/field_controller';
|
||||||
import { CellCache, CellCacheKey } from '../cell/cell_cache';
|
import { CellCache, CellCacheKey } from '../cell/cell_cache';
|
||||||
import { CellIdentifier } from '../cell/cell_bd_svc';
|
import { CellIdentifier } from '../cell/cell_bd_svc';
|
||||||
import { DatabaseEventGetRow } from '../../../../../services/backend/events/flowy-database';
|
import { DatabaseEventGetRow } from '@/services/backend/events/flowy-database';
|
||||||
import { None, Option, Some } from 'ts-results';
|
import { None, Option, Some } from 'ts-results';
|
||||||
import { Log } from '../../../../utils/log';
|
import { Log } from '$app/utils/log';
|
||||||
|
|
||||||
export type CellByFieldId = Map<string, CellIdentifier>;
|
export type CellByFieldId = Map<string, CellIdentifier>;
|
||||||
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { DatabaseViewRowsObserver } from './view_row_observer';
|
import { DatabaseViewRowsObserver } from './view_row_observer';
|
||||||
import { RowCache, RowInfo } from '../row/row_cache';
|
import { RowCache, RowInfo } from '../row/row_cache';
|
||||||
import { FieldController } from '../field/field_controller';
|
import { FieldController } from '../field/field_controller';
|
||||||
import { RowPB } from '../../../../../services/backend';
|
import { RowPB } from '@/services/backend';
|
||||||
import { Subscription } from 'rxjs';
|
|
||||||
|
|
||||||
export class DatabaseViewCache {
|
export class DatabaseViewCache {
|
||||||
private readonly rowsObserver: DatabaseViewRowsObserver;
|
private readonly rowsObserver: DatabaseViewRowsObserver;
|
||||||
|
@ -6,8 +6,8 @@ import {
|
|||||||
ReorderSingleRowPB,
|
ReorderSingleRowPB,
|
||||||
RowsChangesetPB,
|
RowsChangesetPB,
|
||||||
RowsVisibilityChangesetPB,
|
RowsVisibilityChangesetPB,
|
||||||
} from '../../../../../services/backend';
|
} from '@/services/backend';
|
||||||
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
import { ChangeNotifier } from '$app/utils/change_notifier';
|
||||||
import { DatabaseNotificationObserver } from '../notifications/observer';
|
import { DatabaseNotificationObserver } from '../notifications/observer';
|
||||||
|
|
||||||
export type RowsVisibilityNotifyValue = Result<RowsVisibilityChangesetPB, FlowyError>;
|
export type RowsVisibilityNotifyValue = Result<RowsVisibilityChangesetPB, FlowyError>;
|
||||||
|
@ -5,10 +5,10 @@ import {
|
|||||||
FlowyError,
|
FlowyError,
|
||||||
OpenDocumentPayloadPB,
|
OpenDocumentPayloadPB,
|
||||||
ViewIdPB,
|
ViewIdPB,
|
||||||
} from '../../../../services/backend';
|
} from '@/services/backend';
|
||||||
import { DocumentEventApplyEdit, DocumentEventGetDocument } from '../../../../services/backend/events/flowy-document';
|
import { DocumentEventApplyEdit, DocumentEventGetDocument } from '@/services/backend/events/flowy-document';
|
||||||
import { Result } from 'ts-results';
|
import { Result } from 'ts-results';
|
||||||
import { FolderEventCloseView } from '../../../../services/backend/events/flowy-folder';
|
import { FolderEventCloseView } from '@/services/backend/events/flowy-folder';
|
||||||
|
|
||||||
export class DocumentBackendService {
|
export class DocumentBackendService {
|
||||||
constructor(public readonly viewId: string) {}
|
constructor(public readonly viewId: string) {}
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
FolderEventReadApp,
|
FolderEventReadApp,
|
||||||
FolderEventUpdateApp,
|
FolderEventUpdateApp,
|
||||||
ViewLayoutTypePB,
|
ViewLayoutTypePB,
|
||||||
} from '../../../../../services/backend/events/flowy-folder';
|
} from '@/services/backend/events/flowy-folder';
|
||||||
import {
|
import {
|
||||||
AppIdPB,
|
AppIdPB,
|
||||||
UpdateAppPayloadPB,
|
UpdateAppPayloadPB,
|
||||||
@ -16,7 +16,7 @@ import {
|
|||||||
MoveFolderItemPayloadPB,
|
MoveFolderItemPayloadPB,
|
||||||
MoveFolderItemType,
|
MoveFolderItemType,
|
||||||
FlowyError,
|
FlowyError,
|
||||||
} from '../../../../../services/backend';
|
} from '@/services/backend';
|
||||||
import { None, Result, Some } from 'ts-results';
|
import { None, Result, Some } from 'ts-results';
|
||||||
|
|
||||||
export class AppBackendService {
|
export class AppBackendService {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Ok, Result } from 'ts-results';
|
import { Ok, Result } from 'ts-results';
|
||||||
import { AppPB, FlowyError, FolderNotification } from '../../../../../services/backend';
|
import { AppPB, FlowyError, FolderNotification } from '@/services/backend';
|
||||||
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
import { ChangeNotifier } from '$app/utils/change_notifier';
|
||||||
import { FolderNotificationObserver } from '../notifications/observer';
|
import { FolderNotificationObserver } from '../notifications/observer';
|
||||||
|
|
||||||
export type AppUpdateNotifyCallback = (value: Result<AppPB, FlowyError>) => void;
|
export type AppUpdateNotifyCallback = (value: Result<AppPB, FlowyError>) => void;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { OnNotificationError, AFNotificationObserver } from '../../../../../services/backend/notifications';
|
import { OnNotificationError, AFNotificationObserver } from '@/services/backend/notifications';
|
||||||
import { FolderNotificationParser } from './parser';
|
import { FolderNotificationParser } from './parser';
|
||||||
import { FlowyError, FolderNotification } from '../../../../../services/backend';
|
import { FlowyError, FolderNotification } from '@/services/backend';
|
||||||
import { Result } from 'ts-results';
|
import { Result } from 'ts-results';
|
||||||
|
|
||||||
export type ParserHandler = (notification: FolderNotification, payload: Result<Uint8Array, FlowyError>) => void;
|
export type ParserHandler = (notification: FolderNotification, payload: Result<Uint8Array, FlowyError>) => void;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { NotificationParser, OnNotificationError } from '../../../../../services/backend/notifications';
|
import { NotificationParser, OnNotificationError } from '@/services/backend/notifications';
|
||||||
import { FlowyError, FolderNotification } from '../../../../../services/backend';
|
import { FlowyError, FolderNotification } from '@/services/backend';
|
||||||
import { Result } from 'ts-results';
|
import { Result } from 'ts-results';
|
||||||
|
|
||||||
declare type FolderNotificationCallback = (ty: FolderNotification, payload: Result<Uint8Array, FlowyError>) => void;
|
declare type FolderNotificationCallback = (ty: FolderNotification, payload: Result<Uint8Array, FlowyError>) => void;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { UpdateViewPayloadPB, RepeatedViewIdPB, ViewPB } from '../../../../../services/backend/models/flowy-folder/view';
|
import { UpdateViewPayloadPB, RepeatedViewIdPB, ViewPB } from '@/services/backend';
|
||||||
import {
|
import {
|
||||||
FolderEventDeleteView,
|
FolderEventDeleteView,
|
||||||
FolderEventDuplicateView,
|
FolderEventDuplicateView,
|
||||||
FolderEventUpdateView,
|
FolderEventUpdateView,
|
||||||
} from '../../../../../services/backend/events/flowy-folder';
|
} from '@/services/backend/events/flowy-folder';
|
||||||
|
|
||||||
export class ViewBackendService {
|
export class ViewBackendService {
|
||||||
constructor(public readonly viewId: string) {}
|
constructor(public readonly viewId: string) {}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Ok, Result } from 'ts-results';
|
import { Ok, Result } from 'ts-results';
|
||||||
import { FlowyError } from '../../../../../services/backend/models/flowy-error/errors';
|
import { DeletedViewPB, FolderNotification, ViewPB, FlowyError } from '@/services/backend';
|
||||||
import { DeletedViewPB, FolderNotification, ViewPB } from '../../../../../services/backend/models/flowy-folder';
|
import { ChangeNotifier } from '$app/utils/change_notifier';
|
||||||
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
|
||||||
import { FolderNotificationObserver } from '../notifications/observer';
|
import { FolderNotificationObserver } from '../notifications/observer';
|
||||||
|
|
||||||
type DeleteViewNotifyValue = Result<ViewPB, FlowyError>;
|
type DeleteViewNotifyValue = Result<ViewPB, FlowyError>;
|
||||||
|
@ -4,8 +4,8 @@ import {
|
|||||||
FolderEventMoveItem,
|
FolderEventMoveItem,
|
||||||
FolderEventReadWorkspaceApps,
|
FolderEventReadWorkspaceApps,
|
||||||
FolderEventReadWorkspaces,
|
FolderEventReadWorkspaces,
|
||||||
} from '../../../../../services/backend/events/flowy-folder';
|
} from '@/services/backend/events/flowy-folder';
|
||||||
import { CreateAppPayloadPB, WorkspaceIdPB, FlowyError, MoveFolderItemPayloadPB } from '../../../../../services/backend';
|
import { CreateAppPayloadPB, WorkspaceIdPB, FlowyError, MoveFolderItemPayloadPB } from '@/services/backend';
|
||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
|
|
||||||
export class WorkspaceBackendService {
|
export class WorkspaceBackendService {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Ok, Result } from 'ts-results';
|
import { Ok, Result } from 'ts-results';
|
||||||
import { AppPB, FolderNotification, RepeatedAppPB, WorkspacePB, FlowyError } from '../../../../../services/backend';
|
import { AppPB, FolderNotification, RepeatedAppPB, WorkspacePB, FlowyError } from '@/services/backend';
|
||||||
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
import { ChangeNotifier } from '$app/utils/change_notifier';
|
||||||
import { FolderNotificationObserver } from '../notifications/observer';
|
import { FolderNotificationObserver } from '../notifications/observer';
|
||||||
|
|
||||||
export type AppListNotifyValue = Result<AppPB[], FlowyError>;
|
export type AppListNotifyValue = Result<AppPB[], FlowyError>;
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
UserEventSignOut,
|
UserEventSignOut,
|
||||||
UserEventSignUp,
|
UserEventSignUp,
|
||||||
UserEventUpdateUserProfile,
|
UserEventUpdateUserProfile,
|
||||||
} from '../../../../services/backend/events/flowy-user';
|
} from '@/services/backend/events/flowy-user';
|
||||||
import {
|
import {
|
||||||
SignInPayloadPB,
|
SignInPayloadPB,
|
||||||
SignUpPayloadPB,
|
SignUpPayloadPB,
|
||||||
@ -15,13 +15,13 @@ import {
|
|||||||
CreateWorkspacePayloadPB,
|
CreateWorkspacePayloadPB,
|
||||||
WorkspaceSettingPB,
|
WorkspaceSettingPB,
|
||||||
WorkspacePB,
|
WorkspacePB,
|
||||||
} from '../../../../services/backend';
|
} from '@/services/backend';
|
||||||
import {
|
import {
|
||||||
FolderEventCreateWorkspace,
|
FolderEventCreateWorkspace,
|
||||||
FolderEventOpenWorkspace,
|
FolderEventOpenWorkspace,
|
||||||
FolderEventReadCurrentWorkspace,
|
FolderEventReadCurrentWorkspace,
|
||||||
FolderEventReadWorkspaces,
|
FolderEventReadWorkspaces,
|
||||||
} from '../../../../services/backend/events/flowy-folder';
|
} from '@/services/backend/events/flowy-folder';
|
||||||
|
|
||||||
export class UserBackendService {
|
export class UserBackendService {
|
||||||
constructor(public readonly userId: string) {}
|
constructor(public readonly userId: string) {}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { WorkspaceSettingPB } from '../../../../services/backend/models/flowy-folder/workspace';
|
import { WorkspaceSettingPB } from '@/services/backend/models/flowy-folder/workspace';
|
||||||
|
|
||||||
export interface ICurrentUser {
|
export interface ICurrentUser {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { FieldType } from '../../../../services/backend/models/flowy-database/field_entities';
|
import { FieldType } from '@/services/backend/models/flowy-database/field_entities';
|
||||||
import { DateFormat, NumberFormat, SelectOptionColorPB, TimeFormat } from '../../../../services/backend';
|
import { DateFormat, NumberFormat, SelectOptionColorPB, TimeFormat } from '@/services/backend';
|
||||||
|
|
||||||
export interface ISelectOption {
|
export interface ISelectOption {
|
||||||
selectOptionId: string;
|
selectOptionId: string;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { FlowyError, FolderNotification } from '../../../../../services/backend';
|
import { FlowyError, FolderNotification } from '@/services/backend';
|
||||||
import { NotificationParser, OnNotificationError } from '../../../../../services/backend/notifications';
|
import { NotificationParser, OnNotificationError } from '@/services/backend/notifications';
|
||||||
import { Result } from 'ts-results';
|
import { Result } from 'ts-results';
|
||||||
|
|
||||||
declare type FolderNotificationCallback = (ty: FolderNotification, payload: Result<Uint8Array, FlowyError>) => void;
|
declare type FolderNotificationCallback = (ty: FolderNotification, payload: Result<Uint8Array, FlowyError>) => void;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { FieldType } from '../../../../services/backend/models/flowy-database/field_entities';
|
import { FieldType } from '@/services/backend/models/flowy-database/field_entities';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
title: 'My plans on the week',
|
title: 'My plans on the week',
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { ViewLayoutTypePB } from '../../../../services/backend';
|
import { ViewLayoutTypePB } from '@/services/backend';
|
||||||
|
|
||||||
export interface IPage {
|
export interface IPage {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -4,5 +4,6 @@ import App from './appflowy_app/App';
|
|||||||
import './styles/tailwind.css';
|
import './styles/tailwind.css';
|
||||||
import './styles/font.css';
|
import './styles/font.css';
|
||||||
import './styles/template.css';
|
import './styles/template.css';
|
||||||
|
import './styles/Calendar.css';
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />);
|
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />);
|
||||||
|
141
frontend/appflowy_tauri/src/styles/Calendar.css
Normal file
141
frontend/appflowy_tauri/src/styles/Calendar.css
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
.react-calendar {
|
||||||
|
width: 250px;
|
||||||
|
max-width: 100%;
|
||||||
|
background: white;
|
||||||
|
line-height: 1.125em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar--doubleView {
|
||||||
|
width: 700px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar--doubleView .react-calendar__viewContainer {
|
||||||
|
display: flex;
|
||||||
|
margin: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar--doubleView .react-calendar__viewContainer > * {
|
||||||
|
width: 50%;
|
||||||
|
margin: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar,
|
||||||
|
.react-calendar *,
|
||||||
|
.react-calendar *:before,
|
||||||
|
.react-calendar *:after {
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar button {
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar button:enabled:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar__navigation {
|
||||||
|
display: flex;
|
||||||
|
height: 44px;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar__navigation button {
|
||||||
|
min-width: 44px;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar__navigation button:disabled {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar__navigation button:enabled:hover,
|
||||||
|
.react-calendar__navigation button:enabled:focus {
|
||||||
|
background-color: #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar__month-view__weekdays {
|
||||||
|
@apply mb-2 text-center text-xs uppercase text-shade-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar__month-view__weekdays abbr {
|
||||||
|
@apply no-underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar__month-view__weekdays__weekday {
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar__month-view__weekNumbers .react-calendar__tile {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 0.75em;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar__month-view__days__day--weekend {
|
||||||
|
@apply text-main-alert;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar__month-view__days__day--neighboringMonth {
|
||||||
|
@apply text-shade-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar__year-view .react-calendar__tile,
|
||||||
|
.react-calendar__decade-view .react-calendar__tile,
|
||||||
|
.react-calendar__century-view .react-calendar__tile {
|
||||||
|
padding: 2em 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar__tile {
|
||||||
|
max-width: 100%;
|
||||||
|
background: none;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 16px;
|
||||||
|
@apply rounded py-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar__tile:disabled {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar__tile:enabled:hover,
|
||||||
|
.react-calendar__tile:enabled:focus {
|
||||||
|
background-color: #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar__tile--now {
|
||||||
|
@apply bg-shade-6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar__tile--now:enabled:hover,
|
||||||
|
.react-calendar__tile--now:enabled:focus {
|
||||||
|
@apply bg-shade-6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar__tile--hasActive {
|
||||||
|
background: #76baff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar__tile--hasActive:enabled:hover,
|
||||||
|
.react-calendar__tile--hasActive:enabled:focus {
|
||||||
|
background: #a9d4ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar__tile--active {
|
||||||
|
@apply bg-main-accent text-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar__tile--active:enabled:hover,
|
||||||
|
.react-calendar__tile--active:enabled:focus {
|
||||||
|
@apply bg-main-hovered;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-calendar--selectRange .react-calendar__tile--hover {
|
||||||
|
@apply bg-shade-4;
|
||||||
|
}
|
@ -1,6 +1,11 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
content: [
|
||||||
|
'./index.html',
|
||||||
|
'./src/**/*.{js,ts,jsx,tsx}',
|
||||||
|
'./node_modules/react-tailwindcss-datepicker/dist/index.esm.js',
|
||||||
|
],
|
||||||
|
darkMode: 'class',
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
@ -41,6 +46,9 @@ module.exports = {
|
|||||||
fiol: '#2C144B',
|
fiol: '#2C144B',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
boxShadow: {
|
||||||
|
md: '0px 0px 20px rgba(0, 0, 0, 0.1);',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
|
Loading…
Reference in New Issue
Block a user