From d1dbe51e17947dc3b686e53f7f8995c5def7d0f4 Mon Sep 17 00:00:00 2001 From: localadmin Date: Fri, 8 Dec 2017 15:35:46 -0500 Subject: [PATCH 1/9] Create, edit, and delete themes with a WSYIWYG editor. --- .gitignore | 1 + client/index.js | 26 ++- client/preview.js | 12 +- client/themeEditor.js | 501 +++++++++++++++++++++++++++++++++++++++++ client/themeOptions.js | 160 +++++++++++++ editor/css/editor.css | 249 ++++++++++++++++++++ editor/index.html | 2 + editor/themes.html | 97 ++++++++ server/imageAPI.js | 52 +++++ server/index.js | 12 +- server/themesAPI.js | 89 ++++++++ 11 files changed, 1194 insertions(+), 7 deletions(-) create mode 100644 client/themeEditor.js create mode 100644 client/themeOptions.js create mode 100644 editor/themes.html create mode 100644 server/imageAPI.js create mode 100644 server/themesAPI.js diff --git a/.gitignore b/.gitignore index 3829b4ca..6cc15f26 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ junk/ media/ *.log .jobs +settings/backgrounds/ diff --git a/client/index.js b/client/index.js index e65343d6..f4ae9237 100644 --- a/client/index.js +++ b/client/index.js @@ -2,7 +2,8 @@ var d3 = require("d3"), $ = require("jquery"), preview = require("./preview.js"), video = require("./video.js"), - audio = require("./audio.js"); + audio = require("./audio.js"), + themeEditor = require("./themeEditor.js"); d3.json("/settings/themes.json", function(err, themes){ @@ -27,6 +28,9 @@ d3.json("/settings/themes.json", function(err, themes){ return; } + if(themeEditor.isEditor()) + themeEditor.initializeThemes(themes); + for (var key in themes) { themes[key] = $.extend({}, themes.default, themes[key]); } @@ -138,7 +142,11 @@ function initialize(err, themesWithImages) { // Populate dropdown menu d3.select("#input-theme") - .on("change", updateTheme) + .on("change.a", updateTheme) + .on("change.b", function(){ + if(themeEditor.isActive()) + themeEditor.populateThemeFields(); + }) .selectAll("option") .data(themesWithImages) .enter() @@ -182,6 +190,20 @@ function initialize(err, themesWithImages) { d3.select("#submit").on("click", submitted); + d3.select("#theme-edit").on("click", function(){ + var activeTheme = d3.select('#input-theme').property('value'); + var url = window.location.origin + window.location.pathname + 'themes.html?t=' + activeTheme; + window.location = url; + }); + + d3.select("#new-theme").on("click", function(){ + var url = window.location.origin + window.location.pathname + 'themes.html?t=default' + window.location = url; + }); + + if(themeEditor.isEditor()) + themeEditor.initialize(); + } function updateAudioFile() { diff --git a/client/preview.js b/client/preview.js index 65129d46..578733c3 100644 --- a/client/preview.js +++ b/client/preview.js @@ -47,8 +47,12 @@ minimap.onBrush(function(extent){ // Resize video and preview canvas to maintain aspect ratio function resize(width, height) { - var widthFactor = 640 / width, - heightFactor = 360 / height, + var containerWidth = document.querySelector('#preview').clientWidth, + aspectRatio = height/width; + containerWidth = containerWidth > 0 ? containerWidth : 640; + + var widthFactor = containerWidth / width, + heightFactor = containerWidth * aspectRatio / height, factor = Math.min(widthFactor, heightFactor); d3.select("canvas") @@ -69,7 +73,6 @@ function resize(width, height) { } function redraw() { - resize(theme.width, theme.height); video.kill(); @@ -111,5 +114,6 @@ module.exports = { theme: _theme, file: _file, selection: _selection, - loadAudio: loadAudio + loadAudio: loadAudio, + redraw: redraw }; diff --git a/client/themeEditor.js b/client/themeEditor.js new file mode 100644 index 00000000..936374d0 --- /dev/null +++ b/client/themeEditor.js @@ -0,0 +1,501 @@ +var d3 = require('d3'), + $ = require('jquery'), + preview = require('./preview.js'), + options = require('./themeOptions.js'); + +var editorIsActive = false; +var themesJson = {}; + +function updateTheme(options) { + if(options !== undefined){ + if(options.backgroundImage !== '') + getImage(options); + preview.theme(options); + } + else{ + preview.theme(d3.select(this.options[this.selectedIndex]).datum()); + } +} + +function getImage(theme) { + if (theme.backgroundImage) { + theme.backgroundImageFile = new Image(); + + theme.backgroundImageFile.onload = function(){ + updateTheme(anonymousTheme()); + }; + + theme.backgroundImageFile.src = '/settings/backgrounds/' + theme.backgroundImage; + } +} + +function error(msg) { + if (msg.responseText) { + msg = msg.responseText; + } + if (typeof msg !== 'string') { + msg = JSON.stringify(msg); + } + if (!msg) { + msg = 'Unknown error'; + } + d3.select('#loading-message').text('Loading...'); + setClass('error', msg); +} + +function setClass(cl, msg) { + d3.select('body').attr('class', cl || null); + d3.select('#error').text(msg || ''); +} + +function anonymousTheme(){ + var output = {}; + d3.selectAll('.options-attribute-input').each(function(d){ + if(d.type == 'number'){ + if(Number.isNaN(parseFloat(this.value))) + output[d.name] = null; + else + output[d.name] = parseFloat(this.value); + } + else{ + output[d.name] = this.value; + } + }); + + return output; +} + +function postTheme(type, data, cb){ + var postData = { + type: type, + data: data + }; + + $.ajax({ + url: '/api/themes', + type: 'POST', + data: JSON.stringify(postData), + contentType: 'application/json', + cache: false, + success: cb, + error: error + }); +} + +function saveChanges(){ + var activeTheme = preview.theme(); + var activeThemeName = d3.select('#input-theme').property('value'); + + function makeTheme(){ + var file = {}; + + file.name = activeTheme.name; + file.currentName = activeThemeName; + + d3.selectAll('.options-attribute-input').each(function(d){ + if(!this.disabled){ + if(this.getAttribute('type') == 'number') + file[d.name] = parseFloat(this.value); + else + file[d.name] = this.value; + } + }); + + return file; + } + + function reloadPage(){ + var url = window.location.origin + window.location.pathname + '?t=' + activeTheme.name; + window.location = url; + } + + var themeNames = []; + d3.selectAll('#input-theme option').each(function(d){ + themeNames.push(d.name); + }); + themeNames.splice(themeNames.length-1, 1); + + var postData = makeTheme(); + + if(activeTheme.name == 'default' || activeTheme.name == '0' || activeTheme.name == ''){ + window.alert('Please give your theme a name.'); + } + else if(themeNames.indexOf(activeTheme.name) != -1 && activeThemeName == 'default'){ + window.alert('That theme name already exists. Please choose a different name or select it to edit it.'); + } + else if(themeNames.indexOf(activeThemeName) != -1){ + var msg = 'Are you sure you want to override the options for "'; + msg += activeThemeName + '"? This action is permanent and cannot be undone.'; + if(window.confirm(msg)){ + postTheme('UPDATE', postData, reloadPage); + } + } + else{ + postTheme('ADD', postData, reloadPage); + } +} + +function deleteTheme(){ + var activeThemeName = d3.select('#input-theme').property('value'); + + var msg = 'Are you sure you want to delete the theme "'; + msg += activeThemeName + '"? This action is permanent and cannot be undone.'; + + if(window.confirm(msg)){ + var postData = {name: activeThemeName}; + postTheme('DELETE', postData, function(){ + var url = window.location.origin + window.location.pathname; + window.location = url; + }); + } +} + +function refreshTheme(){ + var theme = $.extend({name: preview.theme().name}, themesJson.default, themesJson[preview.theme().name]); + updateTheme(theme); + populateThemeFields(); +} + +function loadBkgndImages(cb){ + $.ajax({ + url: '/api/images', + type: 'GET', + cache: false, + success: function(data){ + $('#attribute-backgroundImage').html(''); + + var bkgndImgSelect = d3.select('#attribute-backgroundImage'); + for(var img of data){ + bkgndImgSelect.append('option') + .attr('value', img) + .text(img); + } + + if(cb !== undefined){ + cb(); + } + + }, + error: error + }); +} + +function uploadImage(){ + // this = the file input calling the function + var img = this.files[0]; + + var confirmed = confirm('Are you sure you want to upload ' + img.name + '?'); + if(confirmed){ + var formData = new FormData(); + formData.append('img', img); + + setClass('loading'); + + $.ajax({ + url: '/api/images', + type: 'POST', + data: formData, + contentType: false, + cache: false, + processData: false, + success: function(){ + var setImg = function(){ + var input = d3.select('#container-backgroundImage').select('.options-attribute-input'); + input.property('value', img.name).attr('data-value', img.name); + + updateTheme(anonymousTheme()); + + setClass(null); + }; + + loadBkgndImages(setImg); + }, + error: error + }); + } +} + +function camelToTitle(string){ + var conversion = string.replace( /([A-Z])/g, ' $1' ); + var title = conversion.charAt(0).toUpperCase() + conversion.slice(1); + return title; +} + +function queryParser(query){ + query = query.substring(1); + let query_string = {}; + const vars = query.split('&'); + for (var i=0;i= top){ + $(container).addClass('sticky'); + } + else{ + $(container).removeClass('sticky'); + } + }); + + preview.redraw(); +} + +function toggleSection(d){ + var name; + + if(typeof(d) == 'object') + name = d.name; + else + name = d; + + $('#section-options-'+ name).slideToggle(500); + $('#section-toggle-' + name).toggleClass('toggled'); +} + +function initialize(){ + var container = d3.select('#options'); + + // Add Option Sections + var sections = container.selectAll('.section').data(options) + .enter() + .append('div') + .attr('class', 'section'); + + var headers = sections.append('div') + .attr('class', 'section-header') + .on('click', toggleSection); + + // Add Section Toggles + headers.append('svg') + .attr('id', function(d){return 'section-toggle-' + d.name;}) + .attr('class', 'section-toggle') + .attr('viewBox', '0 0 24 24') + .append('path') + .attr('d', function(){ + var path = 'M12,13.7c-0.5,0-0.9-0.2-1.2-0.5L0.5,2.9c-0.7-0.7-0.7-1.8,0-2.4C0.8,0.2,1.3,0,1.7,0h20.6C23.3,0,' + + '24,0.8,24,1.7c0,0.4-0.2,0.9-0.5,1.2L13.2,13.2C12.9,13.6,12.5,13.7,12,13.7z'; + return path; + }); + + // Add Section Titles + headers.append('h4') + .attr('class', 'section-title') + .text(function(d){return d.name;}); + + var attributesContainer = sections.append('div') + .attr('class', 'section-options') + .attr('id', function(d){return 'section-options-' + d.name;}) + .style('display', 'none'); + + // Add Option Container + var attributes = attributesContainer.selectAll('.options-attribute') + .data(function(d){return d.options;}) + .enter() + .append('div') + .attr('class','options-attribute') + .attr('id', function(d){return 'container-' + d.name;}) + .attr('data-type', function(d){return d.type;}); + + // Add Enable Checkboxes + attributes.append('input') + .attr('id', function(d){return 'enable-' + d.name;}) + .attr('class', 'options-checkbox options-attribute-child') + .attr('name', function(d){return 'enable-' + d.name;}) + .attr('value', 'true') + .attr('type', 'checkbox') + .property('checked', true) + .on('click', function(d){ + var input = d3.select('#attribute-' + d.name); + if(this.checked){ + input.property('disabled', false); + input.property('value', input.attr('data-value')); + input.attr('value', input.attr('data-value')); + updateTheme(anonymousTheme()); + } + else{ + input.property('value', themesJson.default[d.name]); + input.attr('value', themesJson.default[d.name]); + input.property('disabled', true); + updateTheme(anonymousTheme()); + } + }); + + // Add Option Labels + attributes.append('label') + .attr('for', function(d){return 'attribute-' + d.name;}) + .attr('class', 'options-attribute-child') + .text(function(d){return camelToTitle(d.name);}); + + // Add Help Text Icons + attributes.append('i') + .attr('id', function(d){return 'help-' + d.name;}) + .attr('class', 'attribute-help options-attribute-child fa fa-question') + .attr('title', function(d){return d.help;}); + + // Add Inputs For Text Fields + sections.selectAll('.options-attribute:not([data-type="select"])') + .append('input') + .attr('id', function(d){return 'attribute-' + d.name;}) + .attr('class', 'options-attribute-input options-attribute-child input-text') + .attr('name', function(d){return d.name;}) + .attr('type', function(d){return d.type;}) + .on('input', function(){ + this.setAttribute('data-value', this.value); + updateTheme(anonymousTheme()); + }); + + // Add Inputs For Select Fields + sections.selectAll('.options-attribute[data-type="select"]') + .append('select') + .attr('id', function(d){return 'attribute-' + d.name;}) + .attr('class', 'options-attribute-input options-attribute-child input-select') + .attr('name', function(d){return d.name;}) + .attr('type', function(d){return d.type;}) + .on('input', function(){updateTheme(anonymousTheme());}) + .selectAll('options') + .data(function(d){return d.options;}) + .enter() + .append('option') + .attr('value', function(d){return d;}) + .text(function(d){return camelToTitle(d);}); + + // Add Section Notes + attributesContainer.append('p') + .attr('class', 'options-note note') + .text(function(d){return d.note;}); + + // Add "New..." option to theme select + d3.select('#input-theme') + .append('option') + .data([themesJson.default]) + .attr('value', 'default') + .text('New...'); + + // Add clickHandler for Save Button + d3.select('#saveChanges') + .on('click', saveChanges); + + // Add clickHandler for Delete Button + d3.select('#deleteTheme') + .on('click', deleteTheme); + + // Add clickHandler for Refresh Button + d3.select('#refreshTheme') + .on('click', refreshTheme); + + // Background Images Populate and Add Uploader + loadBkgndImages(populateThemeFields); + var bkgndImgContainer = d3.select('#container-backgroundImage'); + bkgndImgContainer.append('label') + .attr('for', 'imgUploader') + .attr('id', 'imgUploader-label') + .attr('class', 'button') + .append('i') + .attr('class', 'fa fa-upload'); + bkgndImgContainer.append('input') + .attr('type', 'file') + .attr('id', 'imgUploader') + .attr('name', 'imgUploader') + .on('change', uploadImage); + + toggleSection('Metadata'); + + // Active url theme + var selectedTheme = queryParser(window.location.search); + if('t' in selectedTheme && selectedTheme.t in themesJson){ + d3.select('#input-theme') + .property('value', selectedTheme.t) + .dispatch('change'); + } + + populateThemeFields(); + + initializePreview(); + window.addEventListener('resize', function(){ + preview.redraw(); + }); + + d3.select('#toggle-more-instructions').on('click', function(){ + $('#more-instructions').slideToggle(500); + }) + + + // Toggle the editor container to loaded + editorIsActive = true; +} + +function populateThemeFields(){ + var activeTheme = preview.theme(); + var themeJson = activeTheme.name !== undefined && activeTheme.name in themesJson ? + themesJson[activeTheme.name] : + themesJson.default; + + d3.selectAll('.options-attribute').each(function(d){ + var input = d3.select(this).select('.options-attribute-input'); + + var activeValue = d.name in activeTheme ? activeTheme[d.name] : ''; + input.property('value', activeValue).attr('data-value', activeValue); + + if(d.name == 'name'){ + d3.select('#enable-'+d.name).property('checked', true); + input.property('disabled', false); + } + else if(activeTheme.name == undefined || !(d.name in themeJson)){ + d3.select('#enable-'+d.name).property('checked', false); + input.property('disabled', true); + } + else{ + d3.select('#enable-'+d.name).property('checked', true); + input.property('disabled', false); + } + + }); +} + +window.themesJson = themesJson; + +module.exports = { + isEditor, + isActive, + initialize, + initializeThemes, + populateThemeFields +}; diff --git a/client/themeOptions.js b/client/themeOptions.js new file mode 100644 index 00000000..19975e68 --- /dev/null +++ b/client/themeOptions.js @@ -0,0 +1,160 @@ +module.exports = [ + { + 'name': 'Metadata', + 'options': [ + { + 'name': 'name', + 'type': 'text', + 'help': 'The name of the theme.' + } + ] + }, + { + 'name': 'Movie', + 'options': [ + { + 'name': 'width', + 'type': 'number', + 'help': 'Desired video width in pixels.' + }, + { + 'name': 'height', + 'type': 'number', + 'help': 'Desired video height in pixels.' + }, + { + 'name': 'framesPerSecond', + 'type': 'number', + 'help': 'Desired video framerate.' + }, + { + 'name': 'samplesPerFrame', + 'type': 'number', + 'help': 'How many data points to use for the waveform. More points = a more detailed wave. (e.g. 128)' + }, + { + 'name': 'maxDuration', + 'type': 'number', + 'help': 'Maximum duration of an audiogram, in seconds (e.g. set this to 30 to enforce a 30-second time limit).' + } + ] + }, + { + 'name': 'Background', + 'note': 'You can set both a Background Color and a Background Image, in which case the image will be drawn on top of the color.', + 'options': [ + { + 'name': 'backgroundImage', + 'type': 'select', + 'help': 'What image to put in the background of every frame, it should be a file in settings/backgrounds/', + 'options': [] + }, + { + 'name': 'backgroundColor', + 'type': 'color', + 'help': 'A CSS color to fill the background of every frame (e.g. pink or #ff00ff).' + } + ] + }, + { + 'name': 'Caption', + 'note': 'If both Caption Top and Caption Bottom are set, the caption will be roughly vertically centered between them, give or take a few pixels depending on the font.', + 'options': [ + { + 'name': 'captionColor', + 'type': 'color', + 'help': 'A CSS color, what color the text should be (e.g. red or #ffcc00).' + }, + { + 'name': 'captionAlign', + 'type': 'select', + 'help': 'Text alignment of the caption.', + 'options': [ + 'left', + 'right', + 'center' + ] + }, + { + 'name': 'captionFont', + 'type': 'text', + 'help': 'A full CSS font definition to use for the caption. ([weight] size \'family\').' + }, + { + 'name': 'captionLineHeight', + 'type': 'number', + 'help': 'How tall each caption line is in pixels. You\'ll want to adjust this for whatever font and font size you\'re using.' + }, + { + 'name': 'captionLineSpacing', + 'type': 'number', + 'help': 'How many extra pixels to put between caption lines. You\'ll want to adjust this for whatever font and font size you\'re using.' + }, + { + 'name': 'captionLeft', + 'type': 'number', + 'help': 'How many pixels from the left edge to place the caption' + }, + { + 'name': 'captionRight', + 'type': 'number', + 'help': 'How many pixels from the right edge to place the caption' + }, + { + 'name': 'captionBottom', + 'type': 'number', + 'help': 'How many pixels from the bottom edge to place the caption.' + }, + { + 'name': 'captionTop', + 'type': 'number', + 'help': 'How many pixels from the top edge to place the caption.' + } + ] + }, + { + 'name': 'Wave', + 'options': [ + { + 'name': 'pattern', + 'type': 'select', + 'help': 'What waveform shape to draw.', + 'options': [ + 'wave', + 'bars', + 'line', + 'curve', + 'roundBars', + 'pixel', + 'bricks', + 'equalizer' + ] + }, + { + 'name': 'waveTop', + 'type': 'number', + 'help': 'How many pixels from the top edge to start the waveform.' + }, + { + 'name': 'waveBottom', + 'type': 'number', + 'help': 'How many pixels from the top edge to end the waveform.' + }, + { + 'name': 'waveLeft', + 'type': 'number', + 'help': 'How many pixels from the left edge to start the waveform.' + }, + { + 'name': 'waveRight', + 'type': 'number', + 'help': 'How many pixels from the right edge to start the waveform.' + }, + { + 'name': 'waveColor', + 'type': 'color', + 'help': 'A CSS color, what color the wave should be.' + } + ] + } +]; diff --git a/editor/css/editor.css b/editor/css/editor.css index 4712374e..d08a978b 100644 --- a/editor/css/editor.css +++ b/editor/css/editor.css @@ -1,6 +1,7 @@ div.container { width: 640px; margin-bottom: 2rem; + padding: 0 20px; } h1 { @@ -13,6 +14,14 @@ h1 { color: #c00; } +code { + padding: 0.2em 0.4em; + margin: 0; + font-size: 85%; + background-color: rgba(27,31,35,0.05); + border-radius: 3px; +} + /* Buttons/controls */ button, .button { @@ -301,3 +310,243 @@ g.time line { -webkit-transform: scaleY(1.0); } } + +#theme-edit i.fa, +#new-theme i.fa{ + margin: 0; +} + +#themeEditor{ + width: 100%; + max-width: 900px; +} + +#themeEditor #row-instructions p{ + line-height: 1.3; + margin: 0 auto; + padding: 10.5px 0; +} + +#themeEditor #row-instructions p:first-of-type{ + padding-top: 0; +} + +#themeEditor #row-instructions i.fa{ + margin: 0 2px; +} + +#themeEditor #row-instructions #toggle-more-instructions{ + cursor: pointer; +} + +#themeEditor #row-instructions #toggle-more-instructions:hover{ + text-decoration: underline; +} + +#themeEditor #row-instructions #more-instructions{ + display: none; +} + +#themeEditor #editor-tools{ + display: -webkit-box; + display: -ms-flexbox; + display: flex; + width: 100%; +} + +#themeEditor #editor-tools #options-container, +#themeEditor #editor-tools #preview-container{ + width: 50%; +} + +#themeEditor #preview.sticky{ + top: 6px; + position: fixed; +} + +#themeEditor #editor-tools #options-container{ + padding-right: 20px; +} + +#themeEditor .section{ + padding: 13px 0; + border-top: 1px solid #e0e0e0; +} + +#themeEditor .section:first-child{ + padding-top: 0; + border-top: none; +} + +#themeEditor .section-header{ + cursor: pointer; +} + +#themeEditor .section-toggle{ + float: right; + width: 15px; + height: 15px; + margin: 2px 0 0 1px; + background-color: transparent; + border: 0; + padding-top: 3px; +} + +#themeEditor .section-toggle.toggled{ + -webkit-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(180deg); +} + +#themeEditor .section-options{ + overflow: hidden; +} + +#themeEditor .options-attribute{ + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + margin-bottom: 15px; +} + +#themeEditor .options-attribute:first-of-type{ + margin-top: 10px; +} + +#themeEditor .options-attribute:last-of-type{ + margin-bottom: 10px; +} + +#themeEditor .section:last-of-type .options-attribute:last-of-type{ + margin-bottom: 0; +} + +#themeEditor .options-attribute-child{ + -webkit-box-flex: 0; + -ms-flex-positive: 0; + flex-grow: 0; + margin: 0 10px; +} + +#themeEditor .options-attribute-child:first-child{ + -webkit-box-flex: 0; + -ms-flex-positive: 0; + flex-grow: 0; + margin: 0 10px 0 0; +} + +#themeEditor .options-attribute-child:last-child{ + margin: 0 0 0 10px; +} + +#themeEditor .options-attribute label{ + text-align: right; + margin-right: 0; + min-width: 130px; +} + +#themeEditor .options-attribute .attribute-help{ + margin-left: 0px; + border: 0px; + border-radius: 50px; + background-color: white; + padding: 3px 0px 7px 1px; + -webkit-box-sizing: border-box; + box-sizing: border-box; + font-size: 10px; + cursor: help; +} + +#themeEditor .options-attribute .options-attribute-input{ + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + width: auto; + padding: 6px; + color: #666; + font-size: 16px; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-box-shadow: 0 0 0 0; + box-shadow: 0 0 0 0; +} + +#themeEditor .options-attribute .options-attribute-input[type="color"]{ + padding: 0px; + border: 0px; + margin: 10px 6px; +} + +#themeEditor .options-attribute .options-attribute-input[disabled=""]{ + cursor: not-allowed; +} + +#themeEditor .options-note{ + margin-top: -7px; +} + +#themeEditor .options-note:empty{ + display: none; +} + +#themeEditor #imgUploader-label{ + min-width: 0; +} + +#themeEditor #imgUploader-label.button:hover{ + color: #333; + background-color: #e6e6e6; + border-color: #adadad; +} + +#themeEditor #imgUploader-label.button:active{ + -webkit-box-shadow: inset 0 3px 5px rgba(0,0,0,.125); + box-shadow: inset 0 3px 5px rgba(0,0,0,.125); +} + +#themeEditor #imgUploader-label i{ + margin-right: 0; +} + +#themeEditor #imgUploader{ + width: 0.1px; + height: 0.1px; + opacity: 0; + overflow: hidden; + position: absolute; + z-index: -1; +} + +#themeEditor #themeButtons button:first-child{ + margin-left: 0; +} + +#themeEditor #themeButtons i.fa{ + margin: 0; +} + +@media (max-width: 760px) { + #themeEditor #editor-tools{ + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + } + + #themeEditor #editor-tools #options-container, + #themeEditor #editor-tools #preview-container{ + width: 100%; + } + + #themeEditor #preview.sticky{ + top: 0; + position: initial; + } +} diff --git a/editor/index.html b/editor/index.html index 5106c462..c54c672e 100644 --- a/editor/index.html +++ b/editor/index.html @@ -28,6 +28,8 @@

Audiogram

+ +