Merge pull request #6006 from WoltLab/file-processor-can-adopt
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / js / WoltLabSuite / Core / Acp / Ui / Style / Editor.js
CommitLineData
90b4b964 1/**
13744d6e 2 * Provides the style editor.
50aa3a01 3 *
516c05af
AE
4 * @author Alexander Ebert
5 * @copyright 2001-2019 WoltLab GmbH
6 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
90b4b964 7 */
adc5c5db 8define(["require", "exports", "tslib", "../../../Ajax", "../../../Core", "../../../Dom/Util", "../../../Event/Handler", "../../../Ui/Screen", "./DarkMode"], function (require, exports, tslib_1, Ajax, Core, Util_1, EventHandler, UiScreen, DarkMode_1) {
50aa3a01 9 "use strict";
516c05af 10 Object.defineProperty(exports, "__esModule", { value: true });
054537bf
AE
11 exports.setup = setup;
12 exports.hideVisualEditor = hideVisualEditor;
13 exports.showVisualEditor = showVisualEditor;
716617cf
TD
14 Ajax = tslib_1.__importStar(Ajax);
15 Core = tslib_1.__importStar(Core);
16 Util_1 = tslib_1.__importDefault(Util_1);
17 EventHandler = tslib_1.__importStar(EventHandler);
18 UiScreen = tslib_1.__importStar(UiScreen);
516c05af
AE
19 const _stylePreviewRegions = new Map();
20 let _stylePreviewRegionMarker;
21 const _stylePreviewWindow = document.getElementById("spWindow");
22 let _isVisible = true;
23 let _isSmartphone = false;
24 let _updateRegionMarker;
50aa3a01 25 /**
516c05af 26 * Handles the switch between static and fluid layout.
50aa3a01 27 */
516c05af
AE
28 function handleLayoutWidth() {
29 const useFluidLayout = document.getElementById("useFluidLayout");
30 const fluidLayoutMinWidth = document.getElementById("fluidLayoutMinWidth");
31 const fluidLayoutMaxWidth = document.getElementById("fluidLayoutMaxWidth");
32 const fixedLayoutVariables = document.getElementById("fixedLayoutVariables");
33 function change() {
34 if (useFluidLayout.checked) {
35 Util_1.default.show(fluidLayoutMinWidth);
36 Util_1.default.show(fluidLayoutMaxWidth);
37 Util_1.default.hide(fixedLayoutVariables);
50aa3a01 38 }
516c05af
AE
39 else {
40 Util_1.default.hide(fluidLayoutMinWidth);
41 Util_1.default.hide(fluidLayoutMaxWidth);
42 Util_1.default.show(fixedLayoutVariables);
50aa3a01 43 }
516c05af
AE
44 }
45 useFluidLayout.addEventListener("change", change);
46 change();
47 }
48 /**
49 * Handles SCSS input fields.
50 */
51 function handleScss(isTainted) {
52 const individualScss = document.getElementById("individualScss");
53 const overrideScss = document.getElementById("overrideScss");
bdfae1ed
AE
54 const refreshCodeMirror = (element) => {
55 element.codemirror.refresh();
56 element.codemirror.setCursor(element.codemirror.getCursor());
57 };
516c05af
AE
58 if (isTainted) {
59 EventHandler.add("com.woltlab.wcf.simpleTabMenu_styleTabMenuContainer", "select", () => {
bdfae1ed
AE
60 refreshCodeMirror(individualScss);
61 refreshCodeMirror(overrideScss);
516c05af
AE
62 });
63 }
64 else {
65 EventHandler.add("com.woltlab.wcf.simpleTabMenu_advanced", "select", (data) => {
66 if (data.activeName === "advanced-custom") {
bdfae1ed
AE
67 refreshCodeMirror(document.getElementById("individualScssCustom"));
68 refreshCodeMirror(document.getElementById("overrideScssCustom"));
516c05af
AE
69 }
70 else if (data.activeName === "advanced-original") {
bdfae1ed
AE
71 refreshCodeMirror(individualScss);
72 refreshCodeMirror(overrideScss);
516c05af 73 }
50aa3a01 74 });
516c05af
AE
75 }
76 }
77 function handleProtection(styleId) {
78 const button = document.getElementById("styleDisableProtectionSubmit");
79 const checkbox = document.getElementById("styleDisableProtectionConfirm");
80 checkbox.addEventListener("change", () => {
81 button.disabled = !checkbox.checked;
82 });
83 button.addEventListener("click", () => {
84 Ajax.apiOnce({
85 data: {
86 actionName: "markAsTainted",
87 className: "wcf\\data\\style\\StyleAction",
88 objectIDs: [styleId],
89 },
90 success: () => {
91 window.location.reload();
92 },
50aa3a01 93 });
516c05af
AE
94 });
95 }
1c8485c3 96 function initVisualEditor() {
516c05af
AE
97 _stylePreviewWindow.querySelectorAll("[data-region]").forEach((region) => {
98 _stylePreviewRegions.set(region.dataset.region, region);
99 });
100 _stylePreviewRegionMarker = document.createElement("div");
101 _stylePreviewRegionMarker.id = "stylePreviewRegionMarker";
102 _stylePreviewRegionMarker.innerHTML = '<div id="stylePreviewRegionMarkerBottom"></div>';
103 Util_1.default.hide(_stylePreviewRegionMarker);
104 document.getElementById("colors").appendChild(_stylePreviewRegionMarker);
105 const container = document.getElementById("spSidebar");
106 const select = document.getElementById("spCategories");
107 let lastValue = select.value;
108 _updateRegionMarker = () => {
109 if (_isSmartphone) {
110 return;
111 }
112 if (lastValue === "none") {
113 Util_1.default.hide(_stylePreviewRegionMarker);
114 return;
115 }
116 const region = _stylePreviewRegions.get(lastValue);
117 const rect = region.getBoundingClientRect();
118 let top = rect.top + (window.scrollY || window.pageYOffset);
119 Util_1.default.setStyles(_stylePreviewRegionMarker, {
120 height: `${region.clientHeight + 20}px`,
121 left: `${rect.left + document.body.scrollLeft - 10}px`,
122 top: `${top - 10}px`,
123 width: `${region.clientWidth + 20}px`,
50aa3a01 124 });
516c05af
AE
125 Util_1.default.show(_stylePreviewRegionMarker);
126 top = Util_1.default.offset(region).top;
127 // `+ 80` = account for sticky header + selection markers (20px)
128 const firstVisiblePixel = (window.pageYOffset || window.scrollY) + 80;
129 if (firstVisiblePixel > top) {
130 window.scrollTo(0, Math.max(top - 80, 0));
131 }
132 else {
133 const lastVisiblePixel = window.innerHeight + (window.pageYOffset || window.scrollY);
134 if (lastVisiblePixel < top) {
135 window.scrollTo(0, top);
50aa3a01
TD
136 }
137 else {
516c05af
AE
138 const bottom = top + region.offsetHeight + 20;
139 if (lastVisiblePixel < bottom) {
140 window.scrollBy(0, bottom - top);
50aa3a01
TD
141 }
142 }
516c05af
AE
143 }
144 };
516c05af
AE
145 const callbackChange = () => {
146 let element = container.querySelector(`.spSidebarBox[data-category="${lastValue}"]`);
147 Util_1.default.hide(element);
148 lastValue = select.value;
149 element = container.querySelector(`.spSidebarBox[data-category="${lastValue}"]`);
150 Util_1.default.show(element);
516c05af
AE
151 // set region marker
152 _updateRegionMarker();
153 };
154 select.addEventListener("change", callbackChange);
155 // apply CSS rules
156 const style = document.createElement("style");
157 style.appendChild(document.createTextNode(""));
158 style.dataset.createdBy = "WoltLab/Acp/Ui/Style/Editor";
159 document.head.appendChild(style);
1c8485c3 160 const spWindow = document.getElementById("spWindow");
516c05af
AE
161 const wrapper = document.getElementById("spVariablesWrapper");
162 wrapper.querySelectorAll(".styleVariableColor").forEach((colorField) => {
163 const variableName = colorField.dataset.store.replace(/_value$/, "");
164 const observer = new MutationObserver((mutations) => {
165 mutations.forEach((mutation) => {
166 if (mutation.attributeName === "style") {
1c8485c3 167 spWindow.style.setProperty(`--${variableName}`, colorField.style.getPropertyValue("background-color"));
50aa3a01 168 }
50aa3a01
TD
169 });
170 });
516c05af
AE
171 observer.observe(colorField, {
172 attributes: true,
173 });
1c8485c3 174 spWindow.style.setProperty(`--${variableName}`, colorField.style.getPropertyValue("background-color"));
516c05af
AE
175 });
176 // category selection by clicking on the area
177 const buttonToggleColorPalette = document.querySelector(".jsButtonToggleColorPalette");
178 const buttonSelectCategoryByClick = document.querySelector(".jsButtonSelectCategoryByClick");
179 function toggleSelectionMode() {
180 buttonSelectCategoryByClick.classList.toggle("active");
181 buttonToggleColorPalette.classList.toggle("disabled");
182 _stylePreviewWindow.classList.toggle("spShowRegions");
183 _stylePreviewRegionMarker.classList.toggle("forceHide");
184 select.disabled = !select.disabled;
185 }
186 buttonSelectCategoryByClick.addEventListener("click", (event) => {
187 event.preventDefault();
188 toggleSelectionMode();
189 });
190 _stylePreviewWindow.querySelectorAll("[data-region]").forEach((region) => {
191 region.addEventListener("click", (event) => {
192 if (!_stylePreviewWindow.classList.contains("spShowRegions")) {
193 return;
194 }
50aa3a01 195 event.preventDefault();
516c05af
AE
196 event.stopPropagation();
197 toggleSelectionMode();
198 select.value = region.dataset.region;
199 // Programmatically trigger the change event handler, rather than dispatching an event,
200 // because Firefox fails to execute the event if it has previously been disabled.
201 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1426856
202 callbackChange();
50aa3a01 203 });
516c05af
AE
204 });
205 // toggle view
206 const spSelectCategory = document.getElementById("spSelectCategory");
207 buttonToggleColorPalette.addEventListener("click", (event) => {
208 event.preventDefault();
209 buttonSelectCategoryByClick.classList.toggle("disabled");
210 Util_1.default.toggle(spSelectCategory);
211 buttonToggleColorPalette.classList.toggle("active");
212 _stylePreviewWindow.classList.toggle("spColorPalette");
213 _stylePreviewRegionMarker.classList.toggle("forceHide");
214 select.disabled = !select.disabled;
215 });
216 }
217 /**
218 * Sets up dynamic style options.
219 */
220 function setup(options) {
221 handleLayoutWidth();
222 handleScss(options.isTainted);
adc5c5db 223 (0, DarkMode_1.setup)();
516c05af
AE
224 if (!options.isTainted) {
225 handleProtection(options.styleId);
226 }
1c8485c3 227 initVisualEditor();
516c05af
AE
228 UiScreen.on("screen-sm-down", {
229 match() {
230 hideVisualEditor();
231 },
232 unmatch() {
233 showVisualEditor();
234 },
235 setup() {
236 hideVisualEditor();
237 },
238 });
239 function callbackRegionMarker() {
240 if (_isVisible) {
241 _updateRegionMarker();
242 }
50aa3a01 243 }
516c05af
AE
244 window.addEventListener("resize", callbackRegionMarker);
245 EventHandler.add("com.woltlab.wcf.AcpMenu", "resize", callbackRegionMarker);
246 EventHandler.add("com.woltlab.wcf.simpleTabMenu_styleTabMenuContainer", "select", function (data) {
247 _isVisible = data.activeName === "colors";
248 callbackRegionMarker();
249 });
250 }
516c05af
AE
251 function hideVisualEditor() {
252 Util_1.default.hide(_stylePreviewWindow);
253 document.getElementById("spVariablesWrapper").style.removeProperty("transform");
254 Util_1.default.hide(document.getElementById("stylePreviewRegionMarker"));
255 _isSmartphone = true;
256 }
516c05af
AE
257 function showVisualEditor() {
258 Util_1.default.show(_stylePreviewWindow);
259 window.setTimeout(() => {
260 Core.triggerEvent(document.getElementById("spCategories"), "change");
261 }, 100);
262 _isSmartphone = false;
263 }
90b4b964 264});