This commit is contained in:
Jeremy Kauffman 2017-04-09 11:06:23 -04:00
parent 498618e39b
commit 575db85477
23 changed files with 625 additions and 359 deletions

1
.gitignore vendored
View file

@ -6,6 +6,7 @@ dist
/app/node_modules
/build/venv
/lbry-app-venv
/lbry-venv
/daemon/build
/daemon/venv
/daemon/requirements.txt

View file

@ -62,9 +62,9 @@ function getPidsForProcessName(name) {
}
function createWindow () {
win = new BrowserWindow({backgroundColor: '#155B4A'}) //$color-primary
win = new BrowserWindow({backgroundColor: '#155B4A', minWidth: 800, minHeight: 1000 }) //$color-primary
win.maximize()
//win.webContents.openDevTools()
win.webContents.openDevTools();
win.loadURL(`file://${__dirname}/dist/index.html`)
win.on('closed', () => {
win = null

4
doitagain.sh Executable file
View file

@ -0,0 +1,4 @@
#!/bin/bash
rm -rf ~/.lbrynet/
rm -rf ~/.lbryum/
./node_modules/.bin/electron app

View file

@ -47,12 +47,6 @@ var App = React.createClass({
_upgradeDownloadItem: null,
_isMounted: false,
_version: null,
getDefaultProps: function() {
return {
address: window.location.search
};
},
getUpdateUrl: function() {
switch (process.platform) {
case 'darwin':
@ -87,54 +81,19 @@ var App = React.createClass({
pageArgs: pageArgs === undefined ? null : pageArgs
};
},
updateRegistrationStatus: function() {
if (localStorage.getItem('accessToken')) {
this.setState({
registrationCheckComplete: true,
});
} else {
lbry.status().then(({installation_id}) => {
installation_id += parseInt(Date.now(), 10); // temp
installation_id += "X".repeat(96 - installation_id.length); // temp
lbryio.call('user_install', 'exists', {app_id: installation_id}).then((userExists) => {
// TODO: deal with case where user exists already with the same app ID, but we have no access token.
// Possibly merge in to the existing user with the same app ID.
lbryio.call('user', 'new', {
language: 'en',
app_id: installation_id,
}, 'post').then(({ID}) => {
localStorage.setItem('accessToken', ID);
localStorage.setItem('appId', installation_id);
this.setState({
registrationCheckComplete: true,
justRegistered: true,
});
});
})
});
}
},
getInitialState: function() {
var match, param, val, viewingPage, pageArgs,
drawerOpenRaw = sessionStorage.getItem('drawerOpen');
return Object.assign(this.getViewingPageAndArgs(this.props.address), {
return Object.assign(this.getViewingPageAndArgs(window.location.search), {
drawerOpen: drawerOpenRaw !== null ? JSON.parse(drawerOpenRaw) : true,
errorInfo: null,
modal: null,
downloadProgress: null,
downloadComplete: false,
registrationCheckComplete: null,
justRegistered: false,
});
},
componentWillMount: function() {
if (!localStorage.getItem('accessToken') && window.location.search != '?discover') {
// User isn't registered but somehow made it to a page other than Discover, so send them to
// Discover to get them registered and show them the welcome screen.
window.location.search = '?discover';
}
document.addEventListener('unhandledError', (event) => {
this.alertError(event.detail);
});
@ -172,8 +131,6 @@ var App = React.createClass({
});
});
}
this.updateRegistrationStatus();
},
openDrawer: function() {
sessionStorage.setItem('drawerOpen', true);
@ -337,10 +294,6 @@ var App = React.createClass({
}
},
render: function() {
if (!this.state.registrationCheckComplete) {
return null;
}
var mainContent = this.getMainContent(),
headerLinks = this.getHeaderLinks(),
searchQuery = this.state.viewingPage == 'discover' && this.state.pageArgs ? this.state.pageArgs : '';

196
ui/js/component/auth.js Normal file
View file

@ -0,0 +1,196 @@
import React from 'react';
import lbryio from '../lbryio.js';
import ModalPage from './modal-page.js';
import {Link} from '../component/link.js';
import FormField from '../component/form.js';
import Notice from '../component/notice.js'
const SubmitEmailStage = React.createClass({
getInitialState: function() {
return {
rewardType: null,
email: '',
submitting: false
};
},
handleEmailChanged: function(event) {
this.setState({
email: event.target.value,
});
},
handleSubmit: function(event) {
event.preventDefault();
this.setState({
submitting: true,
});
lbryio.call('user_email', 'new', {email: this.state.email}, 'post').then(() => {
this.props.onEmailSaved();
}, (error) => {
if (this._emailField) {
this._emailField.showError(error.message)
}
this.setState({ submitting: false });
});
},
render: function() {
return (
<section>
<form onSubmit={this.handleSubmit}>
<FormField row={true} ref={(field) => { this._emailField = field }} type="text" label="Email" placeholder="webmaster@toplbryfan.com"
name="email" value={this.state.email}
onChange={this.handleEmailChanged} />
<div className="form-row">
<Link button="primary" label="Next" disabled={this.state.submitting} onClick={this.handleSubmit} />
</div>
</form>
</section>
);
}
});
const ConfirmEmailStage = React.createClass({
getInitialState: function() {
return {
rewardType: null,
code: '',
submitting: false,
errorMessage: null,
};
},
handleCodeChanged: function(event) {
this.setState({
code: event.target.value,
});
},
handleSubmit: function(event) {
event.preventDefault();
this.setState({
submitting: true,
});
lbryio.call('user_email', 'confirm', {verification_token: this.state.code}, 'post').then(() => {
rewards.claimReward('confirm_email').then(() => {
this.props.onDone();
}, (err) => {l
this.props.onDone();
});
}, (error) => {
if (this._codeField) {
this._codeField.showError(error.message)
this.setState({ submitting: false })
}
});
},
render: function() {
return (
<section>
<form onSubmit={this.handleSubmit}>
<FormField row={true} label="Verification Code" ref={(field) => { this._codeField = field }} type="text"
name="code" placeholder="a94bXXXXXXXXXXXXXX" value={this.state.code} onChange={this.handleCodeChanged}
helper="A verification code is required to access this version."/>
<div className="form-row">
<Link button="primary" label="Verify" disabled={this.state.submitting} onClick={this.handleSubmit} />
</div>
</form>
</section>
);
}
});
const ErrorStage = React.createClass({
render: function() {
// <section><Link button="primary" label="OK" onClick={this.props.onDone} /></section>
return (
<section>
<p>An error was encountered that we cannot continue from.</p>
<p>At least we're earning the name beta.</p>
<Link button="alt" label="Try Reload" onClick={() => { window.location.reload() } } />
</section>
);
}
});
const PendingStage = React.createClass({
render: function() {
return (
<section>
<p>Preparing for first access <span className="busy-indicator"></span></p>
</section>
);
}
});
export const AuthOverlay = React.createClass({
_stages: {
pending: PendingStage,
error: ErrorStage,
email: SubmitEmailStage,
confirm: ConfirmEmailStage,
},
propTypes: {
// onDone: React.PropTypes.func.isRequired,
},
getInitialState: function() {
return {
stage: "pending",
stageProps: {}
};
},
componentWillMount: function() {
lbryio.authenticate().then(function(user) {
console.log(user);
if (!user.HasVerifiedEmail) {
this.setState({
stage: "email",
stageProps: {
onEmailSaved: function() {
this.setState({
stage: "confirm"
})
}.bind(this)
}
})
} else {
this.setState({ stage: null })
}
}.bind(this)).catch((err) => {
this.setState({
stage: "error",
stageProps: { errorText: err.message }
})
document.dispatchEvent(new CustomEvent('unhandledError', {
detail: {
message: err.message,
data: err.stack
}
}));
})
},
// handleStageDone: function() {
// if (this.state.stageNum >= this._stages.length - 1) {
// this.props.onDone();
// } else {
// this.setState({
// stageNum: this.state.stageNum + 1,
// });
// }
// },
// <Content onDone={this.handleStageDone} />
render: function() {
console.log(lbryio.user);
if (!this.state.stage || lbryio.user && lbryio.user.HasVerifiedEmail) {
return null;
}
const StageContent = this._stages[this.state.stage];
return (
<ModalPage isOpen={true} contentLabel="Authentication" {...this.props}>
<h1>LBRY Early Access</h1>
<StageContent {...this.state.stageProps} />
</ModalPage>
);
}
});

View file

@ -6,6 +6,8 @@ var requiredFieldWarningStyle = {
transition: 'opacity 400ms ease-in',
};
var formFieldCounter = 0;
var FormField = React.createClass({
_fieldRequiredText: 'This field is required',
_type: null,
@ -13,11 +15,12 @@ var FormField = React.createClass({
propTypes: {
type: React.PropTypes.string.isRequired,
row: React.PropTypes.bool,
hidden: React.PropTypes.bool,
},
getInitialState: function() {
return {
adviceState: 'hidden',
errorState: 'hidden',
adviceText: null,
}
},
@ -33,25 +36,25 @@ var FormField = React.createClass({
this._element = this.props.type;
}
},
showAdvice: function(text) {
showError: function(text) {
this.setState({
adviceState: 'shown',
errorState: 'shown',
adviceText: text,
});
setTimeout(() => {
this.setState({
adviceState: 'fading',
});
setTimeout(() => {
this.setState({
adviceState: 'hidden',
});
}, 450);
}, 5000);
// setTimeout(() => {
// this.setState({
// errorState: 'fading',
// });
// setTimeout(() => {
// this.setState({
// errorState: 'hidden',
// });
// }, 450);
// }, 5000);
},
warnRequired: function() {
this.showAdvice(this._fieldRequiredText);
this.showError(this._fieldRequiredText);
},
focus: function() {
this.refs.field.focus();
@ -70,43 +73,39 @@ var FormField = React.createClass({
},
render: function() {
// Pass all unhandled props to the field element
const otherProps = Object.assign({}, this.props);
const otherProps = Object.assign({}, this.props),
hasError = this.state.errorState != 'hidden';
delete otherProps.type;
delete otherProps.hidden;
delete otherProps.label;
delete otherProps.row;
delete otherProps.helper;
return (
!this.props.hidden
? <div className="form-field-container">
<this._element type={this._type} className="form-field" name={this.props.name} ref="field" placeholder={this.props.placeholder}
className={'form-field--' + this.props.type + ' ' + (this.props.className || '')}
{...otherProps}>
{this.props.children}
</this._element>
<FormFieldAdvice field={this.refs.field} state={this.state.adviceState}>{this.state.adviceText}</FormFieldAdvice>
</div>
: null
);
}
});
++formFieldCounter;
const elementId = "form-field-" + formFieldCounter
var FormFieldAdvice = React.createClass({
propTypes: {
state: React.PropTypes.string.isRequired,
},
render: function() {
if (this.props.hidden) {
return null;
}
const field = <div className="form-field">
{ this.props.label ?
<div className={"form-field__label " + (hasError ? 'form-field__label--error' : '')}>
<label htmlFor={elementId}>{this.props.label}</label>
</div> : ''
}
<this._element id={elementId} type={this._type} name={this.props.name} ref="field" placeholder={this.props.placeholder}
className={'form-field__input form-field__input-' + this.props.type + ' ' + (this.props.className || '') + (hasError ? 'form-field__input--error' : '')}
{...otherProps}>
{this.props.children}
</this._element>
{ !hasError && this.props.helper ? <div className="form-field__helper">{this.props.helper}</div> : '' }
{ hasError ? <div className="form-field__error">{this.state.adviceText}</div> : '' }
</div>
return (
this.props.state != 'hidden'
? <div className="form-field-advice-container">
<div className={'form-field-advice' + (this.props.state == 'fading' ? ' form-field-advice--fading' : '')}>
<Icon icon="icon-caret-up" className="form-field-advice__arrow" />
<div className="form-field-advice__content-container">
<span className="form-field-advice__content">
{this.props.children}
</span>
</div>
</div>
</div>
: null
this.props.row ?
<div className="form-row">{field}</div> :
field
);
}
});

View file

@ -13,11 +13,12 @@ var SplashScreen = React.createClass({
isLagging: false,
}
},
updateStatus: function(was_lagging=false) {
lbry.getDaemonStatus(this._updateStatusCallback);
updateStatus: function() {
lbry.status().then(this._updateStatusCallback);
},
_updateStatusCallback: function(status) {
if (status.code == 'started') {
const startupStatus = status.startup_status
if (startupStatus.code == 'started') {
// Wait until we are able to resolve a name before declaring
// that we are done.
// TODO: This is a hack, and the logic should live in the daemon
@ -34,20 +35,28 @@ var SplashScreen = React.createClass({
return;
}
this.setState({
details: status.message + (status.is_lagging ? '' : '...'),
isLagging: status.is_lagging,
details: startupStatus.message + (startupStatus.is_lagging ? '' : '...'),
isLagging: startupStatus.is_lagging,
});
setTimeout(() => {
this.updateStatus(status.is_lagging);
this.updateStatus();
}, 500);
},
componentDidMount: function() {
lbry.connect((connected) => {
this.updateStatus();
});
lbry.connect().then((isConnected) => {
if (isConnected) {
this.updateStatus();
} else {
this.setState({
isLagging: true,
message: "Failed to connect to LBRY",
details: "LBRY was unable to start and connect properly."
})
}
})
},
render: function() {
return <LoadScreen message={this.props.message} details={this.state.details} isWarning={this.state.isLagging} />;
return <LoadScreen message={this.props.message} details={this.state.details} isWarning={this.state.isLagging} />
}
});

View file

@ -118,28 +118,36 @@ lbry.call = function (method, params, callback, errorCallback, connectFailedCall
//core
lbry.connect = function(callback)
{
// Check every half second to see if the daemon is accepting connections
// Once this returns True, can call getDaemonStatus to see where
// we are in the startup process
function checkDaemonStarted(tryNum=0) {
lbry.isDaemonAcceptingConnections(function (runningStatus) {
if (runningStatus) {
lbry.isConnected = true;
callback(true);
} else {
if (tryNum <= 600) { // Move # of tries into constant or config option
setTimeout(function () {
checkDaemonStarted(tryNum + 1);
}, 500);
} else {
callback(false);
}
lbry._connectPromise = null;
lbry.connect = function() {
if (lbry._connectPromise === null) {
lbry._connectPromise = new Promise((resolve, reject) => {
// Check every half second to see if the daemon is accepting connections
function checkDaemonStarted(tryNum = 0) {
lbry.isDaemonAcceptingConnections(function (runningStatus) {
if (runningStatus) {
resolve(true);
}
else {
if (tryNum <= 600) { // Move # of tries into constant or config option
setTimeout(function () {
checkDaemonStarted(tryNum + 1);
}, tryNum < 100 ? 200 : 1000);
}
else {
reject(new Error("Unable to connect to LBRY"));
}
}
});
}
checkDaemonStarted();
});
}
checkDaemonStarted();
return lbry._connectPromise;
}
lbry.isDaemonAcceptingConnections = function (callback) {
@ -147,10 +155,6 @@ lbry.isDaemonAcceptingConnections = function (callback) {
lbry.call('status', {}, () => callback(true), null, () => callback(false))
};
lbry.getDaemonStatus = function (callback) {
lbry.call('daemon_status', {}, callback);
};
lbry.checkFirstRun = function(callback) {
lbry.call('is_first_run', {}, callback);
}
@ -430,6 +434,10 @@ lbry.getClientSettings = function() {
lbry.getClientSetting = function(setting) {
var localStorageVal = localStorage.getItem('setting_' + setting);
if (setting == 'showDeveloperMenu')
{
return true;
}
return (localStorageVal === null ? lbry.defaultClientSettings[setting] : JSON.parse(localStorageVal));
}

View file

@ -1,8 +1,15 @@
import {getLocal, setLocal} from './utils.js';
import lbry from './lbry.js';
const querystring = require('querystring');
const lbryio = {};
const lbryio = {
_accessToken: getLocal('accessToken'),
_authenticationPromise: null,
_user : null
};
const CONNECTION_STRING = 'https://apidev.lbry.tech/';
const CONNECTION_STRING = 'http://localhost:8080/';
const mocks = {
'reward_type.get': ({name}) => {
@ -37,25 +44,18 @@ lbryio.call = function(resource, action, params={}, method='get') {
/* end temp */
console.log('about to create xhr object');
const xhr = new XMLHttpRequest;
xhr.addEventListener('error', function (error) {
console.log('received XHR error:', error);
reject(error);
xhr.addEventListener('error', function (event) {
reject(new Error("Something went wrong making an internal API call."));
});
console.log('about to add timeout listener');
xhr.addEventListener('timeout', function() {
console.log('XHR timed out');
reject(new Error('XMLHttpRequest connection timed out'));
});
console.log('about to create load listener');
xhr.addEventListener('load', function() {
console.log('loaded');
const response = JSON.parse(xhr.responseText);
if (!response.success) {
@ -78,14 +78,12 @@ lbryio.call = function(resource, action, params={}, method='get') {
}
});
console.log('about to call xhr.open');
// For social media auth:
//const accessToken = localStorage.getItem('accessToken');
//const fullParams = {...params, ... accessToken ? {access_token: accessToken} : {}};
// Temp app ID based auth:
const fullParams = {app_id: localStorage.getItem('appId'), ...params};
const fullParams = {app_id: lbryio._accessToken, ...params};
if (method == 'get') {
console.info('GET ', CONNECTION_STRING + resource + '/' + action, ' | params:', fullParams);
@ -100,4 +98,69 @@ lbryio.call = function(resource, action, params={}, method='get') {
});
};
lbryio.setAccessToken = (token) => {
setLocal('accessToken', token)
lbryio._accessToken = token
}
lbryio.authenticate = () => {
if (lbryio._authenticationPromise === null) {
lbryio._authenticationPromise = new Promise((resolve, reject) => {
lbry.status().then(({installation_id}) => {
//temp hack for installation_ids being wrong
installation_id += "Y".repeat(96 - installation_id.length)
function setCurrentUser() {
lbryio.call('user', 'me').then((data) => {
lbryio.user = data
resolve(data)
}).catch(function(err) {
lbryio.setAccessToken(null);
reject(err);
})
}
if (!lbryio._accessToken) {
lbryio.call('user', 'new', {
language: 'en',
app_id: installation_id,
}, 'post').then(function(responseData) {
if (!responseData.ID) {
reject(new Error("Received invalid authentication response."));
}
lbryio.setAccessToken(installation_id)
setCurrentUser()
}).catch(function(error) {
/*
until we have better error code format, assume all errors are duplicate application id
if we're wrong, this will be caught by later attempts to make a valid call
*/
lbryio.setAccessToken(installation_id)
setCurrentUser()
})
} else {
setCurrentUser()
}
// if (!lbryio._
//(data) => {
// resolve(data)
// localStorage.setItem('accessToken', ID);
// localStorage.setItem('appId', installation_id);
// this.setState({
// registrationCheckComplete: true,
// justRegistered: true,
// });
//});
// lbryio.call('user_install', 'exists', {app_id: installation_id}).then((userExists) => {
// // TODO: deal with case where user exists already with the same app ID, but we have no access token.
// // Possibly merge in to the existing user with the same app ID.
// })
}).catch(reject);
});
}
return lbryio._authenticationPromise;
}
export default lbryio;

View file

@ -5,6 +5,8 @@ import lbryio from './lbryio.js';
import lighthouse from './lighthouse.js';
import App from './app.js';
import SplashScreen from './component/splash.js';
import {AuthOverlay} from './component/auth.js';
import {Welcome} from './component/welcome.js';
const {remote} = require('electron');
const contextMenu = remote.require('./menu/context-menu');
@ -20,30 +22,21 @@ window.addEventListener('contextmenu', (event) => {
let init = function() {
window.lbry = lbry;
window.lighthouse = lighthouse;
let canvas = document.getElementById('canvas');
var canvas = document.getElementById('canvas');
if (window.sessionStorage.getItem('loaded') == 'y') {
ReactDOM.render(<App/>, canvas)
lbry.connect().then(function(isConnected) {
lbryio.authenticate() //start auth process as soon as soon as we can get an install ID
})
async function onDaemonReady() {
window.sessionStorage.setItem('loaded', 'y'); //once we've made it here once per session, we don't need to show splash again
ReactDOM.render(<div><AuthOverlay /><App /></div>, canvas)
}
if (window.sessionStorage.getItem('loaded') == 'y' && false) {
onDaemonReady();
} else {
ReactDOM.render(
(
<SplashScreen message="Connecting" onLoadDone={function() {
// There are a couple of conditions where we want to preempt loading the app and send the user to a
// different page. TODO: Find a better place for this logic.
if (!localStorage.getItem('claimCodeDone') && ['', '?', 'discover'].includes(window.location.search)) {
lbry.getBalance((balance) => {
if (balance <= 0) {
window.location.href = '?claim';
} else {
ReactDOM.render(<App/>, canvas);
}
});
} else {
ReactDOM.render(<App/>, canvas);
}
}}/>
), canvas);
ReactDOM.render(<SplashScreen message="Connecting" onLoadDone={onDaemonReady} />, canvas);
}
};

View file

@ -5,7 +5,6 @@ import {FileTile} from '../component/file-tile.js';
import {Link} from '../component/link.js';
import {ToolTip} from '../component/tooltip.js';
import {BusyMessage} from '../component/common.js';
import {Welcome} from '../component/welcome.js';
var fetchResultsStyle = {
color: '#888',
@ -174,13 +173,12 @@ var DiscoverPage = React.createClass({
},
render: function() {
//{ !this.props.query && !this.state.searching ? <FeaturedContent /> : null }
return (
<main>
{ this.state.searching ? <SearchActive /> : null }
{ !this.state.searching && this.props.query && this.state.results.length ? <SearchResults results={this.state.results} /> : null }
{ !this.state.searching && this.props.query && !this.state.results.length ? <SearchNoResults query={this.props.query} /> : null }
{ !this.props.query && !this.state.searching ? <FeaturedContent /> : null }
<Welcome isOpen={this.props.showWelcome && !this.state.welcomeComplete} onDone={this.handleWelcomeDone} />
</main>
);
}

View file

@ -162,7 +162,7 @@ var PublishPage = React.createClass({
}
if (!lbry.nameIsValid(rawName, false)) {
this.refs.name.showAdvice('LBRY names must contain only letters, numbers and dashes.');
this.refs.name.showError('LBRY names must contain only letters, numbers and dashes.');
return;
}

View file

@ -10,7 +10,6 @@ const MESSAGES = {
const rewards = {};
rewards.claimReward = function(type) {
console.log('top of claimReward')
return new Promise((resolve, reject) => {
console.log('top of promise body')
lbry.get_new_address().then((address) => {
@ -30,7 +29,7 @@ rewards.claimReward = function(type) {
document.dispatchEvent(new CustomEvent('globalNotice', {
detail: {
message: MESSAGES[type],
isError: false,
isError: false,
},
}));
@ -41,7 +40,7 @@ rewards.claimReward = function(type) {
document.dispatchEvent(new CustomEvent('globalNotice', {
detail: {
message: `Failed to claim reward: ${error.message}`,
isError: true,
isError: true,
},
}));
document.dispatchEvent(new CustomEvent('rewardFailed', error));

View file

@ -12,4 +12,4 @@ export function getLocal(key) {
*/
export function setLocal(key, value) {
localStorage.setItem(key, JSON.stringify(value));
}
}

66
ui/scss/_form.scss Normal file
View file

@ -0,0 +1,66 @@
@import "global";
/* this probably shouldn't exist but it does so here we are - Jeremy */
textarea,
select,
input[type="text"],
input[type="password"],
input[type="email"],
input[type="number"],
input[type="search"],
input[type="date"] {
@include placeholder {
color: lighten($color-text-dark, 60%);
}
transition: all $transition-standard;
cursor: pointer;
padding-left: 1px;
padding-right: 1px;
box-sizing: border-box;
-webkit-appearance: none;
&[readonly] {
background-color: #bbb;
}
}
input[type="text"],
input[type="password"],
input[type="email"],
input[type="number"],
input[type="search"],
input[type="date"] {
border-bottom: 2px solid $color-form-border;
line-height: $spacing-vertical - 4;
height: $spacing-vertical * 1.5;
&.form-field__input--error {
border-color: $color-error;
}
}
textarea:focus,
input[type="text"]:focus,
input[type="password"]:focus,
input[type="email"]:focus,
input[type="number"]:focus,
input[type="search"]:focus,
input[type="date"]:focus {
border-color: $color-primary;
}
textarea {
border: 2px solid $color-form-border;
}
.form-row
{
+ .form-row
{
margin-top: $spacing-vertical;
}
+ .form-row-submit
{
margin-top: $spacing-vertical;
}
}

View file

@ -9,14 +9,15 @@ $color-primary: #155B4A;
$color-light-alt: hsl(hue($color-primary), 15, 85);
$color-text-dark: #000;
$color-help: rgba(0,0,0,.6);
$color-notice: #921010;
$color-warning: #ffffff;
$color-notice: #8a6d3b;
$color-error: #a94442;
$color-load-screen-text: #c3c3c3;
$color-canvas: #f5f5f5;
$color-bg: #ffffff;
$color-bg-alt: #D9D9D9;
$color-money: #216C2A;
$color-meta-light: #505050;
$color-form-border: rgba(160,160,160,.5);
$font-size: 16px;
$font-line-height: 1.3333;
@ -29,6 +30,9 @@ $height-header: $spacing-vertical * 2.5;
$height-button: $spacing-vertical * 1.5;
$default-box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);
//$focus-box-shadow: 2px 4px 4px 0 rgba(0,0,0,.14),2px 5px 3px -2px rgba(0,0,0,.2),2px 3px 7px 0 rgba(0,0,0,.12);
$transition-standard: .225s ease;
$blur-intensity: 8px;

View file

@ -21,7 +21,7 @@
&:hover
{
opacity: $hover-opacity;
transition: opacity .225s ease;
transition: opacity $transition-standard;
text-decoration: underline;
.icon {
text-decoration: none;
@ -76,11 +76,6 @@ sup, sub {
sup { top: -0.4em; }
sub { top: 0.4em; }
label {
cursor: default;
display: block;
}
code {
font: 0.8em Consolas, 'Lucida Console', 'Source Sans', monospace;
background-color: #eee;
@ -104,23 +99,6 @@ p
opacity: 0.7;
}
input[type="text"], input[type="search"], textarea
{
@include placeholder {
color: lighten($color-text-dark, 60%);
}
border: 2px solid rgba(160,160,160,.5);
padding-left: 5px;
padding-right: 5px;
box-sizing: border-box;
-webkit-appearance: none;
}
input[type="text"], input[type="search"]
{
line-height: $spacing-vertical - 4;
height: $spacing-vertical * 1.5;
}
.truncated-text {
display: inline-block;
}
@ -144,75 +122,6 @@ input[type="text"], input[type="search"]
}
}
.button-set-item {
position: relative;
display: inline-block;
+ .button-set-item
{
margin-left: $padding-button;
}
}
.button-block, .faux-button-block
{
display: inline-block;
height: $height-button;
line-height: $height-button;
text-decoration: none;
border: 0 none;
text-align: center;
border-radius: 2px;
text-transform: uppercase;
.icon
{
top: 0em;
}
.icon:first-child
{
padding-right: 5px;
}
.icon:last-child
{
padding-left: 5px;
}
}
.button-block
{
cursor: pointer;
}
.button__content {
margin: 0 $padding-button;
}
.button-primary
{
color: white;
background-color: $color-primary;
box-shadow: $default-box-shadow;
}
.button-alt
{
background-color: $color-bg-alt;
box-shadow: $default-box-shadow;
}
.button-text
{
@include text-link();
display: inline-block;
.button__content {
margin: 0 $padding-text-link;
}
}
.button-text-help
{
@include text-link(#aaa);
font-size: 0.8em;
}
.icon:only-child {
position: relative;
top: 0.16em;
@ -235,87 +144,6 @@ input[type="text"], input[type="search"]
font-style: italic;
}
.form-row
{
+ .form-row
{
margin-top: $spacing-vertical / 2;
}
.help
{
margin-top: $spacing-vertical / 2;
}
+ .form-row-submit
{
margin-top: $spacing-vertical;
}
}
.form-field-container {
display: inline-block;
}
.form-field--text {
width: 330px;
}
.form-field--text-number {
width: 50px;
}
.form-field-advice-container {
position: relative;
}
.form-field-advice {
position: absolute;
top: 0px;
left: 0px;
display: flex;
flex-direction: column;
white-space: nowrap;
transition: opacity 400ms ease-in;
}
.form-field-advice--fading {
opacity: 0;
}
.form-field-advice__arrow {
text-align: left;
padding-left: 18px;
font-size: 22px;
line-height: 0.3;
color: darken($color-primary, 5%);
}
.form-field-advice__content-container {
display: inline-block;
}
.form-field-advice__content {
display: inline-block;
padding: 5px;
border-radius: 2px;
background-color: darken($color-primary, 5%);
color: #fff;
}
.form-field-label {
width: 118px;
text-align: right;
vertical-align: top;
display: inline-block;
}
.sort-section {
display: block;
margin-bottom: 5px;
@ -350,7 +178,6 @@ input[type="text"], input[type="search"]
background: rgb(255, 255, 255);
overflow: auto;
border-radius: 4px;
outline: none;
padding: 36px;
max-width: 250px;
}

View file

@ -3,20 +3,20 @@ body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, code, form, fiel
margin:0;
padding:0;
}
input:focus, textarea:focus
:focus
{
outline: 0;
outline: 0;
}
table
table
{
border-collapse: collapse;
border-spacing:0;
}
fieldset, img, iframe
fieldset, img, iframe
{
border: 0;
}
h1, h2, h3, h4, h5, h6
h1, h2, h3, h4, h5, h6
{
font-weight:normal;
}
@ -25,11 +25,12 @@ ol, ul
list-style-position: inside;
> li { list-style-position: inside; }
}
input, textarea, select
input, textarea, select
{
font-family:inherit;
font-size:inherit;
font-weight:inherit;
font-family:inherit;
font-size:inherit;
font-weight:inherit;
border: 0 none;
}
img {
width: auto\9;

View file

@ -1,12 +1,15 @@
@import "_reset";
@import "_grid";
@import "_icons";
@import "_form";
@import "_mediaelement";
@import "_canvas";
@import "_gui";
@import "component/_table";
@import "component/_button.scss";
@import "component/_file-actions.scss";
@import "component/_file-tile.scss";
@import "component/_form-field.scss";
@import "component/_menu.scss";
@import "component/_tooltip.scss";
@import "component/_load-screen.scss";

View file

@ -0,0 +1,78 @@
@import "../global";
$button-focus-shift: 12%;
.button-set-item {
position: relative;
display: inline-block;
+ .button-set-item
{
margin-left: $padding-button;
}
}
.button-block, .faux-button-block
{
display: inline-block;
height: $height-button;
line-height: $height-button;
text-decoration: none;
border: 0 none;
text-align: center;
border-radius: 2px;
text-transform: uppercase;
.icon
{
top: 0em;
}
.icon:first-child
{
padding-right: 5px;
}
.icon:last-child
{
padding-left: 5px;
}
}
.button-block
{
cursor: pointer;
}
.button__content {
margin: 0 $padding-button;
}
.button-primary
{
$color-button-text: white;
color: darken($color-button-text, $button-focus-shift * 0.5);
background-color: $color-primary;
box-shadow: $default-box-shadow;
&:focus {
color: $color-button-text;
//box-shadow: $focus-box-shadow;
background-color: mix(black, $color-primary, $button-focus-shift)
}
}
.button-alt
{
background-color: $color-bg-alt;
box-shadow: $default-box-shadow;
}
.button-text
{
@include text-link();
display: inline-block;
.button__content {
margin: 0 $padding-text-link;
}
}
.button-text-help
{
@include text-link(#aaa);
font-size: 0.8em;
}

View file

@ -0,0 +1,36 @@
@import "../global";
.form-field {
display: inline-block;
}
.form-field__label {
margin-top: $spacing-vertical * 2/3;
margin-bottom: $spacing-vertical * 1/3;
line-height: 1;
}
.form-field__label--error {
color: $color-error;
}
.form-field__input-text {
width: 330px;
}
.form-field__input-text-number {
width: 50px;
}
.form-field__error, .form-field__helper {
margin-top: $spacing-vertical * 1/3;
font-size: 0.8em;
transition: opacity $transition-standard;
}
.form-field__error {
color: $color-error;
}
.form-field__helper {
color: $color-help;
}

View file

@ -23,7 +23,7 @@
}
.load-screen__details--warning {
color: $color-warning;
color: white;
}
.load-screen__cancel-link {

View file

@ -12,6 +12,24 @@
z-index: 9999;
}
.modal-page {
position: fixed;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
left: 0;
right: 0;
top: 0;
bottom: 0;
border: 1px solid rgb(204, 204, 204);
background: rgb(255, 255, 255);
overflow: auto;
}
/*
.modal-page {
position: fixed;
display: flex;
@ -31,3 +49,13 @@
right: 25px;
bottom: 25px;
}
*/
.modal-page__content {
h1, h2 {
margin-bottom: $spacing-vertical / 2;
}
h3, h4 {
margin-bottom: $spacing-vertical / 4;
}
}