Implemented direct selection for categories
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / js / WoltLabSuite / Core / Acp / Ui / Style / Editor.js
CommitLineData
90b4b964 1/**
13744d6e 2 * Provides the style editor.
90b4b964
AE
3 *
4 * @author Alexander Ebert
50d96bd8 5 * @copyright 2001-2017 WoltLab GmbH
90b4b964 6 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
58d7e8f8 7 * @module WoltLabSuite/Core/Acp/Ui/Style/Editor
90b4b964 8 */
3926d706 9define(['Ajax', 'Core', 'Dictionary', 'Dom/Util', 'EventHandler', 'Ui/Screen'], function(Ajax, Core, Dictionary, DomUtil, EventHandler, UiScreen) {
90b4b964
AE
10 "use strict";
11
f2b50825
AE
12 var _stylePreviewRegions = new Dictionary();
13 var _stylePreviewRegionMarker = null;
59964c3f 14 var _stylePreviewWindow = elById('spWindow');
f2b50825 15
ad310ee6 16 var _isVisible = true;
76f2a1ce 17 var _isSmartphone = false;
ad310ee6
AE
18 var _updateRegionMarker = null;
19
90b4b964 20 /**
58d7e8f8 21 * @module WoltLabSuite/Core/Acp/Ui/Style/Editor
90b4b964 22 */
ad310ee6 23 return {
90b4b964
AE
24 /**
25 * Sets up dynamic style options.
26 */
27 setup: function(options) {
28 this._handleLayoutWidth();
13744d6e 29 this._handleScss(options.isTainted);
90b4b964
AE
30
31 if (!options.isTainted) {
32 this._handleProtection(options.styleId);
33 }
f2b50825
AE
34
35 this._initVisualEditor(options.styleRuleMap);
3926d706
MW
36
37 UiScreen.on('screen-sm-down', {
38 match: this.hideVisualEditor.bind(this),
39 unmatch: this.showVisualEditor.bind(this),
40 setup: this.hideVisualEditor.bind(this)
41 });
ad310ee6
AE
42
43 var callbackRegionMarker = function () {
44 if (_isVisible) _updateRegionMarker();
45 };
46 window.addEventListener('resize', callbackRegionMarker);
47 EventHandler.add('com.woltlab.wcf.AcpMenu', 'resize', callbackRegionMarker);
48 EventHandler.add('com.woltlab.wcf.simpleTabMenu_styleTabMenuContainer', 'select', function (data) {
ac229adb 49 _isVisible = (data.activeName === 'colors');
ad310ee6
AE
50 callbackRegionMarker();
51 });
90b4b964
AE
52 },
53
54 /**
55 * Handles the switch between static and fluid layout.
56 */
57 _handleLayoutWidth: function() {
58 var useFluidLayout = elById('useFluidLayout');
59 var fluidLayoutMinWidth = elById('fluidLayoutMinWidth');
60 var fluidLayoutMaxWidth = elById('fluidLayoutMaxWidth');
61 var fixedLayoutVariables = elById('fixedLayoutVariables');
62
63 function change() {
64 var checked = useFluidLayout.checked;
65
66 fluidLayoutMinWidth.style[(checked ? 'remove' : 'set') + 'Property']('display', 'none');
67 fluidLayoutMaxWidth.style[(checked ? 'remove' : 'set') + 'Property']('display', 'none');
68 fixedLayoutVariables.style[(checked ? 'set' : 'remove') + 'Property']('display', 'none');
69 }
70
71 useFluidLayout.addEventListener('change', change);
72
73 change();
74 },
75
76 /**
13744d6e 77 * Handles SCSS input fields.
90b4b964
AE
78 *
79 * @param {boolean} isTainted false if style is in protected mode
80 */
13744d6e 81 _handleScss: function(isTainted) {
97ec0367
MW
82 var individualScss = elById('individualScss');
83 var overrideScss = elById('overrideScss');
90b4b964
AE
84
85 if (isTainted) {
86 EventHandler.add('com.woltlab.wcf.simpleTabMenu_styleTabMenuContainer', 'select', function(data) {
97ec0367
MW
87 individualScss.codemirror.refresh();
88 overrideScss.codemirror.refresh();
90b4b964
AE
89 });
90 }
91 else {
92 EventHandler.add('com.woltlab.wcf.simpleTabMenu_advanced', 'select', function(data) {
93 if (data.activeName === 'advanced-custom') {
97ec0367
MW
94 elById('individualScssCustom').codemirror.refresh();
95 elById('overrideScssCustom').codemirror.refresh();
90b4b964
AE
96 }
97 else if (data.activeName === 'advanced-original') {
97ec0367
MW
98 individualScss.codemirror.refresh();
99 overrideScss.codemirror.refresh();
90b4b964
AE
100 }
101 });
102 }
103 },
104
105 _handleProtection: function(styleId) {
106 var button = elById('styleDisableProtectionSubmit');
107 var checkbox = elById('styleDisableProtectionConfirm');
108
109 checkbox.addEventListener('change', function() {
110 button.disabled = !checkbox.checked;
111 });
112
ac188fc5 113 button.addEventListener(WCF_CLICK_EVENT, function() {
90b4b964
AE
114 Ajax.apiOnce({
115 data: {
116 actionName: 'markAsTainted',
117 className: 'wcf\\data\\style\\StyleAction',
118 objectIDs: [styleId]
119 },
120 success: function() {
121 window.location.reload();
122 }
123 });
124 });
f2b50825
AE
125 },
126
127 _initVisualEditor: function(styleRuleMap) {
59964c3f
AE
128 elBySelAll('[data-region]', _stylePreviewWindow, function(region) {
129 _stylePreviewRegions.set(elData(region, 'region'), region);
130 });
f2b50825
AE
131
132 _stylePreviewRegionMarker = elCreate('div');
133 _stylePreviewRegionMarker.id = 'stylePreviewRegionMarker';
134 _stylePreviewRegionMarker.innerHTML = '<div id="stylePreviewRegionMarkerBottom"></div>';
135 elHide(_stylePreviewRegionMarker);
136 elById('colors').appendChild(_stylePreviewRegionMarker);
137
b3ac08d1
AE
138 var container = elById('spSidebar');
139 var select = elById('spCategories');
f2b50825
AE
140 var lastValue = select.value;
141
ad310ee6 142 _updateRegionMarker = function() {
76f2a1ce
AE
143 if (_isSmartphone) {
144 return;
145 }
146
f2b50825
AE
147 if (lastValue === 'none') {
148 elHide(_stylePreviewRegionMarker);
f2b50825
AE
149 return;
150 }
151
152 var region = _stylePreviewRegions.get(lastValue);
153 var rect = region.getBoundingClientRect();
154
39c81cce 155 var top = rect.top + (window.scrollY || window.pageYOffset);
f2b50825
AE
156
157 DomUtil.setStyles(_stylePreviewRegionMarker, {
158 height: (region.clientHeight + 20) + 'px',
159 left: (rect.left + document.body.scrollLeft - 10) + 'px',
160 top: (top - 10) + 'px',
161 width: (region.clientWidth + 20) + 'px'
162 });
163
164 elShow(_stylePreviewRegionMarker);
165
ac229adb
AE
166 top = DomUtil.offset(region).top;
167 // `+ 80` = account for sticky header + selection markers (20px)
168 var firstVisiblePixel = (window.pageYOffset || window.scrollY) + 80;
169 if (firstVisiblePixel > top) {
170 window.scrollTo(0, Math.max(top - 80, 0));
f2b50825
AE
171 }
172 else {
ac229adb
AE
173 var lastVisiblePixel = window.innerHeight + (window.pageYOffset || window.scrollY);
174 if (lastVisiblePixel < top) {
175 window.scrollTo(0, top);
176 }
177 else {
178 var bottom = top + region.offsetHeight + 20;
179 if (lastVisiblePixel < bottom) {
180 window.scrollBy(0, bottom - top);
181 }
f2b50825 182 }
f2b50825 183 }
ac229adb 184 };
f2b50825 185
f2b50825
AE
186 var element;
187 select.addEventListener('change', function() {
b3ac08d1 188 element = elBySel('.spSidebarBox[data-category="' + lastValue + '"]', container);
f2b50825
AE
189 elHide(element);
190
191 lastValue = select.value;
b3ac08d1 192 element = elBySel('.spSidebarBox[data-category="' + lastValue + '"]', container);
f2b50825
AE
193 elShow(element);
194
195 // set region marker
ad310ee6 196 _updateRegionMarker();
f2b50825
AE
197 });
198
f2b50825 199 // apply CSS rules
f5336f4f 200 var style = elCreate('style');
f2b50825
AE
201 style.appendChild(document.createTextNode(''));
202 elData(style, 'created-by', 'WoltLab/Acp/Ui/Style/Editor');
203 document.head.appendChild(style);
204
59964c3f 205 function updateCSSRule(identifier, value) {
f2b50825 206 if (styleRuleMap[identifier] === undefined) {
f2b50825
AE
207 return;
208 }
209
b3ac08d1 210 var rule = styleRuleMap[identifier].replace(/VALUE/g, value + ' !important');
f2b50825 211 if (!rule) {
f2b50825
AE
212 return;
213 }
214
b3ac08d1
AE
215 var rules = [];
216 if (rule.indexOf('__COMBO_RULE__')) {
217 rules = rule.split('__COMBO_RULE__');
218 }
219 else {
220 rules = [rule];
221 }
222
223 for (var i = 0, length = rules.length; i < length; i++) {
f6fc939a
AE
224 try {
225 style.sheet.insertRule(rules[i], style.sheet.cssRules.length);
226 }
227 catch (e) {
228 // ignore errors for unknown placeholder selectors
229 if (!/[a-z]+\-placeholder/.test(rules[i])) {
230 console.debug(e.message);
231 }
232 }
b3ac08d1 233 }
f2b50825 234 }
2935e690 235
ac229adb 236 var elements = elByClass('styleVariableColor', elById('spVariablesWrapper'));
f2b50825
AE
237 [].forEach.call(elements, function(colorField) {
238 var variableName = elData(colorField, 'store').replace(/_value$/, '');
2935e690 239
f2b50825
AE
240 var observer = new MutationObserver(function(mutations) {
241 mutations.forEach(function(mutation) {
242 if (mutation.attributeName === 'style') {
243 updateCSSRule(variableName, colorField.style.getPropertyValue('background-color'));
f2b50825
AE
244 }
245 });
246 });
247
248 observer.observe(colorField, {
249 attributes: true
250 });
251
252 updateCSSRule(variableName, colorField.style.getPropertyValue('background-color'));
253 });
ac229adb
AE
254
255 // category selection by clicking on the area
256 var buttonToggleColorPalette = elBySel('.jsButtonToggleColorPalette');
257 var buttonSelectCategoryByClick = elBySel('.jsButtonSelectCategoryByClick');
258 var toggleSelectionMode = function() {
259 buttonSelectCategoryByClick.classList.toggle('active');
260 buttonToggleColorPalette.classList.toggle('disabled');
261 _stylePreviewWindow.classList.toggle('spShowRegions');
262 select.disabled = !select.disabled;
263 };
264
265 buttonSelectCategoryByClick.addEventListener(WCF_CLICK_EVENT, function (event) {
266 event.preventDefault();
267
268 toggleSelectionMode();
269 });
270
271 elBySelAll('[data-region]', _stylePreviewWindow, function (region) {
272 region.addEventListener(WCF_CLICK_EVENT, function (event) {
273 if (!_stylePreviewWindow.classList.contains('spShowRegions')) {
274 return;
275 }
276
277 event.preventDefault();
278 event.stopPropagation();
279
280 toggleSelectionMode();
281
282 select.value = elData(region, 'region');
283 Core.triggerEvent(select, 'change');
284 });
285 });
3926d706
MW
286 },
287
288 hideVisualEditor: function() {
59964c3f 289 elHide(_stylePreviewWindow);
3926d706
MW
290 elById('spVariablesWrapper').style.removeProperty('transform');
291 elHide(elById('stylePreviewRegionMarker'));
76f2a1ce
AE
292
293 _isSmartphone = true;
3926d706
MW
294 },
295
296 showVisualEditor: function() {
59964c3f 297 elShow(_stylePreviewWindow);
3926d706
MW
298
299 window.setTimeout(function() {
300 Core.triggerEvent(elById('spCategories'), 'change');
301 }, 100);
76f2a1ce
AE
302
303 _isSmartphone = false;
90b4b964
AE
304 }
305 };
90b4b964 306});