Skip to content

Commit

Permalink
feat(WindowManager): Add ability to theme the app window with manifes…
Browse files Browse the repository at this point in the history
…t.json
  • Loading branch information
FranklinWaller committed Oct 16, 2019
1 parent f87155d commit 25f252b
Show file tree
Hide file tree
Showing 16 changed files with 246 additions and 71 deletions.
2 changes: 1 addition & 1 deletion src/js/components/atoms/Time/Time.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class Time extends React.Component {
const className = this.props.className ? this.props.className : styles.Time;

return (
<div className={className}>
<div className={className} style={this.props.style}>
{this.state.timeString}
</div>
);
Expand Down
42 changes: 6 additions & 36 deletions src/js/components/molecules/SideNavigation/SideNavigation.scss
Original file line number Diff line number Diff line change
@@ -1,43 +1,13 @@
:local .drawer {
& > div {
& span {
color: white!important;

&:hover {
background: linear-gradient(to right, rgb(0, 201, 253) 0%, rgb(129, 238, 142) 100%) 0px 0px repeat scroll rgba(0, 0, 0, 0)!important;
}
}
.drawerList {
width: 250px;
}

&:last-child {
background-color: #333333!important;
}
}
.drawer {
// background-color: #333333;
}

:local .profilePic {
.profilePic {
position: absolute;
top:4px;
right: 20px;
}

// Desktop only styles.
@media screen and (min-width: 767px) {
:local .drawer {
// The background for the drawer
& > div:first-child {
background: none!important;
}

// The Drawer
& > div:last-child {
// height: calc(100% - #{$header-height}) !important;
// margin-top: $header-height;
border-left: 1px solid rgba(255, 255, 255, 0.28);

// MenuItem
& > div:first-child {
display: none;
}
}
}
}
54 changes: 54 additions & 0 deletions src/js/components/molecules/SideNavigation/SideNavigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import * as React from 'react';
import { connect } from 'react-redux';
import Drawer from '@material-ui/core/Drawer';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import PowerSettingsNewIcon from '@material-ui/icons/PowerSettingsNew';
import GetAppIcon from '@material-ui/icons/GetApp';
import { setOpenSideBarNavigationState } from '../../../store/SideBarNavigationStore';
import UserService from '../../../services/UserService';
const styles = require('./SideNavigation.scss');

interface Props {
isOpen: boolean;
dispatch: Function;
}

function SideNavigation(props: Props) {
function handleDrawerOnClose() {
props.dispatch(setOpenSideBarNavigationState(false));
}

async function handleLogoutClick() {
await UserService.logout();
location.reload();
}

return (
<Drawer className={styles.drawer} anchor="right" open={props.isOpen} onClose={handleDrawerOnClose}>
<List className={styles.drawerList}>
<ListItem button>
<ListItemIcon>
<GetAppIcon />
</ListItemIcon>
<ListItemText>Install App</ListItemText>
</ListItem>
<ListItem button onClick={handleLogoutClick}>
<ListItemIcon>
<PowerSettingsNewIcon />
</ListItemIcon>
<ListItemText>Logout</ListItemText>
</ListItem>
</List>
</Drawer>
);
}

const mapStateToProps = (state: any) => ({
user: state.UserInfoStore,
isOpen: state.SideBarNavigationStore.isOpen,
});

export default connect(mapStateToProps)(SideNavigation);
7 changes: 5 additions & 2 deletions src/js/components/molecules/new/App/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { connect } from 'react-redux';
import Typography from '@material-ui/core/Typography';
import Application from '../../../../models/Application';
import { openApp } from '../../../../store/AppProcessesStore';
import resolveUrl from '../../../../services/micro/resolveUrl';
const styles = require('./App.scss');

interface Props {
Expand All @@ -15,10 +16,12 @@ function App(props: Props) {
props.dispatch(openApp(props.app));
}

const highestResIcon = resolveUrl(props.app.manifest_url, props.app.icons[0].src);

return (
<button className={styles.App} onClick={handleAppClick}>
<img src={props.app.icon} className={styles.icon} />
<Typography noWrap variant="body1" className={styles.title}>{props.app.title}</Typography>
<img src={highestResIcon} className={styles.icon} />
<Typography noWrap variant="body1" className={styles.title}>{props.app.short_name}</Typography>
</button>
);
}
Expand Down
13 changes: 10 additions & 3 deletions src/js/components/organims/AppProcessesHolder/AppCanvasHolder.scss
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
@import '../../../../scss/variables';

.processHolder {
width: 200%;
height: 200%;
width: 100%;
height: 100%;
position: absolute;
top: $header-height;
top: 0;
z-index: 100;
pointer-events: none;
}

@media screen and (min-width: $desktop-breakpoint) {
.processHolder {
top: $header-height;
}
}
22 changes: 22 additions & 0 deletions src/js/components/organims/AppWindow/AppTitleBar.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@import '../../../../scss/variables';

$appbar-height: 35px;
$window-border-radius: 5px;

.appBar {
width: 100%;
height: $appbar-height;
background: #f6f6f6;
color: #3c3c3c;
display: flex;
align-items: center;
justify-content: space-between;
}

@media screen and (min-width: $desktop-breakpoint) {
.appBar {
border-top-left-radius: $window-border-radius;
border-top-right-radius: $window-border-radius;
}
}

48 changes: 48 additions & 0 deletions src/js/components/organims/AppWindow/AppTitleBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as React from 'react';
import IconButton from '@material-ui/core/IconButton';
import CloseIcon from '@material-ui/icons/Close';
import ArrowBackIcon from '@material-ui/icons/ArrowBack';
import { connect } from 'react-redux';
import { Process, killProcess, closeApp } from '../../../store/AppProcessesStore';
import getIdealTextColor from '../../../services/micro/getIdealTextColor';
import useMedia from '../../../services/hooks/useMedia';
import Time from '../../atoms/Time';
const styles = require('./AppTitleBar.scss');

interface Props {
title: string;
process: Process;
dispatch: Function;
}

function AppTitleBar(props: Props) {
const isDesktop = useMedia('(min-width: 960px)');

function handleCloseClick() {
props.dispatch(killProcess(props.process.id));
}

const textColor = getIdealTextColor(props.process.app.background_color);

return (
<header className={styles.appBar} style={{ backgroundColor: props.process.app.background_color }}>
<IconButton aria-label="Back" style={{ color: textColor }}>
<ArrowBackIcon />
</IconButton>
{isDesktop && <span style={{ color: textColor }}>{props.process.app.short_name}</span>}
{!isDesktop && <Time style={{ color: textColor }} />}
<IconButton aria-label="Close" onClick={handleCloseClick} style={{ color: textColor }}>
<CloseIcon />
</IconButton>
</header>
);
}

const mapStateToProps = (store: any) => {
return {
a: 1,
}
}

// @ts-ignore
export default connect(mapStateToProps)(AppTitleBar);
10 changes: 10 additions & 0 deletions src/js/components/organims/AppWindow/AppWindow.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ $appbar-height: 35px;
pointer-events: all;
}

.rndMobile {
width: 100%!important;
height: 100%!important;
transform: translate(0, 0)!important;
}

.window {
width: 100%;
height: 100%;
Expand Down Expand Up @@ -41,6 +47,10 @@ $appbar-height: 35px;

.appBody {
height: calc(100% - #{$appbar-height});

&.dragging {
pointer-events: none;
}
}

.iframe {
Expand Down
55 changes: 45 additions & 10 deletions src/js/components/organims/AppWindow/AppWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { connect } from 'react-redux';
import classnames from 'classnames';
import { Process, AppProcessesState, setHeighestZIndex } from '../../../store/AppProcessesStore';
import { Rnd } from 'react-rnd';
import useMedia from '../../../services/hooks/useMedia';
import AppTitleBar from './AppTitleBar';
import resolveUrl from '../../../services/micro/resolveUrl';
const styles = require('./AppWindow.scss');
const titleBarStyles = require('./AppTitleBar.scss');

interface Props {
process: Process;
Expand All @@ -17,7 +21,9 @@ function AppWindow(props: Props) {
full: false,
});

const isDesktop = useMedia('(min-width: 960px)');
const [windowZIndex, setWindowZIndex] = React.useState(0);
const [isDragging, setIsDragging] = React.useState(false);

function handleOnDrag(event: MouseEvent, data: any) {
// We are dragging the window so we have to make sure we are almost touching the edges
Expand All @@ -40,7 +46,16 @@ function AppWindow(props: Props) {
}
}

function handleWindowMouseDown() {
function handleDragStart() {
handleWindowClick();
setIsDragging(true);
}

function handleDragStop() {
setIsDragging(false);
}

function handleWindowClick() {
if (windowZIndex < props.appProcessesState.heighestZIndex) {
const newHeighestZIndex = props.appProcessesState.heighestZIndex + 1;

Expand All @@ -54,6 +69,16 @@ function AppWindow(props: Props) {
[styles.fullSnapped]: snapState.full,
});

const rndClassNames = classnames(styles.rnd, {
[styles.rndMobile]: !isDesktop,
});

const appBodyClassNames = classnames(styles.appBody, {
[styles.dragging]: isDragging,
});

const mainUrl = resolveUrl(props.process.app.manifest_url, props.process.app.start_url);

return (
<Rnd
default={{
Expand All @@ -64,19 +89,29 @@ function AppWindow(props: Props) {
}}
minWidth={300}
minHeight={300}
dragHandleClassName={styles.appBar}
dragHandleClassName={titleBarStyles.appBar}
bounds='parent'
className={styles.rnd}
className={rndClassNames}
style={{ zIndex: windowZIndex }}
onDragStart={handleWindowMouseDown}
onDragStart={handleDragStart}
onDragStop={handleDragStop}
disableDragging={!isDesktop}
enableResizing={{
bottom: isDesktop,
bottomLeft: isDesktop,
bottomRight: isDesktop,
left: isDesktop,
right: isDesktop,
top: isDesktop,
topLeft: isDesktop,
topRight: isDesktop,
}}
>
<div className={windowClassNames} onClick={handleWindowMouseDown}>
<header className={styles.appBar}>
<span>{props.process.app.title}</span>
</header>
<div className={styles.appBody}>
<div className={windowClassNames} onClick={handleWindowClick}>
<AppTitleBar process={props.process} />
<div className={appBodyClassNames}>
{/* My new app */}
<iframe src={props.process.app.main} className={styles.iframe}>
<iframe src={mainUrl} className={styles.iframe} onFocus={() => console.log('Iframe')}>
Content could not be loaded
</iframe>
</div>
Expand Down
19 changes: 16 additions & 3 deletions src/js/models/Application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,24 @@ export enum ApplicationStatus {
STANDARD = 'STANDARD',
}

interface ApplicationIcon {
src: string;
type: string;
sizes: string;
}

interface Application {
id: string,
title: string,
main: string,
icon: string,
name: string,
short_name: string;
icons: ApplicationIcon[];
start_url: string;
scope: string;
display: string;
background_color: string;
theme_color: string;
manifest_url: string;

properties?: ApplicationProperties,
status: ApplicationStatus,
isFolder?: boolean,
Expand Down
2 changes: 1 addition & 1 deletion src/js/services/hooks/useMedia.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState, useEffect } from "react";

export default function useMedia(query: string) {
export default function useMedia(query: string): boolean {
const [matches, setMatches] = useState(false);

useEffect(() => {
Expand Down
3 changes: 3 additions & 0 deletions src/js/services/micro/resolveUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function resolveUrl(url: string, relativeUrl: string) {
return new URL(relativeUrl, url).href;
}
Loading

0 comments on commit 25f252b

Please sign in to comment.