Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@
},
"scripts": {
"clean": "rm -rf public/assets public/index.html",
"clean-w": "rmdir /s /q public\\assets && del /f /q public\\index.html",
"build": "npm run clean && npm run build:bundle",
"build-w": "npm run clean-w && npm run build:bundle",
"build:bundle": "webpack --progress",
"build:production": "npm run --prod build",
"dev-server": "npm run build && webpack-dev-server --inline --hot",
"dev-server-w": "npm run build-w && webpack-dev-server --inline --hot",
"flow": "flow check",
"lint": "eslint webpack.config.js --ext .js --ext .jsx src"
},
Expand Down
1 change: 1 addition & 0 deletions src/INITIAL_STATE.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default {
chatSize: localStorage ? Number(localStorage.getItem('chatSize')) || 400 : 400,
showChat: localStorage ? !(localStorage.getItem('showChat') === 'false') : true,
showHeader: true,
imageModalSrc: null,
showFooter: true,
},
self: {
Expand Down
8 changes: 8 additions & 0 deletions src/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,14 @@ export const showHeader = value => dispatch => {
});
};

export const IMAGE_MODAL_SRC = Symbol('IMAGE_MODAL_SRC');
export const imageModalSrc = value => dispatch => {
dispatch({
type: IMAGE_MODAL_SRC,
payload: value,
});
};

