mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
* 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 --------- Co-authored-by: ascarbek <ascarbek@gmail.com>
This commit is contained in:
parent
ed2c5c17d8
commit
5a17716fd8
@ -9,7 +9,7 @@
|
|||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .",
|
"test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .",
|
||||||
"test:errors": "eslint --quiet --ext .js,.ts,.tsx .",
|
"test:errors": "tsc --noEmit",
|
||||||
"test:prettier": "yarn prettier --list-different src",
|
"test:prettier": "yarn prettier --list-different src",
|
||||||
"tauri:clean": "cargo make --cwd .. tauri_clean",
|
"tauri:clean": "cargo make --cwd .. tauri_clean",
|
||||||
"tauri:dev": "tauri dev"
|
"tauri:dev": "tauri dev"
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
use flowy_core::{get_client_server_configuration, AppFlowyCore, AppFlowyCoreConfig};
|
use flowy_core::{get_client_server_configuration, AppFlowyCore, AppFlowyCoreConfig, DEFAULT_NAME};
|
||||||
|
|
||||||
pub fn init_flowy_core() -> AppFlowyCore {
|
pub fn init_flowy_core() -> AppFlowyCore {
|
||||||
let config_json = include_str!("../tauri.conf.json");
|
let config_json = include_str!("../tauri.conf.json");
|
||||||
let config: tauri_utils::config::Config = serde_json::from_str(config_json).unwrap();
|
let config: tauri_utils::config::Config = serde_json::from_str(config_json).unwrap();
|
||||||
|
|
||||||
let mut data_path = tauri::api::path::app_local_data_dir(&config).unwrap();
|
let mut data_path = tauri::api::path::app_local_data_dir(&config).unwrap();
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
data_path.push("dev");
|
data_path.push("dev");
|
||||||
}
|
}
|
||||||
data_path.push("data");
|
data_path.push("data");
|
||||||
|
|
||||||
std::env::set_var("RUST_LOG", "trace");
|
std::env::set_var("RUST_LOG", "debug");
|
||||||
let server_config = get_client_server_configuration().unwrap();
|
let server_config = get_client_server_configuration().unwrap();
|
||||||
let config = AppFlowyCoreConfig::new(
|
let config = AppFlowyCoreConfig::new(
|
||||||
data_path.to_str().unwrap(),
|
data_path.to_str().unwrap(),
|
||||||
"AppFlowy".to_string(),
|
DEFAULT_NAME.to_string(),
|
||||||
server_config,
|
server_config,
|
||||||
)
|
)
|
||||||
.log_filter("trace", vec!["appflowy_tauri".to_string()]);
|
.log_filter("trace", vec!["appflowy_tauri".to_string()]);
|
||||||
AppFlowyCore::new(config)
|
AppFlowyCore::new(config)
|
||||||
}
|
}
|
||||||
|
@ -12,11 +12,13 @@ export const Popup = ({
|
|||||||
className = '',
|
className = '',
|
||||||
onOutsideClick,
|
onOutsideClick,
|
||||||
columns = 1,
|
columns = 1,
|
||||||
|
style,
|
||||||
}: {
|
}: {
|
||||||
items: IPopupItem[];
|
items: IPopupItem[];
|
||||||
className: string;
|
className: string;
|
||||||
onOutsideClick?: () => void;
|
onOutsideClick?: () => void;
|
||||||
columns?: 1 | 2 | 3;
|
columns?: 1 | 2 | 3;
|
||||||
|
style?: any;
|
||||||
}) => {
|
}) => {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
useOutsideClick(ref, () => onOutsideClick && onOutsideClick());
|
useOutsideClick(ref, () => onOutsideClick && onOutsideClick());
|
||||||
@ -27,7 +29,7 @@ export const Popup = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ref} className={`${className} rounded-lg bg-white px-2 py-2 shadow-md`}>
|
<div ref={ref} className={`${className} rounded-lg bg-white px-2 py-2 shadow-md`} style={style}>
|
||||||
<div
|
<div
|
||||||
className={`grid ${columns === 1 && 'grid-cols-1'} ${columns === 2 && 'grid-cols-2'} ${
|
className={`grid ${columns === 1 && 'grid-cols-1'} ${columns === 2 && 'grid-cols-2'} ${
|
||||||
columns === 3 && 'grid-cols-3'
|
columns === 3 && 'grid-cols-3'
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
export const INITIAL_FOLDER_HEIGHT = 40;
|
||||||
|
export const FOLDER_MARGIN = 16;
|
||||||
|
export const PAGE_ITEM_HEIGHT = 40;
|
||||||
|
export const ANIMATION_DURATION = 300;
|
||||||
|
export const NAV_PANEL_MINIMUM_WIDTH = 200;
|
@ -20,9 +20,9 @@ export const useCell = (cellIdentifier: CellIdentifier, cellCache: CellCache, fi
|
|||||||
// ignore the return value, because we are using the subscription
|
// ignore the return value, because we are using the subscription
|
||||||
void cellController.getCellData();
|
void cellController.getCellData();
|
||||||
|
|
||||||
// dispose the cell controller when the component is unmounted
|
|
||||||
return () => {
|
return () => {
|
||||||
void cellController.dispose();
|
// dispose is causing an error
|
||||||
|
// void cellController.dispose();
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -1,30 +1,26 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { DatabaseController } from '../../../stores/effects/database/database_controller';
|
import { DatabaseController } from '../../../stores/effects/database/database_controller';
|
||||||
import {
|
import { databaseActions, DatabaseFieldMap, IDatabaseColumn } from '../../../stores/reducers/database/slice';
|
||||||
databaseActions,
|
import { useAppDispatch } from '../../../stores/store';
|
||||||
DatabaseFieldMap,
|
|
||||||
IDatabaseColumn,
|
|
||||||
IDatabaseRow,
|
|
||||||
} from '../../../stores/reducers/database/slice';
|
|
||||||
import { useAppDispatch, useAppSelector } from '../../../stores/store';
|
|
||||||
import loadField from './loadField';
|
import loadField from './loadField';
|
||||||
import { FieldInfo } from '../../../stores/effects/database/field/field_controller';
|
import { FieldInfo } from '../../../stores/effects/database/field/field_controller';
|
||||||
import { RowInfo } from '../../../stores/effects/database/row/row_cache';
|
import { RowInfo } from '../../../stores/effects/database/row/row_cache';
|
||||||
|
import { ViewLayoutTypePB } from '@/services/backend';
|
||||||
|
import { DatabaseGroupController } from '$app/stores/effects/database/group/group_controller';
|
||||||
|
|
||||||
export const useDatabase = (viewId: string) => {
|
export const useDatabase = (viewId: string, type?: ViewLayoutTypePB) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const databaseStore = useAppSelector((state) => state.database);
|
|
||||||
const boardStore = useAppSelector((state) => state.board);
|
|
||||||
const [controller, setController] = useState<DatabaseController>();
|
const [controller, setController] = useState<DatabaseController>();
|
||||||
const [rows, setRows] = useState<readonly RowInfo[]>([]);
|
const [rows, setRows] = useState<readonly RowInfo[]>([]);
|
||||||
|
const [groups, setGroups] = useState<readonly DatabaseGroupController[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!viewId.length) return;
|
if (!viewId.length) return;
|
||||||
const c = new DatabaseController(viewId);
|
const c = new DatabaseController(viewId);
|
||||||
setController(c);
|
setController(c);
|
||||||
|
|
||||||
// on unmount dispose the controller
|
// dispose is causing an error
|
||||||
return () => void c.dispose();
|
// return () => void c.dispose();
|
||||||
}, [viewId]);
|
}, [viewId]);
|
||||||
|
|
||||||
const loadFields = async (fieldInfos: readonly FieldInfo[]) => {
|
const loadFields = async (fieldInfos: readonly FieldInfo[]) => {
|
||||||
@ -45,7 +41,6 @@ export const useDatabase = (viewId: string) => {
|
|||||||
|
|
||||||
dispatch(databaseActions.updateFields({ fields }));
|
dispatch(databaseActions.updateFields({ fields }));
|
||||||
dispatch(databaseActions.updateColumns({ columns }));
|
dispatch(databaseActions.updateColumns({ columns }));
|
||||||
console.log(fields, columns);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -61,8 +56,12 @@ export const useDatabase = (viewId: string) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
await controller.open();
|
await controller.open();
|
||||||
|
|
||||||
|
if (type === ViewLayoutTypePB.Board) {
|
||||||
|
setGroups(controller.groups.value);
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
}, [controller]);
|
}, [controller]);
|
||||||
|
|
||||||
return { loadFields, controller, rows };
|
return { loadFields, controller, rows, groups };
|
||||||
};
|
};
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { useAppDispatch, useAppSelector } from '../../stores/store';
|
|
||||||
import { boardActions } from '../../stores/reducers/board/slice';
|
|
||||||
import { ISelectOption, ISelectOptionType } from '../../stores/reducers/database/slice';
|
|
||||||
|
|
||||||
export const useBoard = () => {
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const groupingFieldId = useAppSelector((state) => state.board);
|
|
||||||
const database = useAppSelector((state) => state.database);
|
|
||||||
const [title, setTitle] = useState('');
|
|
||||||
const [boardColumns, setBoardColumns] = useState<ISelectOption[]>([]);
|
|
||||||
const [movingRowId, setMovingRowId] = useState<string | undefined>(undefined);
|
|
||||||
const [ghostLocation, setGhostLocation] = useState<{ column: number; row: number }>({ column: 0, row: 0 });
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTitle(database.title);
|
|
||||||
if (database.fields[groupingFieldId]) {
|
|
||||||
setBoardColumns(
|
|
||||||
(database.fields[groupingFieldId].fieldOptions as ISelectOptionType | undefined)?.selectOptions || []
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [database, groupingFieldId]);
|
|
||||||
|
|
||||||
const changeGroupingField = (fieldId: string) => {
|
|
||||||
dispatch(
|
|
||||||
boardActions.setGroupingFieldId({
|
|
||||||
fieldId,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onGhostItemMove = (columnIndex: number, rowIndex: number) => {
|
|
||||||
setGhostLocation({ column: columnIndex, row: rowIndex });
|
|
||||||
};
|
|
||||||
|
|
||||||
const startMove = (rowId: string) => {
|
|
||||||
setMovingRowId(rowId);
|
|
||||||
};
|
|
||||||
|
|
||||||
const endMove = () => {
|
|
||||||
setMovingRowId(undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
title,
|
|
||||||
boardColumns,
|
|
||||||
groupingFieldId,
|
|
||||||
changeGroupingField,
|
|
||||||
startMove,
|
|
||||||
endMove,
|
|
||||||
onGhostItemMove,
|
|
||||||
movingRowId,
|
|
||||||
ghostLocation,
|
|
||||||
};
|
|
||||||
};
|
|
@ -2,29 +2,17 @@ import { SettingsSvg } from '../_shared/svg/SettingsSvg';
|
|||||||
import { SearchInput } from '../_shared/SearchInput';
|
import { SearchInput } from '../_shared/SearchInput';
|
||||||
import { BoardBlock } from './BoardBlock';
|
import { BoardBlock } from './BoardBlock';
|
||||||
import { NewBoardBlock } from './NewBoardBlock';
|
import { NewBoardBlock } from './NewBoardBlock';
|
||||||
import { useBoard } from './Board.hooks';
|
|
||||||
import { useDatabase } from '../_shared/database-hooks/useDatabase';
|
import { useDatabase } from '../_shared/database-hooks/useDatabase';
|
||||||
|
import { ViewLayoutTypePB } from '@/services/backend';
|
||||||
|
|
||||||
export const Board = ({ viewId }: { viewId: string }) => {
|
export const Board = ({ viewId }: { viewId: string }) => {
|
||||||
const { controller, rows } = useDatabase(viewId);
|
const { controller, rows, groups } = useDatabase(viewId, ViewLayoutTypePB.Board);
|
||||||
|
|
||||||
const {
|
|
||||||
title,
|
|
||||||
boardColumns,
|
|
||||||
groupingFieldId,
|
|
||||||
changeGroupingField,
|
|
||||||
startMove,
|
|
||||||
endMove,
|
|
||||||
onGhostItemMove,
|
|
||||||
movingRowId,
|
|
||||||
ghostLocation,
|
|
||||||
} = useBoard();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='flex w-full items-center justify-between'>
|
<div className='flex w-full items-center justify-between'>
|
||||||
<div className={'flex items-center text-xl font-semibold'}>
|
<div className={'flex items-center text-xl font-semibold'}>
|
||||||
<div>{title}</div>
|
<div>{'Kanban'}</div>
|
||||||
<button className={'ml-2 h-5 w-5'}>
|
<button className={'ml-2 h-5 w-5'}>
|
||||||
<SettingsSvg></SettingsSvg>
|
<SettingsSvg></SettingsSvg>
|
||||||
</button>
|
</button>
|
||||||
@ -37,16 +25,15 @@ export const Board = ({ viewId }: { viewId: string }) => {
|
|||||||
<div className={'relative w-full flex-1 overflow-auto'}>
|
<div className={'relative w-full flex-1 overflow-auto'}>
|
||||||
<div className={'absolute flex h-full flex-shrink-0 items-start justify-start gap-4'}>
|
<div className={'absolute flex h-full flex-shrink-0 items-start justify-start gap-4'}>
|
||||||
{controller &&
|
{controller &&
|
||||||
boardColumns?.map((column, index) => (
|
groups &&
|
||||||
|
groups.map((group, index) => (
|
||||||
<BoardBlock
|
<BoardBlock
|
||||||
|
key={index}
|
||||||
viewId={viewId}
|
viewId={viewId}
|
||||||
controller={controller}
|
controller={controller}
|
||||||
key={index}
|
rows={group.rows}
|
||||||
title={column.title}
|
title={group.name}
|
||||||
rows={rows}
|
allRows={rows}
|
||||||
groupingFieldId={groupingFieldId}
|
|
||||||
startMove={startMove}
|
|
||||||
endMove={endMove}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
@ -3,23 +3,20 @@ import AddSvg from '../_shared/svg/AddSvg';
|
|||||||
import { BoardCard } from './BoardCard';
|
import { BoardCard } from './BoardCard';
|
||||||
import { RowInfo } from '../../stores/effects/database/row/row_cache';
|
import { RowInfo } from '../../stores/effects/database/row/row_cache';
|
||||||
import { DatabaseController } from '../../stores/effects/database/database_controller';
|
import { DatabaseController } from '../../stores/effects/database/database_controller';
|
||||||
|
import { RowPB } from '@/services/backend';
|
||||||
|
|
||||||
export const BoardBlock = ({
|
export const BoardBlock = ({
|
||||||
viewId,
|
viewId,
|
||||||
controller,
|
controller,
|
||||||
title,
|
title,
|
||||||
groupingFieldId,
|
|
||||||
rows,
|
rows,
|
||||||
startMove,
|
allRows,
|
||||||
endMove,
|
|
||||||
}: {
|
}: {
|
||||||
viewId: string;
|
viewId: string;
|
||||||
controller: DatabaseController;
|
controller: DatabaseController;
|
||||||
title: string;
|
title: string;
|
||||||
groupingFieldId: string;
|
rows: RowPB[];
|
||||||
rows: readonly RowInfo[];
|
allRows: readonly RowInfo[];
|
||||||
startMove: (id: string) => void;
|
|
||||||
endMove: () => void;
|
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className={'flex h-full w-[250px] flex-col rounded-lg bg-surface-1'}>
|
<div className={'flex h-full w-[250px] flex-col rounded-lg bg-surface-1'}>
|
||||||
@ -38,17 +35,14 @@ export const BoardBlock = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={'flex flex-1 flex-col gap-1 overflow-auto px-2'}>
|
<div className={'flex flex-1 flex-col gap-1 overflow-auto px-2'}>
|
||||||
{rows.map((row, index) => (
|
{rows.map((row_pb, index) => {
|
||||||
<BoardCard
|
const row = allRows.find((r) => r.row.id === row_pb.id);
|
||||||
viewId={viewId}
|
return row ? (
|
||||||
controller={controller}
|
<BoardCard viewId={viewId} controller={controller} key={index} rowInfo={row}></BoardCard>
|
||||||
key={index}
|
) : (
|
||||||
groupingFieldId={groupingFieldId}
|
<span key={index}></span>
|
||||||
row={row}
|
);
|
||||||
startMove={() => startMove(row.row.id)}
|
})}
|
||||||
endMove={() => endMove()}
|
|
||||||
></BoardCard>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
<div className={'p-2'}>
|
<div className={'p-2'}>
|
||||||
<button className={'flex w-full items-center gap-2 rounded-lg px-2 py-2 hover:bg-surface-2'}>
|
<button className={'flex w-full items-center gap-2 rounded-lg px-2 py-2 hover:bg-surface-2'}>
|
||||||
|
@ -1,121 +1,38 @@
|
|||||||
import { DatabaseFieldMap, IDatabaseColumn, IDatabaseRow } from '../../stores/reducers/database/slice';
|
|
||||||
import { Details2Svg } from '../_shared/svg/Details2Svg';
|
import { Details2Svg } from '../_shared/svg/Details2Svg';
|
||||||
import { FieldType } from '../../../services/backend';
|
|
||||||
import { getBgColor } from '../_shared/getColor';
|
|
||||||
import { MouseEventHandler, useEffect, useRef, useState } from 'react';
|
|
||||||
import { RowInfo } from '../../stores/effects/database/row/row_cache';
|
import { RowInfo } from '../../stores/effects/database/row/row_cache';
|
||||||
import { useRow } from '../_shared/database-hooks/useRow';
|
import { useRow } from '../_shared/database-hooks/useRow';
|
||||||
import { DatabaseController } from '../../stores/effects/database/database_controller';
|
import { DatabaseController } from '../../stores/effects/database/database_controller';
|
||||||
import { useAppSelector } from '../../stores/store';
|
|
||||||
import { BoardCell } from './BoardCell';
|
import { BoardCell } from './BoardCell';
|
||||||
|
|
||||||
export const BoardCard = ({
|
export const BoardCard = ({
|
||||||
viewId,
|
viewId,
|
||||||
controller,
|
controller,
|
||||||
groupingFieldId,
|
rowInfo,
|
||||||
// fields,
|
|
||||||
// columns,
|
|
||||||
row,
|
|
||||||
startMove,
|
|
||||||
endMove,
|
|
||||||
}: {
|
}: {
|
||||||
viewId: string;
|
viewId: string;
|
||||||
controller: DatabaseController;
|
controller: DatabaseController;
|
||||||
groupingFieldId: string;
|
rowInfo: RowInfo;
|
||||||
// fields: DatabaseFieldMap;
|
|
||||||
// columns: IDatabaseColumn[];
|
|
||||||
row: RowInfo;
|
|
||||||
startMove: () => void;
|
|
||||||
endMove: () => void;
|
|
||||||
}) => {
|
}) => {
|
||||||
const { cells } = useRow(viewId, controller, row);
|
const { cells } = useRow(viewId, controller, rowInfo);
|
||||||
|
|
||||||
const databaseStore = useAppSelector((state) => state.database);
|
|
||||||
const [isMoving, setIsMoving] = useState(false);
|
|
||||||
const [isDown, setIsDown] = useState(false);
|
|
||||||
const [ghostWidth, setGhostWidth] = useState(0);
|
|
||||||
const [ghostHeight, setGhostHeight] = useState(0);
|
|
||||||
const [ghostLeft, setGhostLeft] = useState(0);
|
|
||||||
const [ghostTop, setGhostTop] = useState(0);
|
|
||||||
const el = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (el.current?.getBoundingClientRect && isMoving) {
|
|
||||||
const { left, top, width, height } = el.current.getBoundingClientRect();
|
|
||||||
setGhostWidth(width);
|
|
||||||
setGhostHeight(height);
|
|
||||||
setGhostLeft(left);
|
|
||||||
setGhostTop(top);
|
|
||||||
|
|
||||||
startMove();
|
|
||||||
|
|
||||||
const gEl = document.getElementById('ghost-block');
|
|
||||||
if (gEl?.innerHTML) {
|
|
||||||
gEl.innerHTML = el.current.innerHTML;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [el, isMoving]);
|
|
||||||
|
|
||||||
const onMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {
|
|
||||||
setGhostLeft(ghostLeft + e.movementX);
|
|
||||||
setGhostTop(ghostTop + e.movementY);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onMouseUp: MouseEventHandler<HTMLDivElement> = (e) => {
|
|
||||||
setIsMoving(false);
|
|
||||||
endMove();
|
|
||||||
};
|
|
||||||
|
|
||||||
const dragStart = () => {
|
|
||||||
if (isDown) {
|
|
||||||
setIsMoving(true);
|
|
||||||
setIsDown(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div
|
||||||
<div
|
onClick={() => console.log('on click')}
|
||||||
ref={el}
|
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 `}
|
||||||
onMouseDown={() => setIsDown(true)}
|
>
|
||||||
onMouseMove={dragStart}
|
<button className={'absolute right-4 top-2.5 h-5 w-5 rounded hover:bg-surface-2'}>
|
||||||
onMouseUp={() => setIsDown(false)}
|
<Details2Svg></Details2Svg>
|
||||||
onClick={() => console.log('on click')}
|
</button>
|
||||||
className={`relative cursor-pointer select-none rounded-lg border border-shade-6 bg-white px-3 py-2 transition-transform duration-100 hover:bg-main-selector `}
|
<div className={'flex flex-col gap-3'}>
|
||||||
>
|
{cells.map((cell, index) => (
|
||||||
<button className={'absolute right-4 top-2.5 h-5 w-5 rounded hover:bg-surface-2'}>
|
<BoardCell
|
||||||
<Details2Svg></Details2Svg>
|
key={index}
|
||||||
</button>
|
cellIdentifier={cell.cellIdentifier}
|
||||||
<div className={'flex flex-col gap-3'}>
|
cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
|
||||||
{cells.map((cell, index) => (
|
fieldController={controller.fieldController}
|
||||||
<BoardCell
|
></BoardCell>
|
||||||
key={index}
|
))}
|
||||||
cellIdentifier={cell.cellIdentifier}
|
|
||||||
cellCache={controller.databaseViewCache.getRowCache().getCellCache()}
|
|
||||||
fieldController={controller.fieldController}
|
|
||||||
></BoardCell>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{isMoving && (
|
</div>
|
||||||
<div
|
|
||||||
onMouseMove={onMouseMove}
|
|
||||||
onMouseUp={onMouseUp}
|
|
||||||
onMouseLeave={onMouseUp}
|
|
||||||
id={'ghost-block'}
|
|
||||||
className={
|
|
||||||
'fixed z-10 rotate-6 scale-105 cursor-pointer select-none rounded-lg border border-shade-6 bg-white px-3 py-2'
|
|
||||||
}
|
|
||||||
style={{
|
|
||||||
width: `${ghostWidth}px`,
|
|
||||||
height: `${ghostHeight}px`,
|
|
||||||
left: `${ghostLeft}px`,
|
|
||||||
top: `${ghostTop}px`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,30 @@
|
|||||||
import { ShowMenuSvg } from '../../_shared/svg/ShowMenuSvg';
|
import { ShowMenuSvg } from '../../_shared/svg/ShowMenuSvg';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useAppSelector } from '../../../stores/store';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
export const Breadcrumbs = ({ menuHidden, onShowMenuClick }: { menuHidden: boolean; onShowMenuClick: () => void }) => {
|
export const Breadcrumbs = ({ menuHidden, onShowMenuClick }: { menuHidden: boolean; onShowMenuClick: () => void }) => {
|
||||||
|
const [folderName, setFolderName] = useState('');
|
||||||
|
const [pageName, setPageName] = useState('');
|
||||||
|
const [activePageId, setActivePageId] = useState<string>('');
|
||||||
|
const currentLocation = useLocation();
|
||||||
|
const pagesStore = useAppSelector((state) => state.pages);
|
||||||
|
const foldersStore = useAppSelector((state) => state.folders);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const { pathname } = currentLocation;
|
||||||
|
const parts = pathname.split('/');
|
||||||
|
const pageId = parts[parts.length - 1];
|
||||||
|
setActivePageId(pageId);
|
||||||
|
}, [currentLocation]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const page = pagesStore.find((p) => p.id === activePageId);
|
||||||
|
const folder = foldersStore.find((f) => f.id === page?.folderId);
|
||||||
|
setFolderName(folder?.title || '');
|
||||||
|
setPageName(page?.title || '');
|
||||||
|
}, [pagesStore, foldersStore, activePageId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'flex items-center'}>
|
<div className={'flex items-center'}>
|
||||||
<div className={'mr-4 flex items-center'}>
|
<div className={'mr-4 flex items-center'}>
|
||||||
@ -11,16 +35,16 @@ export const Breadcrumbs = ({ menuHidden, onShowMenuClick }: { menuHidden: boole
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<button className={'p-1'} onClick={() => history.back()}>
|
<button className={'p-1'} onClick={() => history.back()}>
|
||||||
<img src={'/images/home/arrow_left.svg'} />
|
<img src={'/images/home/arrow_left.svg'} alt={''} />
|
||||||
</button>
|
</button>
|
||||||
<button className={'p-1'}>
|
<button className={'p-1'} onClick={() => history.forward()}>
|
||||||
<img src={'/images/home/arrow_right.svg'} />
|
<img src={'/images/home/arrow_right.svg'} alt={''} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className={'flex items-center'}>
|
<div className={'mr-8 flex items-center gap-4'}>
|
||||||
<span className={'mr-8'}>Getting Started</span>
|
<span>{folderName}</span>
|
||||||
<span className={'mr-8'}>/</span>
|
<span>/</span>
|
||||||
<span className={'mr-8'}>Read Me</span>
|
<span>{pageName}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { ReactNode, useEffect, useState } from 'react';
|
import { ReactNode, useEffect, useState } from 'react';
|
||||||
import { HeaderPanel } from './HeaderPanel/HeaderPanel';
|
import { HeaderPanel } from './HeaderPanel/HeaderPanel';
|
||||||
import { FooterPanel } from './FooterPanel';
|
import { FooterPanel } from './FooterPanel';
|
||||||
|
import { ANIMATION_DURATION } from '../_shared/constants';
|
||||||
const ANIMATION_DURATION = 300;
|
|
||||||
|
|
||||||
export const MainPanel = ({
|
export const MainPanel = ({
|
||||||
left,
|
left,
|
||||||
|
@ -7,15 +7,15 @@ import { AppBackendService } from '../../../stores/effects/folder/app/app_bd_svc
|
|||||||
import { WorkspaceBackendService } from '../../../stores/effects/folder/workspace/workspace_bd_svc';
|
import { WorkspaceBackendService } from '../../../stores/effects/folder/workspace/workspace_bd_svc';
|
||||||
import { useError } from '../../error/Error.hooks';
|
import { useError } from '../../error/Error.hooks';
|
||||||
import { AppObserver } from '../../../stores/effects/folder/app/app_observer';
|
import { AppObserver } from '../../../stores/effects/folder/app/app_observer';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
const initialFolderHeight = 40;
|
import { INITIAL_FOLDER_HEIGHT, PAGE_ITEM_HEIGHT } from '../../_shared/constants';
|
||||||
const initialPageHeight = 40;
|
|
||||||
const animationDuration = 500;
|
|
||||||
|
|
||||||
export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
|
export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
|
||||||
const appDispatch = useAppDispatch();
|
const appDispatch = useAppDispatch();
|
||||||
const workspace = useAppSelector((state) => state.workspace);
|
const workspace = useAppSelector((state) => state.workspace);
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
const [showPages, setShowPages] = useState(false);
|
const [showPages, setShowPages] = useState(false);
|
||||||
const [showFolderOptions, setShowFolderOptions] = useState(false);
|
const [showFolderOptions, setShowFolderOptions] = useState(false);
|
||||||
@ -23,7 +23,7 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
|
|||||||
const [showRenamePopup, setShowRenamePopup] = useState(false);
|
const [showRenamePopup, setShowRenamePopup] = useState(false);
|
||||||
|
|
||||||
// UI configurations
|
// UI configurations
|
||||||
const [folderHeight, setFolderHeight] = useState(`${initialFolderHeight}px`);
|
const [folderHeight, setFolderHeight] = useState(`${INITIAL_FOLDER_HEIGHT}px`);
|
||||||
|
|
||||||
// Observers
|
// Observers
|
||||||
const appObserver = new AppObserver(folder.id);
|
const appObserver = new AppObserver(folder.id);
|
||||||
@ -58,15 +58,15 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showPages) {
|
if (showPages) {
|
||||||
setFolderHeight(`${initialFolderHeight + pages.length * initialPageHeight}px`);
|
setFolderHeight(`${INITIAL_FOLDER_HEIGHT + pages.length * PAGE_ITEM_HEIGHT}px`);
|
||||||
}
|
}
|
||||||
}, [pages]);
|
}, [pages]);
|
||||||
|
|
||||||
const onFolderNameClick = () => {
|
const onFolderNameClick = () => {
|
||||||
if (showPages) {
|
if (showPages) {
|
||||||
setFolderHeight(`${initialFolderHeight}px`);
|
setFolderHeight(`${INITIAL_FOLDER_HEIGHT}px`);
|
||||||
} else {
|
} else {
|
||||||
setFolderHeight(`${initialFolderHeight + pages.length * initialPageHeight}px`);
|
setFolderHeight(`${INITIAL_FOLDER_HEIGHT + pages.length * PAGE_ITEM_HEIGHT}px`);
|
||||||
}
|
}
|
||||||
setShowPages(!showPages);
|
setShowPages(!showPages);
|
||||||
};
|
};
|
||||||
@ -140,6 +140,10 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
|
|||||||
id: newView.id,
|
id: newView.id,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
setShowPages(true);
|
||||||
|
|
||||||
|
navigate(`/page/document/${newView.id}`);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
error.showError(e?.message);
|
error.showError(e?.message);
|
||||||
}
|
}
|
||||||
@ -153,6 +157,8 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
|
|||||||
layoutType: ViewLayoutTypePB.Board,
|
layoutType: ViewLayoutTypePB.Board,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setShowPages(true);
|
||||||
|
|
||||||
appDispatch(
|
appDispatch(
|
||||||
pagesActions.addPage({
|
pagesActions.addPage({
|
||||||
folderId: folder.id,
|
folderId: folder.id,
|
||||||
@ -161,6 +167,8 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
|
|||||||
id: newView.id,
|
id: newView.id,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
navigate(`/page/board/${newView.id}`);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
error.showError(e?.message);
|
error.showError(e?.message);
|
||||||
}
|
}
|
||||||
@ -174,6 +182,8 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
|
|||||||
layoutType: ViewLayoutTypePB.Grid,
|
layoutType: ViewLayoutTypePB.Grid,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setShowPages(true);
|
||||||
|
|
||||||
appDispatch(
|
appDispatch(
|
||||||
pagesActions.addPage({
|
pagesActions.addPage({
|
||||||
folderId: folder.id,
|
folderId: folder.id,
|
||||||
@ -182,11 +192,17 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
|
|||||||
id: newView.id,
|
id: newView.id,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
navigate(`/page/grid/${newView.id}`);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
error.showError(e?.message);
|
error.showError(e?.message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
appDispatch(foldersActions.setShowPages({ id: folder.id, showPages: showPages }));
|
||||||
|
}, [showPages]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
showPages,
|
showPages,
|
||||||
onFolderNameClick,
|
onFolderNameClick,
|
||||||
@ -208,6 +224,5 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
|
|||||||
|
|
||||||
closePopup,
|
closePopup,
|
||||||
folderHeight,
|
folderHeight,
|
||||||
animationDuration,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -8,10 +8,9 @@ import { IPage } from '../../../stores/reducers/pages/slice';
|
|||||||
import { PageItem } from './PageItem';
|
import { PageItem } from './PageItem';
|
||||||
import { Button } from '../../_shared/Button';
|
import { Button } from '../../_shared/Button';
|
||||||
import { RenamePopup } from './RenamePopup';
|
import { RenamePopup } from './RenamePopup';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { DropDownShowSvg } from '../../_shared/svg/DropDownShowSvg';
|
import { DropDownShowSvg } from '../../_shared/svg/DropDownShowSvg';
|
||||||
|
import { ANIMATION_DURATION } from '../../_shared/constants';
|
||||||
let timeoutHandle: any;
|
|
||||||
|
|
||||||
export const FolderItem = ({
|
export const FolderItem = ({
|
||||||
folder,
|
folder,
|
||||||
@ -43,28 +42,24 @@ export const FolderItem = ({
|
|||||||
|
|
||||||
closePopup,
|
closePopup,
|
||||||
folderHeight,
|
folderHeight,
|
||||||
animationDuration,
|
|
||||||
} = useFolderEvents(folder, pages);
|
} = useFolderEvents(folder, pages);
|
||||||
|
|
||||||
const [hideOverflow, setHideOverflow] = useState(!showPages);
|
const [popupY, setPopupY] = useState(0);
|
||||||
|
|
||||||
|
const el = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
clearTimeout(timeoutHandle);
|
if (el.current) {
|
||||||
if (showPages) {
|
const { top } = el.current.getBoundingClientRect();
|
||||||
timeoutHandle = setTimeout(() => {
|
setPopupY(top);
|
||||||
setHideOverflow(!showPages);
|
|
||||||
}, animationDuration);
|
|
||||||
} else {
|
|
||||||
setHideOverflow(!showPages);
|
|
||||||
}
|
}
|
||||||
}, [showPages]);
|
}, [showFolderOptions, showNewPageOptions, showRenamePopup]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
/*transitionTimingFunction:'cubic-bezier(.36,1.55,.65,1.1)'*/
|
<div ref={el}>
|
||||||
<div className={'relative'}>
|
|
||||||
<div
|
<div
|
||||||
className={`relative my-2 ${hideOverflow ? 'overflow-hidden' : ''} transition-all `}
|
className={`my-2 overflow-hidden transition-all`}
|
||||||
style={{ height: folderHeight, transitionDuration: `${animationDuration}ms` }}
|
style={{ height: folderHeight, transitionDuration: `${ANIMATION_DURATION}ms` }}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
onClick={() => onFolderNameClick()}
|
onClick={() => onFolderNameClick()}
|
||||||
@ -78,7 +73,7 @@ export const FolderItem = ({
|
|||||||
{folder.title}
|
{folder.title}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<div className={'relative flex items-center'}>
|
<div className={'flex items-center'}>
|
||||||
<Button size={'box-small-transparent'} onClick={() => onFolderOptionsClick()}>
|
<Button size={'box-small-transparent'} onClick={() => onFolderOptionsClick()}>
|
||||||
<Details2Svg></Details2Svg>
|
<Details2Svg></Details2Svg>
|
||||||
</Button>
|
</Button>
|
||||||
@ -98,6 +93,7 @@ export const FolderItem = ({
|
|||||||
onDeleteClick={() => deleteFolder()}
|
onDeleteClick={() => deleteFolder()}
|
||||||
onDuplicateClick={() => duplicateFolder()}
|
onDuplicateClick={() => duplicateFolder()}
|
||||||
onClose={() => closePopup()}
|
onClose={() => closePopup()}
|
||||||
|
top={popupY - 124 + 40}
|
||||||
></NavItemOptionsPopup>
|
></NavItemOptionsPopup>
|
||||||
)}
|
)}
|
||||||
{showNewPageOptions && (
|
{showNewPageOptions && (
|
||||||
@ -106,6 +102,7 @@ export const FolderItem = ({
|
|||||||
onBoardClick={() => onAddNewBoardPage()}
|
onBoardClick={() => onAddNewBoardPage()}
|
||||||
onGridClick={() => onAddNewGridPage()}
|
onGridClick={() => onAddNewGridPage()}
|
||||||
onClose={() => closePopup()}
|
onClose={() => closePopup()}
|
||||||
|
top={popupY - 124 + 40}
|
||||||
></NewPagePopup>
|
></NewPagePopup>
|
||||||
)}
|
)}
|
||||||
{showRenamePopup && (
|
{showRenamePopup && (
|
||||||
@ -113,6 +110,7 @@ export const FolderItem = ({
|
|||||||
value={folder.title}
|
value={folder.title}
|
||||||
onChange={(newTitle) => changeFolderTitle(newTitle)}
|
onChange={(newTitle) => changeFolderTitle(newTitle)}
|
||||||
onClose={closeRenamePopup}
|
onClose={closeRenamePopup}
|
||||||
|
top={popupY - 124 + 40}
|
||||||
></RenamePopup>
|
></RenamePopup>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,11 +8,13 @@ export const NavItemOptionsPopup = ({
|
|||||||
onDeleteClick,
|
onDeleteClick,
|
||||||
onDuplicateClick,
|
onDuplicateClick,
|
||||||
onClose,
|
onClose,
|
||||||
|
top,
|
||||||
}: {
|
}: {
|
||||||
onRenameClick: () => void;
|
onRenameClick: () => void;
|
||||||
onDeleteClick: () => void;
|
onDeleteClick: () => void;
|
||||||
onDuplicateClick: () => void;
|
onDuplicateClick: () => void;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
|
top: number;
|
||||||
}) => {
|
}) => {
|
||||||
const items: IPopupItem[] = [
|
const items: IPopupItem[] = [
|
||||||
{
|
{
|
||||||
@ -48,7 +50,8 @@ export const NavItemOptionsPopup = ({
|
|||||||
<Popup
|
<Popup
|
||||||
onOutsideClick={() => onClose && onClose()}
|
onOutsideClick={() => onClose && onClose()}
|
||||||
items={items}
|
items={items}
|
||||||
className={'absolute right-0 top-[40px] z-10'}
|
className={`absolute right-0`}
|
||||||
|
style={{ top: `${top}px` }}
|
||||||
></Popup>
|
></Popup>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,79 +0,0 @@
|
|||||||
import { AppLogo } from '../AppLogo';
|
|
||||||
import { WorkspaceUser } from '../WorkspaceUser';
|
|
||||||
import { FolderItem } from './FolderItem';
|
|
||||||
import { PluginsButton } from './PluginsButton';
|
|
||||||
import { TrashButton } from './TrashButton';
|
|
||||||
import { NewFolderButton } from './NewFolderButton';
|
|
||||||
import { IFolder } from '../../../stores/reducers/folders/slice';
|
|
||||||
import { IPage } from '../../../stores/reducers/pages/slice';
|
|
||||||
import { useEffect, useRef, useState } from 'react';
|
|
||||||
|
|
||||||
const animationDuration = 500;
|
|
||||||
|
|
||||||
export const NavigationFloatingPanel = ({
|
|
||||||
onFixNavigationClick,
|
|
||||||
slideInFloatingPanel,
|
|
||||||
folders,
|
|
||||||
pages,
|
|
||||||
onPageClick,
|
|
||||||
setWidth,
|
|
||||||
}: {
|
|
||||||
onFixNavigationClick: () => void;
|
|
||||||
slideInFloatingPanel: boolean;
|
|
||||||
folders: IFolder[];
|
|
||||||
pages: IPage[];
|
|
||||||
onPageClick: (page: IPage) => void;
|
|
||||||
setWidth: (v: number) => void;
|
|
||||||
}) => {
|
|
||||||
const el = useRef<HTMLDivElement>(null);
|
|
||||||
const [panelLeft, setPanelLeft] = useState(0);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!el?.current) return;
|
|
||||||
|
|
||||||
const { width } = el.current.getBoundingClientRect();
|
|
||||||
setWidth(width);
|
|
||||||
|
|
||||||
if (slideInFloatingPanel) {
|
|
||||||
setPanelLeft(0);
|
|
||||||
} else {
|
|
||||||
setPanelLeft(-width);
|
|
||||||
}
|
|
||||||
}, [el.current, slideInFloatingPanel]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={el}
|
|
||||||
className={
|
|
||||||
'fixed top-16 z-10 flex flex-col justify-between rounded-tr rounded-br border border-l-0 border-shade-4 bg-white text-sm shadow-md transition-all'
|
|
||||||
}
|
|
||||||
style={{ left: panelLeft, transitionDuration: `${animationDuration}ms` }}
|
|
||||||
>
|
|
||||||
<div className={'flex flex-col'}>
|
|
||||||
<AppLogo iconToShow={'show'} onShowMenuClick={onFixNavigationClick}></AppLogo>
|
|
||||||
|
|
||||||
<WorkspaceUser></WorkspaceUser>
|
|
||||||
|
|
||||||
<div className={'flex flex-col px-2'}>
|
|
||||||
{folders.map((folder, index) => (
|
|
||||||
<FolderItem
|
|
||||||
key={index}
|
|
||||||
folder={folder}
|
|
||||||
pages={pages.filter((page) => page.folderId === folder.id)}
|
|
||||||
onPageClick={onPageClick}
|
|
||||||
></FolderItem>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={'flex flex-col'}>
|
|
||||||
<div className={'border-b border-shade-6 px-2 pb-4'}>
|
|
||||||
<PluginsButton></PluginsButton>
|
|
||||||
<TrashButton></TrashButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<NewFolderButton></NewFolderButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,36 +1,17 @@
|
|||||||
import { useAppDispatch, useAppSelector } from '../../../stores/store';
|
import { useAppSelector } from '../../../stores/store';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { IPage } from '../../../stores/reducers/pages/slice';
|
import { IPage } from '../../../stores/reducers/pages/slice';
|
||||||
import { ViewLayoutTypePB } from '../../../../services/backend';
|
import { ViewLayoutTypePB } from '../../../../services/backend';
|
||||||
import { MouseEventHandler, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { activePageIdActions } from '../../../stores/reducers/activePageId/slice';
|
|
||||||
|
|
||||||
// number of pixels from left side of screen to show hidden navigation panel
|
|
||||||
const FLOATING_PANEL_SHOW_WIDTH = 10;
|
|
||||||
const FLOATING_PANEL_HIDE_EXTRA_WIDTH = 10;
|
|
||||||
|
|
||||||
export const useNavigationPanelHooks = function () {
|
export const useNavigationPanelHooks = function () {
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const folders = useAppSelector((state) => state.folders);
|
const folders = useAppSelector((state) => state.folders);
|
||||||
const pages = useAppSelector((state) => state.pages);
|
const pages = useAppSelector((state) => state.pages);
|
||||||
const width = useAppSelector((state) => state.navigationWidth);
|
const width = useAppSelector((state) => state.navigationWidth);
|
||||||
const [navigationPanelFixed, setNavigationPanelFixed] = useState(true);
|
|
||||||
const [slideInFloatingPanel, setSlideInFloatingPanel] = useState(true);
|
|
||||||
const [menuHidden, setMenuHidden] = useState(false);
|
const [menuHidden, setMenuHidden] = useState(false);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const onCollapseNavigationClick = () => {
|
|
||||||
setSlideInFloatingPanel(true);
|
|
||||||
setNavigationPanelFixed(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onFixNavigationClick = () => {
|
|
||||||
setNavigationPanelFixed(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const [floatingPanelWidth, setFloatingPanelWidth] = useState(0);
|
|
||||||
|
|
||||||
const onHideMenuClick = () => {
|
const onHideMenuClick = () => {
|
||||||
setMenuHidden(true);
|
setMenuHidden(true);
|
||||||
};
|
};
|
||||||
@ -54,31 +35,14 @@ export const useNavigationPanelHooks = function () {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
dispatch(activePageIdActions.setActivePageId(page.id));
|
|
||||||
|
|
||||||
navigate(`/page/${pageTypeRoute}/${page.id}`);
|
navigate(`/page/${pageTypeRoute}/${page.id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onScreenMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {
|
|
||||||
if (e.screenX <= FLOATING_PANEL_SHOW_WIDTH) {
|
|
||||||
setSlideInFloatingPanel(true);
|
|
||||||
} else if (e.screenX > floatingPanelWidth + FLOATING_PANEL_HIDE_EXTRA_WIDTH) {
|
|
||||||
setSlideInFloatingPanel(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
width,
|
width,
|
||||||
folders,
|
folders,
|
||||||
pages,
|
pages,
|
||||||
navigate,
|
|
||||||
onPageClick,
|
onPageClick,
|
||||||
onCollapseNavigationClick,
|
|
||||||
onFixNavigationClick,
|
|
||||||
navigationPanelFixed,
|
|
||||||
onScreenMouseMove,
|
|
||||||
slideInFloatingPanel,
|
|
||||||
setFloatingPanelWidth,
|
|
||||||
menuHidden,
|
menuHidden,
|
||||||
onHideMenuClick,
|
onHideMenuClick,
|
||||||
onShowMenuClick,
|
onShowMenuClick,
|
||||||
|
@ -6,11 +6,16 @@ import { NewFolderButton } from './NewFolderButton';
|
|||||||
import { NavigationResizer } from './NavigationResizer';
|
import { NavigationResizer } from './NavigationResizer';
|
||||||
import { IFolder } from '../../../stores/reducers/folders/slice';
|
import { IFolder } from '../../../stores/reducers/folders/slice';
|
||||||
import { IPage } from '../../../stores/reducers/pages/slice';
|
import { IPage } from '../../../stores/reducers/pages/slice';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import React from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import { useAppSelector } from '../../../stores/store';
|
||||||
const MINIMUM_WIDTH = 200;
|
import {
|
||||||
const ANIMATION_DURATION = 300;
|
ANIMATION_DURATION,
|
||||||
|
FOLDER_MARGIN,
|
||||||
|
INITIAL_FOLDER_HEIGHT,
|
||||||
|
NAV_PANEL_MINIMUM_WIDTH,
|
||||||
|
PAGE_ITEM_HEIGHT,
|
||||||
|
} from '../../_shared/constants';
|
||||||
|
|
||||||
export const NavigationPanel = ({
|
export const NavigationPanel = ({
|
||||||
onHideMenuClick,
|
onHideMenuClick,
|
||||||
@ -27,6 +32,66 @@ export const NavigationPanel = ({
|
|||||||
pages: IPage[];
|
pages: IPage[];
|
||||||
onPageClick: (page: IPage) => void;
|
onPageClick: (page: IPage) => void;
|
||||||
}) => {
|
}) => {
|
||||||
|
const el = useRef<HTMLDivElement>(null);
|
||||||
|
const foldersStore = useAppSelector((state) => state.folders);
|
||||||
|
const pagesStore = useAppSelector((state) => state.pages);
|
||||||
|
const [activePageId, setActivePageId] = useState<string>('');
|
||||||
|
const currentLocation = useLocation();
|
||||||
|
const [maxHeight, setMaxHeight] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const { pathname } = currentLocation;
|
||||||
|
const parts = pathname.split('/');
|
||||||
|
const pageId = parts[parts.length - 1];
|
||||||
|
setActivePageId(pageId);
|
||||||
|
}, [currentLocation]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!el.current) return;
|
||||||
|
if (!activePageId?.length) return;
|
||||||
|
const activePage = pagesStore.find((page) => page.id === activePageId);
|
||||||
|
if (!activePage) return;
|
||||||
|
|
||||||
|
const folderIndex = foldersStore.findIndex((folder) => folder.id === activePage.folderId);
|
||||||
|
if (folderIndex === -1) return;
|
||||||
|
|
||||||
|
let height = 0;
|
||||||
|
for (let i = 0; i < folderIndex; i++) {
|
||||||
|
height += INITIAL_FOLDER_HEIGHT + FOLDER_MARGIN;
|
||||||
|
if (foldersStore[i].showPages === true) {
|
||||||
|
height += pagesStore.filter((p) => p.folderId === foldersStore[i].id).length * PAGE_ITEM_HEIGHT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
height += INITIAL_FOLDER_HEIGHT + FOLDER_MARGIN / 2;
|
||||||
|
|
||||||
|
const pageIndex = pagesStore
|
||||||
|
.filter((p) => p.folderId === foldersStore[folderIndex].id)
|
||||||
|
.findIndex((p) => p.id === activePageId);
|
||||||
|
for (let i = 0; i <= pageIndex; i++) {
|
||||||
|
height += PAGE_ITEM_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
const elHeight = el.current.getBoundingClientRect().height;
|
||||||
|
const scrollTop = el.current.scrollTop;
|
||||||
|
|
||||||
|
if (scrollTop + elHeight < height || scrollTop > height) {
|
||||||
|
el.current.scrollTo({ top: height - elHeight, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
}, ANIMATION_DURATION);
|
||||||
|
}, [activePageId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMaxHeight(foldersStore.length * (INITIAL_FOLDER_HEIGHT + FOLDER_MARGIN) + pagesStore.length * PAGE_ITEM_HEIGHT);
|
||||||
|
}, [foldersStore, pagesStore]);
|
||||||
|
|
||||||
|
const scrollDown = () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
el?.current?.scrollTo({ top: maxHeight, behavior: 'smooth' });
|
||||||
|
}, ANIMATION_DURATION);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
@ -40,7 +105,11 @@ export const NavigationPanel = ({
|
|||||||
<div className={'flex flex-col'}>
|
<div className={'flex flex-col'}>
|
||||||
<AppLogo iconToShow={'hide'} onHideMenuClick={onHideMenuClick}></AppLogo>
|
<AppLogo iconToShow={'hide'} onHideMenuClick={onHideMenuClick}></AppLogo>
|
||||||
<WorkspaceUser></WorkspaceUser>
|
<WorkspaceUser></WorkspaceUser>
|
||||||
<WorkspaceApps folders={folders} pages={pages} onPageClick={onPageClick} />
|
<div className={'relative flex flex-col'} style={{ height: 'calc(100vh - 300px)' }}>
|
||||||
|
<div className={'flex flex-col overflow-auto px-2'} ref={el}>
|
||||||
|
<WorkspaceApps folders={folders} pages={pages} onPageClick={onPageClick} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={'flex flex-col'}>
|
<div className={'flex flex-col'}>
|
||||||
@ -55,10 +124,10 @@ export const NavigationPanel = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/*New Folder Button*/}
|
{/*New Folder Button*/}
|
||||||
<NewFolderButton></NewFolderButton>
|
<NewFolderButton scrollDown={scrollDown}></NewFolderButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<NavigationResizer minWidth={MINIMUM_WIDTH}></NavigationResizer>
|
<NavigationResizer minWidth={NAV_PANEL_MINIMUM_WIDTH}></NavigationResizer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -70,7 +139,7 @@ type AppsContext = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const WorkspaceApps: React.FC<AppsContext> = ({ folders, pages, onPageClick }) => (
|
const WorkspaceApps: React.FC<AppsContext> = ({ folders, pages, onPageClick }) => (
|
||||||
<div className={'flex flex-col overflow-auto px-2'} style={{ height: 'calc(100vh - 300px)' }}>
|
<>
|
||||||
{folders.map((folder, index) => (
|
{folders.map((folder, index) => (
|
||||||
<FolderItem
|
<FolderItem
|
||||||
key={index}
|
key={index}
|
||||||
@ -79,7 +148,7 @@ const WorkspaceApps: React.FC<AppsContext> = ({ folders, pages, onPageClick }) =
|
|||||||
onPageClick={onPageClick}
|
onPageClick={onPageClick}
|
||||||
></FolderItem>
|
></FolderItem>
|
||||||
))}
|
))}
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const TestBackendButton = () => {
|
export const TestBackendButton = () => {
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
import AddSvg from '../../_shared/svg/AddSvg';
|
import AddSvg from '../../_shared/svg/AddSvg';
|
||||||
import { useNewFolder } from './NewFolderButton.hooks';
|
import { useNewFolder } from './NewFolderButton.hooks';
|
||||||
|
|
||||||
export const NewFolderButton = () => {
|
export const NewFolderButton = ({ scrollDown }: { scrollDown: () => void }) => {
|
||||||
const { onNewFolder } = useNewFolder();
|
const { onNewFolder } = useNewFolder();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button onClick={() => onNewFolder()} className={'flex h-[50px] w-full items-center px-6 hover:bg-surface-2'}>
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
void onNewFolder();
|
||||||
|
scrollDown();
|
||||||
|
}}
|
||||||
|
className={'flex h-[50px] w-full items-center px-6 hover:bg-surface-2'}
|
||||||
|
>
|
||||||
<div className={'mr-2 rounded-full bg-main-accent text-white'}>
|
<div className={'mr-2 rounded-full bg-main-accent text-white'}>
|
||||||
<div className={'h-[24px] w-[24px] text-white'}>
|
<div className={'h-[24px] w-[24px] text-white'}>
|
||||||
<AddSvg></AddSvg>
|
<AddSvg></AddSvg>
|
||||||
|
@ -8,11 +8,13 @@ export const NewPagePopup = ({
|
|||||||
onGridClick,
|
onGridClick,
|
||||||
onBoardClick,
|
onBoardClick,
|
||||||
onClose,
|
onClose,
|
||||||
|
top,
|
||||||
}: {
|
}: {
|
||||||
onDocumentClick: () => void;
|
onDocumentClick: () => void;
|
||||||
onGridClick: () => void;
|
onGridClick: () => void;
|
||||||
onBoardClick: () => void;
|
onBoardClick: () => void;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
|
top: number;
|
||||||
}) => {
|
}) => {
|
||||||
const items: IPopupItem[] = [
|
const items: IPopupItem[] = [
|
||||||
{
|
{
|
||||||
@ -48,7 +50,8 @@ export const NewPagePopup = ({
|
|||||||
<Popup
|
<Popup
|
||||||
onOutsideClick={() => onClose && onClose()}
|
onOutsideClick={() => onClose && onClose()}
|
||||||
items={items}
|
items={items}
|
||||||
className={'absolute right-0 top-[40px] z-10'}
|
className={'absolute right-0'}
|
||||||
|
style={{ top: `${top}px` }}
|
||||||
></Popup>
|
></Popup>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,18 +1,27 @@
|
|||||||
import { IPage, pagesActions } from '../../../stores/reducers/pages/slice';
|
import { IPage, pagesActions } from '../../../stores/reducers/pages/slice';
|
||||||
import { useAppDispatch, useAppSelector } from '../../../stores/store';
|
import { useAppDispatch } from '../../../stores/store';
|
||||||
import { useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { ViewBackendService } from '../../../stores/effects/folder/view/view_bd_svc';
|
import { ViewBackendService } from '../../../stores/effects/folder/view/view_bd_svc';
|
||||||
import { useError } from '../../error/Error.hooks';
|
import { useError } from '../../error/Error.hooks';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
export const usePageEvents = (page: IPage) => {
|
export const usePageEvents = (page: IPage) => {
|
||||||
const appDispatch = useAppDispatch();
|
const appDispatch = useAppDispatch();
|
||||||
const [showPageOptions, setShowPageOptions] = useState(false);
|
const [showPageOptions, setShowPageOptions] = useState(false);
|
||||||
const [showRenamePopup, setShowRenamePopup] = useState(false);
|
const [showRenamePopup, setShowRenamePopup] = useState(false);
|
||||||
const activePageId = useAppSelector((state) => state.activePageId);
|
const [activePageId, setActivePageId] = useState<string>('');
|
||||||
|
const currentLocation = useLocation();
|
||||||
const viewBackendService: ViewBackendService = new ViewBackendService(page.id);
|
const viewBackendService: ViewBackendService = new ViewBackendService(page.id);
|
||||||
const error = useError();
|
const error = useError();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const { pathname } = currentLocation;
|
||||||
|
const parts = pathname.split('/');
|
||||||
|
const pageId = parts[parts.length - 1];
|
||||||
|
setActivePageId(pageId);
|
||||||
|
}, [currentLocation]);
|
||||||
|
|
||||||
const onPageOptionsClick = () => {
|
const onPageOptionsClick = () => {
|
||||||
setShowPageOptions(!showPageOptions);
|
setShowPageOptions(!showPageOptions);
|
||||||
};
|
};
|
||||||
|
@ -8,6 +8,8 @@ import { Button } from '../../_shared/Button';
|
|||||||
import { usePageEvents } from './PageItem.hooks';
|
import { usePageEvents } from './PageItem.hooks';
|
||||||
import { RenamePopup } from './RenamePopup';
|
import { RenamePopup } from './RenamePopup';
|
||||||
import { ViewLayoutTypePB } from '../../../../services/backend';
|
import { ViewLayoutTypePB } from '../../../../services/backend';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import { PAGE_ITEM_HEIGHT } from '../../_shared/constants';
|
||||||
|
|
||||||
export const PageItem = ({ page, onPageClick }: { page: IPage; onPageClick: () => void }) => {
|
export const PageItem = ({ page, onPageClick }: { page: IPage; onPageClick: () => void }) => {
|
||||||
const {
|
const {
|
||||||
@ -23,13 +25,25 @@ export const PageItem = ({ page, onPageClick }: { page: IPage; onPageClick: () =
|
|||||||
activePageId,
|
activePageId,
|
||||||
} = usePageEvents(page);
|
} = usePageEvents(page);
|
||||||
|
|
||||||
|
const el = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const [popupY, setPopupY] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (el.current) {
|
||||||
|
const { top } = el.current.getBoundingClientRect();
|
||||||
|
setPopupY(top);
|
||||||
|
}
|
||||||
|
}, [showPageOptions, showRenamePopup]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'relative'}>
|
<div ref={el}>
|
||||||
<div
|
<div
|
||||||
onClick={() => onPageClick()}
|
onClick={() => onPageClick()}
|
||||||
className={`flex cursor-pointer items-center justify-between rounded-lg py-2 pl-8 pr-4 hover:bg-surface-2 ${
|
className={`flex cursor-pointer items-center justify-between rounded-lg pl-8 pr-4 hover:bg-surface-2 ${
|
||||||
activePageId === page.id ? 'bg-surface-2' : ''
|
activePageId === page.id ? 'bg-surface-2' : ''
|
||||||
}`}
|
}`}
|
||||||
|
style={{ height: PAGE_ITEM_HEIGHT }}
|
||||||
>
|
>
|
||||||
<button className={'flex min-w-0 flex-1 items-center'}>
|
<button className={'flex min-w-0 flex-1 items-center'}>
|
||||||
<i className={'ml-1 mr-1 h-[16px] w-[16px]'}>
|
<i className={'ml-1 mr-1 h-[16px] w-[16px]'}>
|
||||||
@ -41,7 +55,7 @@ export const PageItem = ({ page, onPageClick }: { page: IPage; onPageClick: () =
|
|||||||
{page.title}
|
{page.title}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<div className={'relative flex items-center'}>
|
<div className={'flex items-center'}>
|
||||||
<Button size={'box-small-transparent'} onClick={() => onPageOptionsClick()}>
|
<Button size={'box-small-transparent'} onClick={() => onPageOptionsClick()}>
|
||||||
<Details2Svg></Details2Svg>
|
<Details2Svg></Details2Svg>
|
||||||
</Button>
|
</Button>
|
||||||
@ -53,6 +67,7 @@ export const PageItem = ({ page, onPageClick }: { page: IPage; onPageClick: () =
|
|||||||
onDeleteClick={() => deletePage()}
|
onDeleteClick={() => deletePage()}
|
||||||
onDuplicateClick={() => duplicatePage()}
|
onDuplicateClick={() => duplicatePage()}
|
||||||
onClose={() => closePopup()}
|
onClose={() => closePopup()}
|
||||||
|
top={popupY - 124 + 40}
|
||||||
></NavItemOptionsPopup>
|
></NavItemOptionsPopup>
|
||||||
)}
|
)}
|
||||||
{showRenamePopup && (
|
{showRenamePopup && (
|
||||||
@ -60,6 +75,7 @@ export const PageItem = ({ page, onPageClick }: { page: IPage; onPageClick: () =
|
|||||||
value={page.title}
|
value={page.title}
|
||||||
onChange={(newTitle) => changePageTitle(newTitle)}
|
onChange={(newTitle) => changePageTitle(newTitle)}
|
||||||
onClose={closeRenamePopup}
|
onClose={closeRenamePopup}
|
||||||
|
top={popupY - 124 + 40}
|
||||||
></RenamePopup>
|
></RenamePopup>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,11 +6,13 @@ export const RenamePopup = ({
|
|||||||
onChange,
|
onChange,
|
||||||
onClose,
|
onClose,
|
||||||
className = '',
|
className = '',
|
||||||
|
top,
|
||||||
}: {
|
}: {
|
||||||
value: string;
|
value: string;
|
||||||
onChange: (newTitle: string) => void;
|
onChange: (newTitle: string) => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
top?: number;
|
||||||
}) => {
|
}) => {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
@ -32,6 +34,7 @@ export const RenamePopup = ({
|
|||||||
className={
|
className={
|
||||||
'absolute left-[50px] top-[40px] z-10 flex w-[300px] rounded bg-white py-1 px-1.5 shadow-md ' + className
|
'absolute left-[50px] top-[40px] z-10 flex w-[300px] rounded bg-white py-1 px-1.5 shadow-md ' + className
|
||||||
}
|
}
|
||||||
|
style={{ top: `${top}px` }}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
|
@ -2,7 +2,6 @@ import React, { ReactNode, useEffect } from 'react';
|
|||||||
import { NavigationPanel } from './NavigationPanel/NavigationPanel';
|
import { NavigationPanel } from './NavigationPanel/NavigationPanel';
|
||||||
import { MainPanel } from './MainPanel';
|
import { MainPanel } from './MainPanel';
|
||||||
import { useNavigationPanelHooks } from './NavigationPanel/NavigationPanel.hooks';
|
import { useNavigationPanelHooks } from './NavigationPanel/NavigationPanel.hooks';
|
||||||
import { NavigationFloatingPanel } from './NavigationPanel/NavigationFloatingPanel';
|
|
||||||
import { useWorkspace } from './Workspace.hooks';
|
import { useWorkspace } from './Workspace.hooks';
|
||||||
import { useAppSelector } from '../../stores/store';
|
import { useAppSelector } from '../../stores/store';
|
||||||
|
|
||||||
@ -15,24 +14,10 @@ export const Screen = ({ children }: { children: ReactNode }) => {
|
|||||||
})();
|
})();
|
||||||
}, [currentUser.isAuthenticated]);
|
}, [currentUser.isAuthenticated]);
|
||||||
|
|
||||||
const {
|
const { width, folders, pages, onPageClick, onHideMenuClick, onShowMenuClick, menuHidden } = useNavigationPanelHooks();
|
||||||
width,
|
|
||||||
folders,
|
|
||||||
pages,
|
|
||||||
onPageClick,
|
|
||||||
onCollapseNavigationClick,
|
|
||||||
onFixNavigationClick,
|
|
||||||
navigationPanelFixed,
|
|
||||||
onScreenMouseMove,
|
|
||||||
slideInFloatingPanel,
|
|
||||||
setFloatingPanelWidth,
|
|
||||||
onHideMenuClick,
|
|
||||||
onShowMenuClick,
|
|
||||||
menuHidden,
|
|
||||||
} = useNavigationPanelHooks();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onMouseMove={onScreenMouseMove} className='flex h-screen w-screen bg-white text-black'>
|
<div className='flex h-screen w-screen bg-white text-black'>
|
||||||
<NavigationPanel
|
<NavigationPanel
|
||||||
onHideMenuClick={onHideMenuClick}
|
onHideMenuClick={onHideMenuClick}
|
||||||
width={width}
|
width={width}
|
||||||
|
@ -39,10 +39,19 @@ export class DatabaseBackendService {
|
|||||||
return FolderEventCloseView(payload);
|
return FolderEventCloseView(payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
createRow = async (rowId?: string, groupId?: string) => {
|
/// Create a row in database
|
||||||
const payload = CreateRowPayloadPB.fromObject({ view_id: this.viewId, start_row_id: rowId ?? undefined });
|
/// 1.The row will be the last row in database if the params is undefined
|
||||||
if (groupId !== undefined) {
|
/// 2.The row will be placed after the passed-in rowId
|
||||||
payload.group_id = groupId;
|
/// 3.The row will be moved to the group with groupId. Currently, grouping is
|
||||||
|
/// only support in kanban board.
|
||||||
|
createRow = async (params?: { rowId?: string; groupId?: string }) => {
|
||||||
|
const payload = CreateRowPayloadPB.fromObject({ view_id: this.viewId });
|
||||||
|
if (params?.rowId !== undefined) {
|
||||||
|
payload.start_row_id = params.rowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params?.groupId !== undefined) {
|
||||||
|
payload.group_id = params.groupId;
|
||||||
}
|
}
|
||||||
return DatabaseEventCreateRow(payload);
|
return DatabaseEventCreateRow(payload);
|
||||||
};
|
};
|
||||||
|
@ -94,7 +94,7 @@ export class DatabaseGroupController {
|
|||||||
};
|
};
|
||||||
|
|
||||||
createRow = async () => {
|
createRow = async () => {
|
||||||
return this.databaseBackendSvc.createRow(this.group.group_id);
|
return this.databaseBackendSvc.createRow({ groupId: this.group.group_id });
|
||||||
};
|
};
|
||||||
|
|
||||||
subscribe = (callbacks: GroupDataCallbacks) => {
|
subscribe = (callbacks: GroupDataCallbacks) => {
|
||||||
|
@ -3,6 +3,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
|||||||
export interface IFolder {
|
export interface IFolder {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
showPages?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: IFolder[] = [];
|
const initialState: IFolder[] = [];
|
||||||
@ -15,7 +16,7 @@ export const foldersSlice = createSlice({
|
|||||||
state.push(action.payload);
|
state.push(action.payload);
|
||||||
},
|
},
|
||||||
renameFolder(state, action: PayloadAction<{ id: string; newTitle: string }>) {
|
renameFolder(state, action: PayloadAction<{ id: string; newTitle: string }>) {
|
||||||
return state.map((f) => (f.id === action.payload.id ? { id: f.id, title: action.payload.newTitle } : f));
|
return state.map((f) => (f.id === action.payload.id ? { ...f, title: action.payload.newTitle } : f));
|
||||||
},
|
},
|
||||||
deleteFolder(state, action: PayloadAction<{ id: string }>) {
|
deleteFolder(state, action: PayloadAction<{ id: string }>) {
|
||||||
return state.filter((f) => f.id !== action.payload.id);
|
return state.filter((f) => f.id !== action.payload.id);
|
||||||
@ -23,6 +24,9 @@ export const foldersSlice = createSlice({
|
|||||||
clearFolders() {
|
clearFolders() {
|
||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
|
setShowPages(state, action: PayloadAction<{ id: string; showPages: boolean }>) {
|
||||||
|
return state.map((f) => (f.id === action.payload.id ? { ...f, showPages: action.payload.showPages } : f));
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -15,12 +15,7 @@ export const pagesSlice = createSlice({
|
|||||||
initialState: initialState,
|
initialState: initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
didReceivePages(state, action: PayloadAction<IPage[]>) {
|
didReceivePages(state, action: PayloadAction<IPage[]>) {
|
||||||
action.payload.forEach((updatedPage) => {
|
return action.payload;
|
||||||
const index = state.findIndex((page) => page.id === updatedPage.id);
|
|
||||||
if (index !== -1) {
|
|
||||||
state.splice(index, 1, updatedPage);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
addPage(state, action: PayloadAction<IPage>) {
|
addPage(state, action: PayloadAction<IPage>) {
|
||||||
state.push(action.payload);
|
state.push(action.payload);
|
||||||
|
@ -30,7 +30,7 @@ pub extern "C" fn init_sdk(path: *mut c_char) -> i64 {
|
|||||||
|
|
||||||
let server_config = get_client_server_configuration().unwrap();
|
let server_config = get_client_server_configuration().unwrap();
|
||||||
let log_crates = vec!["flowy-ffi".to_string()];
|
let log_crates = vec!["flowy-ffi".to_string()];
|
||||||
let config = AppFlowyCoreConfig::new(path, "appflowy".to_string(), server_config)
|
let config = AppFlowyCoreConfig::new(path, DEFAULT_NAME.to_string(), server_config)
|
||||||
.log_filter("info", log_crates);
|
.log_filter("info", log_crates);
|
||||||
*APPFLOWY_CORE.write() = Some(AppFlowyCore::new(config));
|
*APPFLOWY_CORE.write() = Some(AppFlowyCore::new(config));
|
||||||
|
|
||||||
|
@ -33,6 +33,10 @@ use user_model::UserProfile;
|
|||||||
|
|
||||||
static INIT_LOG: AtomicBool = AtomicBool::new(false);
|
static INIT_LOG: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
/// This name will be used as to identify the current [AppFlowyCore] instance.
|
||||||
|
/// Don't change this.
|
||||||
|
pub const DEFAULT_NAME: &str = "appflowy";
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AppFlowyCoreConfig {
|
pub struct AppFlowyCoreConfig {
|
||||||
/// Different `AppFlowyCoreConfig` instance should have different name
|
/// Different `AppFlowyCoreConfig` instance should have different name
|
||||||
|
Loading…
Reference in New Issue
Block a user