Skip to content

Commit e1da59b

Browse files
authored
feat: Interactively customize CSS variables (#163)
* feat: Interactively customize CSS variables * Cleanup * Support Ruby 2.5
1 parent 469fb94 commit e1da59b

File tree

14 files changed

+308
-6
lines changed

14 files changed

+308
-6
lines changed

app/assets/javascripts/application.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
//= require jquery.ui.autocomplete
1818
//= require highcharts
1919
//= require chartkick
20+
//= require ./vendor/debounce
2021
//= require_directory .
2122
//= require_directory ./channels
2223
//= require ./vendor/simplemde.min.js
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
//= require ../vendor/pickr.min.js
2+
3+
(function() {
4+
function _onTextChange() {
5+
var $input = $(this);
6+
var variable = $input.attr('name');
7+
var value = $input.val();
8+
9+
document.documentElement.style.setProperty(variable, value);
10+
window.pickrs[variable].setColor(value, true);
11+
}
12+
13+
function _onPickrChange(color, instance) {
14+
var value = color ? '#' + color.toHEXA().join('') : '';
15+
var $variable = $(instance.getRoot().root).parents('.theming-editor__variable');
16+
$input = $variable.find('.theming-editor__input');
17+
var variable = $input.attr('name');
18+
19+
// Don't update input if the user is typing in it
20+
if (!$input.is(':focus')) {
21+
$input.val(value);
22+
}
23+
// Update the picker button color (doesn't by default)
24+
instance.applyColor(true);
25+
// Update CSS property
26+
document.documentElement.style.setProperty(variable, value);
27+
}
28+
29+
function onSave() {
30+
var $editor = $(this).parents('.theming-editor__wrapper');
31+
var css = ':root {\n';
32+
$editor.find('.theming-editor__input').each(function() {
33+
var variable = $(this).attr('name');
34+
var value = $(this).val();
35+
css += ' ' + variable + ': ' + value + ';\n';
36+
});
37+
css += '}';
38+
saveConfig(css);
39+
}
40+
41+
function saveConfig(css) {
42+
var data = {
43+
hackathon_config: {
44+
custom_css: css,
45+
},
46+
};
47+
fetch('/manage/configs/custom_css', {
48+
method: 'PATCH',
49+
redirect: 'manual',
50+
headers: {
51+
'Content-Type': 'application/json',
52+
'X-CSRF-Token': Rails.csrfToken(),
53+
},
54+
body: JSON.stringify(data),
55+
})
56+
.then(function(response) {
57+
if (!response.ok && response.type != 'opaqueredirect') {
58+
alert('There was an error attempting to save. Please try again or refresh the page.');
59+
return;
60+
}
61+
window.location.replace('/manage/configs/exit_theming_editor');
62+
})
63+
.catch(function() {
64+
alert('There was an error attempting to save. Please try again or refresh the page.');
65+
});
66+
}
67+
68+
var onTextChange = throttle(_onTextChange, 100);
69+
var onPickrChange = throttle(_onPickrChange, 100);
70+
71+
function initHtml() {
72+
var variables = Object.values(getComputedStyle(document.documentElement))
73+
.filter(function(x) {
74+
return x.startsWith('--primary');
75+
})
76+
.sort();
77+
var $editor = $('#theming-editor');
78+
var $variables = $editor.find('.theming-editor__variables');
79+
var $variable_template = $editor.find('.theming-editor__variable').remove();
80+
variables.forEach(function(variable) {
81+
var $new_variable = $variable_template.clone();
82+
var $code = $new_variable.find('.theming-editor__variable-name-code');
83+
var $input = $new_variable.find('.theming-editor__input');
84+
var value = getComputedStyle(document.documentElement)
85+
.getPropertyValue(variable)
86+
.trim();
87+
88+
$code.text(variable);
89+
$input.val(value);
90+
$input.attr('name', variable);
91+
$input.on('input', onTextChange);
92+
$variables.append($new_variable);
93+
});
94+
$editor.find('.theming-editor__button-save').on('click', onSave);
95+
}
96+
97+
function initColorPickers() {
98+
window.pickrs = {};
99+
$('.theming-editor__color-picker').each(function() {
100+
var $input = $(this)
101+
.parents('.theming-editor__variable')
102+
.find('.theming-editor__input');
103+
var pickr = Pickr.create({
104+
el: this,
105+
default: $input.val(),
106+
components: {
107+
opacity: false,
108+
hue: true,
109+
interaction: false,
110+
},
111+
});
112+
pickr.on('change', onPickrChange);
113+
var variable = $input.attr('name');
114+
window.pickrs[variable] = pickr;
115+
});
116+
}
117+
118+
function init() {
119+
initHtml();
120+
initColorPickers();
121+
}
122+
123+
document.addEventListener('turbolinks:load', init);
124+
})();

app/assets/javascripts/manage/lib/debounce.js renamed to app/assets/javascripts/vendor/debounce.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,21 @@ function debounce(func, wait, immediate) {
1818
if (callNow) func.apply(context, args);
1919
};
2020
}
21+
22+
// Returns a function, that, as long as it continues to be invoked, will only
23+
// trigger every N milliseconds. If `immediate` is passed, trigger the
24+
// function on the leading edge, instead of the trailing.
25+
function throttle(func, wait, immediate) {
26+
var timeout;
27+
return function() {
28+
var context = this,
29+
args = arguments;
30+
var later = function() {
31+
timeout = null;
32+
if (!immediate) func.apply(context, args);
33+
};
34+
var callNow = immediate && !timeout;
35+
if (!timeout) timeout = setTimeout(later, wait);
36+
if (callNow) func.apply(context, args);
37+
};
38+
}