export const SHOW_FOOTER = Symbol('SHOW_FOOTER');
export const showFooter = value => dispatch => {
dispatch({
Expand Down
14 changes: 13 additions & 1 deletion src/client.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,20 @@ import { render } from 'react-dom';
import App from './components/App';
import store from './store';


const mountPoint = document.getElementById('main');

if (process.env.NODE_ENV !== 'production') {
// used for testing redux dispatch events in console, since we're using symbols
// they need to be attached to the window for them to match
// otherwise it goes into a catchall(default)
// you can now dispatch events using similar structure to line below
// window.store.dispatch({type:window.types.IMAGE_MODAL_SRC, payload:"url"})
import("./actions/index").then((types) => {
window.store = store;
window.types = types;
});
}

render(
<App store={store} />,
mountPoint,
Expand Down
82 changes: 82 additions & 0 deletions src/components/ImagePreviewModal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React from "react";
import cs from "classnames";

import "../css/ImageModal";
import { connect } from "react-redux";
import { imageModalSrc } from "../actions";

const ImageModal = ({ dispatch, src, onClose }) => {
const modalCopyButton = React.useRef();

const copyToClipboard = () => {
modalCopyButton.current.classList.remove("copySuccessful");

requestAnimationFrame(() => {
requestAnimationFrame(() => {
modalCopyButton.current.classList.add("copySuccessful");
});
});

const url = src;
navigator.clipboard.writeText(url);
};

return (
<div
className="image-modal"
tabIndex={-1}
onClick={onClose}
onKeyDown={(e) => {
if (e.key === "Escape") dispatch(imageModalSrc(null));
}}
>
<div
className="image-modal__backdrop"
onClick={() => {
dispatch(imageModalSrc(null));
}}
></div>

<div
className="image-modal__content"
onClick={(e) => e.stopPropagation()}
>
<div className="image-modal__toolbar">
<button
className="image-modal__btn button-orange"
tabIndex={0}
onClick={() => {
window.open(src, "_blank");
}}
>
<span className="glyphicon glyphicon-new-window" />
</button>
<button
ref={modalCopyButton}
className="image-modal__btn button-orange"
tabIndex={0}
onClick={() => copyToClipboard()}
>
<span className="glyphicon glyphicon-duplicate" />
</button>

<button
className="image-modal__btn image-modal__btn--close button-orange"
aria-label="Close"
tabIndex={0}
autoFocus
onClick={() => {
dispatch(imageModalSrc(null));
}}
>
<span className="glyphicon glyphicon-remove" />
</button>
</div>

<img src={src} alt="" />
</div>
</div>
);
};

export default connect()(ImageModal);
11 changes: 10 additions & 1 deletion src/components/RoutesWithChat.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Header from './Header';
import Footer from './Footer';

import CustomScrollbar from './CustomScrollbar';
import ImageModal from './ImagePreviewModal';

import '../css/Stream';

Expand All @@ -22,7 +23,7 @@ import {
showHeader as headerFunc
} from '../actions';

export const RoutesWithChat = ({showHeader, showFooter, setChatSize, showChat, showLeftChat=false, chatClosed, chatSize, headerFunc}) =>
export const RoutesWithChat = ({showHeader, showFooter, setChatSize, showChat, showLeftChat=false, chatClosed, chatSize, headerFunc, imageModalSrc}) =>
{
let left = (
<div className='flex-shrink-0 stream-embed' style={{ width: chatClosed ? '100%' : `calc(100% - ${chatSize}px)`, height: chatClosed ? '100%' : '', display: 'flex', flexDirection: 'column'}}>
Expand Down Expand Up @@ -53,6 +54,11 @@ export const RoutesWithChat = ({showHeader, showFooter, setChatSize, showChat, s
<div title={showHeader ? "Close Header" : "Open Header"} className= {showHeader ?"close-header-btn" : 'open-header-btn'}>
<span className={showHeader ? 'close-header-caret': 'open-header-caret'} onClick={() => showHeader ? headerFunc(false) : headerFunc(true)}>&#8250;</span>
</div>
{imageModalSrc && (
<ImageModal
src={imageModalSrc}
/>
)}
<Resizeable
className='flex-grow-1 flex-column flex-lg-row'
onResize={e => {
Expand Down Expand Up @@ -80,6 +86,8 @@ RoutesWithChat.propTypes = {
showLeftChat: PropTypes.bool,
chatClosed: PropTypes.bool,

imageModalSrc: PropTypes.string,

chatSize: PropTypes.number.isRequired,

setChatSize: PropTypes.func.isRequired,
Expand All @@ -96,6 +104,7 @@ export default compose(
showLeftChat: idx(state, _ => _.self.profile.data.left_chat),
chatClosed: !state.ui.showChat || !state.self.isLoggedIn,
headerClosed: !state.ui.showHeader,
imageModalSrc: state.ui.imageModalSrc,
}),
{
setChatSize,
Expand Down
66 changes: 66 additions & 0 deletions src/css/ImageModal.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// modal when image is opened
.image-modal {
position: fixed;
inset: 0;
z-index: 9999;
}

.image-modal__backdrop {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.8);
}

.image-modal__content {
height: 100%;
place-items: end;
margin-left: auto;
margin-right: auto;
display: flex;
justify-content: center;
flex-flow: column;
width: max-content;
z-index: 100;
position: relative;
}

.image-modal__btn {
margin-left: 15px;
cursor: pointer;
&.copySuccessful {
animation: greenFade 1000ms 1;
&:hover {
color: darken(#d17134, 20%);
}
}
}

@keyframes greenFade {
0% {
color: green;
&:hover {
color: green;
}
}
100% {
color: #d17134;
&:hover {
color: darken(#d17134, 20%);
}
}
}

.image-modal__toolbar {
margin-bottom: 10px;
button {
span {
font-size: 25px;
}
}
}

.image-modal img {
max-width: 90vw;
max-height: 90vh;
user-select: none;
}
9 changes: 9 additions & 0 deletions src/css/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ a {
}
}

.button-orange{
border: none;
background-color: transparent;
color: $rustle-orange;
&:hover, &:focus {
color: darken($rustle-orange, 20%);
}
}

.form-control, .form-control, .btn-group > .navbar-btn, .input-group-btn > .btn {
background-color: #222;
color: #fff;
Expand Down
6 changes: 6 additions & 0 deletions src/reducers/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
SHOW_CHAT,
SHOW_HEADER,
SHOW_FOOTER,
IMAGE_MODAL_SRC,
} from '../actions';
import { actions } from '../actions/websocket';

Expand Down Expand Up @@ -38,6 +39,11 @@ function uiReducer(state = INITIAL_STATE.ui, action) {
...state,
showHeader: action.payload,
};
case IMAGE_MODAL_SRC:
return {
...state,
imageModalSrc: action.payload,
};
case SHOW_FOOTER:
return {
...state,
Expand Down
1 change: 1 addition & 0 deletions src/redux/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,6 @@ export type State = {|
+showChat: boolean,
+showHeader: boolean,
+showFooter: boolean,
+imageModalSrc: string,
|}
|};
Loading