move windows to subfolder

This commit is contained in:
HF 2021-04-27 21:30:19 +02:00
parent 7d550d5a20
commit 6d1ad02b26
22 changed files with 483 additions and 320 deletions

View File

@ -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',

View File

@ -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 }

View File

@ -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 = () => (
<div>
@ -34,7 +34,7 @@ const App = () => (
<ExpandMenuButton />
<UI />
<ModalRoot />
<WindowsRoot />
<WindowManager />
</IconContext.Provider>
</div>
);

View File

@ -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) && (
<div
className={(chatOpen && render) ? 'chatbox show' : 'chatbox'}
onTransitionEnd={onTransitionEnd}
>
<Chat showExpand />
</div>
)
);
};
export default React.memo(ChatBox);

View File

@ -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,

View File

@ -1,38 +0,0 @@
/**
*
* @flow
*/
import React from 'react';
import { t } from 'ttag';
import Chat from './Chat';
const ChatModal = () => (
<div style={{
position: 'fixed',
top: 80,
padding: 10,
bottom: 10,
left: 10,
right: 10,
}}
>
<div
className="inarea"
style={{
height: '95%',
}}
>
<Chat />
</div>
</div>
);
const data = {
content: ChatModal,
title: t`Chat`,
};
export default data;

View File

@ -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 }) => (
<div
id="minecraftbutton"
className="actionbuttons"
onClick={open}
role="button"
tabIndex={-1}
>
<Creeper />
</div>
);
function mapDispatchToProps(dispatch) {
return {
open() {
dispatch(showMinecraftModal());
},
};
}
export default connect(null, mapDispatchToProps)(MinecraftButton);

View File

@ -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: <div />, 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)
&& [
<div
className={(modalOpen && render)
className={(open && render)
? 'OverlayModal show'
: 'OverlayModal'}
onTransitionEnd={onTransitionEnd}
tabIndex={-1}
onClick={close}
onClick={() => dispatch(closeWindow(0))}
/>,
<div
className={(modalOpen && render) ? 'Modal show' : 'Modal'}
className={(open && render) ? 'Modal show' : 'Modal'}
>
<h2 style={{ paddingLeft: '5%' }}>{title}</h2>
<div
onClick={close}
onClick={() => dispatch(closeWindow(0))}
className="ModalClose"
role="button"
label="close"
title={t`Close`}
tabIndex={-1}
><MdClose /></div>
<SpecificModal windowId={0} />
<div
onClick={() => dispatch(restoreWindow())}
className="ModalRestore"
role="button"
label="restore"
title={t`Restore`}
tabIndex={-1}
></div>
<div className="win-content">
<Content windowId={0} />
</div>
</div>,
]
);

View File

@ -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: <div />,
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"
>
<span
className="win-topbtnn"
className="win-topbtn"
onClick={() => dispatch(cloneWindow(id))}
>
+
</span>
@ -79,20 +106,30 @@ const Window = ({ id }) => {
className="win-title"
onMouseDown={startMove}
>
Move Here
{title}
</span>
<span
className="win-topbtnn"
className="win-topbtn"
onClick={() => dispatch(maximizeWindow(id))}
>
</span>
<span
className="win-topbtnn"
className="win-topbtn"
onClick={() => dispatch(closeWindow(id))}
>
X
</span>
</div>
<Content windowId={id} />
<div className="win-content">
<Content windowId={id} />
</div>
<div
onMouseDown={startResize}
className="win-resize"
>
R
</div>
</div>
);
};

View File

@ -14,7 +14,7 @@ const WindowsRoot = () => {
const windowIds = useSelector(selectWindowIds, shallowEqual);
return windowIds.map((id) => (
<Window id={id} />
<Window key={id} id={id} />
));
};

View File

@ -14,7 +14,7 @@ const imageStyle = {
verticalAlign: 'middle',
};
const ArchiveModal = () => (
const Archive = () => (
<p style={{ textAlign: 'center', paddingLeft: '5%', paddingRight: '5%' }}>
<p className="modaltext">
{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
</p>
);
const data = {
content: ArchiveModal,
title: t`Canvas Archive`,
};
export default data;
export default Archive;

View File

@ -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);

View File

@ -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 (
<div
ref={targetRef}
style={{
display: 'flex',
position: 'relative',
width: '100%',
height: '100%',
flexDirection: 'column',
}}
className="chat-container"
>
<div
className="chatlink"

View File

@ -7,10 +7,10 @@ import React from 'react';
import { connect } from 'react-redux';
import { t } from 'ttag';
import { showUserAreaModal } from '../actions';
import NewPasswordForm from './NewPasswordForm';
import { showUserAreaModal } from '../../actions';
import NewPasswordForm from '../NewPasswordForm';
const ForgotPasswordModal = ({ login }) => (
const ForgotPassword = ({ login }) => (
<p style={{ paddingLeft: '5%', paddingRight: '5%' }}>
<p className="modaltext">
{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);

View File

@ -11,7 +11,7 @@ import { MdTouchApp } from 'react-icons/md';
/* eslint-disable max-len */
const HelpModal = () => {
const Help = () => {
const bindG = <kbd>{c('keybinds').t`G`}</kbd>;
const bindX = <kbd>{c('keybinds').t`X`}</kbd>;
const bindH = <kbd>{c('keybinds').t`H`}</kbd>;
@ -89,9 +89,4 @@ can be downloaded from mega.nz here: `}<a href="https://mega.nz/#!JpkBwAbJ!EnSLl
);
};
const data = {
content: HelpModal,
title: t`Welcome to PixelPlanet.fun`,
};
export default data;
export default Help;

View File

@ -7,13 +7,13 @@ import React from 'react';
import { connect } from 'react-redux';
import { t } from 'ttag';
import { showUserAreaModal } from '../actions';
import { showUserAreaModal } from '../../actions';
// import { send_registration } from '../ui/register';
import SignUpForm from './SignUpForm';
import SignUpForm from '../SignUpForm';
const RegisterModal = ({ login }) => (
const Register = ({ login }) => (
<p style={{ paddingLeft: '5%', paddingRight: '5%' }}>
<p className="modaltext">{t`Register new account here`}</p><br />
<p style={{ textAlign: 'center' }}>
@ -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);

View File

@ -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 = ({
</div>
);
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);

View File

@ -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 }) => (
</p>
);
const UserAreaModal = ({
const UserArea = ({
name,
register,
forgotPassword,
@ -105,7 +105,7 @@ const UserAreaModal = ({
: (
<Tabs>
<div label={t`Profile`}>
<UserArea
<UserAreaContent
setName={setUserName}
setMailreg={setUserMailreg}
/>
@ -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);

View File

@ -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: <div />,
HELP: Help,
SETTINGS: Settings,
USERAREA: UserArea,
REGISTER: Register,
FORGOT_PASSWORD: ForgotPassword,
CHAT: Chat,
CANVAS_SELECTION: CanvasSelect,
ARCHIVE: Archive,
/* other modals */
};

View File

@ -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;
}

View File

@ -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;
}