app/assets/javascripts/vendor/pickr.min.js

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/assets/stylesheets/application.sass

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,16 @@
2121
@import general/flashes
2222
@import general/main
2323
@import general/header
24-
@import general/table
2524
@import general/status-colors
25+
@import general/table
2626
@import forms/forms
2727
@import forms/confirmation
2828

29+
@import general/theming-editor
30+
2931
// media queries needs to be last
3032
@import general/media-queries
3133

3234
// external dependencies
3335
@import font-awesome
36+
@import vendor/pickr.min
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
.theming-editor__wrapper
2+
position: fixed
3+
bottom: 10px
4+
right: 10px
5+
display: block
6+
background: #fff
7+
border: 3px solid #ccc
8+
padding: 15px 20px
9+
border-radius: 4px
10+
max-width: 470px
11+
max-height: 700px
12+
overflow: auto
13+
14+
.theming-editor__title
15+
font-weight: bold
16+
margin-bottom: 1em
17+
font-size: 16px
18+
19+
.theming-editor__variables
20+
display: flex
21+
flex-flow: row wrap
22+
margin: -10px -10px 10px
23+
24+
.theming-editor__variable
25+
display: flex
26+
flex-flow: column nowrap
27+
margin: 10px
28+
width: 200px
29+
30+
.theming-editor__variable-name
31+
color: #e83e8c
32+
margin-bottom: 5px
33+
font-size: 11px
34+
35+
.theming-editor__input-group
36+
display: flex
37+
flex-flow: row nowrap
38+
align-items: stretch
39+
40+
.theming-editor__input-group .pickr
41+
margin-left: 5px
42+
display: flex
43+
width: 80px
44+
button
45+
box-shadow: 0 3px 4px rgba(0,0,0,0.1)
46+
display: flex
47+
height: 100%
48+
width: 100%
49+
50+
input, textarea
51+
&.theming-editor__input, &.theming-editor__input:focus
52+
font-size: 12px
53+
font-family: monospace
54+
padding: 6px 9px
55+
border: 1px solid #ccc
56+
margin: 0
57+
58+
.theming-editor__button-wrapper
59+
margin-top: 1em

app/assets/stylesheets/vendor/pickr.min.css

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controllers/manage/configs_controller.rb

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
class Manage::ConfigsController < Manage::ApplicationController
22
before_action :limit_access_admin
3-
before_action :get_config, only: [:edit, :update]
3+
before_action :get_config, only: [:edit, :update, :update_only_css_variables]
44

55
respond_to :html, :json
66

@@ -15,8 +15,8 @@ def edit
1515
def update
1616
key = @config.var.to_sym
1717
value = params[:hackathon_config][key]
18-
value = true if value == 'true'
19-
value = false if value == 'false'
18+
value = true if value == "true"
19+
value = false if value == "false"
2020
if @config.value != value
2121
@config.value = value
2222
@config.save
@@ -26,6 +26,35 @@ def update
2626
end
2727
end
2828

29+
def update_only_css_variables
30+
key = @config.var.to_sym
31+
old_value = @config.value.strip
32+
posted_value = params[:hackathon_config][key].strip
33+
if old_value.include? ':root {'
34+
# Replace the old CSS variables and keep the extra css
35+
start_index = old_value.index(':root {')
36+
end_index = old_value.index('}', start_index) + 1
37+
pre_value = old_value[0...start_index].rstrip
38+
post_value = old_value[end_index..-1].lstrip
39+
new_value = "#{pre_value}\n\n#{posted_value}\n\n#{post_value}".strip
40+
else
41+
# Prepend the variable definitions to the existing value
42+
new_value = "#{posted_value}\n\n#{old_value}"
43+
end
44+
params[:hackathon_config][key] = new_value
45+
update
46+
end
47+
48+
def enter_theming_editor
49+
cookies[:theming_editor] = true
50+
redirect_to root_path
51+
end
52+
53+
def exit_theming_editor
54+
cookies.delete :theming_editor
55+
redirect_to manage_configs_path
56+
end
57+
2958
private
3059

3160
def get_config
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.theming-editor__wrapper#theming-editor
2+
.theming-editor__title Custom CSS Variables
3+
.theming-editor__variables
4+
.theming-editor__variable
5+
%small.theming-editor__variable-name
6+
%code.theming-editor__variable-name-code my-variable
7+
.theming-editor__input-group
8+
%input.theming-editor__input{type: 'text'}
9+
.theming-editor__color-picker
10+
11+
.theming-editor__button-wrapper
12+
%button.theming-editor__button-save{type: 'button'} Save
13+
= link_to 'Cancel', exit_theming_editor_manage_configs_path
14+
15+
= javascript_include_tag "theming-editor/theming-editor.js", "data-turbolinks-eval": "false"

app/views/layouts/application.html.haml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,5 @@
2626
%section.section
2727
.container
2828
= yield
29+
- if cookies[:theming_editor]
30+
= render "layouts/theming_editor"

0 commit comments

Comments
 (0)