feat(themes): theme builder app

This commit is contained in:
Adam Bradley
2017-12-21 12:04:33 -06:00
parent 884836dfd4
commit 76afd9d77f
24 changed files with 1148 additions and 0 deletions

1
.gitignore vendored
View File

@@ -18,6 +18,7 @@ dist/
node_modules/
tmp/
temp/
packages/core/theme-builder/
$RECYCLE.BIN/
.DS_Store

View File

@@ -15,6 +15,7 @@
"@types/jest": "^21.1.6",
"chai": "^4.1.2",
"chromedriver": "^2.33.2",
"glob": "^7.1.2",
"ionicons": "4.0.0-11",
"jest": "^21.2.1",
"mocha": "^4.0.1",
@@ -36,6 +37,9 @@
"clean": "rm -rf dist",
"lint": "npm run tslint & npm run sass-lint",
"sass-lint": "sass-lint -v -q",
"theme-app-build": "stencil build --dev --config scripts/theme-builder/stencil.config.js",
"theme-server": "node scripts/theme-builder/server.js",
"theme-builder": "npm run theme-app-build && sd concurrent \"stencil build --dev --watch\" \"stencil-dev-server\" \"npm run theme-server\" ",
"tslint": "tslint --project .",
"tslint-fix": "tslint --project . --fix",
"validate": "npm run clean && npm run lint && npm run test && npm run build",

View File

@@ -0,0 +1,3 @@
# Theme Builder
npm run theme-builder

View File

