make window close transition
This commit is contained in:
parent
1c486f3105
commit
8f0ec19999
|
@ -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: '' });
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user