diff --git a/src/actions/index.js b/src/actions/index.js
index d46ab22..e58d53c 100644
--- a/src/actions/index.js
+++ b/src/actions/index.js
@@ -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: '' });
diff --git a/src/actions/types.js b/src/actions/types.js
index efb9b93..6c066de 100644
--- a/src/actions/types.js
+++ b/src/actions/types.js
@@ -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 }
diff --git a/src/client.js b/src/client.js
index a380699..eab7b36 100644
--- a/src/client.js
+++ b/src/client.js
@@ -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,
diff --git a/src/components/ChatButton.jsx b/src/components/ChatButton.jsx
index 0971b8a..dda5356 100644
--- a/src/components/ChatButton.jsx
+++ b/src/components/ChatButton.jsx
@@ -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 = ({
{
+ 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);
diff --git a/src/components/ModalRoot.jsx b/src/components/ModalRoot.jsx
index 49e70f1..9fc1f31 100644
--- a/src/components/ModalRoot.jsx
+++ b/src/components/ModalRoot.jsx
@@ -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}
>
+ {(showWindows) && (
dispatch(restoreWindow())}
className="ModalRestore"
@@ -72,6 +78,7 @@ const ModalRoot = () => {
title={t`Restore`}
tabIndex={-1}
>↓
+ )}
diff --git a/src/components/Window.jsx b/src/components/Window.jsx
index 552e677..729b34f 100644
--- a/src/components/Window.jsx
+++ b/src/components/Window.jsx
@@ -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 (
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) => (
diff --git a/src/reducers/windows.js b/src/reducers/windows.js
index 3bd2dc0..78adc2c 100644
--- a/src/reducers/windows.js
+++ b/src/reducers/windows.js
@@ -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
*/
diff --git a/src/styles/default.css b/src/styles/default.css
index 2a6d1c2..d4f58eb 100644
--- a/src/styles/default.css
+++ b/src/styles/default.css
@@ -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;
}