make window close transition

This commit is contained in:
HF 2021-04-29 23:48:43 +02:00
parent 1c486f3105
commit 8f0ec19999
9 changed files with 212 additions and 116 deletions

View File

@ -752,6 +752,13 @@ export function closeWindow(windowId): Action {
}; };
} }
export function removeWindow(windowId): Action {
return {
type: 'REMOVE_WINDOW',
windowId,
};
}
export function focusWindow(windowId): Action { export function focusWindow(windowId): Action {
return { return {
type: 'FOCUS_WINDOW', type: 'FOCUS_WINDOW',
@ -797,6 +804,25 @@ export function resizeWindow(windowId, xDiff, yDiff): Action {
}; };
} }
export function closeAllWindowTypes(windowType: string): Action {
return {
type: 'CLOSE_ALL_WINDOW_TYPE',
windowType,
};
}
export function hideAllWindowTypes(
windowType: string,
hide: boolean,
): Action {
console.log(`hideAllWindowTypes`, hide);
return {
type: 'HIDE_ALL_WINDOW_TYPE',
windowType,
hide,
};
}
export function openChatWindow(): Action { export function openChatWindow(): Action {
return openWindow('CHAT', t`Chat`, false, true, return openWindow('CHAT', t`Chat`, false, true,
{ chatChannel: 1, inputMessage: '' }); { chatChannel: 1, inputMessage: '' });

View File

@ -85,6 +85,9 @@ export type Action =
args: Object, args: Object,
} }
| { type: 'CLOSE_WINDOW', windowId: number } | { type: 'CLOSE_WINDOW', windowId: number }
| { type: 'REMOVE_WINDOW', windowId: number }
| { type: 'HIDE_ALL_WINDOW_TYPE', windowType: string, hide: boolean }
| { type: 'CLOSE_ALL_WINDOW_TYPE', windowType: string}
| { type: 'FOCUS_WINDOW', windowId: number } | { type: 'FOCUS_WINDOW', windowId: number }
| { type: 'CLONE_WINDOW', windowId: number } | { type: 'CLONE_WINDOW', windowId: number }
| { type: 'MAXIMIZE_WINDOW', windowId: number } | { type: 'MAXIMIZE_WINDOW', windowId: number }

View File

@ -63,7 +63,14 @@ function init() {
) => { ) => {
const state = store.getState(); const state = store.getState();
const { nameRegExp } = state.user; const { nameRegExp } = state.user;
const isRead = Object.values(state.windows.args).find(((args) => args.chatChannel === channelId));
// assume that if one chat window is not hidden, all are
let isRead = state.windows.showWindows
&& state.windows.windows.find((win) => win.windowType === 'CHAT' && win.hidden === false)
&& Object.values(state.windows.args).find((args) => args.chatChannel === channelId);
isRead = isRead || state.windows.modal.open
&& state.windows.args[0].chatChannel === channelId;
const isPing = (nameRegExp && text.match(nameRegExp)); const isPing = (nameRegExp && text.match(nameRegExp));
store.dispatch(receiveChatMessage( store.dispatch(receiveChatMessage(
name, name,

View File

@ -6,31 +6,51 @@
import React, { import React, {
useState, useEffect, useState, useEffect,
} from 'react'; } from 'react';
import { connect } from 'react-redux'; import { useSelector, useDispatch, shallowEqual } from 'react-redux';
import { MdForum } from 'react-icons/md'; import { MdForum } from 'react-icons/md';
import { t } from 'ttag'; import { t } from 'ttag';
import { openChatWindow } from '../actions'; import {
hideAllWindowTypes,
openChatWindow,
} from '../actions';
/*
* return [ showWindows, chatOpen, chatHiden ]
* showWindows: if windows are enabled (false on small screens)
* chatOpen: if any chat window or modal is open
* chatHidden: if any chat windows are hidden
*/
const selectChatWindowStatus = (state) => [
state.windows.showWindows,
state.windows.windows.find((win) => win.windowType === 'CHAT' && win.hidden === false) || (
state.windows.modal.open
&& state.windows.modal.windowType === 'CHAT'
),
state.windows.windows.find((win) => win.windowType === 'CHAT' && win.hidden === true),
];
const ChatButton = ({ const ChatButton = () => {
chatNotify,
channels,
unread,
mute,
open,
}) => {
const [unreadAny, setUnreadAny] = useState(false); const [unreadAny, setUnreadAny] = useState(false);
// TODO what do here?
const chatOpen = false; const dispatch = useDispatch();
const modalOpen = false;
const [showWindows, chatOpen, chatHidden] = useSelector(
selectChatWindowStatus,
shallowEqual,
);
const chatNotify = useSelector((state) => state.audio.chatNotify);
const channels = useSelector((state) => state.chat.channels);
const [unread, mute] = useSelector((state) => [state.chatRead.unread, state.chatRead.mute],
shallowEqual);
/* /*
* almost the same as in ChannelDropDown * almost the same as in ChannelDropDown
* just cares about chatNotify too * just cares about chatNotify too
*/ */
useEffect(() => { useEffect(() => {
if (!chatNotify || modalOpen || chatOpen) { if (!chatNotify || chatOpen) {
setUnreadAny(false); setUnreadAny(false);
return; return;
} }
@ -57,7 +77,15 @@ const ChatButton = ({
<div <div
id="chatbutton" id="chatbutton"
className="actionbuttons" className="actionbuttons"
onClick={open} onClick={() => {
if (chatOpen) {
dispatch(hideAllWindowTypes('CHAT', true));
} else if (chatHidden && showWindows) {
dispatch(hideAllWindowTypes('CHAT', false));
} else {
dispatch(openChatWindow());
}
}}
role="button" role="button"
title={(chatOpen) ? t`Close Chat` : t`Open Chat`} title={(chatOpen) ? t`Close Chat` : t`Open Chat`}
tabIndex={0} tabIndex={0}
@ -78,31 +106,4 @@ const ChatButton = ({
); );
}; };
function mapDispatchToProps(dispatch) { export default React.memo(ChatButton);
return {
open() {
// dispatch(showChatModal(false));
dispatch(openChatWindow());
},
};
}
function mapStateToProps(state) {
const {
chatNotify,
} = state.audio;
const {
channels,
} = state.chat;
const {
unread,
mute,
} = state.chatRead;
return {
channels,
unread,
mute,
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ChatButton);

View File

@ -13,6 +13,7 @@ import { t } from 'ttag';
import { import {
closeWindow, closeWindow,
restoreWindow, restoreWindow,
removeWindow,
} from '../actions'; } from '../actions';
import COMPONENTS from './windows'; import COMPONENTS from './windows';
@ -22,11 +23,15 @@ const ModalRoot = () => {
const { windowType, open, title } = useSelector( const { windowType, open, title } = useSelector(
(state) => state.windows.modal, (state) => state.windows.modal,
); );
const showWindows = useSelector((state) => state.windows.showWindows);
const dispatch = useDispatch(); const dispatch = useDispatch();
const onTransitionEnd = () => { const onTransitionEnd = () => {
if (!open) setRender(false); if (!open) {
setRender(false);
dispatch(removeWindow(0));
}
}; };
useEffect(() => { useEffect(() => {
@ -64,6 +69,7 @@ const ModalRoot = () => {
title={t`Close`} title={t`Close`}
tabIndex={-1} tabIndex={-1}
><MdClose /></div> ><MdClose /></div>
{(showWindows) && (
<div <div
onClick={() => dispatch(restoreWindow())} onClick={() => dispatch(restoreWindow())}
className="ModalRestore" className="ModalRestore"
@ -72,6 +78,7 @@ const ModalRoot = () => {
title={t`Restore`} title={t`Restore`}
tabIndex={-1} tabIndex={-1}
></div> ></div>
)}
<div className="Modal-content"> <div className="Modal-content">
<Content windowId={0} /> <Content windowId={0} />
</div> </div>

View File

@ -3,11 +3,14 @@
* @flow * @flow
*/ */
import React, { useCallback, useRef } from 'react'; import React, {
useState, useCallback, useRef, useEffect,
} from 'react';
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import { import {
moveWindow, moveWindow,
removeWindow,
resizeWindow, resizeWindow,
closeWindow, closeWindow,
maximizeWindow, maximizeWindow,
@ -20,6 +23,8 @@ import COMPONENTS from './windows';
const selectWindowById = (state, windowId) => state.windows.windows.find((win) => win.windowId === windowId); const selectWindowById = (state, windowId) => state.windows.windows.find((win) => win.windowId === windowId);
const Window = ({ id }) => { const Window = ({ id }) => {
const [render, setRender] = useState(false);
const titleBarRef = useRef(null); const titleBarRef = useRef(null);
const resizeRef = useRef(null); const resizeRef = useRef(null);
@ -28,6 +33,18 @@ const Window = ({ id }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const focus = useCallback(() => dispatch(focusWindow(id)), []); const focus = useCallback(() => dispatch(focusWindow(id)), []);
const clone = (evt) => {
evt.stopPropagation();
dispatch(cloneWindow(id));
};
const maximize = (evt) => {
evt.stopPropagation();
dispatch(maximizeWindow(id));
};
const close = (evt) => {
evt.stopPropagation();
dispatch(closeWindow(id));
};
useDrag( useDrag(
titleBarRef, titleBarRef,
@ -41,39 +58,34 @@ const Window = ({ id }) => {
useCallback((xDiff, yDiff) => dispatch(resizeWindow(id, xDiff, yDiff)), []), useCallback((xDiff, yDiff) => dispatch(resizeWindow(id, xDiff, yDiff)), []),
); );
const clone = (evt) => {
evt.stopPropagation();
dispatch(cloneWindow(id));
};
const maximize = (evt) => {
evt.stopPropagation();
dispatch(maximizeWindow(id));
};
const close = (evt) => {
evt.stopPropagation();
dispatch(closeWindow(id));
};
if (!win) {
return null;
}
const { const {
width, height, width, height,
xPos, yPos, xPos, yPos,
windowType, windowType,
title, title,
open,
} = win; } = win;
const onTransitionEnd = () => {
if (!open) {
dispatch(removeWindow(id));
}
};
useEffect(() => {
window.setTimeout(() => {
if (open) setRender(true);
}, 10);
}, [open]);
const Content = COMPONENTS[windowType]; const Content = COMPONENTS[windowType];
console.log(`render window ${id}`); console.log(`render window ${id}`);
return ( return (
<div <div
className={`window ${windowType}`} className={`window ${windowType}${(open && render) ? ' show' : ''}`}
onTransitionEnd={onTransitionEnd}
onClick={focus} onClick={focus}
style={{ style={{
left: xPos, left: xPos,

View File

@ -12,6 +12,9 @@ const selectWindowIds = (state) => state.windows.windows.map((win) => win.window
const WindowsRoot = () => { const WindowsRoot = () => {
const windowIds = useSelector(selectWindowIds, shallowEqual); const windowIds = useSelector(selectWindowIds, shallowEqual);
const showWindows = useSelector((state) => state.windows.showWindows);
if (!showWindows) return null;
return windowIds.map((id) => ( return windowIds.map((id) => (
<Window key={id} id={id} /> <Window key={id} id={id} />

View File

@ -12,6 +12,9 @@ const SCREEN_MARGIN_S = 30;
const SCREEN_MARGIN_EW = 70; const SCREEN_MARGIN_EW = 70;
const MIN_WIDTH = 70; const MIN_WIDTH = 70;
const MIN_HEIGHT = 50; const MIN_HEIGHT = 50;
// if screen smaller than this, hide all windows and just
// allow Modals
const SCREEN_WIDTH_THRESHOLD = 604;
function generateWindowId(state) { function generateWindowId(state) {
let windowId = Math.floor(Math.random() * 99999) + 1; let windowId = Math.floor(Math.random() * 99999) + 1;
@ -24,6 +27,7 @@ function generateWindowId(state) {
export type WindowsState = { export type WindowsState = {
// modal is considerd as "fullscreen window" // modal is considerd as "fullscreen window"
// its windowId is considered 0 and args are under args[0] // its windowId is considered 0 and args are under args[0]
showWindows: boolean,
modal: { modal: {
windowType: ?string, windowType: ?string,
title: ?string, title: ?string,
@ -32,7 +36,8 @@ export type WindowsState = {
// [ // [
// { // {
// windowId: number, // windowId: number,
// windowOpen: boolean, // open: boolean,
// hidden: boolean,
// windowType: string, // windowType: string,
// title: string, // title: string,
// width: number, // width: number,
@ -52,6 +57,7 @@ export type WindowsState = {
} }
const initialState: WindowsState = { const initialState: WindowsState = {
showWindows: true,
modal: { modal: {
windowType: null, windowType: null,
title: null, title: null,
@ -70,10 +76,10 @@ export default function windows(
const { const {
windowType, windowType,
title, title,
fullscreen,
cloneable, cloneable,
args, args,
} = action; } = action;
const fullscreen = !state.showWindows || action.fullscreen;
if (fullscreen) { if (fullscreen) {
return { return {
...state, ...state,
@ -98,7 +104,8 @@ export default function windows(
{ {
windowId, windowId,
windowType, windowType,
windowOpen: true, open: true,
hidden: false,
title, title,
width: 600, width: 600,
height: 300, height: 300,
@ -152,25 +159,69 @@ export default function windows(
}, },
}; };
} }
/*
const newWindows = state.windows.map((win) => { const newWindows = state.windows.map((win) => {
if (win.windowId !== windowId) return win; if (win.windowId !== windowId) return win;
return {
...win,
windowOpen: false,
}
});
return { return {
...state, ...win,
windows: newWindows, open: false,
}; };
*/ });
const args = { ...state.args };
delete args[windowId];
return { return {
...state, ...state,
windows: state.windows.filter((win) => win.windowId !== windowId), windows: newWindows,
args, };
}
case 'CLOSE_ALL_WINDOW_TYPE': {
const {
windowType,
} = action;
const newWindows = state.windows.map((win) => {
if (win.windowType !== windowType) return win;
return {
...win,
open: false,
};
});
let { modal } = state;
if (modal.open && modal.windowType === windowType) {
modal = {
...modal,
open: false,
};
}
return {
...state,
modal,
windows: newWindows,
};
}
case 'HIDE_ALL_WINDOW_TYPE': {
const {
windowType,
hide,
} = action;
console.log(`hideAllWindowTypes`, windowType, hide);
const newWindows = state.windows.map((win) => {
if (win.windowType !== windowType) return win;
return {
...win,
hidden: hide,
};
});
let { modal } = state;
if (hide && modal.open && modal.windowType === windowType) {
modal = {
...modal,
open: false,
};
}
return {
...state,
modal,
windows: newWindows,
}; };
} }
@ -266,7 +317,8 @@ export default function windows(
{ {
windowType, windowType,
windowId, windowId,
windowOpen: true, open: true,
hidden: false,
title, title,
width: 600, width: 600,
height: 300, height: 300,
@ -301,7 +353,7 @@ export default function windows(
xPos: clamp( xPos: clamp(
win.xPos + xDiff, win.xPos + xDiff,
-win.width + SCREEN_MARGIN_EW, -win.width + SCREEN_MARGIN_EW,
width - SCREEN_MARGIN_S, width - SCREEN_MARGIN_EW,
), ),
yPos: clamp(win.yPos + yDiff, 0, height - SCREEN_MARGIN_S), yPos: clamp(win.yPos + yDiff, 0, height - SCREEN_MARGIN_S),
}; };
@ -340,11 +392,20 @@ export default function windows(
}; };
} }
case 'RECEIVE_ME':
case 'WINDOW_RESIZE': { case 'WINDOW_RESIZE': {
const { const {
width, width,
height, height,
} = action; } = action;
if (width <= SCREEN_WIDTH_THRESHOLD) {
return {
...state,
showWindows: false,
};
}
const xMax = width - SCREEN_MARGIN_EW; const xMax = width - SCREEN_MARGIN_EW;
const yMax = height - SCREEN_MARGIN_S; const yMax = height - SCREEN_MARGIN_S;
let modified = false; let modified = false;
@ -373,18 +434,13 @@ export default function windows(
} }
} }
if (!modified) return state;
return { return {
...state, ...state,
windows: newWindows, showWindows: true,
windows: (modified) ? newWindows : state.windows,
}; };
} }
case 'CLOSE_ALL_WINDOWS': {
return initialState;
}
/* /*
* args specific actions * args specific actions
*/ */

View File

@ -127,20 +127,6 @@ tr:nth-child(even) {
background-color: #dddddd; background-color: #dddddd;
} }
.chatbox {
position: fixed;
background-color: rgba(226, 226, 226, 0.92);
border: solid black;
border-width: thin;
width: 0px;
height: 36px;
bottom: 16px;
right: 98px;
visibility: hidden;
overflow: hidden;
transition: 0.3s;
}
.window { .window {
position: fixed; position: fixed;
background-color: rgba(226, 226, 226, 0.92); background-color: rgba(226, 226, 226, 0.92);
@ -149,6 +135,8 @@ tr:nth-child(even) {
overflow: hidden; overflow: hidden;
padding: 3px; padding: 3px;
z-index: 3; z-index: 3;
transition: opacity 200ms ease-in-out;
opacity: 0;
} }
.win-topbar { .win-topbar {
@ -182,7 +170,7 @@ tr:nth-child(even) {
.win-resize { .win-resize {
position: absolute; position: absolute;
bottom: -3px; bottom: -4px;
right: -3px; right: -3px;
font-size: 22px; font-size: 22px;
cursor: se-resize; cursor: se-resize;
@ -227,14 +215,6 @@ tr:nth-child(even) {
cursor: pointer; cursor: pointer;
} }
.chatbox.show {
height: 200px;
width: 350px;
bottom: 16px;
right: 98px;
visibility: visible;
}
.channelbtn { .channelbtn {
position: relative; position: relative;
background-color: #ebebeb; background-color: #ebebeb;
@ -397,6 +377,7 @@ tr:nth-child(even) {
top: 50%; top: 50%;
left: 50%; left: 50%;
right: auto; right: auto;
padding: 0px 5px 5px 5px;
bottom: auto; bottom: auto;
border: 1px solid rgb(204, 204, 204); border: 1px solid rgb(204, 204, 204);
background: rgb(255, 255, 255) none repeat scroll 0% 0%; background: rgb(255, 255, 255) none repeat scroll 0% 0%;
@ -802,6 +783,6 @@ tr:nth-child(even) {
visibility: hidden; visibility: hidden;
} }
.Modal.show, .Alert.show, .OverlayAlert.show, .OverlayModal.show { .Modal.show, .Alert.show, .OverlayAlert.show, .OverlayModal.show, .window.show {
opacity: 1; opacity: 1;
} }