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 {
return {
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 {
return openWindow('CHAT', t`Chat`, false, true,
{ chatChannel: 1, inputMessage: '' });

View File

@ -85,6 +85,9 @@ export type Action =
args: Object,
}
| { 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: 'CLONE_WINDOW', windowId: number }
| { type: 'MAXIMIZE_WINDOW', windowId: number }

View File

@ -63,7 +63,14 @@ function init() {
) => {
const state = store.getState();
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));
store.dispatch(receiveChatMessage(
name,

View File

@ -6,31 +6,51 @@
import React, {
useState, useEffect,
} from 'react';
import { connect } from 'react-redux';
import { useSelector, useDispatch, shallowEqual } from 'react-redux';
import { MdForum } from 'react-icons/md';
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 = ({
chatNotify,
channels,
unread,
mute,
open,
}) => {
const ChatButton = () => {
const [unreadAny, setUnreadAny] = useState(false);
// TODO what do here?
const chatOpen = false;
const modalOpen = false;
const dispatch = useDispatch();
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
* just cares about chatNotify too
*/
useEffect(() => {
if (!chatNotify || modalOpen || chatOpen) {
if (!chatNotify || chatOpen) {
setUnreadAny(false);
return;
}
@ -57,7 +77,15 @@ const ChatButton = ({
<div
id="chatbutton"
className="actionbuttons"
onClick={open}
onClick={() => {
if (chatOpen) {
dispatch(hideAllWindowTypes('CHAT', true));
} else if (chatHidden && showWindows) {
dispatch(hideAllWindowTypes('CHAT', false));
} else {
dispatch(openChatWindow());
}
}}
role="button"
title={(chatOpen) ? t`Close Chat` : t`Open Chat`}
tabIndex={0}
@ -78,31 +106,4 @@ const ChatButton = ({
);
};
function mapDispatchToProps(dispatch) {
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);
export default React.memo(ChatButton);

View File

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

View File

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

View File

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

View File

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

View File

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