@@ -0,0 +1,141 @@
const http = require('http');
const url = require('url');
const glob = require('glob');
const fs = require('fs');
const path = require('path');
const port = 5454;
const componentsPath = '/src/components/';
const cssPath = '/src/themes/css/';
const srcComponentsDir = path.join(__dirname, '../../', componentsPath);
const srcCssDir = path.join(__dirname, '../../', cssPath);
function requestHandler(request, response) {
const parsedUrl = url.parse(request.url, true);
response.setHeader('Access-Control-Allow-Origin', '*');
response.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
if (parsedUrl.pathname === '/data') {
requestDataHandler(response);
} else if (parsedUrl.pathname === '/save-css') {
requestSaveCssHandler(parsedUrl, response);
} else if (parsedUrl.pathname === '/delete-css') {
requestDeleteCssHandler(parsedUrl, response);
} else {
response.end();
}
}
function requestDataHandler(response) {
try {
const demoPaths = glob.sync('**/index.html', {
cwd: srcComponentsDir
});
const demos = demoPaths.map(demo => {
return {
name: demo.toLowerCase()
.replace(/\\/g, ' ')
.replace(/\//g, ' ')
.replace(/ test/g, '')
.replace(/ index.html/g, ''),
url: componentsPath + demo.replace(/\\/g, '/')
};
}).sort((a, b) => {
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1;
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1;
return 0
});
const themePaths = glob.sync('**/*.css', {
cwd: srcCssDir
});
const themes = themePaths.map(theme => {
return {
name: theme.replace(/.css/g, '')
};
}).sort((a, b) => {
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1;
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1;
return 0
});
const data = JSON.stringify({
demos: demos,
themes: themes
}, null, 2);
response.end(data, 'utf8');
} catch (e) {
console.log(e);
response.end('err: ' + e);
}
}
function requestSaveCssHandler(parsedUrl, response) {
try {
const theme = (parsedUrl.query.theme || '').toLowerCase().trim();
if (!theme) {
response.end('missing theme querystring');
return;
}
const filePath = path.join(srcCssDir, theme + '.css');
const css = parsedUrl.query.css || '';
fs.writeFileSync(filePath, css, { encoding: 'utf8' });
console.log('css saved!', filePath);
response.end('css saved! ' + filePath, 'utf8');
} catch (e) {
console.log(e);
response.end('err: ' + e);
}
}
function requestDeleteCssHandler(parsedUrl, response) {
try {
const theme = (parsedUrl.query.theme || '').toLowerCase().trim();
if (!theme) {
response.end('missing theme querystring');
return;
}
const filePath = path.join(srcCssDir, theme + '.css');
fs.unlinkSync(filePath);
console.log('css deleted!', filePath);
response.end('css deleted! ' + filePath, 'utf8');
} catch (e) {
console.log(e);
response.end('err: ' + e);
}
}
const server = http.createServer(requestHandler);
server.listen(port, (err) => {
if (err) {
return console.log(__filename, err);
}
console.log(`theme server: http://localhost:${port}/`);
});

View File

@@ -0,0 +1,193 @@
/**
* This is an autogenerated file created by the Stencil build process.
* It contains typing information for all components that exist in this project
* and imports for stencil collections that might be configured in your stencil.config.js file
*/
import {
AppPreview as AppPreview
} from './components/app-preview/app-preview';
declare global {
interface HTMLAppPreviewElement extends AppPreview, HTMLElement {
}
var HTMLAppPreviewElement: {
prototype: HTMLAppPreviewElement;
new (): HTMLAppPreviewElement;
};
interface HTMLElementTagNameMap {
"app-preview": HTMLAppPreviewElement;
}
interface ElementTagNameMap {
"app-preview": HTMLAppPreviewElement;
}
namespace JSX {
interface IntrinsicElements {
"app-preview": JSXElements.AppPreviewAttributes;
}
}
namespace JSXElements {
export interface AppPreviewAttributes extends HTMLAttributes {
cssText?: string;
demoMode?: string;
demoUrl?: string;
}
}
}
import {
ColorSelector as ColorSelector
} from './components/color-selector/color-selector';
declare global {
interface HTMLColorSelectorElement extends ColorSelector, HTMLElement {
}
var HTMLColorSelectorElement: {
prototype: HTMLColorSelectorElement;
new (): HTMLColorSelectorElement;
};
interface HTMLElementTagNameMap {
"color-selector": HTMLColorSelectorElement;
}
interface ElementTagNameMap {
"color-selector": HTMLColorSelectorElement;
}
namespace JSX {
interface IntrinsicElements {
"color-selector": JSXElements.ColorSelectorAttributes;
}
}
namespace JSXElements {
export interface ColorSelectorAttributes extends HTMLAttributes {
isRgb?: boolean;
property?: string;
value?: string;
}
}
}
import {
CssText as CssText
} from './components/css-text/css-text';
declare global {
interface HTMLCssTextElement extends CssText, HTMLElement {
}
var HTMLCssTextElement: {
prototype: HTMLCssTextElement;
new (): HTMLCssTextElement;
};
interface HTMLElementTagNameMap {
"css-text": HTMLCssTextElement;
}
interface ElementTagNameMap {
"css-text": HTMLCssTextElement;
}
namespace JSX {
interface IntrinsicElements {
"css-text": JSXElements.CssTextAttributes;
}
}
namespace JSXElements {
export interface CssTextAttributes extends HTMLAttributes {
cssText?: string;
themeName?: string;
}
}
}
import {
DemoSelection as DemoSelection
} from './components/demo-selection/demo-selection';
declare global {
interface HTMLDemoSelectionElement extends DemoSelection, HTMLElement {
}
var HTMLDemoSelectionElement: {
prototype: HTMLDemoSelectionElement;
new (): HTMLDemoSelectionElement;
};
interface HTMLElementTagNameMap {
"demo-selection": HTMLDemoSelectionElement;
}
interface ElementTagNameMap {
"demo-selection": HTMLDemoSelectionElement;
}
namespace JSX {
interface IntrinsicElements {
"demo-selection": JSXElements.DemoSelectionAttributes;
}
}
namespace JSXElements {
export interface DemoSelectionAttributes extends HTMLAttributes {
demoData?: { name: string, url: string }[];
demoMode?: string;
demoUrl?: string;
}
}
}
import {
ThemeBuilder as ThemeBuilder
} from './components/theme-builder/theme-builder';
declare global {
interface HTMLThemeBuilderElement extends ThemeBuilder, HTMLElement {
}
var HTMLThemeBuilderElement: {
prototype: HTMLThemeBuilderElement;
new (): HTMLThemeBuilderElement;
};
interface HTMLElementTagNameMap {
"theme-builder": HTMLThemeBuilderElement;
}
interface ElementTagNameMap {
"theme-builder": HTMLThemeBuilderElement;
}
namespace JSX {
interface IntrinsicElements {
"theme-builder": JSXElements.ThemeBuilderAttributes;
}
}
namespace JSXElements {
export interface ThemeBuilderAttributes extends HTMLAttributes {
}
}
}
import {
ThemeSelector as ThemeSelector
} from './components/theme-selector/theme-selector';
declare global {
interface HTMLThemeSelectorElement extends ThemeSelector, HTMLElement {
}
var HTMLThemeSelectorElement: {
prototype: HTMLThemeSelectorElement;
new (): HTMLThemeSelectorElement;
};
interface HTMLElementTagNameMap {
"theme-selector": HTMLThemeSelectorElement;
}
interface ElementTagNameMap {
"theme-selector": HTMLThemeSelectorElement;
}
namespace JSX {
interface IntrinsicElements {
"theme-selector": JSXElements.ThemeSelectorAttributes;
}
}
namespace JSXElements {
export interface ThemeSelectorAttributes extends HTMLAttributes {
themeData?: { name: string }[];
}
}
}

View File

@@ -0,0 +1,15 @@
div {
box-sizing: border-box;
width: 100%;
height: calc(100% - 20px);
padding: 10px;
}
iframe {
box-sizing: border-box;
display: block;
width: 100%;
height: 100%;
border: 1px solid gray;
}

View File

@@ -0,0 +1,52 @@
import { Component, Prop, PropDidChange } from '@stencil/core';
@Component({
tag: 'app-preview',
styleUrl: 'app-preview.css',
shadow: true
})
export class AppPreview {
@Prop() demoUrl: string;
@Prop() demoMode: string;
@Prop() cssText: string;
iframe: HTMLIFrameElement;
@PropDidChange('cssText')
onCssTextChange() {
console.log('AppPreview onCssTextChange');
this.applyStyles();
}
applyStyles() {
if (this.iframe && this.iframe.contentDocument && this.iframe.contentDocument.documentElement) {
const iframeDoc = this.iframe.contentDocument;
const themerStyleId = 'themer-style';
let themerStyle: HTMLStyleElement = iframeDoc.getElementById(themerStyleId) as any;
if (!themerStyle) {
themerStyle = iframeDoc.createElement('style');
themerStyle.id = themerStyleId;
iframeDoc.documentElement.appendChild(themerStyle);
}
themerStyle.innerHTML = this.cssText;
}
}
onIframeLoad() {
this.applyStyles();
}
render() {
const url = `${this.demoUrl}?ionicplatform=${this.demoMode}`;
return [
<div>
<iframe src={url} ref={elm => this.iframe = elm as any} onLoad={this.onIframeLoad.bind(this)}></iframe>
</div>
];
}
}

View File

@@ -0,0 +1,35 @@
section {
display: flex;
}
.property-label {
font-family: Courier New, Courier, monospace;
white-space: nowrap;
flex: 1;
padding-left: 10px;
}
input[type="text"] {
font-family: Courier New, Courier, monospace;
font-size: 14px;
width: 150px;
}
input[type="color"] {
-webkit-appearance: none;
border: none;
width: 64px;
height: 20px;
outline: none;
background: transparent;
}
input[type="color"]::-webkit-color-swatch-wrapper {
padding: 0;
}
input[type="color"]::-webkit-color-swatch {
border: 1px solid black;
}

View File

@@ -0,0 +1,77 @@
import { Component, Event, EventEmitter, Prop } from '@stencil/core';
@Component({
tag: 'color-selector',
styleUrl: 'color-selector.css',
shadow: true
})
export class ColorSelector {
@Prop() property: string;
@Prop({ mutable: true }) value: string;
@Prop() isRgb: boolean;
onChange(ev) {
if (this.isRgb) {
this.value = hexToRgb(ev.currentTarget.value);
} else {
this.value = ev.currentTarget.value;
}
this.colorChange.emit({
property: this.property,
value: this.value
});
}
@Event() colorChange: EventEmitter;
render() {
const value = this.value.trim().toLowerCase();
const hex = rgbToHex(value);
return [
<section>
<div class='color-square'>
<input type='color' value={hex} onInput={this.onChange.bind(this)} tabindex='-1' />
</div>
<div class='color-value'>
<input type='text' value={value} onInput={this.onChange.bind(this)} />
</div>
<div class='property-label'>
{this.property}
</div>
</section>
];
}
}
function rgbToHex(value: string) {
if (value.indexOf('rgb') === -1) {
return value;
}
var c = value.replace(/[\sa-z\(\);]+/gi, '').split(',');
c = c.map(s => parseInt(s, 10).toString(16).replace(/^([a-z\d])$/i, '0$1'));
return '#' + c[0] + c[1] + c[2];
}
function hexToRgb(c: any) {
if (c.indexOf('#') === -1) {
return c;
}
c = c.replace(/#/, '');
c = c.length % 6 ? c.replace(/(.)(.)(.)/, '$1$1$2$2$3$3') : c;
c = parseInt(c, 16);
var a = parseFloat(a) || null;
const r = (c >> 16) & 255;
const g = (c >> 8) & 255;
const b = (c >> 0) & 255;
return `rgb${a ? 'a' : ''}(${[r, g, b, a].join().replace(/,$/, '')})`;
}

View File

@@ -0,0 +1,20 @@
h1 {
margin: 10px 0 0 0;
padding: 0;
font-size: 14px;
}
textarea {
margin: 12px 0 0 0;
min-width: 280px;
height: 500px;
font-family: Courier New, Courier, monospace;
}
button {
margin: 10px 10px 0 0;
padding: 10px 15px;
font-size: 14px;
cursor: pointer;
}

View File

@@ -0,0 +1,87 @@
import { Component, Prop } from '@stencil/core';
import { STORED_THEME_KEY, deleteCssUrl, getThemeUrl, saveCssUrl } from '../../theme-variables';
@Component({
tag: 'css-text',
styleUrl: 'css-text.css',
shadow: true
})
export class CssText {
@Prop() themeName: string;
@Prop() cssText: string;
submitUpdate(ev: UIEvent) {
ev.stopPropagation();
ev.preventDefault();
this.saveCss(this.themeName, this.cssText);
}
saveCss(themeName: string, cssText: string) {
const url = saveCssUrl(themeName, cssText);
fetch(url).then(rsp => {
return rsp.text().then(txt => {
console.log('theme server response:', txt);
});
}).catch(err => {
console.log(err);
});
}
createNew(ev: UIEvent) {
ev.stopPropagation();
ev.preventDefault();
const name = prompt(`New theme name:`);
if (name) {
const themeName = name.split('.')[0].trim().toLowerCase();
if (themeName.length) {
console.log('createNew themeName', themeName);
localStorage.setItem(STORED_THEME_KEY, themeName);
this.saveCss(themeName, this.cssText);
}
}
}
deleteTheme(ev: UIEvent) {
ev.stopPropagation();
ev.preventDefault();
const shouldDelete = confirm(`Sure you want to delete "${this.themeName}"?`);
if (shouldDelete) {
const url = deleteCssUrl(this.themeName);
fetch(url).then(rsp => {
return rsp.text().then(txt => {
console.log('theme server response:', txt);
});
}).catch(err => {
console.log(err);
});
localStorage.removeItem(STORED_THEME_KEY);
}
}
render() {
return [
<h1>
{getThemeUrl(this.themeName)}
</h1>,
<div>
<textarea readOnly spellcheck='false'>{this.cssText}</textarea>
</div>,
<div>
<button type='button' onClick={this.submitUpdate.bind(this)}>Save Theme</button>
<button type='button' onClick={this.createNew.bind(this)}>Create</button>
<button type='button' onClick={this.deleteTheme.bind(this)}>Delete</button>
</div>
];
}
}

View File

@@ -0,0 +1,4 @@
select {
margin: 10px 0 0px 10px;
}

View File

@@ -0,0 +1,41 @@
import { Component, Event, EventEmitter, Prop } from '@stencil/core';
@Component({
tag: 'demo-selection',
styleUrl: 'demo-selection.css',
shadow: true
})
export class DemoSelection {
@Prop() demoData: { name: string, url: string }[];
@Prop() demoUrl: string;
@Prop() demoMode: string;
@Event() demoUrlChange: EventEmitter;
@Event() demoModeChange: EventEmitter;
onChangeUrl(ev) {
this.demoUrlChange.emit(ev.currentTarget.value);
}
onChangeMode(ev) {
this.demoModeChange.emit(ev.currentTarget.value);
}
render() {
return [
<div>
<select onChange={this.onChangeUrl.bind(this)}>
{this.demoData.map(d => <option value={d.url} selected={d.url === this.demoUrl}>{d.name}</option>)}
</select>
<select onChange={this.onChangeMode.bind(this)}>
<option value='md' selected={'md' === this.demoMode}>md</option>
<option value='ios' selected={'ios' === this.demoMode}>ios</option>
</select>
</div>
];
}
}

View File

@@ -0,0 +1,18 @@
main {
display: flex;
height: calc(100% - 10px);
}
main > section {
flex: 1 auto;
}
.preview-column {
max-width: 480px;
min-height: 700px;
}
.selector-column {
max-width: 600px;
}

View File

@@ -0,0 +1,84 @@
import { Component, Listen, State } from '@stencil/core';
import { DATA_URL, STORED_DEMO_MODE_KEY, STORED_DEMO_URL_KEY } from '../../theme-variables';
@Component({
tag: 'theme-builder',
styleUrl: 'theme-builder.css',
shadow: true
})
export class ThemeBuilder {
demoData: { name: string, url: string }[];
themeData: { name: string }[];
@State() demoUrl: string;
@State() demoMode: string;
@State() cssText: string = '';
@State() themeName: string = '';
componentWillLoad() {
return fetch(DATA_URL).then(rsp => {
return rsp.json().then(data => {
this.demoData = data.demos;
this.themeData = data.themes;
this.initUrl();
});
}).catch(err => {
console.log('ThemeBuilder componentWillLoad', err);
});
}
initUrl() {
console.log('ThemeBuilder initUrl');
const storedUrl = localStorage.getItem(STORED_DEMO_URL_KEY);
const defaultUrl = this.demoData[0].url;
this.demoUrl = storedUrl || defaultUrl;
const storedMode = localStorage.getItem(STORED_DEMO_MODE_KEY);
const defaultMode = 'md';
this.demoMode = storedMode || defaultMode;
}
@Listen('demoUrlChange')
onDemoUrlChange(ev) {
this.demoUrl = ev.detail;
localStorage.setItem(STORED_DEMO_URL_KEY, this.demoUrl);
}
@Listen('demoModeChange')
onDemoModeChange(ev) {
this.demoMode = ev.detail;
localStorage.setItem(STORED_DEMO_MODE_KEY, this.demoMode);
}
@Listen('themeCssChange')
onThemeCssChange(ev) {
this.cssText = ev.detail.cssText;
this.themeName = ev.detail.themeName;
console.log('ThemeBuilder themeCssChange', this.themeName);
}
render() {
return [
<main>
<section class='preview-column'>
<demo-selection demoData={this.demoData} demoUrl={this.demoUrl} demoMode={this.demoMode}></demo-selection>
<app-preview demoUrl={this.demoUrl} demoMode={this.demoMode} cssText={this.cssText}></app-preview>
</section>
<section class='selector-column'>
<theme-selector themeData={this.themeData}></theme-selector>
</section>
<section>
<css-text themeName={this.themeName} cssText={this.cssText}></css-text>
</section>
</main>
];
}
}

View File

@@ -0,0 +1,8 @@
select {
margin: 10px 0 0 10px;
}
section {
margin: 10px;
}

View File

@@ -0,0 +1,121 @@
import { Component, Event, EventEmitter, Listen, Prop, State } from '@stencil/core';
import { STORED_THEME_KEY, THEME_VARIABLES, getThemeUrl } from '../../theme-variables';
@Component({
tag: 'theme-selector',
styleUrl: 'theme-selector.css',
shadow: true
})
export class ThemeSelector {
@State() themeName: string;
@State() themeVariables: { property: string; value?: string; isRgb?: boolean; }[] = [];
@Prop() themeData: { name: string }[];
@Event() themeCssChange: EventEmitter;
onChangeUrl(ev) {
this.themeName = ev.currentTarget.value;
localStorage.setItem(STORED_THEME_KEY, this.themeName);
this.loadThemeCss();
}
componentWillLoad() {
const storedThemeName = localStorage.getItem(STORED_THEME_KEY);
const defaultThemeName = this.themeData[0].name;
this.themeName = storedThemeName || defaultThemeName;
this.loadThemeCss();
}
loadThemeCss() {
console.log('ThemeSelector loadThemeCss');
const themeUrl = getThemeUrl(this.themeName);
return fetch(themeUrl).then(rsp => {
return rsp.text().then(css => {
this.parseCss(css);
this.generateCss();
});
});
}
parseCss(css: string) {
console.log('ThemeSelector parseCss');
const themer = document.getElementById('themer') as HTMLStyleElement;
themer.innerHTML = css;
const computed = window.getComputedStyle(document.body);
this.themeVariables = THEME_VARIABLES.map(themeVariable => {
const value = (computed.getPropertyValue(themeVariable.property) || '#eeeeee').trim().toLowerCase();
return {
property: themeVariable.property.trim(),
value: value,
isRgb: value.indexOf('rgb') > -1
};
});
}
generateCss() {
console.log('ThemeSelector generateCss', this.themeName);
const c: string[] = [];
c.push(`/** ${this.themeName} theme **/`);
c.push(`\n`);
c.push(':root {');
this.themeVariables.forEach(themeVariable => {
themeVariable.value = (themeVariable.value || '').trim();
c.push(` ${themeVariable.property}: ${themeVariable.value};`);
});
c.push('}');
const cssText = c.join('\n');
this.themeCssChange.emit({
cssText: cssText,
themeName: this.themeName
});
}
@Listen('colorChange')
onColorChange(ev) {
console.log('ThemeSelector colorChange');
this.themeVariables = this.themeVariables.map(themeVariable => {
let value = themeVariable.value;
if (ev.detail.property === themeVariable.property) {
value = ev.detail.value;
}
return {
property: themeVariable.property,
value: value,
isRgb: themeVariable.isRgb
};
});
this.generateCss();
}
render() {
return [
<div>
<select onChange={this.onChangeUrl.bind(this)}>
{this.themeData.map(d => <option value={d.name} selected={this.themeName === d.name}>{d.name}</option>)}
</select>
<section>
{this.themeVariables.map(d => <color-selector property={d.property} value={d.value} isRgb={d.isRgb}></color-selector>)}
</section>
</div>
];
}
}

View File

@@ -0,0 +1,19 @@
html,
body {
height: 100%;
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
background: #eee;
margin: 0;
padding: 0;
}
theme-builder {
display: block;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
overflow: auto;
}

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html dir="ltr" lang="en">
<head>
<meta charset="utf-8">
<title>Ionic Theme Builder</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0">
<meta name="theme-color" content="#16161d">
<meta name="apple-mobile-web-app-capable" content="yes">
<link href="/theme-builder/build/app.css" rel="stylesheet">
<script src="/theme-builder/build/app.js"></script>
<style id="themer"></style>
</head>
<body>
<theme-builder></theme-builder>
</body>
</html>

View File

@@ -0,0 +1,109 @@
export const THEME_VARIABLES = [
{
property: '--primary'
},
{
property: '--primary-contrast'
},
{
property: '--secondary'
},
{
property: '--secondary-contrast'
},
{
property: '--tertiary'
},
{
property: '--tertiary-contrast'
},
{
property: '--success'
},
{
property: '--success-contrast'
},
{
property: '--warning'
},
{
property: '--warning-contrast'
},
{
property: '--danger'
},
{
property: '--danger-contrast'
},
{
property: '--light'
},
{
property: '--light-contrast'
},
{
property: '--medium'
},
{
property: '--medium-contrast'
},
{
property: '--dark'
},
{
property: '--dark-contrast'
},
{
property: '--content-color'
},
{
property: '--content-sub-color'
},
{
property: '--content-background'
},
{
property: '--content-sub-background'
},
{
property: '--toolbar-background'
},
{
property: '--tabbar-background'
},
{
property: '--item-background'
},
{
property: '--item-sub-background'
},
{
property: '--border-color'
},
];
export const SERVER_DOMAIN = `http://localhost:5454`;
export const DATA_URL = `${SERVER_DOMAIN}/data`;
export const SAVE_CSS_URL = `${SERVER_DOMAIN}/save-css`;
export const DELETE_CSS_URL = `${SERVER_DOMAIN}/delete-css`;
export const CSS_THEME_FILE_PATH = `/src/themes/css`;
export function saveCssUrl(themeName: string, cssText: string) {
cssText = encodeURIComponent(cssText);
return `${SAVE_CSS_URL}?theme=${themeName}&css=${cssText}`;
}
export function deleteCssUrl(themeName: string) {
return `${DELETE_CSS_URL}?theme=${themeName}`;
}
export function getThemeUrl(themeName: string) {
return `${CSS_THEME_FILE_PATH}/${themeName}.css`;
}
export const STORED_DEMO_URL_KEY = 'theme-builder-demo-url';
export const STORED_DEMO_MODE_KEY = 'theme-builder-demo-mode';
export const STORED_THEME_KEY = 'theme-builder-theme-url';

View File

@@ -0,0 +1,11 @@
exports.config = {
globalStyle: 'src/global/app.css',
wwwDir: '../../theme-builder',
serviceWorker: false
};
exports.devServer = {
root: '../../',
watchGlob: 'src/**',
openUrl: '/theme-builder'
}

View File

@@ -0,0 +1,22 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"allowUnreachableCode": false,
"declaration": false,
"experimentalDecorators": true,
"lib": [
"dom",
"es2015"
],
"moduleResolution": "node",
"module": "es2015",
"target": "es2015",
"noUnusedLocals": true,
"noUnusedParameters": true,
"jsx": "react",
"jsxFactory": "h"
},
"include": [
"src"
]
}

