[UNSTABLE] welp, navbars gone
parent
f1e29dc39f
commit
26ccce2dab
|
@ -1,5 +1,9 @@
|
|||
/* Styles used throughout the frontend */
|
||||
|
||||
body {
|
||||
background: $gray;
|
||||
}
|
||||
|
||||
.ticker-list::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
@ -27,9 +31,7 @@ input[type=time] {
|
|||
}
|
||||
|
||||
.all-content-wrapper {
|
||||
padding: 3rem 3rem 0rem 3rem;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
max-width: 160rem;
|
||||
animation: page-transition 0.2s ease-in-out;
|
||||
|
|
|
@ -1,151 +1,8 @@
|
|||
.nav-wrapper {
|
||||
box-shadow: 0 4px 10px $translucent;
|
||||
background: $dark_gray;
|
||||
background: palegoldenrod;
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: $dark_gray;
|
||||
color: $light_gray;
|
||||
font-size: 1.2rem;
|
||||
letter-spacing: .15rem;
|
||||
text-transform: uppercase;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1rem 2rem 0.6rem;
|
||||
max-width: 140rem;
|
||||
margin: 3rem auto 0;
|
||||
.nav-sync {
|
||||
margin-right: 2rem !important;
|
||||
}
|
||||
.mobile-menu-extras {
|
||||
display: none;
|
||||
}
|
||||
.mobile-only {
|
||||
display: none;
|
||||
}
|
||||
.page-name {
|
||||
display: none;
|
||||
}
|
||||
.right-nav-content {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.links {
|
||||
.fa {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
ul {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
button {
|
||||
margin: 0 !important;
|
||||
font-size: 1.2rem !important;
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
a {
|
||||
white-space: nowrap;
|
||||
padding: 1.8rem 1rem 2rem;
|
||||
color: $gray !important;
|
||||
position: relative;
|
||||
transition: all 0.6s ease;
|
||||
&:hover {
|
||||
color: $white !important;
|
||||
font-weight: bold;
|
||||
transition: all 0.6s ease;
|
||||
}
|
||||
&.active {
|
||||
color: $white !important;
|
||||
font-weight: bold;
|
||||
transition: all 0.4s ease;
|
||||
&:before {
|
||||
content: "";
|
||||
background: $dark_gray;
|
||||
position: absolute;
|
||||
bottom: -3px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
z-index: 1;
|
||||
animation: slide-up 0.4s ease forwards;
|
||||
}
|
||||
&:after {
|
||||
content: "";
|
||||
background: $white;
|
||||
position: absolute;
|
||||
bottom: -3px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.nav-dropdown {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
padding: 1.4rem;
|
||||
&:hover,
|
||||
&:active {
|
||||
.nav-dropdown-content {
|
||||
opacity: 1;
|
||||
transition: all 0.2s ease-in-out;
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
.name {
|
||||
white-space: nowrap !important;
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis !important;
|
||||
max-width: 16rem;
|
||||
display: inline-block;
|
||||
}
|
||||
.nav-dropdown-content {
|
||||
top: 4rem;
|
||||
transition: all 0.2s ease-in-out;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
|
||||
position: absolute;
|
||||
display: block;
|
||||
background: #434343;
|
||||
padding: 0 2.6rem 2.6rem;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
transition: all 0.2s ease-in-out;
|
||||
pointer-events: all;
|
||||
}
|
||||
i {
|
||||
padding-right: 1rem;
|
||||
}
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 20rem;
|
||||
li {
|
||||
padding: 1rem;
|
||||
width: 22rem;
|
||||
}
|
||||
}
|
||||
.version-links {
|
||||
position: absolute;
|
||||
font-size: 1rem;
|
||||
background: #000;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
text-align: center;
|
||||
padding: 0.8rem;
|
||||
a {
|
||||
text-decoration: underline !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
max-width: 160rem;
|
||||
padding: 1rem 4.4rem;
|
||||
}
|
|
@ -2,7 +2,7 @@ import * as React from "react";
|
|||
import { connect } from "react-redux";
|
||||
import { HardwareSettings } from "./components/hardware_settings";
|
||||
import { FarmbotOsSettings } from "./components/farmbot_os_settings";
|
||||
import { Page, Col } from "../ui/index";
|
||||
import { Page, Col, Row } from "../ui";
|
||||
import { mapStateToProps } from "./state_to_props";
|
||||
import { Props } from "./interfaces";
|
||||
|
||||
|
@ -11,22 +11,26 @@ export class Devices extends React.Component<Props, {}> {
|
|||
render() {
|
||||
if (this.props.auth) {
|
||||
return <Page className="devices">
|
||||
<Col xs={12} sm={6}>
|
||||
<FarmbotOsSettings
|
||||
account={this.props.deviceAccount}
|
||||
dispatch={this.props.dispatch}
|
||||
bot={this.props.bot}
|
||||
auth={this.props.auth} />
|
||||
</Col>
|
||||
<Col xs={12} sm={6}>
|
||||
<HardwareSettings
|
||||
controlPanelState={this.props.bot.controlPanelState}
|
||||
dispatch={this.props.dispatch}
|
||||
bot={this.props.bot} />
|
||||
</Col>
|
||||
<Row>
|
||||
<Col xs={12} sm={6}>
|
||||
<FarmbotOsSettings
|
||||
account={this.props.deviceAccount}
|
||||
dispatch={this.props.dispatch}
|
||||
bot={this.props.bot}
|
||||
auth={this.props.auth}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={12} sm={6}>
|
||||
<HardwareSettings
|
||||
controlPanelState={this.props.bot.controlPanelState}
|
||||
dispatch={this.props.dispatch}
|
||||
bot={this.props.bot}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Page>;
|
||||
} else {
|
||||
throw new Error("Log in first");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import * as React from "react";
|
||||
import { Link } from "react-router";
|
||||
import { AdditonalMenuProps } from "./interfaces";
|
||||
import { t } from "i18next";
|
||||
|
||||
export let AdditionalMenu = ({ user, onClick }: AdditonalMenuProps) => {
|
||||
if (!user) {
|
||||
return <span></span>;
|
||||
} else {
|
||||
let hasName = user && user.body.name;
|
||||
let firstName = hasName ? `${hasName.split(" ")[0]} ▾` : "▾";
|
||||
|
||||
return <div className="nav-dropdown">
|
||||
<div className="nav-dropdown-content">
|
||||
<ul>
|
||||
<li>
|
||||
<Link to="/app/account">
|
||||
<i className="fa fa-cog"></i>
|
||||
{t("Account Settings")}
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://software.farmbot.io/docs/the-farmbot-web-app"
|
||||
target="_blank">
|
||||
<i className="fa fa-file-text-o"></i>{t("Documentation")}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a onClick={onClick}>
|
||||
<i className="fa fa-sign-out"></i>
|
||||
{t("Logout")}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="version-links">
|
||||
<span>{t("Application")}:
|
||||
<a
|
||||
href="https://github.com/FarmBot/Farmbot-Web-API"
|
||||
target="_blank"
|
||||
>
|
||||
{(process.env.SHORT_REVISION || "NONE").slice(0, 8)}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
};
|
|
@ -1,201 +1,45 @@
|
|||
import * as React from "react";
|
||||
import { Link } from "react-router";
|
||||
import { DropDownProps, NavBarState, NavBarProps } from "./interfaces";
|
||||
import { NavBarState, NavBarProps } from "./interfaces";
|
||||
import { EStopButton } from "../devices/components/e_stop_btn";
|
||||
import { t } from "i18next";
|
||||
import { Session } from "../session";
|
||||
import { Markdown } from "../ui";
|
||||
import { Markdown, Row, Col } from "../ui";
|
||||
import * as moment from "moment";
|
||||
import { SyncButton } from "./sync_button";
|
||||
import { history } from "../history";
|
||||
import { updatePageInfo } from "../util";
|
||||
|
||||
let DropDown = ({ user, onClick }: DropDownProps) => {
|
||||
// Just checking if user is logged in, otherwise nothing is returned.
|
||||
if (!user) {
|
||||
return <span></span>;
|
||||
} else {
|
||||
// Displaying the user's name in the top right of the screen if available.
|
||||
let hasName = user && user.body.name;
|
||||
let fullName = hasName ? `${hasName}` : "";
|
||||
let firstName = fullName.split(" ")[0] + " ▾";
|
||||
|
||||
// The bit shown while hovering over username in top right of screen.
|
||||
return <div className="nav-dropdown">
|
||||
<span className="name">
|
||||
{firstName}
|
||||
</span>
|
||||
<div className="nav-dropdown-content">
|
||||
<ul>
|
||||
<li>
|
||||
<Link to="/app/account">
|
||||
<i className="fa fa-cog"></i>
|
||||
{t("Account Settings")}
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://software.farmbot.io/docs/the-farmbot-web-app"
|
||||
target="_blank">
|
||||
<i className="fa fa-file-text-o"></i>{t("Documentation")}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a onClick={onClick}>
|
||||
<i className="fa fa-sign-out"></i>
|
||||
{t("Logout")}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="version-links">
|
||||
<span>{t("Application")}:
|
||||
<a href="https://github.com/FarmBot/Farmbot-Web-API"
|
||||
target="_blank">
|
||||
{(process.env.SHORT_REVISION || "NONE").slice(0, 8)}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
};
|
||||
|
||||
// Easier way to keep track of links in the navbar.
|
||||
let links = [
|
||||
{ name: "Farm Designer", icon: "leaf", url: "/app/designer" },
|
||||
{ name: "Controls", icon: "keyboard-o", url: "/app/controls" },
|
||||
{ name: "Device", icon: "cog", url: "/app/device" },
|
||||
{ name: "Sequences", icon: "server", url: "/app/sequences" },
|
||||
{ name: "Regimens", icon: "calendar-check-o", url: "/app/regimens" },
|
||||
{ name: "Tools", icon: "wrench", url: "/app/tools" },
|
||||
{ name: "Farmware", icon: "crosshairs", url: "/app/farmware" }
|
||||
];
|
||||
|
||||
export class NavBar extends React.Component<NavBarProps, NavBarState> {
|
||||
|
||||
state: NavBarState = { mobileNavExpanded: false, tickerExpanded: false };
|
||||
|
||||
toggleMobileNav = () => {
|
||||
let { mobileNavExpanded } = this.state;
|
||||
/** Don't let user scroll when nav is open */
|
||||
this.setState({ mobileNavExpanded: !mobileNavExpanded });
|
||||
}
|
||||
|
||||
toggleTicker = () => {
|
||||
let { tickerExpanded } = this.state;
|
||||
this.setState({ tickerExpanded: !tickerExpanded });
|
||||
}
|
||||
|
||||
logout = () => Session.clear(true);
|
||||
|
||||
render() {
|
||||
// Class for toggling the left sliding menu on mobile and tablets.
|
||||
let mobileMenuClass = this.state.mobileNavExpanded ? "expanded" : "";
|
||||
|
||||
// Class for toggling the black bar, top of the screen containing logs.
|
||||
let tickerClass = this.state.tickerExpanded ? "expanded" : "";
|
||||
|
||||
// The way our app is laid out, we'll pretty much always want this bit.
|
||||
let pageName = history.getCurrentLocation().pathname.split("/")[2] || "";
|
||||
|
||||
// Change document meta title on every route change.
|
||||
updatePageInfo(pageName);
|
||||
|
||||
let { toggleMobileNav, toggleTicker, logout } = this;
|
||||
let user = this.props.user;
|
||||
return <div className="nav-wrapper">
|
||||
<nav role="navigation">
|
||||
<button
|
||||
className="mobile-and-tablet-only fb-button"
|
||||
onClick={toggleMobileNav}>
|
||||
<i className="fa fa-bars"></i>
|
||||
</button>
|
||||
<span className="page-name">{pageName}</span>
|
||||
<div className={`links ${mobileMenuClass}`}>
|
||||
<ul>
|
||||
{links.map(link => {
|
||||
return <li key={link.url}>
|
||||
<Link to={link.url}
|
||||
activeClassName="active">
|
||||
<i className={`fa fa-${link.icon}`} />
|
||||
{link.name}
|
||||
</Link>
|
||||
</li>;
|
||||
})}
|
||||
</ul>
|
||||
|
||||
{/** TODO: Getting the links from the desktop dropdown to the
|
||||
mobile slide-out menu involves gnarly (and probably mobile-
|
||||
incompatible) CSS. I'll look into this one. -CV */}
|
||||
<ul className="mobile-menu-extras">
|
||||
<li>
|
||||
<Link to="/app/account">
|
||||
<i className="fa fa-cog"></i>{t("Account Settings")}
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://software.farmbot.io/docs/the-farmbot-web-app"
|
||||
target="_blank">
|
||||
<i className="fa fa-file-text-o"></i>{t("Documentation")}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a onClick={logout}>
|
||||
<i className="fa fa-sign-out"></i>
|
||||
{t("Logout")}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="version-links mobile-only">
|
||||
{t("Frontend")}:
|
||||
<a href="https://github.com/FarmBot/farmbot-web-frontend"
|
||||
target="_blank">
|
||||
{/** SHORT_REVISION is the last frontend commit. */}
|
||||
{process.env.SHORT_REVISION}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`ticker-list ${tickerClass}`} onClick={toggleTicker}>
|
||||
{this.props.logs.map((log, index) => {
|
||||
let isFiltered = log.message.toLowerCase().includes("filtered");
|
||||
let time = moment.unix(log.created_at).local().format("h:mm a");
|
||||
if (!isFiltered) {
|
||||
return <div key={index} className="status-ticker-wrapper">
|
||||
<div className={`saucer ${log.meta.type}`} />
|
||||
<label className="status-ticker-message">
|
||||
<Markdown>
|
||||
{log.message || "Loading"}
|
||||
</Markdown>
|
||||
</label>
|
||||
<label className="status-ticker-created-at">
|
||||
{time}
|
||||
</label>
|
||||
</div>;
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/** Everything to the right of the navigation links. */}
|
||||
<div className="right-nav-content">
|
||||
<SyncButton
|
||||
bot={this.props.bot}
|
||||
user={this.props.user}
|
||||
dispatch={this.props.dispatch}
|
||||
/>
|
||||
<EStopButton
|
||||
bot={this.props.bot}
|
||||
user={this.props.user}
|
||||
/>
|
||||
<DropDown
|
||||
onClick={logout}
|
||||
user={user}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/** The darkish opaque backdrop when mobile/tablet is open */}
|
||||
<div className={`underlay ${mobileMenuClass}`}
|
||||
onClick={toggleMobileNav}></div>
|
||||
</nav>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<nav role="navigation">
|
||||
<span className="page-name visible-xs-inline-block">
|
||||
{pageName}
|
||||
</span>
|
||||
<SyncButton
|
||||
bot={this.props.bot}
|
||||
user={this.props.user}
|
||||
dispatch={this.props.dispatch}
|
||||
/>
|
||||
<EStopButton
|
||||
bot={this.props.bot}
|
||||
user={this.props.user}
|
||||
/>
|
||||
</nav>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ export interface NavButtonProps {
|
|||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export interface DropDownProps {
|
||||
export interface AdditonalMenuProps {
|
||||
user: TaggedUser | undefined;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import * as React from "react";
|
||||
import { Link } from "react-router";
|
||||
import { t } from "i18next";
|
||||
|
||||
let links = [
|
||||
{ name: "Farm Designer", icon: "leaf", url: "/app/designer" },
|
||||
{ name: "Controls", icon: "keyboard-o", url: "/app/controls" },
|
||||
{ name: "Device", icon: "cog", url: "/app/device" },
|
||||
{ name: "Sequences", icon: "server", url: "/app/sequences" },
|
||||
{ name: "Regimens", icon: "calendar-check-o", url: "/app/regimens" },
|
||||
{ name: "Tools", icon: "wrench", url: "/app/tools" },
|
||||
{ name: "Farmware", icon: "crosshairs", url: "/app/farmware" },
|
||||
{ name: "Account", icon: "cog", url: "/app/account" },
|
||||
{ name: "Logout", icon: "sign-out", url: "" }
|
||||
];
|
||||
|
||||
export let NavLinks = () => {
|
||||
<div className="links">
|
||||
{links.map(link => {
|
||||
return <Link
|
||||
to={link.url}
|
||||
activeClassName="active"
|
||||
key={link.url}
|
||||
>
|
||||
<i className={`fa fa-${link.icon}`} />
|
||||
{link.name}
|
||||
</Link>;
|
||||
})}
|
||||
<a href="https://software.farmbot.io/docs/the-farmbot-web-app"
|
||||
target="_blank">
|
||||
<i className="fa fa-file-text-o"></i>{t("Documentation")}
|
||||
</a>
|
||||
<div className="version-links">
|
||||
{t("Frontend")}:
|
||||
<a
|
||||
href="https://github.com/FarmBot/farmbot-web-frontend"
|
||||
target="_blank"
|
||||
>
|
||||
{/** SHORT_REVISION is the last frontend commit. */}
|
||||
{process.env.SHORT_REVISION}
|
||||
</a>
|
||||
</div>
|
||||
</div>;
|
||||
};
|
|
@ -33,9 +33,7 @@ export function SyncButton({ user, bot, dispatch }: NavButtonProps) {
|
|||
let text = TEXT_MAPPING[sync_status] || "DISCONNECTED";
|
||||
return <button
|
||||
className={`nav-sync ${color} fb-button`}
|
||||
onClick={() => {
|
||||
dispatch(sync());
|
||||
}}>
|
||||
onClick={() => dispatch(sync())}>
|
||||
{text}
|
||||
</button>;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import * as React from "react";
|
||||
import { Markdown } from "./ui/index";
|
||||
import * as moment from "moment";
|
||||
|
||||
export function TickerList(props: any) {
|
||||
<div className="ticker-list">
|
||||
{props.logs.map((log, index) => {
|
||||
let isFiltered = log.message.toLowerCase().includes("filtered");
|
||||
let time = moment.unix(log.created_at).local().format("h:mm a");
|
||||
if (!isFiltered) {
|
||||
return <div key={index} className="status-ticker-wrapper">
|
||||
<div className={`saucer ${log.meta.type}`} />
|
||||
<label className="status-ticker-message">
|
||||
<Markdown>
|
||||
{log.message || "Loading"}
|
||||
</Markdown>
|
||||
</label>
|
||||
<label className="status-ticker-created-at">
|
||||
{time}
|
||||
</label>
|
||||
</div>;
|
||||
}
|
||||
})}
|
||||
</div>;
|
||||
}
|
Loading…
Reference in New Issue