1/**
2
3The code below is based on the Doxygen Awesome project with some minor modifications
4https://github.com/jothepro/doxygen-awesome-css
5
6MIT License
7
8Copyright (c) 2021 - 2022 jothepro
9
10Permission is hereby granted, free of charge, to any person obtaining a copy
11of this software and associated documentation files (the "Software"), to deal
12in the Software without restriction, including without limitation the rights
13to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14copies of the Software, and to permit persons to whom the Software is
15furnished to do so, subject to the following conditions:
16
17The above copyright notice and this permission notice shall be included in all
18copies or substantial portions of the Software.
19
20THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26SOFTWARE.
27
28*/
29
30class DarkModeToggle extends HTMLElement {
31    static icon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="1.2em" width="1.2em"><g fill="none" fill-rule="evenodd"><path d="M0 0h24v24H0z"></path><rect width="1" height="3" x="12" fill="currentColor" rx=".5"></rect><rect width="1" height="3" x="12" y="21" fill="currentColor" rx=".5"></rect><rect width="1" height="3" x="22" y="10.5" fill="currentColor" rx=".5" transform="rotate(90 22.5 12)"></rect><rect width="1" height="3" x="1" y="10.5" fill="currentColor" rx=".5" transform="rotate(90 1.5 12)"></rect><rect width="1" height="3" x="19" y="3" fill="currentColor" rx=".5" transform="rotate(-135 19.5 4.5)"></rect><rect width="1" height="3" x="19" y="18" fill="currentColor" rx=".5" transform="rotate(135 19.5 19.5)"></rect><rect width="1" height="3" x="4" y="3" fill="currentColor" rx=".5" transform="scale(1 -1) rotate(45 15.37 0)"></rect><rect width="1" height="3" x="4" y="18" fill="currentColor" rx=".5" transform="scale(1 -1) rotate(-45 -42.57 0)"></rect><circle cx="12" cy="12" r="6.5" stroke="currentColor"></circle></g></svg>'
32    static icond = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="1.2em" width="1.2em"><g fill="none" fill-rule="evenodd"><path d="M0 0h24v24H0z"></path><rect width="1" height="3" x="12" fill="currentColor" rx=".5"></rect><rect width="1" height="3" x="12" y="21" fill="currentColor" rx=".5"></rect><rect width="1" height="3" x="22" y="10.5" fill="currentColor" rx=".5" transform="rotate(90 22.5 12)"></rect><rect width="1" height="3" x="1" y="10.5" fill="currentColor" rx=".5" transform="rotate(90 1.5 12)"></rect><rect width="1" height="3" x="19" y="3" fill="currentColor" rx=".5" transform="rotate(-135 19.5 4.5)"></rect><rect width="1" height="3" x="19" y="18" fill="currentColor" rx=".5" transform="rotate(135 19.5 19.5)"></rect><rect width="1" height="3" x="4" y="3" fill="currentColor" rx=".5" transform="scale(1 -1) rotate(45 15.37 0)"></rect><rect width="1" height="3" x="4" y="18" fill="currentColor" rx=".5" transform="scale(1 -1) rotate(-45 -42.57 0)"></rect><circle cx="12" cy="12" r="6.5" stroke="currentColor" fill="currentColor"></circle></g></svg>'
33    static title = "Toggle Light/Dark Mode"
34
35    static prefersLightModeInDarkModeKey = "prefers-light-mode-in-dark-mode"
36    static prefersDarkModeInLightModeKey = "prefers-dark-mode-in-light-mode"
37
38    static _staticConstructor = function() {
39        DarkModeToggle.enableDarkMode(DarkModeToggle.userPreference)
40        // Update the color scheme when the browsers preference changes
41        // without user interaction on the website.
42        window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
43            DarkModeToggle.onSystemPreferenceChanged()
44        })
45        // Update the color scheme when the tab is made visible again.
46        // It is possible that the appearance was changed in another tab
47        // while this tab was in the background.
48        document.addEventListener("visibilitychange", visibilityState => {
49            if (document.visibilityState === 'visible') {
50                DarkModeToggle.onSystemPreferenceChanged()
51            }
52        });
53    }()
54
55    static addButton() {
56
57        var tbuttons = document.getElementsByTagName("dark-mode-toggle");
58        var toggleButton;
59        var titleArea = document.getElementById("titlearea");
60        var searchBox = document.getElementById("MSearchBox");
61        var mainMenu  = document.getElementById("main-menu");
62        var navRow1   = document.getElementById("navrow1");
63        var mainMenuVisible = false;
64        if (!tbuttons.length){
65          toggleButton = document.createElement('dark-mode-toggle')
66          toggleButton.title = DarkModeToggle.title
67        } else {toggleButton=tbuttons[0]}
68
69
70        if (DarkModeToggle.darkModeEnabled){
71          toggleButton.innerHTML=DarkModeToggle.icond
72        } else {
73          toggleButton.innerHTML=DarkModeToggle.icon
74        }
75
76        if (mainMenu) {
77          var menuStyle = window.getComputedStyle(mainMenu);
78          mainMenuVisible = menuStyle.display!=='none'
79        }
80        var searchBoxPos1 = document.getElementById("searchBoxPos1");
81        if (searchBox) { // (1) search box visible
82          searchBox.parentNode.appendChild(toggleButton)
83        } else if (navRow1) { // (2) no search box, static menu bar
84          var li = document.createElement('li');
85          li.style = 'float: right;'
86          li.appendChild(toggleButton);
87          toggleButton.style = 'width: 24px; height: 25px; padding-top: 11px; float: right;';
88          var row = document.querySelector('#navrow1 > ul:first-of-type');
89          row.appendChild(li)
90        } else if (mainMenu && mainMenuVisible) { // (3) no search box + dynamic menu bar expanded
91          var li = document.createElement('li');
92          li.style = 'float: right;'
93          li.appendChild(toggleButton);
94          toggleButton.style = 'width: 14px; height: 36px; padding-top: 10px; float: right;';
95          mainMenu.appendChild(li)
96        } else if (searchBoxPos1) { // (4) no search box + dynamic menu bar collapsed
97          toggleButton.style = 'width: 24px; height: 36px; padding-top: 10px; float: right;';
98          searchBoxPos1.style = 'top: 0px;'
99          searchBoxPos1.appendChild(toggleButton);
100        } else if (titleArea) { // (5) no search box and no navigation tabs
101          toggleButton.style = 'width: 24px; height: 24px; position: absolute; right: 0px; top: 34px;';
102          titleArea.append(toggleButton);
103        }
104    }
105
106    static init() {
107        $(function() {
108            $(document).ready(function() {
109
110                $(document).ready(function(){
111                    DarkModeToggle.addButton();
112                })
113                $(window).resize(function(){
114                    DarkModeToggle.addButton();
115                })
116                DarkModeToggle.setDarkModeVisibility(DarkModeToggle.darkModeEnabled)
117            })
118        })
119    }
120
121    constructor() {
122        super();
123        this.onclick=this.toggleDarkMode
124    }
125
126
127    static createCookie(name, value, days) {
128        if (days) {
129            var date = new Date();
130            date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
131            var expires = "; expires=" + date.toGMTString();
132        }
133        else var expires = "";
134
135        document.cookie = name + "=" + value + expires + "; path=/";
136    }
137
138    static readCookie(name) {
139        var nameEQ = name + "=";
140        var ca = document.cookie.split(';');
141        for (var i = 0; i < ca.length; i++) {
142            var c = ca[i];
143            while (c.charAt(0) == ' ') c = c.substring(1, c.length);
144            if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
145        }
146        return null;
147    }
148
149    static eraseCookie(name) {
150        DarkModeToggle.createCookie(name, "", -1);
151    }
152
153    /**
154     * @returns `true` for dark-mode, `false` for light-mode system preference
155     */
156    static get systemPreference() {
157        return window.matchMedia('(prefers-color-scheme: dark)').matches
158    }
159
160    static get prefersDarkModeInLightMode() {
161        if (window.chrome) { // Chrome supports localStorage in combination with file:// but not cookies
162          return localStorage.getItem(DarkModeToggle.prefersDarkModeInLightModeKey)
163        } else { // Other browsers support cookies in combination with file:// but not localStorage
164          return DarkModeToggle.readCookie('doxygen_prefers_dark')=='1'
165        }
166    }
167
168    static set prefersDarkModeInLightMode(preference) {
169        if (window.chrome) {
170            if (preference) {
171                localStorage.setItem(DarkModeToggle.prefersDarkModeInLightModeKey, true)
172            } else {
173                localStorage.removeItem(DarkModeToggle.prefersDarkModeInLightModeKey)
174            }
175        } else {
176            if (preference) {
177               DarkModeToggle.createCookie('doxygen_prefers_dark','1',365)
178            } else {
179               DarkModeToggle.eraseCookie('doxygen_prefers_dark')
180            }
181        }
182    }
183
184    static get prefersLightModeInDarkMode() {
185        if (window.chrome) { // Chrome supports localStorage in combination with file:// but not cookies
186          return localStorage.getItem(DarkModeToggle.prefersLightModeInDarkModeKey)
187        } else { // Other browsers support cookies in combination with file:// but not localStorage
188          return DarkModeToggle.readCookie('doxygen_prefers_light')=='1'
189        }
190    }
191
192    static set prefersLightModeInDarkMode(preference) {
193        if (window.chrome) {
194            if (preference) {
195                localStorage.setItem(DarkModeToggle.prefersLightModeInDarkModeKey, true)
196            } else {
197                localStorage.removeItem(DarkModeToggle.prefersLightModeInDarkModeKey)
198            }
199        } else {
200            if (preference) {
201               DarkModeToggle.createCookie('doxygen_prefers_light','1',365)
202            } else {
203               DarkModeToggle.eraseCookie('doxygen_prefers_light')
204            }
205        }
206    }
207
208
209    /**
210     * @returns `true` for dark-mode, `false` for light-mode user preference
211     */
212    static get userPreference() {
213        return (!DarkModeToggle.systemPreference && DarkModeToggle.prefersDarkModeInLightMode) ||
214        (DarkModeToggle.systemPreference && !DarkModeToggle.prefersLightModeInDarkMode)
215    }
216
217    static set userPreference(userPreference) {
218        DarkModeToggle.darkModeEnabled = userPreference
219        if (!userPreference) {
220            if (DarkModeToggle.systemPreference) {
221                DarkModeToggle.prefersLightModeInDarkMode = true
222            } else {
223                DarkModeToggle.prefersDarkModeInLightMode = false
224            }
225        } else {
226            if (!DarkModeToggle.systemPreference) {
227                DarkModeToggle.prefersDarkModeInLightMode = true
228            } else {
229                DarkModeToggle.prefersLightModeInDarkMode = false
230            }
231        }
232        DarkModeToggle.onUserPreferenceChanged()
233    }
234
235    static setDarkModeVisibility(enable) {
236        var darkModeStyle, lightModeStyle;
237        if(enable) {
238          darkModeStyle  = 'inline-block';
239          lightModeStyle = 'none'
240        } else {
241          darkModeStyle  = 'none';
242          lightModeStyle = 'inline-block'
243        }
244        document.querySelectorAll('.dark-mode-visible').forEach(function(el) {
245            el.style.display = darkModeStyle;
246        });
247        document.querySelectorAll('.light-mode-visible').forEach(function(el) {
248            el.style.display = lightModeStyle;
249        });
250    }
251    static enableDarkMode(enable) {
252        if(enable) {
253            DarkModeToggle.darkModeEnabled = true
254            document.documentElement.classList.add("dark-mode")
255            document.documentElement.classList.remove("light-mode")
256        } else {
257            DarkModeToggle.darkModeEnabled = false
258            document.documentElement.classList.remove("dark-mode")
259            document.documentElement.classList.add("light-mode")
260        }
261        DarkModeToggle.setDarkModeVisibility(enable)
262    }
263
264    static onSystemPreferenceChanged() {
265        DarkModeToggle.darkModeEnabled = DarkModeToggle.userPreference
266        DarkModeToggle.enableDarkMode(DarkModeToggle.darkModeEnabled)
267    }
268
269    static onUserPreferenceChanged() {
270        DarkModeToggle.enableDarkMode(DarkModeToggle.darkModeEnabled)
271    }
272
273    toggleDarkMode() {
274        DarkModeToggle.userPreference = !DarkModeToggle.userPreference
275        DarkModeToggle.addButton();
276    }
277}
278
279customElements.define("dark-mode-toggle", DarkModeToggle);
280
281DarkModeToggle.init();
282