Skip to content

Commit

Permalink
canvas updates
Browse files Browse the repository at this point in the history
  • Loading branch information
rayzhou-bit committed Mar 11, 2024
1 parent 8927bf2 commit 0f3a6f5
Show file tree
Hide file tree
Showing 27 changed files with 706 additions and 518 deletions.
22 changes: 17 additions & 5 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useRef, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { useSelector } from 'react-redux';

import './App.scss';
import Loader from './components/Loader/Loader';
import HeaderMenu from './components/HeaderMenu';
import ToolMenu from './components/ToolMenu';
import Library from './components/Library/Library';
Expand All @@ -10,19 +10,31 @@ import Canvas from './components/Canvas';
import Popup from './components/Popup';

const App = () => {
const [ isToolMenuOpen, setIsToolMenuOpen ] = useState(true);
const activeProject = useSelector(state => state.sessionManager.activeCampaignId || '');
const [ isToolMenuOpen, setIsToolMenuOpen ] = useState(!!activeProject);
const toolMenuRef = useRef();

useEffect(() => {
setIsToolMenuOpen(!!activeProject);
}, [activeProject]);

// Disable scrolling
document.body.scroll = 'no';
document.body.style.backgroundColor = 'gray';
document.body.style.overflow = 'hidden';
document.height = window.innerHeight;

// TODO create initialize function and move login stuff into it from Canvas.jsx

return (
<div className='layout'>
<Loader />
<HeaderMenu isToolMenuOpen={isToolMenuOpen} toggleToolMenu={() => setIsToolMenuOpen(!isToolMenuOpen)} />
<HeaderMenu
isToolMenuOpen={isToolMenuOpen}
toggleToolMenu={() => {
if (activeProject) {
setIsToolMenuOpen(!isToolMenuOpen);
}
}} />
<ToolMenu toolMenuRef={toolMenuRef} isOpen={isToolMenuOpen} />
<Library />
<TabBar />
Expand Down
1 change: 1 addition & 0 deletions src/App.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
right: 0;
bottom: 0;
left: 0;
background-color: #E8EEF5;
}

input:focus,
Expand Down
19 changes: 19 additions & 0 deletions src/assets/icons/library.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/icons/plus.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 6 additions & 1 deletion src/components-shared/Dropdowns/ActionDropdown.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@ import useOutsideClick from '../../utils/useOutsideClick';
Each item in the items array should be similar to the following object:
{
title: 'Move to unsorted',
type: 'danger',
type: ACTION_TYPE.danger,
callback: () => dispatch(actions.unlinkCardFromView(cardId)),
icon: LibraryIcon,
},
*/

export const ACTION_TYPE = {
danger: 'danger',
disabled: 'disabled',
};

export const ActionDropdown = ({
btnRef,
isOpen,
Expand Down
5 changes: 4 additions & 1 deletion src/components-shared/Dropdowns/ActionDropdown.scss
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ $y: 38px;

.danger {
font-weight: 600 !important;
line-height: 30px !important;
color: #ed5e31 !important;
}

.disabled {
color: #C4C4C4 !important;
}
4 changes: 2 additions & 2 deletions src/components-shared/Dropdowns/ColorDropdown.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useRef } from 'react';

import './ColorDropdown.scss';
import '../../styles/colors.scss'
import { CardColorKeys } from '../../styles/colors';
import { CARD_COLOR_KEYS } from '../../styles/colors';
import useOutsideClick from '../../utils/useOutsideClick';

export const ColorDropdown = ({
Expand All @@ -19,7 +19,7 @@ export const ColorDropdown = ({
if (!isOpen) return null;

let colorList = [];
Object.values(CardColorKeys).forEach(color => {
Object.values(CARD_COLOR_KEYS).forEach(color => {
let className = 'item ' + color;
if (cardColor === color) {
className += ' selected';
Expand Down
183 changes: 183 additions & 0 deletions src/components/Canvas/hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { ActionCreators } from "redux-undo";

import * as actions from '../../store/actionIndex';
import * as fireactions from '../../store/firestoreIndex';
import { manageUser } from "../../store/firestoreAPI/authTransactions";

import { GRID } from '../../shared/constants/grid';

// TODO separate out the network code into functions data/request or something
// link the authlistener and app status management to App.js

export const NETWORK_STATUS = {
idle: 'idle',
saving: 'saving',
loading: 'loading',
};

export const CANVAS_STATES = {
empty: 'empty',
loading: 'loading',
loaded: 'loaded',
};

export const CANVAS_DIMENSIONS = {
width: 100 * GRID.size,
height: 70 * GRID.size,
};

export const useCanvasHooks = () => {
const dispatch = useDispatch();
const userId = useSelector((state) => state.userData.userId);
const status = useSelector(state => state.sessionManager.status || NETWORK_STATUS.idle);
const activeProject = useSelector(state => state.sessionManager.activeCampaignId || '');
const isProjectEdited = useSelector(state => state.sessionManager.campaignEdit);
const isIntroCampaignEdited = useSelector(state => state.sessionManager.introCampaignEdit);
const activeTab = useSelector(state => state.campaignData.present.activeViewId || '');
const activeTabPosition = useSelector(state => activeTab ? state.campaignData.present.views[activeTab].pos : null);
const activeTabScale = useSelector(state => activeTab ? state.campaignData.present.views[activeTab].scale : 1);
const projectData = useSelector(state => state.campaignData.present);
const cardCollection = useSelector(state => state.campaignData.present.cards);
const latestUnfiltered = useSelector(state => state.campaignData._latestUnfiltered);

const [ canvasState, setCanvasState ] = useState(CANVAS_STATES.empty);
const [ cardAnimation, setCardAnimation ] = useState({});

const isLoggedIn = !!userId;

// set canvas state
useEffect(() => {
if (status === NETWORK_STATUS.loading) {
setCanvasState(CANVAS_STATES.loading);
} else if (!!userId && !!activeProject && !!activeTab) {
setCanvasState(CANVAS_STATES.loaded);
} else {
setCanvasState(CANVAS_STATES.empty);
}
}, [status, userId, activeProject, activeTab]);

// auth listener
useEffect(() => {
const authListener = manageUser({
dispatch,
introCampaignEdit: isIntroCampaignEdited,
campaignData: projectData,
});
return () => authListener();
}, [dispatch]);

// load data for active project
useEffect(() => {
if (isLoggedIn) {
if (activeProject) {
dispatch(fireactions.fetchCampaignData(
activeProject,
() => dispatch(ActionCreators.clearHistory()),
));
} else {
console.log("[Status] idle. Triggered by activeCampaignId change.");
dispatch(actions.setStatus(NETWORK_STATUS.idle));
}
}
}, [dispatch, activeProject]);

// when project data changes, set edited flag
useEffect(() => {
if (isLoggedIn) {
if ((status === NETWORK_STATUS.idle) && !!projectData && (Object.keys(projectData).length !== 0)) {
if (!isProjectEdited) {
dispatch(actions.setCampaignEdit(true));
}
} else {
console.log("[Status] idle. Triggered by post-load.");
dispatch(actions.setStatus(NETWORK_STATUS.idle));
}
} else {
if ((status === NETWORK_STATUS.idle) && !!projectData && (Object.keys(projectData).length !== 0)) {
if (!isIntroCampaignEdited) {
dispatch(actions.setIntroCampaignEdit(true));
}
} else {
console.log("[Status] idle. Triggered by post-load.");
dispatch(actions.setStatus(NETWORK_STATUS.idle));
}
}
}, [dispatch, latestUnfiltered])

// auto-save every minute
useEffect(() => {
const autoSave = setInterval(() => {
if ((status === NETWORK_STATUS.idle) && isLoggedIn && activeProject && isProjectEdited) {
console.log("[Status] saving. Triggered by autosave.");
dispatch(actions.setStatus(NETWORK_STATUS.saving));
dispatch(fireactions.saveCampaignData(
activeProject,
projectData,
() => {
console.log("[Status] idle. Triggered by autosave completion.");
dispatch(actions.setStatus(NETWORK_STATUS.idle));
}
));
}
}, 60000);
return () => clearInterval(autoSave);
}, [dispatch, status, userId, activeProject, isProjectEdited, projectData]);

let cardArgs = {};
for (let card in cardCollection) {
if (cardCollection[card].views && cardCollection[card].views[activeTab]) {
cardArgs[card] = {
key: card,
cardId: card,
cardAnimation: cardAnimation,
setCardAnimation: setCardAnimation,
};
}
}

return {
canvasState,
canvasPosition: activeTabPosition ?? { x: 0, y: 0 },
canvasScale: activeTabScale,
cardArgs,
dragStopHandler: (event, data) => {
if (activeTabPosition) {
if (activeTabPosition.x !== data.x || activeTabPosition.y !== data.y) {
dispatch(actions.updActiveViewPos({ x: data.x, y: data.y }));
} else {
dispatch(actions.updActiveViewPos({ x: data.x, y: data.y }));
}
}
},
wheelHandler: (event) => {
let newScale = activeTabScale ?? 1;
newScale += event.deltaY * -0.001;
newScale = Math.round(newScale * 10) / 10;
newScale = Math.min(Math.max(GRID.scaleMin, newScale), GRID.scaleMax);
dispatch(actions.updActiveViewScale(newScale));
},
cardDropHandler: (event) => {
event.preventDefault();
const droppedCard = event.dataTransfer.getData('text');
if (cardCollection[droppedCard]) {
if (!cardCollection[droppedCard].views[activeTab]) {
// TODO more precise position calculation
let x = Math.round((event.clientX - GRID.size - GRID.size) / GRID.size) * GRID.size;
let y = Math.round((event.clientY - GRID.size - GRID.size) / GRID.size) * GRID.size;
x = (x < 0) ? 0 : x;
y = (y < 0) ? 0 : y;
const position = { x, y };
dispatch(actions.linkCardToView(droppedCard, position));
} else {
setCardAnimation({
...cardAnimation,
[droppedCard]: 'card-blink .25s step-end 3 alternate',
});
}
}
},
createNewProject: () => dispatch(fireactions.createProject()),
};
};
Loading

0 comments on commit 0f3a6f5

Please sign in to comment.