1
0
mirror of https://github.com/chinchang/web-maker.git synced 2025-07-09 16:06:21 +02:00

Merge pull request #350 from chinchang/settings-ui-refactor

Settings ui refactor
This commit is contained in:
Kushagra Gour
2018-11-13 20:18:57 +05:30
committed by GitHub
6 changed files with 573 additions and 316 deletions

View File

@ -1,32 +1,21 @@
import { h, Component } from 'preact';
import { editorThemes } from '../editorThemes';
import Switch from './Switch';
import Tabs, { TabPanel } from './Tabs';
import { Divider } from './common';
function CheckboxSetting({
title,
label,
onChange,
pref,
name,
showWhenExtension
}) {
function CheckboxSetting({ label, onChange, pref }) {
return (
<label
class={`line ${showWhenExtension ? 'show-when-extension' : ''} `}
title={title}
>
<input
type="checkbox"
checked={pref}
onChange={onChange}
data-setting={name}
/>{' '}
<Switch checked={pref} onChange={onChange}>
{label}
</label>
</Switch>
);
}
export default class Settings extends Component {
updateSetting(e) {
this.props.onChange(e);
updateSetting(e, settingName) {
const value =
e.target.type === 'checkbox' ? e.target.checked : e.target.value;
this.props.onChange(settingName, value);
}
shouldComponentUpdate() {
// TODO: add check on prefs
@ -38,304 +27,335 @@ export default class Settings extends Component {
<div>
<h1>Settings</h1>
<h3>Indentation</h3>
<div
class="line"
title="I know this is tough, but you have to decide one!"
>
<label>
<input
type="radio"
name="indentation"
value="spaces"
checked={prefs.indentWith === 'spaces'}
onChange={this.updateSetting.bind(this)}
data-setting="indentWith"
/>{' '}
Spaces
</label>
<label class="ml-1">
<input
type="radio"
name="indentation"
value="tabs"
checked={prefs.indentWith === 'tabs'}
onChange={this.updateSetting.bind(this)}
data-setting="indentWith"
/>{' '}
Tabs
</label>
</div>
<label class="line" title="">
Indentation Size{' '}
<input
type="range"
class="va-m ml-1"
value={prefs.indentSize}
min="1"
max="7"
list="indentationSizeList"
data-setting="indentSize"
onChange={this.updateSetting.bind(this)}
/>
<span id="indentationSizeValueEl">{prefs.indentSize}</span>
<datalist id="indentationSizeList">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
<option>6</option>
<option>7</option>
</datalist>
</label>
<hr />
<h3>Editor</h3>
<div class="flex block--mobile">
<div>
<label class="line">Default Preprocessors</label>
<div class="flex line">
<select
style="flex:1;margin-left:20px"
data-setting="htmlMode"
value={prefs.htmlMode}
onChange={this.updateSetting.bind(this)}
>
<option value="html">HTML</option>
<option value="markdown">Markdown</option>
<option value="jade">Pug</option>
</select>
<select
style="flex:1;margin-left:20px"
data-setting="cssMode"
value={prefs.cssMode}
onChange={this.updateSetting.bind(this)}
>
<option value="css">CSS</option>
<option value="scss">SCSS</option>
<option value="sass">SASS</option>
<option value="less">LESS</option>
<option value="stylus">Stylus</option>
<option value="acss">Atomic CSS</option>
</select>
<select
style="flex:1;margin-left:20px"
data-setting="jsMode"
value={prefs.jsMode}
onChange={this.updateSetting.bind(this)}
>
<option value="js">JS</option>
<option value="coffee">CoffeeScript</option>
<option value="es6">ES6 (Babel)</option>
<option value="typescript">TypeScript</option>
</select>
</div>
<label class="line">
Theme
<select
style="flex:1;margin:0 20px"
data-setting="editorTheme"
value={prefs.editorTheme}
onChange={this.updateSetting.bind(this)}
>
{editorThemes.map(theme => (
<option value={theme}>{theme}</option>
))}
</select>
</label>
<label class="line">
Font
<select
style="flex:1;margin:0 20px"
data-setting="editorFont"
value={prefs.editorFont}
onChange={this.updateSetting.bind(this)}
>
<option value="FiraCode">Fira Code</option>
<option value="Inconsolata">Inconsolata</option>
<option value="Monoid">Monoid</option>
<option value="FixedSys">FixedSys</option>
<option disabled="disabled">----</option>
<option value="other">Other font from system</option>
</select>
{prefs.editorFont === 'other' && (
<input
id="customEditorFontInput"
type="text"
value={prefs.editorCustomFont}
placeholder="Custom font name here"
data-setting="editorCustomFont"
onChange={this.updateSetting.bind(this)}
/>
)}
</label>
<label class="line">
Font Size{' '}
<input
style="width:70px"
type="number"
value={prefs.fontSize}
data-setting="fontSize"
onChange={this.updateSetting.bind(this)}
/>{' '}
px
</label>
<div class="line">
Key bindings
<label class="ml-1">
<input
type="radio"
name="keymap"
value="sublime"
checked={prefs.keymap === 'sublime'}
data-setting="keymap"
onChange={this.updateSetting.bind(this)}
/>{' '}
Sublime
</label>
<label class="ml-1">
<input
type="radio"
name="keymap"
value="vim"
checked={prefs.keymap === 'vim'}
data-setting="keymap"
onChange={this.updateSetting.bind(this)}
/>{' '}
Vim
</label>
</div>
</div>
<div class="ml-2 ml-0--mobile">
<Tabs>
<TabPanel label="General">
<CheckboxSetting
name="lineWrap"
title="Toggle wrapping of long sentences onto new line"
label="Line wrap"
pref={prefs.lineWrap}
onChange={this.updateSetting.bind(this)}
/>
<CheckboxSetting
name="refreshOnResize"
title="Your Preview will refresh when you resize the preview split"
label="Refresh preview on resize"
pref={prefs.refreshOnResize}
onChange={this.updateSetting.bind(this)}
/>
<CheckboxSetting
name="autoComplete"
title="Turns on the auto-completion suggestions as you type"
label="Auto-complete suggestions"
pref={prefs.autoComplete}
onChange={this.updateSetting.bind(this)}
/>
<CheckboxSetting
name="autoPreview"
title="Refreshes the preview as you code. Otherwise use the Run button"
label="Auto-preview"
pref={prefs.autoPreview}
onChange={this.updateSetting.bind(this)}
/>
<CheckboxSetting
name="autoSave"
title="Auto-save keeps saving your code at regular intervals after you hit the first save manually"
label="Auto-save"
pref={prefs.autoSave}
onChange={this.updateSetting.bind(this)}
/>
<CheckboxSetting
name="preserveLastCode"
title="Loads the last open creation when app starts"
label="Preserve last written code"
pref={prefs.preserveLastCode}
onChange={this.updateSetting.bind(this)}
onChange={e => this.updateSetting(e, 'preserveLastCode')}
/>
<p class="help-text">
Loads the last open creation when app starts
</p>
<Divider />
<CheckboxSetting
name="replaceNewTab"
title="Turning this on will start showing Web Maker in every new tab you open"
label="Replace new tab page"
pref={prefs.replaceNewTab}
onChange={this.updateSetting.bind(this)}
showWhenExtension
/>
<CheckboxSetting
name="preserveConsoleLogs"
title="Preserves the console logs across your preview refreshes"
label="Preserve console logs"
pref={prefs.preserveConsoleLogs}
onChange={this.updateSetting.bind(this)}
/>
<CheckboxSetting
name="lightVersion"
title="Switch to lighter version for better performance. Removes things like blur etc."
label="Fast/light version"
pref={prefs.lightVersion}
onChange={this.updateSetting.bind(this)}
onChange={e => this.updateSetting(e, 'lightVersion')}
/>
</div>
</div>
<p class="help-text">
Switch to lighter version for better performance. Removes things
like blur etc.
</p>
<Divider />
<CheckboxSetting
label="Auto-preview"
pref={prefs.autoPreview}
onChange={e => this.updateSetting(e, 'autoPreview')}
/>
<p class="help-text">
Refreshes the preview as you code. Otherwise use the 'Run' button
</p>
<Divider />
<CheckboxSetting
label="Auto-save"
pref={prefs.autoSave}
onChange={e => this.updateSetting(e, 'autoSave')}
/>
<p class="help-text">
Auto-save keeps saving your code at regular intervals after you
hit save manually the first time
</p>
<Divider />
<CheckboxSetting
label="Refresh preview on resize"
pref={prefs.refreshOnResize}
onChange={e => this.updateSetting(e, 'refreshOnResize')}
/>
<p class="help-text">
Preview will refresh when you resize the preview pane
</p>
<div class="show-when-extension">
<Divider />
<CheckboxSetting
label="Replace new tab page"
pref={prefs.replaceNewTab}
onChange={e => this.updateSetting(e, 'replaceNewTab')}
showWhenExtension
/>
<p class="help-text">
Turning this on will start showing Web Maker in every new tab
you open
</p>
</div>
<Divider />
<CheckboxSetting
label="Preserve console logs"
pref={prefs.preserveConsoleLogs}
onChange={e => this.updateSetting(e, 'preserveConsoleLogs')}
/>
<p class="help-text">
Preserves the console logs across your preview refreshes
</p>
</TabPanel>
<TabPanel label="Indentation">
<div class="line">
<div>
<label>
<input
type="radio"
name="indentation"
value="spaces"
checked={prefs.indentWith === 'spaces'}
onChange={e => this.updateSetting(e, 'indentWith')}
/>{' '}
Spaces
</label>
<label class="ml-1">
<input
type="radio"
name="indentation"
value="tabs"
checked={prefs.indentWith === 'tabs'}
onChange={e => this.updateSetting(e, 'indentWith')}
/>{' '}
Tabs
</label>
</div>
</div>
<label class="line" title="">
Indentation Size
<div>
<input
type="range"
class="va-m ml-1"
value={prefs.indentSize}
min="1"
max="7"
list="indentationSizeList"
onChange={e => this.updateSetting(e, 'indentSize')}
/>
<span id="indentationSizeValueEl">{prefs.indentSize}</span>
<datalist id="indentationSizeList">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
<option>6</option>
<option>7</option>
</datalist>
</div>
</label>
</TabPanel>
<TabPanel label="Editor">
<div>
<div>
<div class="line">
<span>Default Preprocessors</span>
<div class="flex">
<select
aria-label="Default HTML preprocessor"
style="flex:1;margin-left:20px"
value={prefs.htmlMode}
onChange={e => this.updateSetting(e, 'htmlMode')}
>
<option value="html">HTML</option>
<option value="markdown">Markdown</option>
<option value="jade">Pug</option>
</select>
<select
aria-label="Default CSS preprocessor"
style="flex:1;margin-left:20px"
value={prefs.cssMode}
onChange={e => this.updateSetting(e, 'cssMode')}
>
<option value="css">CSS</option>
<option value="scss">SCSS</option>
<option value="sass">SASS</option>
<option value="less">LESS</option>
<option value="stylus">Stylus</option>
<option value="acss">Atomic CSS</option>
</select>
<select
aria-label="Default JavaScript preprocessor"
style="flex:1;margin-left:20px"
value={prefs.jsMode}
onChange={e => this.updateSetting(e, 'jsMode')}
>
<option value="js">JS</option>
<option value="coffee">CoffeeScript</option>
<option value="es6">ES6 (Babel)</option>
<option value="typescript">TypeScript</option>
</select>
</div>
</div>
<Divider />
<label class="line">
Theme
<div>
<select
value={prefs.editorTheme}
onChange={e => this.updateSetting(e, 'editorTheme')}
>
{editorThemes.map(theme => (
<option value={theme}>{theme}</option>
))}
</select>
</div>
</label>
<Divider />
<hr />
<label class="line">
Font
<div>
<select
value={prefs.editorFont}
onChange={e => this.updateSetting(e, 'editorFont')}
>
<option value="FiraCode">Fira Code</option>
<option value="Inconsolata">Inconsolata</option>
<option value="Monoid">Monoid</option>
<option value="FixedSys">FixedSys</option>
<option disabled="disabled">----</option>
<option value="other">Other font from system</option>
</select>
{prefs.editorFont === 'other' && (
<input
style="margin-left:20px"
id="customEditorFontInput"
type="text"
value={prefs.editorCustomFont}
placeholder="Custom font name here"
onChange={e =>
this.updateSetting(e, 'editorCustomFont')
}
/>
)}
</div>
</label>
<Divider />
<h3>Fun</h3>
<p>
<CheckboxSetting
title="Enjoy wonderful particle blasts while you type"
label="Code blast!"
name="isCodeBlastOn"
pref={prefs.isCodeBlastOn}
onChange={this.updateSetting.bind(this)}
/>
<label class="line">
Font Size
<div>
<input
style="width:70px"
type="number"
value={prefs.fontSize}
onChange={e => this.updateSetting(e, 'fontSize')}
/>{' '}
px
</div>
</label>
<Divider />
<CheckboxSetting
title="Get ready to build some games at JS13KGames"
label="Js13kGames Mode"
name="isJs13kModeOn"
pref={prefs.isJs13kModeOn}
onChange={this.updateSetting.bind(this)}
/>
</p>
<div class="line">
Key bindings
<div>
<label class="ml-1">
<input
type="radio"
name="keymap"
value="sublime"
checked={prefs.keymap === 'sublime'}
onChange={e => this.updateSetting(e, 'keymap')}
/>{' '}
Sublime
</label>
<label class="ml-1">
<input
type="radio"
name="keymap"
value="vim"
checked={prefs.keymap === 'vim'}
onChange={e => this.updateSetting(e, 'keymap')}
/>{' '}
Vim
</label>
</div>
</div>
</div>
<Divider />
<hr />
<div class="flex-grow">
<CheckboxSetting
title="Toggle wrapping of long sentences onto new line"
label="Line wrap"
pref={prefs.lineWrap}
onChange={e => this.updateSetting(e, 'lineWrap')}
/>
<Divider />
<h3>Advanced</h3>
<p>
<label
class="line"
title="This timeout is used to indentify a possible infinite loop and prevent it."
>
Maximum time allowed in a loop iteration{' '}
<input
type="number"
value={prefs.infiniteLoopTimeout}
data-setting="infiniteLoopTimeout"
onChange={this.updateSetting.bind(this)}
/>{' '}
ms
</label>
<div class="help-text">
If any loop iteration takes more than the defined time, it is
detected as a potential infinite loop and further iterations are
stopped.
</div>
</p>
<CheckboxSetting
title="Turns on the auto-completion suggestions as you type"
label="Auto-complete suggestions"
pref={prefs.autoComplete}
onChange={e => this.updateSetting(e, 'autoComplete')}
/>
</div>
</div>
</TabPanel>
<TabPanel label="Fun">
<CheckboxSetting
label="Code blast!"
pref={prefs.isCodeBlastOn}
onChange={e => this.updateSetting(e, 'isCodeBlastOn')}
/>
<p class="help-text">
Enjoy wonderful particle blasts while you type
</p>
<Divider />
<CheckboxSetting
label="Js13kGames Mode"
pref={prefs.isJs13kModeOn}
onChange={e => this.updateSetting(e, 'isJs13kModeOn')}
/>
<p class="help-text">
Make the app ready to build some games for{' '}
<a href="https://js13kgames.com/" target="_blank" rel="noopener">
Js13kGames
</a>.
</p>
</TabPanel>
<TabPanel label="Advanced">
<div>
<label class="line">
Maximum time allowed in a loop iteration
<div>
<input
type="number"
style="width:120px"
value={prefs.infiniteLoopTimeout}
onChange={e => this.updateSetting(e, 'infiniteLoopTimeout')}
/>{' '}
ms
</div>
</label>
<p class="help-text">
If any loop iteration takes more than the defined time, it is
detected as a potential infinite loop and further iterations are
stopped.
</p>
</div>
<Divider />
<p>
<label class="line">
Language
<select
data-setting="lang"
value={prefs.lang}
onChange={this.updateSetting.bind(this)}
>
<option value="en">English</option>
<option value="hi">Hindi</option>
<option value="ja">Japanese</option>
<option value="sa">Sanskrit</option>
</select>
</label>
</p>
<div>
<label class="line">
Language
<select
value={prefs.lang}
onChange={e => this.updateSetting(e, 'lang')}
>
<option value="en">English</option>
<option value="hi">Hindi</option>
<option value="ja">Japanese</option>
<option value="sa">Sanskrit</option>
</select>
</label>
</div>
</TabPanel>
</Tabs>
</div>
);
}

35
src/components/Switch.jsx Normal file
View File

@ -0,0 +1,35 @@
import { h, Component } from 'preact';
export default class Switch extends Component {
render() {
return (
<label class="check-switch">
<input
role="switch"
type="checkbox"
checked={this.props.checked}
onChange={this.props.onChange}
/>
<div class="check-switch__toggle-wrap">
<span
aria-hidden="true"
class="check-switch__status"
style={`visibility:${this.props.checked ? 'hidden' : 'visible'}`}
>
Off
</span>
<span aria-hidden="true" class="check-switch__toggle" />
<span
aria-hidden="true"
class="check-switch__status"
style={`visibility:${this.props.checked ? 'visible' : 'hidden'}`}
>
On
</span>
</div>
{this.props.children}
</label>
);
}
}

89
src/components/Tabs.jsx Normal file
View File

@ -0,0 +1,89 @@
import { h, Component } from 'preact';
function hyphenate(text) {
return text.replace(/\s/g, '-');
}
const ID_PREFIX = 'tab-panel-';
export function TabPanel({ label }) {
return (
<div
class="tabs__tabpanel"
role="tabpanel"
id={`${ID_PREFIX}${hyphenate(label)}`}
>
{this.props.children}
</div>
);
}
function Tab({ label, isSelected, onKeyUp, onClick }) {
return (
<button
class={`tabs__tab ${isSelected ? 'tabs__tab--selected' : ''}`}
role="tab"
tabindex={isSelected ? null : -1}
aria-selected={`${isSelected}`}
aria-controls={`${ID_PREFIX}${hyphenate(label)}`}
onKeyUp={onKeyUp}
onClick={onClick}
>
{label}
</button>
);
}
export default class Tabs extends Component {
constructor(props) {
super(props);
this.state = {
selectedTab: 0
};
}
isSelected(index) {
return this.state.selectedTab === index;
}
switchTab(selectedTab) {
this.setState({ selectedTab: selectedTab });
this.tabListEl.querySelectorAll('[role=tab]')[selectedTab].focus();
}
keyUpHandler(e) {
let { selectedTab } = this.state;
if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
selectedTab--;
selectedTab =
selectedTab < 0 ? this.props.children.length - 1 : selectedTab;
this.switchTab(selectedTab);
e.preventDefault();
} else if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
selectedTab++;
selectedTab %= this.props.children.length;
this.switchTab(selectedTab);
e.preventDefault();
}
}
render() {
const tabs = this.props.children;
return (
<div class="tabs">
<div
class="tabs__tablist"
role="tablist"
ref={el => (this.tabListEl = el)}
>
{tabs.map((child, index) => (
<Tab
isSelected={this.isSelected(index)}
label={child.props.label}
onKeyUp={this.keyUpHandler.bind(this)}
onClick={() => this.setState({ selectedTab: index })}
/>
))}
</div>
<div class="tabs__tabpanel-wrap">
{tabs.map(
(child, index) => (this.state.selectedTab === index ? child : null)
)}
</div>
</div>
);
}
}

View File

@ -887,15 +887,14 @@ export default class App extends Component {
/**
* Handles all user triggered preference changes in the UI.
*/
updateSetting(e) {
updateSetting(settingName, value) {
// If this was triggered from user interaction, save the setting
if (e) {
var settingName = e.target.dataset.setting;
if (settingName) {
// var settingName = e.target.dataset.setting;
var obj = {};
var el = e.target;
log(settingName, el.type === 'checkbox' ? el.checked : el.value);
log(settingName, value);
const prefs = { ...this.state.prefs };
prefs[settingName] = el.type === 'checkbox' ? el.checked : el.value;
prefs[settingName] = value;
obj[settingName] = prefs[settingName];
this.setState({ prefs });

View File

@ -32,3 +32,7 @@ export function AutoFocusInput(props) {
<input ref={el => el && setTimeout(() => el.focus(), 100)} {...props} />
);
}
export function Divider(props) {
return <div class="divider" />;
}

View File

@ -212,6 +212,11 @@ hr {
label {
cursor: pointer;
}
.divider {
margin: 10px 0;
height: 1px;
background: rgba(255, 255, 255, 0.1);
}
[class*='hint--']:after {
text-transform: none;
@ -221,8 +226,10 @@ label {
}
.line {
display: block;
/* display: block; */
margin-bottom: 1em;
display: flex;
justify-content: space-between;
}
.caret {
@ -249,6 +256,86 @@ a > svg {
bottom: 0;
}
.check-switch {
display: block;
overflow: hidden;
position: relative;
/* breathing space for outline */
padding: 2px 0;
}
.check-switch:hover {
background: rgba(0, 0, 0, 0.1);
}
.check-switch .check-switch__toggle {
position: relative;
margin: 0 5px;
}
.check-switch .check-switch__toggle:after {
background: #fff;
position: absolute;
border-radius: 100%;
width: 1.1em;
height: 1.1em;
top: 3px;
right: 1.5em;
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.5);
transition: right 0.1825s ease-in-out;
}
.check-switch .check-switch__toggle:before,
.check-switch .check-switch__toggle:after {
content: '';
display: block;
}
.check-switch .check-switch__toggle:before {
background: rgba(255, 255, 255, 0.2);
border-radius: 1.75em;
width: 2.75em;
height: 1.45em;
right: 0.25em;
transition: background 0.2s ease-in-out;
}
.check-switch input:not([role='button']) {
pointer-events: none;
}
.check-switch input {
top: 0;
left: 0;
opacity: 0.0001;
position: absolute;
}
.check-switch__status {
text-transform: uppercase;
font-size: 14px;
font-weight: 600;
}
.check-switch input:focus + * .check-switch__toggle:before {
outline: 2px solid;
outline-color: var(--color-focus-outline);
}
.check-switch input:checked + * .check-switch__toggle:after {
right: 0.15em;
left: auto;
}
.check-switch input:checked + * .check-switch__toggle:before {
background: #61ad1c;
}
.check-switch__toggle-wrap {
float: right;
display: flex;
align-items: center;
}
.btn {
display: inline-block;
color: var(--color-button);
@ -907,10 +994,9 @@ body > #demo-frame {
/* Make settings modal smaller */
@media screen and (min-width: 600px) {
.modal--settings {
/* width: 600px; */
/* margin-lef.t: -300px; */
@media screen and (min-width: 850px) {
.modal--settings > .modal__content {
width: 850px;
}
}
@ -1577,6 +1663,7 @@ body:not(.is-app) .show-when-app {
.help-text {
font-size: 0.9em;
color: rgba(255, 255, 255, 0.5);
margin: 5px 0;
}
.social-login-btn:after,
@ -1822,6 +1909,29 @@ while the theme CSS file is loading */
padding: 5px;
z-index: 1;
}
.tabs {
display: flex;
}
.tabs__tablist {
margin-right: 40px;
flex-shrink: 0;
}
.tabs__tab {
display: block;
margin-bottom: 10px;
background: transparent;
border: 0;
text-align: left;
width: 100%;
color: inherit;
}
.tabs__tab--selected {
background-color: rgba(0, 0, 0, 0.5);
}
.tabs__tabpanel-wrap {
flex: 1;
}
@media screen and (max-width: 600px) {
body {
font-size: 70%;