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",
|
||||
"@tanstack/react-virtual": "3.0.0-beta.54",
|
||||
"@tauri-apps/api": "^1.2.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"events": "^3.3.0",
|
||||
"google-protobuf": "^3.21.2",
|
||||
"i18next": "^22.4.10",
|
||||
@ -32,6 +33,8 @@
|
||||
"nanoid": "^4.0.0",
|
||||
"protoc-gen-ts": "^0.8.5",
|
||||
"react": "^18.2.0",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-calendar": "^4.1.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-error-boundary": "^3.1.4",
|
||||
"react-i18next": "^12.2.0",
|
||||
@ -44,8 +47,8 @@
|
||||
"slate-react": "^0.91.9",
|
||||
"ts-results": "^3.3.0",
|
||||
"utf8": "^3.0.0",
|
||||
"yjs": "^13.5.51",
|
||||
"y-indexeddb": "^9.0.9"
|
||||
"y-indexeddb": "^9.0.9",
|
||||
"yjs": "^13.5.51"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^1.2.2",
|
||||
@ -53,6 +56,7 @@
|
||||
"@types/is-hotkey": "^0.1.7",
|
||||
"@types/node": "^18.7.10",
|
||||
"@types/react": "^18.0.15",
|
||||
"@types/react-beautiful-dnd": "^13.1.3",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/utf8": "^3.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 }) => {
|
||||
const items: IPopupItem[] = supportedLanguages.map<IPopupItem>((item) => ({
|
||||
onClick: () => void i18n.changeLanguage(item.key),
|
||||
onClick: () => {
|
||||
void i18n.changeLanguage(item.key);
|
||||
onClose();
|
||||
},
|
||||
title: item.title,
|
||||
icon: <></>,
|
||||
}));
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
NumberFormat,
|
||||
SingleSelectTypeOptionPB,
|
||||
TimeFormat,
|
||||
} from '../../../../services/backend';
|
||||
} from '@/services/backend';
|
||||
import {
|
||||
makeChecklistTypeOptionContext,
|
||||
makeDateTypeOptionContext,
|
||||
|
@ -1,32 +1,43 @@
|
||||
import { CellIdentifier } from '../../../stores/effects/database/cell/cell_bd_svc';
|
||||
import { CellCache } from '../../../stores/effects/database/cell/cell_cache';
|
||||
import { FieldController } from '../../../stores/effects/database/field/field_controller';
|
||||
import { CellControllerBuilder } from '../../../stores/effects/database/cell/controller_builder';
|
||||
import { DateCellDataPB, SelectOptionCellDataPB, URLCellDataPB } from '../../../../services/backend';
|
||||
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 { CellControllerBuilder } from '$app/stores/effects/database/cell/controller_builder';
|
||||
import { DateCellDataPB, SelectOptionCellDataPB, URLCellDataPB } from '$app/../services/backend';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { CellController } from '$app/stores/effects/database/cell/cell_controller';
|
||||
|
||||
export const useCell = (cellIdentifier: CellIdentifier, cellCache: CellCache, fieldController: FieldController) => {
|
||||
const [data, setData] = useState<DateCellDataPB | URLCellDataPB | SelectOptionCellDataPB | string | undefined>();
|
||||
const [cellController, setCellController] = useState<CellController<any, any>>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!cellIdentifier || !cellCache || !fieldController) return;
|
||||
const builder = new CellControllerBuilder(cellIdentifier, cellCache, fieldController);
|
||||
const cellController = builder.build();
|
||||
cellController.subscribeChanged({
|
||||
onCellChanged: (value) => {
|
||||
setData(value.unwrap());
|
||||
const c = builder.build();
|
||||
setCellController(c);
|
||||
|
||||
c.subscribeChanged({
|
||||
onCellChanged: (cellData) => {
|
||||
if (cellData.some) {
|
||||
setData(cellData.val);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// ignore the return value, because we are using the subscription
|
||||
void cellController.getCellData();
|
||||
void (async () => {
|
||||
const cellData = await c.getCellData();
|
||||
if (cellData.some) {
|
||||
setData(cellData.unwrap());
|
||||
}
|
||||
})();
|
||||
|
||||
return () => {
|
||||
// dispose is causing an error
|
||||
// void cellController.dispose();
|
||||
void c.dispose();
|
||||
};
|
||||
}, []);
|
||||
}, [cellIdentifier, cellCache, fieldController]);
|
||||
|
||||
return {
|
||||
cellController,
|
||||
data,
|
||||
};
|
||||
};
|
||||
|
@ -1,26 +1,27 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { DatabaseController } from '../../../stores/effects/database/database_controller';
|
||||
import { databaseActions, DatabaseFieldMap, IDatabaseColumn } from '../../../stores/reducers/database/slice';
|
||||
import { useAppDispatch } from '../../../stores/store';
|
||||
import { DatabaseController } from '$app/stores/effects/database/database_controller';
|
||||
import { databaseActions, DatabaseFieldMap, IDatabaseColumn } from '$app/stores/reducers/database/slice';
|
||||
import { useAppDispatch } from '$app/stores/store';
|
||||
import loadField from './loadField';
|
||||
import { FieldInfo } from '../../../stores/effects/database/field/field_controller';
|
||||
import { RowInfo } from '../../../stores/effects/database/row/row_cache';
|
||||
import { FieldInfo } from '$app/stores/effects/database/field/field_controller';
|
||||
import { RowInfo } from '$app/stores/effects/database/row/row_cache';
|
||||
import { ViewLayoutTypePB } from '@/services/backend';
|
||||
import { DatabaseGroupController } from '$app/stores/effects/database/group/group_controller';
|
||||
import { OnDragEndResponder } from 'react-beautiful-dnd';
|
||||
|
||||
export const useDatabase = (viewId: string, type?: ViewLayoutTypePB) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const [controller, setController] = useState<DatabaseController>();
|
||||
const [rows, setRows] = useState<readonly RowInfo[]>([]);
|
||||
const [groups, setGroups] = useState<readonly DatabaseGroupController[]>([]);
|
||||
const [groupByFieldId, setGroupByFieldId] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (!viewId.length) return;
|
||||
const c = new DatabaseController(viewId);
|
||||
setController(c);
|
||||
|
||||
// dispose is causing an error
|
||||
// return () => void c.dispose();
|
||||
return () => void c.dispose();
|
||||
}, [viewId]);
|
||||
|
||||
const loadFields = async (fieldInfos: readonly FieldInfo[]) => {
|
||||
@ -58,10 +59,37 @@ export const useDatabase = (viewId: string, type?: ViewLayoutTypePB) => {
|
||||
await controller.open();
|
||||
|
||||
if (type === ViewLayoutTypePB.Board) {
|
||||
const fieldId = await controller.getGroupByFieldId();
|
||||
setGroupByFieldId(fieldId.unwrap());
|
||||
setGroups(controller.groups.value);
|
||||
}
|
||||
})();
|
||||
}, [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 { RowController } from '../../../stores/effects/database/row/row_controller';
|
||||
import { RowInfo } from '../../../stores/effects/database/row/row_cache';
|
||||
import { CellIdentifier } from '../../../stores/effects/database/cell/cell_bd_svc';
|
||||
import { DatabaseController } from '$app/stores/effects/database/database_controller';
|
||||
import { RowController } from '$app/stores/effects/database/row/row_controller';
|
||||
import { RowInfo } from '$app/stores/effects/database/row/row_cache';
|
||||
import { CellIdentifier } from '$app/stores/effects/database/cell/cell_bd_svc';
|
||||
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) => {
|
||||
const [cells, setCells] = useState<{ fieldId: string; cellIdentifier: CellIdentifier }[]>([]);
|
||||
const [rowController, setRowController] = useState<RowController>();
|
||||
const databaseStore = useAppSelector((state) => state.database);
|
||||
|
||||
useEffect(() => {
|
||||
if (!databaseController || !rowInfo) return;
|
||||
const rowCache = databaseController.databaseViewCache.getRowCache();
|
||||
const fieldController = databaseController.fieldController;
|
||||
const c = new RowController(rowInfo, fieldController, rowCache);
|
||||
@ -17,7 +22,7 @@ export const useRow = (viewId: string, databaseController: DatabaseController, r
|
||||
return () => {
|
||||
// dispose row controller in future
|
||||
};
|
||||
}, []);
|
||||
}, [databaseController, rowInfo]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!rowController) return;
|
||||
@ -35,9 +40,16 @@ export const useRow = (viewId: string, databaseController: DatabaseController, r
|
||||
|
||||
setCells(loadingCells);
|
||||
})();
|
||||
}, [rowController]);
|
||||
}, [rowController, databaseStore.columns]);
|
||||
|
||||
const onNewColumnClick = async () => {
|
||||
if (!databaseController) return;
|
||||
const controller = new TypeOptionController(viewId, None);
|
||||
await controller.initialize();
|
||||
};
|
||||
|
||||
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 { useDatabase } from '../_shared/database-hooks/useDatabase';
|
||||
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 }) => {
|
||||
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 (
|
||||
<>
|
||||
@ -22,24 +36,35 @@ export const Board = ({ viewId }: { viewId: string }) => {
|
||||
<SearchInput />
|
||||
</div>
|
||||
</div>
|
||||
<div className={'relative w-full flex-1 overflow-auto'}>
|
||||
<div className={'absolute flex h-full flex-shrink-0 items-start justify-start gap-4'}>
|
||||
{controller &&
|
||||
groups &&
|
||||
groups.map((group, index) => (
|
||||
<BoardBlock
|
||||
key={index}
|
||||
viewId={viewId}
|
||||
controller={controller}
|
||||
rows={group.rows}
|
||||
title={group.name}
|
||||
allRows={rows}
|
||||
/>
|
||||
))}
|
||||
|
||||
<NewBoardBlock onClick={() => console.log('new block')}></NewBoardBlock>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<div className={'relative w-full flex-1 overflow-auto'}>
|
||||
<div className={'absolute flex h-full flex-shrink-0 items-start justify-start gap-4'}>
|
||||
{controller &&
|
||||
groups &&
|
||||
groups.map((group, index) => (
|
||||
<BoardBlock
|
||||
key={group.groupId}
|
||||
viewId={viewId}
|
||||
controller={controller}
|
||||
group={group}
|
||||
allRows={rows}
|
||||
groupByFieldId={groupByFieldId}
|
||||
onNewRowClick={() => onNewRowClick(index)}
|
||||
onOpenRow={onOpenRow}
|
||||
/>
|
||||
))}
|
||||
<NewBoardBlock onClick={() => console.log('new block')}></NewBoardBlock>
|
||||
</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 { RowInfo } from '../../stores/effects/database/row/row_cache';
|
||||
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 = ({
|
||||
viewId,
|
||||
controller,
|
||||
title,
|
||||
rows,
|
||||
allRows,
|
||||
groupByFieldId,
|
||||
onNewRowClick,
|
||||
onOpenRow,
|
||||
group,
|
||||
}: {
|
||||
viewId: string;
|
||||
controller: DatabaseController;
|
||||
title: string;
|
||||
rows: RowPB[];
|
||||
allRows: readonly RowInfo[];
|
||||
groupByFieldId: string;
|
||||
onNewRowClick: () => void;
|
||||
onOpenRow: (rowId: RowInfo) => void;
|
||||
group: DatabaseGroupController;
|
||||
}) => {
|
||||
return (
|
||||
<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 gap-2'}>
|
||||
<span>{title}</span>
|
||||
<span>{group.name}</span>
|
||||
<span className={'text-shade-4'}>()</span>
|
||||
</div>
|
||||
<div className={'flex items-center gap-2'}>
|
||||
@ -34,18 +39,37 @@ export const BoardBlock = ({
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className={'flex flex-1 flex-col gap-1 overflow-auto px-2'}>
|
||||
{rows.map((row_pb, index) => {
|
||||
const row = allRows.find((r) => r.row.id === row_pb.id);
|
||||
return row ? (
|
||||
<BoardCard viewId={viewId} controller={controller} key={index} rowInfo={row}></BoardCard>
|
||||
) : (
|
||||
<span key={index}></span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<Droppable droppableId={group.groupId}>
|
||||
{(provided) => (
|
||||
<div
|
||||
className={'flex flex-1 flex-col gap-1 overflow-auto px-2'}
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
{group.rows.map((row_pb, index) => {
|
||||
const row = allRows.find((r) => r.row.id === row_pb.id);
|
||||
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'}>
|
||||
<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'}>
|
||||
<AddSvg></AddSvg>
|
||||
</span>
|
||||
|
@ -3,36 +3,52 @@ import { RowInfo } from '../../stores/effects/database/row/row_cache';
|
||||
import { useRow } from '../_shared/database-hooks/useRow';
|
||||
import { DatabaseController } from '../../stores/effects/database/database_controller';
|
||||
import { BoardCell } from './BoardCell';
|
||||
import { Draggable } from 'react-beautiful-dnd';
|
||||
|
||||
export const BoardCard = ({
|
||||
index,
|
||||
viewId,
|
||||
controller,
|
||||
rowInfo,
|
||||
groupByFieldId,
|
||||
onOpenRow,
|
||||
}: {
|
||||
index: number;
|
||||
viewId: string;
|
||||
controller: DatabaseController;
|
||||
rowInfo: RowInfo;
|
||||
groupByFieldId: string;
|
||||
onOpenRow: (rowId: RowInfo) => void;
|
||||
}) => {
|
||||
const { cells } = useRow(viewId, controller, rowInfo);
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => console.log('on click')}
|
||||
className={`relative cursor-pointer select-none rounded-lg border border-shade-6 bg-white px-3 py-2 transition-transform duration-100 hover:bg-main-selector `}
|
||||
>
|
||||
<button className={'absolute right-4 top-2.5 h-5 w-5 rounded hover:bg-surface-2'}>
|
||||
<Details2Svg></Details2Svg>
|
||||
</button>
|
||||
<div className={'flex flex-col gap-3'}>
|
||||
{cells.map((cell, index) => (
|
||||
<BoardCell
|
||||
key={index}
|
||||
cellIdentifier={cell.cellIdentifier}
|
||||
cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
|
||||
fieldController={controller.fieldController}
|
||||
></BoardCell>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<Draggable draggableId={rowInfo.row.id} index={index}>
|
||||
{(provided) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
onClick={() => onOpenRow(rowInfo)}
|
||||
className={`relative cursor-pointer select-none rounded-lg border border-shade-6 bg-white px-3 py-2 transition-transform duration-100 hover:bg-main-selector `}
|
||||
>
|
||||
<button className={'absolute right-4 top-2.5 h-5 w-5 rounded hover:bg-surface-2'}>
|
||||
<Details2Svg></Details2Svg>
|
||||
</button>
|
||||
<div className={'flex flex-col gap-3'}>
|
||||
{cells
|
||||
.filter((cell) => cell.fieldId !== groupByFieldId)
|
||||
.map((cell, cellIndex) => (
|
||||
<BoardCell
|
||||
key={cellIndex}
|
||||
cellIdentifier={cell.cellIdentifier}
|
||||
cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
|
||||
fieldController={controller.fieldController}
|
||||
></BoardCell>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
};
|
||||
|
@ -5,6 +5,7 @@ import { FieldType } from '../../../services/backend';
|
||||
import { BoardOptionsCell } from './BoardOptionsCell';
|
||||
import { BoardDateCell } from './BoardDateCell';
|
||||
import { BoardTextCell } from './BoardTextCell';
|
||||
import { BoardUrlCell } from '$app/components/board/BoardUrlCell';
|
||||
|
||||
export const BoardCell = ({
|
||||
cellIdentifier,
|
||||
@ -31,6 +32,12 @@ export const BoardCell = ({
|
||||
cellCache={cellCache}
|
||||
fieldController={fieldController}
|
||||
></BoardDateCell>
|
||||
) : cellIdentifier.fieldType === FieldType.URL ? (
|
||||
<BoardUrlCell
|
||||
cellIdentifier={cellIdentifier}
|
||||
cellCache={cellCache}
|
||||
fieldController={fieldController}
|
||||
></BoardUrlCell>
|
||||
) : (
|
||||
<BoardTextCell
|
||||
cellIdentifier={cellIdentifier}
|
||||
|
@ -3,6 +3,7 @@ import { useCell } from '../_shared/database-hooks/useCell';
|
||||
import { CellIdentifier } from '../../stores/effects/database/cell/cell_bd_svc';
|
||||
import { CellCache } from '../../stores/effects/database/cell/cell_cache';
|
||||
import { FieldController } from '../../stores/effects/database/field/field_controller';
|
||||
import { getBgColor } from '$app/components/_shared/getColor';
|
||||
|
||||
export const BoardOptionsCell = ({
|
||||
cellIdentifier,
|
||||
@ -16,10 +17,13 @@ export const BoardOptionsCell = ({
|
||||
const { data } = useCell(cellIdentifier, cellCache, fieldController);
|
||||
|
||||
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) => (
|
||||
<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 { CellCache } from '../../stores/effects/database/cell/cell_cache';
|
||||
import { FieldController } from '../../stores/effects/database/field/field_controller';
|
||||
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 '../_shared/database-hooks/useCell';
|
||||
|
||||
export const BoardTextCell = ({
|
||||
@ -14,5 +14,11 @@ export const BoardTextCell = ({
|
||||
}) => {
|
||||
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 }) => {
|
||||
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
|
||||
className={
|
||||
'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
|
||||
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'}>
|
||||
<CloseSvg></CloseSvg>
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 { useAppDispatch, useAppSelector } from '../../../stores/store';
|
||||
|
||||
|
@ -7,7 +7,7 @@ import { SingleSelectTypeSvg } from '../../_shared/svg/SingleSelectTypeSvg';
|
||||
import { MultiSelectTypeSvg } from '../../_shared/svg/MultiSelectTypeSvg';
|
||||
import { ChecklistTypeSvg } from '../../_shared/svg/ChecklistTypeSvg';
|
||||
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 = () => {
|
||||
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 { usePageOptions } from './PageOptions.hooks';
|
||||
import { OptionsPopup } from './OptionsPopup';
|
||||
import { LanguageButton } from '$app/components/layout/HeaderPanel/LanguageButton';
|
||||
|
||||
export const PageOptions = () => {
|
||||
const { showOptionsPopup, onOptionsClick, onClose, onSignOutClick } = usePageOptions();
|
||||
@ -13,7 +14,9 @@ export const PageOptions = () => {
|
||||
Share
|
||||
</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>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@ import { foldersActions, IFolder } from '../../../stores/reducers/folders/slice'
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useAppDispatch, useAppSelector } from '../../../stores/store';
|
||||
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 { WorkspaceBackendService } from '../../../stores/effects/folder/workspace/workspace_bd_svc';
|
||||
import { useError } from '../../error/Error.hooks';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useAppSelector } from '../../../stores/store';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { IPage } from '../../../stores/reducers/pages/slice';
|
||||
import { ViewLayoutTypePB } from '../../../../services/backend';
|
||||
import { ViewLayoutTypePB } from '@/services/backend';
|
||||
import { useState } from 'react';
|
||||
|
||||
export const useNavigationPanelHooks = function () {
|
||||
|
@ -7,7 +7,7 @@ import { IPage } from '../../../stores/reducers/pages/slice';
|
||||
import { Button } from '../../_shared/Button';
|
||||
import { usePageEvents } from './PageItem.hooks';
|
||||
import { RenamePopup } from './RenamePopup';
|
||||
import { ViewLayoutTypePB } from '../../../../services/backend';
|
||||
import { ViewLayoutTypePB } from '@/services/backend';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { PAGE_ITEM_HEIGHT } from '../../_shared/constants';
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { FlowyError, UserNotification } from '../../../../../services/backend';
|
||||
import { NotificationParser, OnNotificationError } from '../../../../../services/backend/notifications';
|
||||
import { FlowyError, UserNotification } from '@/services/backend';
|
||||
import { NotificationParser, OnNotificationError } from '@/services/backend/notifications';
|
||||
import { Result } from 'ts-results';
|
||||
|
||||
declare type UserNotificationCallback = (ty: UserNotification, payload: Result<Uint8Array, FlowyError>) => void;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { FlowyError, UserNotification, UserProfilePB } from '../../../../../services/backend';
|
||||
import { AFNotificationObserver, OnNotificationError } from '../../../../../services/backend/notifications';
|
||||
import { FlowyError, UserNotification, UserProfilePB } from '@/services/backend';
|
||||
import { AFNotificationObserver, OnNotificationError } from '@/services/backend/notifications';
|
||||
import { UserNotificationParser } from './parser';
|
||||
import { Ok, Result } from 'ts-results';
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { DatabaseEventGetCell, DatabaseEventUpdateCell } from '../../../../../services/backend/events/flowy-database';
|
||||
import { CellChangesetPB, CellIdPB } from '../../../../../services/backend/models/flowy-database/cell_entities';
|
||||
import { FieldType } from '../../../../../services/backend/models/flowy-database/field_entities';
|
||||
import { DatabaseEventGetCell, DatabaseEventUpdateCell } from '@/services/backend/events/flowy-database';
|
||||
import { CellChangesetPB, CellIdPB, FieldType } from '@/services/backend';
|
||||
|
||||
class CellIdentifier {
|
||||
constructor(
|
||||
|
@ -108,6 +108,7 @@ export class CellController<T, D> {
|
||||
};
|
||||
|
||||
dispose = async () => {
|
||||
this.cellDataNotifier.unsubscribe();
|
||||
await this.cellObserver.unsubscribe();
|
||||
await this.fieldNotifier.unsubscribe();
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
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 { DatabaseNotification, FlowyError } from '../../../../../services/backend';
|
||||
import { DatabaseNotification, FlowyError } from '@/services/backend';
|
||||
|
||||
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 { CellController } from './cell_controller';
|
||||
import {
|
||||
|
@ -1,10 +1,8 @@
|
||||
import utf8 from 'utf8';
|
||||
import { CellBackendService, CellIdentifier } from './cell_bd_svc';
|
||||
import { DateCellDataPB } from '../../../../../services/backend/models/flowy-database/date_type_option_entities';
|
||||
import { SelectOptionCellDataPB } from '../../../../../services/backend/models/flowy-database/select_type_option';
|
||||
import { URLCellDataPB } from '../../../../../services/backend/models/flowy-database/url_type_option_entities';
|
||||
import { SelectOptionCellDataPB, URLCellDataPB, DateCellDataPB } from '@/services/backend';
|
||||
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 parserData(data: Uint8Array): Option<T>;
|
||||
|
@ -1,10 +1,8 @@
|
||||
import { Result } from 'ts-results';
|
||||
import { FlowyError } from '../../../../../services/backend/models/flowy-error';
|
||||
import { CellBackendService, CellIdentifier } from './cell_bd_svc';
|
||||
import { CalendarData } from './controller_builder';
|
||||
import { DateChangesetPB } from '../../../../../services/backend/models/flowy-database/date_type_option_entities';
|
||||
import { CellIdPB } from '../../../../../services/backend/models/flowy-database/cell_entities';
|
||||
import { DatabaseEventUpdateDateCell } from '../../../../../services/backend/events/flowy-database';
|
||||
import { DateChangesetPB, FlowyError, CellIdPB } from '@/services/backend';
|
||||
import { DatabaseEventUpdateDateCell } from '@/services/backend/events/flowy-database';
|
||||
|
||||
export abstract class CellDataPersistence<D> {
|
||||
abstract save(data: D): Promise<Result<void, FlowyError>>;
|
||||
|
@ -5,13 +5,13 @@ import {
|
||||
SelectOptionCellChangesetPB,
|
||||
SelectOptionChangesetPB,
|
||||
SelectOptionPB,
|
||||
} from '../../../../../services/backend';
|
||||
} from '@/services/backend';
|
||||
import {
|
||||
DatabaseEventCreateSelectOption,
|
||||
DatabaseEventGetSelectOptionCellData,
|
||||
DatabaseEventUpdateSelectOption,
|
||||
DatabaseEventUpdateSelectOptionCell,
|
||||
} from '../../../../../services/backend/events/flowy-database';
|
||||
} from '@/services/backend/events/flowy-database';
|
||||
|
||||
export class SelectOptionBackendService {
|
||||
constructor(public readonly viewId: string, public readonly fieldId: string) {}
|
||||
|
@ -1,15 +1,20 @@
|
||||
import {
|
||||
DatabaseEventCreateRow,
|
||||
DatabaseEventGetDatabase,
|
||||
DatabaseEventGetDatabaseSetting,
|
||||
DatabaseEventGetFields,
|
||||
DatabaseEventGetGroup,
|
||||
DatabaseEventGetGroups,
|
||||
DatabaseEventMoveField,
|
||||
DatabaseEventMoveGroup,
|
||||
DatabaseEventMoveGroupRow,
|
||||
DatabaseEventMoveRow,
|
||||
DatabaseGroupIdPB,
|
||||
MoveFieldPayloadPB,
|
||||
MoveGroupPayloadPB,
|
||||
MoveGroupRowPayloadPB,
|
||||
} from '../../../../services/backend/events/flowy-database';
|
||||
MoveRowPayloadPB,
|
||||
} from '@/services/backend/events/flowy-database';
|
||||
import {
|
||||
GetFieldPayloadPB,
|
||||
RepeatedFieldIdPB,
|
||||
@ -17,9 +22,10 @@ import {
|
||||
DatabaseViewIdPB,
|
||||
CreateRowPayloadPB,
|
||||
ViewIdPB,
|
||||
} from '../../../../services/backend';
|
||||
import { FolderEventCloseView } from '../../../../services/backend/events/flowy-folder';
|
||||
} from '@/services/backend';
|
||||
import { FolderEventCloseView } from '@/services/backend/events/flowy-folder';
|
||||
|
||||
/// A service that wraps the backend service
|
||||
export class DatabaseBackendService {
|
||||
viewId: string;
|
||||
|
||||
@ -27,6 +33,7 @@ export class DatabaseBackendService {
|
||||
this.viewId = viewId;
|
||||
}
|
||||
|
||||
/// Open a database
|
||||
openDatabase = async () => {
|
||||
const payload = DatabaseViewIdPB.fromObject({
|
||||
value: this.viewId,
|
||||
@ -34,6 +41,7 @@ export class DatabaseBackendService {
|
||||
return DatabaseEventGetDatabase(payload);
|
||||
};
|
||||
|
||||
/// Close a database
|
||||
closeDatabase = async () => {
|
||||
const payload = ViewIdPB.fromObject({ value: this.viewId });
|
||||
return FolderEventCloseView(payload);
|
||||
@ -72,6 +80,15 @@ export class DatabaseBackendService {
|
||||
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) => {
|
||||
const payload = MoveGroupPayloadPB.fromObject({
|
||||
view_id: this.viewId,
|
||||
@ -81,6 +98,17 @@ export class DatabaseBackendService {
|
||||
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[]) => {
|
||||
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));
|
||||
};
|
||||
|
||||
/// Get a group by id
|
||||
getGroup = (groupId: string) => {
|
||||
const payload = DatabaseGroupIdPB.fromObject({ view_id: this.viewId, group_id: groupId });
|
||||
return DatabaseEventGetGroup(payload);
|
||||
@ -102,4 +131,9 @@ export class DatabaseBackendService {
|
||||
const payload = DatabaseViewIdPB.fromObject({ value: this.viewId });
|
||||
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 { FieldController, FieldInfo } from './field/field_controller';
|
||||
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 { Err } from 'ts-results';
|
||||
import { Err, Ok } from 'ts-results';
|
||||
import { DatabaseGroupController } from './group/group_controller';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { DatabaseGroupObserver } from './group/group_observer';
|
||||
import { Log } from '../../../utils/log';
|
||||
import { Log } from '$app/utils/log';
|
||||
|
||||
export type DatabaseSubscriberCallbacks = {
|
||||
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 = () => {
|
||||
return this.backendService.createRow();
|
||||
};
|
||||
@ -79,10 +93,19 @@ export class DatabaseController {
|
||||
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) => {
|
||||
return this.backendService.moveGroup(fromGroupId, toGroupId);
|
||||
};
|
||||
|
||||
moveField = (fieldId: string, fromIndex: number, toIndex: number) => {
|
||||
return this.backendService.moveField(fieldId, fromIndex, toIndex);
|
||||
};
|
||||
|
||||
private loadGroup = async () => {
|
||||
const result = await this.backendService.loadGroups();
|
||||
if (result.ok) {
|
||||
@ -146,6 +169,10 @@ export class DatabaseController {
|
||||
};
|
||||
|
||||
dispose = async () => {
|
||||
this.groups.value.forEach((group) => {
|
||||
void group.dispose();
|
||||
});
|
||||
await this.groupsObserver.unsubscribe();
|
||||
await this.backendService.closeDatabase();
|
||||
await this.fieldController.dispose();
|
||||
await this.databaseViewCache.dispose();
|
||||
|
@ -5,14 +5,14 @@ import {
|
||||
FieldType,
|
||||
TypeOptionChangesetPB,
|
||||
TypeOptionPathPB,
|
||||
} from '../../../../../services/backend/models/flowy-database/field_entities';
|
||||
} from '@/services/backend';
|
||||
import {
|
||||
DatabaseEventDeleteField,
|
||||
DatabaseEventDuplicateField,
|
||||
DatabaseEventGetTypeOption,
|
||||
DatabaseEventUpdateField,
|
||||
DatabaseEventUpdateFieldTypeOption,
|
||||
} from '../../../../../services/backend/events/flowy-database';
|
||||
} from '@/services/backend/events/flowy-database';
|
||||
|
||||
export abstract class TypeOptionParser<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 { DatabaseFieldChangesetObserver } from './field_observer';
|
||||
import { FieldIdPB, FieldPB, IndexFieldPB } from '../../../../../services/backend';
|
||||
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
||||
import { FieldIdPB, FieldPB, IndexFieldPB } from '@/services/backend';
|
||||
import { ChangeNotifier } from '$app/utils/change_notifier';
|
||||
|
||||
export class FieldController {
|
||||
private backendService: DatabaseBackendService;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Ok, Result } from 'ts-results';
|
||||
import { DatabaseNotification, DatabaseFieldChangesetPB, FlowyError, FieldPB } from '../../../../../services/backend';
|
||||
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
||||
import { DatabaseNotification, DatabaseFieldChangesetPB, FlowyError, FieldPB } from '@/services/backend';
|
||||
import { ChangeNotifier } from '$app/utils/change_notifier';
|
||||
import { DatabaseNotificationObserver } from '../notifications/observer';
|
||||
|
||||
export type FieldChangesetSubscribeCallback = (value: Result<DatabaseFieldChangesetPB, FlowyError>) => void;
|
||||
|
@ -3,12 +3,12 @@ import {
|
||||
FieldType,
|
||||
TypeOptionPathPB,
|
||||
UpdateFieldTypePayloadPB,
|
||||
} from '../../../../../../services/backend';
|
||||
} from '@/services/backend';
|
||||
import {
|
||||
DatabaseEventCreateTypeOption,
|
||||
DatabaseEventGetTypeOption,
|
||||
DatabaseEventUpdateFieldType,
|
||||
} from '../../../../../../services/backend/events/flowy-database';
|
||||
} from '@/services/backend/events/flowy-database';
|
||||
|
||||
export class TypeOptionBackendService {
|
||||
constructor(public readonly viewId: string) {}
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
NumberTypeOptionPB,
|
||||
SingleSelectTypeOptionPB,
|
||||
URLTypeOptionPB,
|
||||
} from '../../../../../../services/backend';
|
||||
} from '@/services/backend';
|
||||
import { utf8Decoder, utf8Encoder } from '../../cell/data_parser';
|
||||
import { DatabaseFieldObserver } from '../field_observer';
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { FieldPB, FieldType, TypeOptionPB } from '../../../../../../services/backend';
|
||||
import { ChangeNotifier } from '../../../../../utils/change_notifier';
|
||||
import { FieldPB, FieldType, TypeOptionPB } from '@/services/backend';
|
||||
import { ChangeNotifier } from '$app/utils/change_notifier';
|
||||
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 { FieldInfo } from '../field_controller';
|
||||
import { TypeOptionBackendService } from './type_option_bd_svc';
|
||||
|
@ -1,14 +1,8 @@
|
||||
import {
|
||||
DatabaseNotification,
|
||||
FlowyError,
|
||||
GroupPB,
|
||||
GroupRowsNotificationPB,
|
||||
RowPB,
|
||||
} from '../../../../../services/backend';
|
||||
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
||||
import { DatabaseNotification, FlowyError, GroupPB, GroupRowsNotificationPB, RowPB } from '@/services/backend';
|
||||
import { ChangeNotifier } from '$app/utils/change_notifier';
|
||||
import { None, Ok, Option, Result, Some } from 'ts-results';
|
||||
import { DatabaseNotificationObserver } from '../notifications/observer';
|
||||
import { Log } from '../../../../utils/log';
|
||||
import { Log } from '$app/utils/log';
|
||||
import { DatabaseBackendService } from '../database_bd_svc';
|
||||
|
||||
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 { DatabaseNotification, FlowyError, GroupChangesetPB, GroupPB } from '../../../../../services/backend';
|
||||
import { DatabaseNotification, FlowyError, GroupChangesetPB, GroupPB } from '@/services/backend';
|
||||
import { DatabaseNotificationObserver } from '../notifications/observer';
|
||||
|
||||
export type GroupByFieldCallback = (value: Result<GroupPB[], FlowyError>) => void;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { DatabaseNotification, FlowyError } from '../../../../../services/backend';
|
||||
import { AFNotificationObserver } from '../../../../../services/backend/notifications';
|
||||
import { DatabaseNotification, FlowyError } from '@/services/backend';
|
||||
import { AFNotificationObserver } from '@/services/backend/notifications';
|
||||
import { DatabaseNotificationParser } from './parser';
|
||||
import { Result } from 'ts-results';
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { DatabaseNotification, FlowyError } from '../../../../../services/backend';
|
||||
import { NotificationParser } from '../../../../../services/backend/notifications';
|
||||
import { DatabaseNotification, FlowyError } from '@/services/backend';
|
||||
import { NotificationParser } from '@/services/backend/notifications';
|
||||
import { Result } from 'ts-results';
|
||||
|
||||
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 {
|
||||
DatabaseEventCreateRow,
|
||||
DatabaseEventDeleteRow,
|
||||
DatabaseEventDuplicateRow,
|
||||
DatabaseEventGetRow,
|
||||
} from '../../../../../services/backend/events/flowy-database';
|
||||
} from '@/services/backend/events/flowy-database';
|
||||
|
||||
export class RowBackendService {
|
||||
constructor(public readonly viewId: string) {}
|
||||
|
@ -7,14 +7,14 @@ import {
|
||||
RowsChangesetPB,
|
||||
RowsVisibilityChangesetPB,
|
||||
ReorderSingleRowPB,
|
||||
} from '../../../../../services/backend';
|
||||
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
||||
} from '@/services/backend';
|
||||
import { ChangeNotifier } from '$app/utils/change_notifier';
|
||||
import { FieldInfo } from '../field/field_controller';
|
||||
import { CellCache, CellCacheKey } from '../cell/cell_cache';
|
||||
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 { Log } from '../../../../utils/log';
|
||||
import { Log } from '$app/utils/log';
|
||||
|
||||
export type CellByFieldId = Map<string, CellIdentifier>;
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { DatabaseViewRowsObserver } from './view_row_observer';
|
||||
import { RowCache, RowInfo } from '../row/row_cache';
|
||||
import { FieldController } from '../field/field_controller';
|
||||
import { RowPB } from '../../../../../services/backend';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { RowPB } from '@/services/backend';
|
||||
|
||||
export class DatabaseViewCache {
|
||||
private readonly rowsObserver: DatabaseViewRowsObserver;
|
||||
|
@ -6,8 +6,8 @@ import {
|
||||
ReorderSingleRowPB,
|
||||
RowsChangesetPB,
|
||||
RowsVisibilityChangesetPB,
|
||||
} from '../../../../../services/backend';
|
||||
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
||||
} from '@/services/backend';
|
||||
import { ChangeNotifier } from '$app/utils/change_notifier';
|
||||
import { DatabaseNotificationObserver } from '../notifications/observer';
|
||||
|
||||
export type RowsVisibilityNotifyValue = Result<RowsVisibilityChangesetPB, FlowyError>;
|
||||
|
@ -5,10 +5,10 @@ import {
|
||||
FlowyError,
|
||||
OpenDocumentPayloadPB,
|
||||
ViewIdPB,
|
||||
} from '../../../../services/backend';
|
||||
import { DocumentEventApplyEdit, DocumentEventGetDocument } from '../../../../services/backend/events/flowy-document';
|
||||
} from '@/services/backend';
|
||||
import { DocumentEventApplyEdit, DocumentEventGetDocument } from '@/services/backend/events/flowy-document';
|
||||
import { Result } from 'ts-results';
|
||||
import { FolderEventCloseView } from '../../../../services/backend/events/flowy-folder';
|
||||
import { FolderEventCloseView } from '@/services/backend/events/flowy-folder';
|
||||
|
||||
export class DocumentBackendService {
|
||||
constructor(public readonly viewId: string) {}
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
FolderEventReadApp,
|
||||
FolderEventUpdateApp,
|
||||
ViewLayoutTypePB,
|
||||
} from '../../../../../services/backend/events/flowy-folder';
|
||||
} from '@/services/backend/events/flowy-folder';
|
||||
import {
|
||||
AppIdPB,
|
||||
UpdateAppPayloadPB,
|
||||
@ -16,7 +16,7 @@ import {
|
||||
MoveFolderItemPayloadPB,
|
||||
MoveFolderItemType,
|
||||
FlowyError,
|
||||
} from '../../../../../services/backend';
|
||||
} from '@/services/backend';
|
||||
import { None, Result, Some } from 'ts-results';
|
||||
|
||||
export class AppBackendService {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Ok, Result } from 'ts-results';
|
||||
import { AppPB, FlowyError, FolderNotification } from '../../../../../services/backend';
|
||||
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
||||
import { AppPB, FlowyError, FolderNotification } from '@/services/backend';
|
||||
import { ChangeNotifier } from '$app/utils/change_notifier';
|
||||
import { FolderNotificationObserver } from '../notifications/observer';
|
||||
|
||||
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 { FlowyError, FolderNotification } from '../../../../../services/backend';
|
||||
import { FlowyError, FolderNotification } from '@/services/backend';
|
||||
import { Result } from 'ts-results';
|
||||
|
||||
export type ParserHandler = (notification: FolderNotification, payload: Result<Uint8Array, FlowyError>) => void;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { NotificationParser, OnNotificationError } from '../../../../../services/backend/notifications';
|
||||
import { FlowyError, FolderNotification } from '../../../../../services/backend';
|
||||
import { NotificationParser, OnNotificationError } from '@/services/backend/notifications';
|
||||
import { FlowyError, FolderNotification } from '@/services/backend';
|
||||
import { Result } from 'ts-results';
|
||||
|
||||
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 {
|
||||
FolderEventDeleteView,
|
||||
FolderEventDuplicateView,
|
||||
FolderEventUpdateView,
|
||||
} from '../../../../../services/backend/events/flowy-folder';
|
||||
} from '@/services/backend/events/flowy-folder';
|
||||
|
||||
export class ViewBackendService {
|
||||
constructor(public readonly viewId: string) {}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Ok, Result } from 'ts-results';
|
||||
import { FlowyError } from '../../../../../services/backend/models/flowy-error/errors';
|
||||
import { DeletedViewPB, FolderNotification, ViewPB } from '../../../../../services/backend/models/flowy-folder';
|
||||
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
||||
import { DeletedViewPB, FolderNotification, ViewPB, FlowyError } from '@/services/backend';
|
||||
import { ChangeNotifier } from '$app/utils/change_notifier';
|
||||
import { FolderNotificationObserver } from '../notifications/observer';
|
||||
|
||||
type DeleteViewNotifyValue = Result<ViewPB, FlowyError>;
|
||||
|
@ -4,8 +4,8 @@ import {
|
||||
FolderEventMoveItem,
|
||||
FolderEventReadWorkspaceApps,
|
||||
FolderEventReadWorkspaces,
|
||||
} from '../../../../../services/backend/events/flowy-folder';
|
||||
import { CreateAppPayloadPB, WorkspaceIdPB, FlowyError, MoveFolderItemPayloadPB } from '../../../../../services/backend';
|
||||
} from '@/services/backend/events/flowy-folder';
|
||||
import { CreateAppPayloadPB, WorkspaceIdPB, FlowyError, MoveFolderItemPayloadPB } from '@/services/backend';
|
||||
import assert from 'assert';
|
||||
|
||||
export class WorkspaceBackendService {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Ok, Result } from 'ts-results';
|
||||
import { AppPB, FolderNotification, RepeatedAppPB, WorkspacePB, FlowyError } from '../../../../../services/backend';
|
||||
import { ChangeNotifier } from '../../../../utils/change_notifier';
|
||||
import { AppPB, FolderNotification, RepeatedAppPB, WorkspacePB, FlowyError } from '@/services/backend';
|
||||
import { ChangeNotifier } from '$app/utils/change_notifier';
|
||||
import { FolderNotificationObserver } from '../notifications/observer';
|
||||
|
||||
export type AppListNotifyValue = Result<AppPB[], FlowyError>;
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
UserEventSignOut,
|
||||
UserEventSignUp,
|
||||
UserEventUpdateUserProfile,
|
||||
} from '../../../../services/backend/events/flowy-user';
|
||||
} from '@/services/backend/events/flowy-user';
|
||||
import {
|
||||
SignInPayloadPB,
|
||||
SignUpPayloadPB,
|
||||
@ -15,13 +15,13 @@ import {
|
||||
CreateWorkspacePayloadPB,
|
||||
WorkspaceSettingPB,
|
||||
WorkspacePB,
|
||||
} from '../../../../services/backend';
|
||||
} from '@/services/backend';
|
||||
import {
|
||||
FolderEventCreateWorkspace,
|
||||
FolderEventOpenWorkspace,
|
||||
FolderEventReadCurrentWorkspace,
|
||||
FolderEventReadWorkspaces,
|
||||
} from '../../../../services/backend/events/flowy-folder';
|
||||
} from '@/services/backend/events/flowy-folder';
|
||||
|
||||
export class UserBackendService {
|
||||
constructor(public readonly userId: string) {}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
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 {
|
||||
id?: string;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { FieldType } from '../../../../services/backend/models/flowy-database/field_entities';
|
||||
import { DateFormat, NumberFormat, SelectOptionColorPB, TimeFormat } from '../../../../services/backend';
|
||||
import { FieldType } from '@/services/backend/models/flowy-database/field_entities';
|
||||
import { DateFormat, NumberFormat, SelectOptionColorPB, TimeFormat } from '@/services/backend';
|
||||
|
||||
export interface ISelectOption {
|
||||
selectOptionId: string;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { FlowyError, FolderNotification } from '../../../../../services/backend';
|
||||
import { NotificationParser, OnNotificationError } from '../../../../../services/backend/notifications';
|
||||
import { FlowyError, FolderNotification } from '@/services/backend';
|
||||
import { NotificationParser, OnNotificationError } from '@/services/backend/notifications';
|
||||
import { Result } from 'ts-results';
|
||||
|
||||
declare type FolderNotificationCallback = (ty: FolderNotification, payload: Result<Uint8Array, FlowyError>) => void;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
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 = {
|
||||
title: 'My plans on the week',
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { ViewLayoutTypePB } from '../../../../services/backend';
|
||||
import { ViewLayoutTypePB } from '@/services/backend';
|
||||
|
||||
export interface IPage {
|
||||
id: string;
|
||||
|
@ -4,5 +4,6 @@ import App from './appflowy_app/App';
|
||||
import './styles/tailwind.css';
|
||||
import './styles/font.css';
|
||||
import './styles/template.css';
|
||||
import './styles/Calendar.css';
|
||||
|
||||
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} */
|
||||
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: {
|
||||
extend: {
|
||||
colors: {
|
||||
@ -41,6 +46,9 @@ module.exports = {
|
||||
fiol: '#2C144B',
|
||||
},
|
||||
},
|
||||
boxShadow: {
|
||||
md: '0px 0px 20px rgba(0, 0, 0, 0.1);',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
|
Loading…
Reference in New Issue
Block a user