View File

@@ -0,0 +1,32 @@
/** default theme **/
:root {
--primary: #3880ff;
--primary-contrast: #ffffff;
--secondary: #0cd1e8;
--secondary-contrast: #ffffff;
--tertiary: #7044ff;
--tertiary-contrast: #ffffff;
--success: #10dc60;
--success-contrast: #ffffff;
--warning: #ffce00;
--warning-contrast: #ffffff;
--danger: #f04141;
--danger-contrast: #ffffff;
--light: #f4f5f8;
--light-contrast: #000000;
--medium: #898b93;
--medium-contrast: #000000;
--dark: #222428;
--dark-contrast: #f4f5f8;
--content-color: #222428;
--content-sub-color: #222428;
--content-background: #eeeeee;
--content-sub-background: #eeeeee;
--toolbar-background: #eeeeee;
--tabbar-background: #eeeeee;
--item-background: #eeeeee;
--item-sub-background: #eeeeee;
--border-color: #222428;
}

View File

@@ -0,0 +1,32 @@
/** oceanic theme **/
:root {
--primary: #3880ff;
--primary-contrast: #ffffff;
--secondary: #0cd1e8;
--secondary-contrast: #ffffff;
--tertiary: #7044ff;
--tertiary-contrast: #ffffff;
--success: #10dc60;
--success-contrast: #ffffff;
--warning: #ffce00;
--warning-contrast: #ffffff;
--danger: #f04141;
--danger-contrast: #ffffff;
--light: #f4f5f8;
--light-contrast: #000000;
--medium: #898b93;
--medium-contrast: #000000;
--dark: #222428;
--dark-contrast: #f4f5f8;
--content-color: #222428;
--content-sub-color: #222428;
--content-background: #eeeeee;
--content-sub-background: #eeeeee;
--toolbar-background: #eeeeee;
--tabbar-background: #eeeeee;
--item-background: #eeeeee;
--item-sub-background: #eeeeee;
--border-color: #222428;
}