diff --git a/src/actions/index.js b/src/actions/index.js
index 1a98662..99a60c4 100644
--- a/src/actions/index.js
+++ b/src/actions/index.js
@@ -1,5 +1,7 @@
/* @flow */
+import { t } from 'ttag';
+
import type {
Action,
ThunkAction,
@@ -557,42 +559,62 @@ export function initTimer(): ThunkAction {
};
}
-export function showModal(modalType: string): Action {
- return {
- type: 'SHOW_MODAL',
+export function showModal(modalType: string, title: string): Action {
+ return openWindow(
modalType,
- };
+ title,
+ true,
+ false,
+ {},
+ );
}
export function showSettingsModal(): Action {
- return showModal('SETTINGS');
+ return showModal(
+ 'SETTINGS',
+ t`Settings`,
+ );
}
export function showUserAreaModal(): Action {
- return showModal('USERAREA');
-}
-
-export function showMinecraftModal(): Action {
- return showModal('MINECRAFT');
+ return showModal(
+ 'USERAREA',
+ t`User Area`,
+ );
}
export function showRegisterModal(): Action {
- return showModal('REGISTER');
+ return showModal(
+ 'REGISTER',
+ t`Register New Account`,
+ );
}
export function showForgotPasswordModal(): Action {
- return showModal('FORGOT_PASSWORD');
+ return showModal(
+ 'FORGOT_PASSWORD',
+ t`Restore my Password`,
+ );
}
export function showHelpModal(): Action {
- return showModal('HELP');
+ return showModal(
+ 'HELP',
+ t`Welcome to PixelPlanet.fun`,
+ );
}
export function showArchiveModal(): Action {
- return showModal('ARCHIVE');
+ return showModal(
+ 'ARCHIVE',
+ t`Canvas Archive`,
+ );
}
export function showCanvasSelectionModal(): Action {
- return showModal('CANVAS_SELECTION');
+ return showModal(
+ 'CANVAS_SELECTION',
+ t`Canvas Selection`,
+ );
}
export function showContextMenu(
@@ -610,6 +632,7 @@ export function showContextMenu(
};
}
+// TODO CHAT MODAL
export function showChatModal(forceModal: boolean = false): Action {
if (window.innerWidth > 604 && !forceModal) { return toggleChatBox(); }
return showModal('CHAT');
@@ -704,6 +727,53 @@ export function addToChatInputMessage(windowId: number, msg: string): Action {
};
}
+/*
+ * fullscreen means to open as modal
+ */
+export function openWindow(
+ windowType: string,
+ title: string,
+ fullscreen: boolean,
+ cloneable: boolean,
+ args: Object,
+): Action {
+ return {
+ type: 'OPEN_WINDOW',
+ windowType,
+ title,
+ fullscreen,
+ cloneable,
+ args,
+ };
+}
+
+export function closeWindow(windowId): Action {
+ return {
+ type: 'CLOSE_WINDOW',
+ windowId,
+ };
+}
+
+export function cloneWindow(windowId): Action {
+ return {
+ type: 'CLONE_WINDOW',
+ windowId,
+ };
+}
+
+export function maximizeWindow(windowId): Action {
+ return {
+ type: 'MAXIMIZE_WINDOW',
+ windowId,
+ };
+}
+
+export function restoreWindow(): Action {
+ return {
+ type: 'RESTORE_WINDOW',
+ };
+}
+
export function moveWindow(windowId, xDiff, yDiff): Action {
return {
type: 'MOVE_WINDOW',
@@ -713,22 +783,20 @@ export function moveWindow(windowId, xDiff, yDiff): Action {
};
}
-export function openChatWindow(): Action {
+export function resizeWindow(windowId, xDiff, yDiff): Action {
return {
- type: 'OPEN_WINDOW',
- windowType: 'CHAT',
- title: 'chat',
- width: 700,
- height: 300,
- xPos: 100,
- yPos: 100,
- args: {
- chatChannel: 1,
- inputMessage: '',
- },
+ type: 'RESIZE_WINDOW',
+ windowId,
+ xDiff,
+ yDiff,
};
}
+export function openChatWindow(): Action {
+ return openWindow('CHAT', t`Chat`, false, true,
+ { chatChannel: 1, inputMessage: '' });
+}
+
/*
* query: Object with either userId: number or userName: string
*/
@@ -823,12 +891,6 @@ export function setLeaveChannel(
};
}
-export function hideModal(): Action {
- return {
- type: 'HIDE_MODAL',
- };
-}
-
export function hideContextMenu(): Action {
return {
type: 'HIDE_CONTEXT_MENU',
diff --git a/src/actions/types.js b/src/actions/types.js
index 5801255..69b0431 100644
--- a/src/actions/types.js
+++ b/src/actions/types.js
@@ -75,7 +75,19 @@ export type Action =
| { type: 'SET_CHAT_INPUT_MSG', windowId: number, msg: string }
| { type: 'ADD_CHAT_INPUT_MSG', windowId: number, msg: string }
| { type: 'SET_CHAT_FETCHING', fetching: boolean }
+ | { type: 'OPEN_WINDOW',
+ windowType: string,
+ title: string,
+ fullscreen: boolean,
+ cloneable: boolean,
+ args: Object,
+ }
+ | { type: 'CLOSE_WINDOW', windowId: number }
+ | { type: 'CLONE_WINDOW', windowId: number }
+ | { type: 'MAXIMIZE_WINDOW', windowId: number }
+ | { type: 'RESTORE_WINDOW' }
| { type: 'MOVE_WINDOW', windowId: number, xDiff: number, yDiff: number }
+ | { type: 'RESIZE_WINDOW', windowId: number, xDiff: number, yDiff: number }
| { type: 'BLOCK_USER', userId: number, userName: string }
| { type: 'UNBLOCK_USER', userId: number, userName: string }
| { type: 'SET_BLOCKING_DM', blockDm: boolean }
@@ -117,14 +129,12 @@ export type Action =
| { type: 'SET_MINECRAFT_NAME', minecraftname: string }
| { type: 'SET_MAILREG', mailreg: boolean }
| { type: 'REM_FROM_MESSAGES', message: string }
- | { type: 'SHOW_MODAL', modalType: string }
| { type: 'SHOW_CONTEXT_MENU',
menuType: string,
xPos: number,
yPos: number,
args: Object,
}
- | { type: 'HIDE_MODAL' }
| { type: 'HIDE_CONTEXT_MENU' }
| { type: 'RELOAD_URL' }
| { type: 'SET_HISTORICAL_TIME', date: string, time: string }
diff --git a/src/components/App.jsx b/src/components/App.jsx
index 14cfdc7..a1c5b38 100644
--- a/src/components/App.jsx
+++ b/src/components/App.jsx
@@ -19,7 +19,7 @@ import Menu from './Menu';
import UI from './UI';
import ExpandMenuButton from './ExpandMenuButton';
import ModalRoot from './ModalRoot';
-import WindowsRoot from './WindowsRoot';
+import WindowManager from './WindowManager';
const App = () => (
@@ -34,7 +34,7 @@ const App = () => (
-
+
);
diff --git a/src/components/ChatBox.jsx b/src/components/ChatBox.jsx
deleted file mode 100644
index df0b9ee..0000000
--- a/src/components/ChatBox.jsx
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- *
- * @flow
- */
-
-import React, { useEffect, useState } from 'react';
-import { useSelector, useDispatch } from 'react-redux';
-
-import useWindowSize from '../utils/reactHookResize';
-import { showChatModal } from '../actions';
-
-import Chat from './Chat';
-
-
-const ChatBox = () => {
- const [render, setRender] = useState(false);
-
- const chatOpen = useSelector((state) => state.modal.chatOpen);
-
- const dispatch = useDispatch();
-
- useEffect(() => {
- window.setTimeout(() => {
- if (chatOpen) setRender(true);
- }, 10);
- }, [chatOpen]);
-
- const onTransitionEnd = () => {
- if (!chatOpen) setRender(false);
- };
-
- const { width } = useWindowSize();
- if (width < 604 && chatOpen) {
- dispatch(showChatModal(true));
- }
-
- return (
- (render || chatOpen) && (
-
-
-
- )
- );
-};
-
-export default React.memo(ChatBox);
diff --git a/src/components/ChatButton.jsx b/src/components/ChatButton.jsx
index 459d93d..92e8c5a 100644
--- a/src/components/ChatButton.jsx
+++ b/src/components/ChatButton.jsx
@@ -14,7 +14,6 @@ import { showChatModal, openChatWindow } from '../actions';
const ChatButton = ({
- modalOpen,
chatNotify,
channels,
unread,
@@ -24,6 +23,7 @@ const ChatButton = ({
const [unreadAny, setUnreadAny] = useState(false);
// TODO what do here?
const chatOpen = false;
+ const modalOpen = false;
/*
* almost the same as in ChannelDropDown
@@ -88,9 +88,6 @@ function mapDispatchToProps(dispatch) {
}
function mapStateToProps(state) {
- const {
- modalOpen,
- } = state.windows;
const {
chatNotify,
} = state.audio;
@@ -102,8 +99,6 @@ function mapStateToProps(state) {
mute,
} = state.chatRead;
return {
- modalOpen,
- chatNotify,
channels,
unread,
mute,
diff --git a/src/components/ChatModal.jsx b/src/components/ChatModal.jsx
deleted file mode 100644
index 763b4d9..0000000
--- a/src/components/ChatModal.jsx
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
- *
- * @flow
- */
-
-import React from 'react';
-import { t } from 'ttag';
-
-import Chat from './Chat';
-
-
-const ChatModal = () => (
-
-);
-
-const data = {
- content: ChatModal,
- title: t`Chat`,
-};
-
-export default data;
diff --git a/src/components/MinecraftButton.jsx b/src/components/MinecraftButton.jsx
deleted file mode 100644
index 448a906..0000000
--- a/src/components/MinecraftButton.jsx
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- *
- * @flow
- */
-
-import React from 'react';
-import { connect } from 'react-redux';
-import Creeper from './Creeper.svg';
-
-import { showMinecraftModal } from '../actions';
-
-const MinecraftButton = ({ open }) => (
-
-
-
-);
-
-function mapDispatchToProps(dispatch) {
- return {
- open() {
- dispatch(showMinecraftModal());
- },
- };
-}
-
-export default connect(null, mapDispatchToProps)(MinecraftButton);
diff --git a/src/components/ModalRoot.jsx b/src/components/ModalRoot.jsx
index ca977c0..f11d919 100644
--- a/src/components/ModalRoot.jsx
+++ b/src/components/ModalRoot.jsx
@@ -5,88 +5,76 @@
* @flow
*/
-import React, { useState, useEffect, useCallback } from 'react';
+import React, { useState, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { MdClose } from 'react-icons/md';
import { t } from 'ttag';
import {
- hideModal,
+ closeWindow,
+ restoreWindow,
} from '../actions';
-
-import HelpModal from './HelpModal';
-import SettingsModal from './SettingsModal';
-import UserAreaModal from './UserAreaModal';
-import RegisterModal from './RegisterModal';
-import CanvasSelectModal from './CanvasSelectModal';
-import ArchiveModal from './ArchiveModal';
-import ChatModal from './ChatModal';
-import ForgotPasswordModal from './ForgotPasswordModal';
-
-
-const MODAL_COMPONENTS = {
- NONE: { content: , title: '' },
- HELP: HelpModal,
- SETTINGS: SettingsModal,
- USERAREA: UserAreaModal,
- REGISTER: RegisterModal,
- FORGOT_PASSWORD: ForgotPasswordModal,
- CHAT: ChatModal,
- CANVAS_SELECTION: CanvasSelectModal,
- ARCHIVE: ArchiveModal,
- /* other modals */
-};
+import COMPONENTS from './windows';
const ModalRoot = () => {
const [render, setRender] = useState(false);
- const modalType = useSelector((state) => state.windows.modalType);
- const modalOpen = useSelector((state) => state.windows.modalOpen);
-
- const {
- title,
- content: SpecificModal,
- } = MODAL_COMPONENTS[modalType || 'NONE'];
+ const { windowType, open, title } = useSelector(
+ (state) => state.windows.modal,
+ );
const dispatch = useDispatch();
- const close = useCallback(() => {
- dispatch(hideModal());
- }, [dispatch]);
const onTransitionEnd = () => {
- if (!modalOpen) setRender(false);
+ if (!open) setRender(false);
};
useEffect(() => {
window.setTimeout(() => {
- if (modalOpen) setRender(true);
+ if (open) setRender(true);
}, 10);
- }, [modalOpen]);
+ }, [open]);
+
+ if (!windowType) {
+ return null;
+ }
+
+ const Content = COMPONENTS[windowType || 'NONE'];
return (
- (render || modalOpen)
+ (render || open)
&& [
dispatch(closeWindow(0))}
/>,
{title}
dispatch(closeWindow(0))}
className="ModalClose"
role="button"
label="close"
title={t`Close`}
tabIndex={-1}
>
-
+
dispatch(restoreWindow())}
+ className="ModalRestore"
+ role="button"
+ label="restore"
+ title={t`Restore`}
+ tabIndex={-1}
+ >♥
+
+
+
,
]
);
diff --git a/src/components/Window.jsx b/src/components/Window.jsx
index b171660..6208a2b 100644
--- a/src/components/Window.jsx
+++ b/src/components/Window.jsx
@@ -6,18 +6,17 @@
import React, { useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
-import Chat from './Chat';
import {
moveWindow,
+ resizeWindow,
+ closeWindow,
+ maximizeWindow,
+ cloneWindow,
} from '../actions';
+import COMPONENTS from './windows';
const selectWindowById = (state, windowId) => state.windows.windows.find((win) => win.windowId === windowId);
-const WINDOW_COMPONENTS = {
- NONE:
,
- CHAT: Chat,
-};
-
const Window = ({ id }) => {
const win = useSelector((state) => selectWindowById(state, id));
@@ -46,6 +45,33 @@ const Window = ({ id }) => {
document.addEventListener('mouseleave', stopMove, { once: true });
}, []);
+ const startResize = useCallback((event) => {
+ event.preventDefault();
+ let {
+ clientX: startX,
+ clientY: startY,
+ } = event;
+ const resize = (evt) => {
+ const {
+ clientX: curX,
+ clientY: curY,
+ } = evt;
+ dispatch(resizeWindow(id, curX - startX, curY - startY));
+ startX = curX;
+ startY = curY;
+ };
+ document.addEventListener('mousemove', resize);
+ const stopResize = () => {
+ document.removeEventListener('mousemove', resize);
+ };
+ document.addEventListener('mouseup', stopResize, { once: true });
+ document.addEventListener('mouseleave', stopResize, { once: true });
+ }, []);
+
+ if (!win) {
+ return null;
+ }
+
const {
width, height,
xPos, yPos,
@@ -53,7 +79,7 @@ const Window = ({ id }) => {
title,
} = win;
- const Content = WINDOW_COMPONENTS[windowType];
+ const Content = COMPONENTS[windowType];
console.log(`render window ${id}`);
@@ -71,7 +97,8 @@ const Window = ({ id }) => {
className="win-topbar"
>
dispatch(cloneWindow(id))}
>
+
@@ -79,20 +106,30 @@ const Window = ({ id }) => {
className="win-title"
onMouseDown={startMove}
>
- Move Here
+ {title}
dispatch(maximizeWindow(id))}
>
↑
dispatch(closeWindow(id))}
>
X
-
+
+
+
+
+ R
+
);
};
diff --git a/src/components/WindowsRoot.jsx b/src/components/WindowManager.jsx
similarity index 92%
rename from src/components/WindowsRoot.jsx
rename to src/components/WindowManager.jsx
index 486d491..510bc6f 100644
--- a/src/components/WindowsRoot.jsx
+++ b/src/components/WindowManager.jsx
@@ -14,7 +14,7 @@ const WindowsRoot = () => {
const windowIds = useSelector(selectWindowIds, shallowEqual);
return windowIds.map((id) => (
-
+
));
};
diff --git a/src/components/ArchiveModal.jsx b/src/components/windows/Archive.jsx
similarity index 93%
rename from src/components/ArchiveModal.jsx
rename to src/components/windows/Archive.jsx
index 15113ea..3593eff 100644
--- a/src/components/ArchiveModal.jsx
+++ b/src/components/windows/Archive.jsx
@@ -14,7 +14,7 @@ const imageStyle = {
verticalAlign: 'middle',
};
-const ArchiveModal = () => (
+const Archive = () => (
{t`While we tend to not delete canvases, some canvases are started for fun or as a request by users who currently like a meme. \
@@ -45,9 +45,4 @@ Those canvases can get boring after a while and after weeks of no major change a
);
-const data = {
- content: ArchiveModal,
- title: t`Canvas Archive`,
-};
-
-export default data;
+export default Archive;
diff --git a/src/components/CanvasSelectModal.jsx b/src/components/windows/CanvasSelect.jsx
similarity index 79%
rename from src/components/CanvasSelectModal.jsx
rename to src/components/windows/CanvasSelect.jsx
index b2d23bb..94f3f13 100644
--- a/src/components/CanvasSelectModal.jsx
+++ b/src/components/windows/CanvasSelect.jsx
@@ -7,13 +7,13 @@ import React from 'react';
import { connect } from 'react-redux';
import { t } from 'ttag';
-import CanvasItem from './CanvasItem';
-import { showArchiveModal } from '../actions';
+import CanvasItem from '../CanvasItem';
+import { showArchiveModal } from '../../actions';
-import type { State } from '../reducers';
+import type { State } from '../../reducers';
-const CanvasSelectModal = ({
+const CanvasSelect = ({
canvases,
showHiddenCanvases,
showArchive,
@@ -62,9 +62,4 @@ function mapStateToProps(state: State) {
return { canvases, showHiddenCanvases };
}
-const data = {
- content: connect(mapStateToProps, mapDispatchToProps)(CanvasSelectModal),
- title: t`Canvas Selection`,
-};
-
-export default data;
+export default connect(mapStateToProps, mapDispatchToProps)(CanvasSelect);
diff --git a/src/components/Chat.jsx b/src/components/windows/Chat.jsx
similarity index 93%
rename from src/components/Chat.jsx
rename to src/components/windows/Chat.jsx
index bdb37e7..19c6d96 100644
--- a/src/components/Chat.jsx
+++ b/src/components/windows/Chat.jsx
@@ -10,9 +10,8 @@ import useStayScrolled from 'react-stay-scrolled';
import { useSelector, useDispatch } from 'react-redux';
import { t } from 'ttag';
-import type { State } from '../reducers';
-import ChatMessage from './ChatMessage';
-import ChannelDropDown from './ChannelDropDown';
+import ChatMessage from '../ChatMessage';
+import ChannelDropDown from '../ChannelDropDown';
import {
showUserAreaModal,
@@ -21,9 +20,9 @@ import {
setChatInputMessage,
fetchChatMessages,
showContextMenu,
-} from '../actions';
-import ProtocolClient from '../socket/ProtocolClient';
-import splitChatMessage from '../core/chatMessageFilter';
+} from '../../actions';
+import ProtocolClient from '../../socket/ProtocolClient';
+import splitChatMessage from '../../core/chatMessageFilter';
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
@@ -115,13 +114,7 @@ const Chat = ({
return (
(
+const ForgotPassword = ({ login }) => (
{t`Enter your mail address and we will send you a new password:`}
@@ -32,9 +32,4 @@ function mapDispatchToProps(dispatch) {
};
}
-const data = {
- content: connect(null, mapDispatchToProps)(ForgotPasswordModal),
- title: t`Restore my Password`,
-};
-
-export default data;
+export default connect(null, mapDispatchToProps)(ForgotPassword);
diff --git a/src/components/HelpModal.jsx b/src/components/windows/Help.jsx
similarity index 97%
rename from src/components/HelpModal.jsx
rename to src/components/windows/Help.jsx
index c6d4abe..6f20805 100644
--- a/src/components/HelpModal.jsx
+++ b/src/components/windows/Help.jsx
@@ -11,7 +11,7 @@ import { MdTouchApp } from 'react-icons/md';
/* eslint-disable max-len */
-const HelpModal = () => {
+const Help = () => {
const bindG = {c('keybinds').t`G`};
const bindX = {c('keybinds').t`X`};
const bindH = {c('keybinds').t`H`};
@@ -89,9 +89,4 @@ can be downloaded from mega.nz here: `}{t`Register new account here`}
@@ -33,9 +33,4 @@ function mapDispatchToProps(dispatch) {
};
}
-const data = {
- content: connect(null, mapDispatchToProps)(RegisterModal),
- title: t`Register New Account`,
-};
-
-export default data;
+export default connect(null, mapDispatchToProps)(Register);
diff --git a/src/components/SettingsModal.jsx b/src/components/windows/Settings.jsx
similarity index 95%
rename from src/components/SettingsModal.jsx
rename to src/components/windows/Settings.jsx
index 8a27e22..59580c4 100644
--- a/src/components/SettingsModal.jsx
+++ b/src/components/windows/Settings.jsx
@@ -7,8 +7,8 @@ import React from 'react';
import { connect } from 'react-redux';
import { c, t } from 'ttag';
-import LanguageSelect from './LanguageSelect';
-import MdToggleButtonHover from './MdToggleButtonHover';
+import LanguageSelect from '../LanguageSelect';
+import MdToggleButtonHover from '../MdToggleButtonHover';
import {
toggleGrid,
togglePixelNotify,
@@ -20,9 +20,9 @@ import {
toggleLightGrid,
toggleHistoricalView,
selectStyle,
-} from '../actions';
+} from '../../actions';
-import type { State } from '../reducers';
+import type { State } from '../../reducers';
const flexy = {
@@ -97,7 +97,7 @@ const SettingsItem = ({
);
-function SettingsModal({
+function Settings({
isMuted,
isGridShown,
isPixelNotifyShown,
@@ -273,9 +273,4 @@ function mapDispatchToProps(dispatch) {
};
}
-const data = {
- content: connect(mapStateToProps, mapDispatchToProps)(SettingsModal),
- title: t`Settings`,
-};
-
-export default data;
+export default connect(mapStateToProps, mapDispatchToProps)(Settings);
diff --git a/src/components/UserAreaModal.jsx b/src/components/windows/UserArea.jsx
similarity index 88%
rename from src/components/UserAreaModal.jsx
rename to src/components/windows/UserArea.jsx
index 390782b..15c81d1 100644
--- a/src/components/UserAreaModal.jsx
+++ b/src/components/windows/UserArea.jsx
@@ -7,21 +7,21 @@ import React, { Suspense } from 'react';
import { connect } from 'react-redux';
import { t } from 'ttag';
-import type { State } from '../reducers';
+import type { State } from '../../reducers';
import {
showRegisterModal, showForgotPasswordModal, setName, setMailreg,
-} from '../actions';
-import LogInForm from './LogInForm';
-import Tabs from './Tabs';
-import UserArea from './UserArea';
-import Rankings from './Rankings';
+} from '../../actions';
+import LogInForm from '../LogInForm';
+import Tabs from '../Tabs';
+import UserAreaContent from '../UserArea';
+import Rankings from '../Rankings';
// eslint-disable-next-line max-len
-const Converter = React.lazy(() => import(/* webpackChunkName: "converter" */ './Converter'));
+const Converter = React.lazy(() => import(/* webpackChunkName: "converter" */ '../Converter'));
// eslint-disable-next-line max-len
-const Admintools = React.lazy(() => import(/* webpackChunkName: "admintools" */ './Admintools'));
+const Admintools = React.lazy(() => import(/* webpackChunkName: "admintools" */ '../Admintools'));
const logoStyle = {
marginRight: 5,
@@ -86,7 +86,7 @@ const LogInArea = ({ register, forgotPassword, me }) => (
);
-const UserAreaModal = ({
+const UserArea = ({
name,
register,
forgotPassword,
@@ -105,7 +105,7 @@ const UserAreaModal = ({
: (
-
@@ -155,9 +155,4 @@ function mapStateToProps(state: State) {
return { name, userlvl };
}
-const data = {
- content: connect(mapStateToProps, mapDispatchToProps)(UserAreaModal),
- title: t`User Area`,
-};
-
-export default data;
+export default connect(mapStateToProps, mapDispatchToProps)(UserArea);
diff --git a/src/components/windows/index.jsx b/src/components/windows/index.jsx
new file mode 100644
index 0000000..977c6b1
--- /dev/null
+++ b/src/components/windows/index.jsx
@@ -0,0 +1,25 @@
+/*
+ * @flow
+ */
+
+import Help from './Help';
+import Settings from './Settings';
+import UserArea from './UserArea';
+import Register from './Register';
+import CanvasSelect from './CanvasSelect';
+import Archive from './Archive';
+import Chat from './Chat';
+import ForgotPassword from './ForgotPassword';
+
+export default {
+ NONE:
,
+ HELP: Help,
+ SETTINGS: Settings,
+ USERAREA: UserArea,
+ REGISTER: Register,
+ FORGOT_PASSWORD: ForgotPassword,
+ CHAT: Chat,
+ CANVAS_SELECTION: CanvasSelect,
+ ARCHIVE: Archive,
+ /* other modals */
+};
diff --git a/src/reducers/windows.js b/src/reducers/windows.js
index 5ee83ea..e106ca9 100644
--- a/src/reducers/windows.js
+++ b/src/reducers/windows.js
@@ -6,20 +6,33 @@
import type { Action } from '../actions/types';
+function generateWindowId(state) {
+ let windowId = Math.floor(Math.random() * 99999) + 1;
+ while (state.args[windowId]) {
+ windowId += 1;
+ }
+ return windowId;
+}
+
export type WindowsState = {
// modal is considerd as "fullscreen window"
// its windowId is considered 0 and args are under args[0]
- modalOpen: boolean,
- modalType: ?string,
+ modal: {
+ windowType: ?string,
+ title: ?string,
+ open: boolean,
+ },
// [
// {
// windowId: number,
+ // windowOpen: boolean,
// windowType: string,
// title: string,
// width: number,
// height: number,
// xPos: percentage,
// yPos: percentage,
+ // cloneable: boolean,
// },
// ]
windows: Array,
@@ -32,8 +45,11 @@ export type WindowsState = {
}
const initialState: WindowsState = {
- modalOpen: false,
- modalType: null,
+ modal: {
+ windowType: null,
+ title: null,
+ open: false,
+ },
windows: [],
args: {},
};
@@ -47,16 +63,27 @@ export default function windows(
const {
windowType,
title,
- width,
- height,
- xPos,
- yPos,
+ fullscreen,
+ cloneable,
args,
} = action;
- let windowId = Math.floor(Math.random() * 99999) + 1;
- while (state.args[windowId]) {
- windowId += 1;
+ if (fullscreen) {
+ return {
+ ...state,
+ modal: {
+ windowType,
+ title,
+ open: true,
+ },
+ args: {
+ ...state.args,
+ 0: {
+ ...args,
+ },
+ },
+ };
}
+ const windowId = generateWindowId(state);
return {
...state,
windows: [
@@ -64,12 +91,13 @@ export default function windows(
{
windowId,
windowType,
+ windowOpen: true,
title,
- width,
- height,
- xPos,
- yPos,
- args,
+ width: 600,
+ height: 300,
+ xPos: 200,
+ yPos: 200,
+ cloneable,
},
],
args: {
@@ -79,10 +107,57 @@ export default function windows(
};
}
+ case 'REMOVE_WINDOW': {
+ const {
+ windowId,
+ } = action;
+ const args = { ...state.args };
+ delete args[windowId];
+
+ if (windowId === 0) {
+ return {
+ ...state,
+ modal: {
+ windowType: null,
+ title: null,
+ open: false,
+ },
+ args,
+ };
+ }
+ return {
+ ...state,
+ windows: state.windows.filter((win) => win.windowId !== windowId),
+ args,
+ };
+ }
+
case 'CLOSE_WINDOW': {
const {
windowId,
} = action;
+ if (windowId === 0) {
+ return {
+ ...state,
+ modal: {
+ ...state.modal,
+ open: false,
+ },
+ };
+ }
+ /*
+ const newWindows = state.windows.map((win) => {
+ if (win.windowId !== windowId) return win;
+ return {
+ ...win,
+ windowOpen: false,
+ }
+ });
+ return {
+ ...state,
+ windows: newWindows,
+ };
+ */
const args = { ...state.args };
delete args[windowId];
return {
@@ -92,6 +167,87 @@ export default function windows(
};
}
+ case 'CLONE_WINDOW': {
+ const {
+ windowId,
+ } = action;
+ const win = state.windows.find((w) => w.windowId === windowId);
+ const newWindowId = generateWindowId(state);
+ return {
+ ...state,
+ windows: [
+ ...state.windows,
+ {
+ ...win,
+ windowId: newWindowId,
+ xPos: win.xPos + 15,
+ yPos: win.yPos + 15,
+ },
+ ],
+ args: {
+ ...state.args,
+ [newWindowId]: {
+ ...state.args[windowId],
+ },
+ },
+ };
+ }
+
+ case 'MAXIMIZE_WINDOW': {
+ const {
+ windowId,
+ } = action;
+ const args = {
+ ...state.args,
+ 0: state.args[windowId],
+ };
+ const { windowType, title } = state.windows.find((w) => w.windowId === windowId);
+ delete args[windowId];
+ return {
+ ...state,
+ modal: {
+ windowType,
+ title,
+ open: true,
+ },
+ windows: state.windows.filter((w) => w.windowId !== windowId),
+ args,
+ };
+ }
+
+ case 'RESTORE_WINDOW': {
+ const windowId = generateWindowId(state);
+ const { windowType, title } = state.modal;
+ const cloneable = true;
+ return {
+ ...state,
+ modal: {
+ ...state.modal,
+ open: false,
+ },
+ windows: [
+ ...state.windows,
+ {
+ windowType,
+ windowId,
+ windowOpen: true,
+ title,
+ width: 600,
+ height: 300,
+ xPos: 200,
+ yPos: 200,
+ cloneable,
+ },
+ ],
+ args: {
+ ...state.args,
+ [windowId]: {
+ ...state.args[0],
+ },
+ },
+ };
+ }
+
case 'MOVE_WINDOW': {
const {
windowId,
@@ -112,6 +268,26 @@ export default function windows(
};
}
+ case 'RESIZE_WINDOW': {
+ const {
+ windowId,
+ xDiff,
+ yDiff,
+ } = action;
+ const newWindows = state.windows.map((win) => {
+ if (win.windowId !== windowId) return win;
+ return {
+ ...win,
+ width: win.width + xDiff,
+ height: win.height + yDiff,
+ };
+ });
+ return {
+ ...state,
+ windows: newWindows,
+ };
+ }
+
case 'CLOSE_ALL_WINDOWS': {
return initialState;
}
diff --git a/src/styles/default.css b/src/styles/default.css
index c171fa4..4c97ab4 100644
--- a/src/styles/default.css
+++ b/src/styles/default.css
@@ -144,6 +144,7 @@ tr:nth-child(even) {
.win-topbar {
display: flex;
+ height: 16px;
}
.win-title {
@@ -155,6 +156,29 @@ tr:nth-child(even) {
border: solid black;
border-width: thin;
background-color: blue;
+ cursor: pointer;
+}
+
+.win-resize {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ cursor: se-resize;
+}
+
+.chat-container {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ height: calc(100% - 4px);
+}
+
+.win-content {
+ position: relative;
+ width: 100%;
+ height: calc(100% - 16px);
+ overflow-y: auto;
}
.contextmenu {
@@ -444,7 +468,7 @@ tr:nth-child(even) {
width: 75%;
}
-.ModalClose {
+.ModalClose, .ModalRestore {
position: fixed;
display: flex;
justify-content: center;
@@ -459,11 +483,18 @@ tr:nth-child(even) {
background-color: #f6f6f7;
border-color: #dcddde;
top: 30px;
- right: 40px;
z-index: 5;
}
-.ModalClose:hover {
+.ModalClose {
+ right: 40px;
+}
+
+.ModalRestore {
+ right: 90px;
+}
+
+.ModalClose:hover, .ModalRestore:hover {
background-color: #e3e3e4;
}