diff --git a/public/static/settings.js b/public/static/settings.js new file mode 100644 index 0000000..87c4f58 --- /dev/null +++ b/public/static/settings.js @@ -0,0 +1,186 @@ +/* + UI: Method for selecting all search engines + + This function is called when user click on + `select all` and `deselect all` toogle element. + + - If select all is pressed toggle all options and insert all value + joined with comma to input which is saved in cookie. + then changes text to `deselect all`. + eg value: 'ddg,searx,' + + - If deselect all is pressed uncheck all options and insert empty + value to input which is saved in cookie. +*/ +function selectAllHandler(elem) { + let span = elem.parentElement.querySelector("span"); + let mainInput = document.getElementsByName("searchEng")[0]; + let prevStateSelectAll = span.innerText == "Select all" ? true : false; + document.querySelectorAll(".searchEng-elem").forEach((engine) => { + if (prevStateSelectAll) { + engine.querySelector('input[type="checkbox"]').checked = true; + } else { + engine.querySelector('input[type="checkbox"]').checked = false; + } + }); + if (prevStateSelectAll) { + let getValues = () => { + let value = ""; + document + .querySelectorAll('[data-isCheckbox]:not([data-value="all"])') + .forEach((elem) => { + value += elem.getAttribute("data-value") + ","; + }); + return value; + }; + mainInput.value = getValues(); + } else { + mainInput.value = ""; + } + span.innerText = prevStateSelectAll ? "Deselect all" : "Select all"; +} + +/* + UI: Filter settings as per category + + There are two elements one is `category` and `detail`. + Category contains `data-id` when a user click it + we need to show detail element having + `data-detailId` whose value is same as `data-id`. + + When a user clicks on a category on sidebar, view + settings containing `data-id` of sidebar element's `data-detailId` + and hide other settings + + - if `all` is clicked then show all settings. +*/ +document.querySelectorAll(".settings-sidebar .set-name").forEach((filter) => { + let target = filter.getAttribute("data-detailId"); + filter.addEventListener("click", () => { + try { + document.querySelector(".set-name.active").classList.remove("active"); + } catch (e) {} + filter.classList.add("active"); + if (target == "all") { + document.querySelectorAll(".set-item").forEach((elem) => { + elem.style.display = "block"; + }); + return; + } + document + .querySelectorAll('.set-item[data-id="' + target + '"]') + .forEach((elem) => { + elem.style.display = "block"; + }); + document + .querySelectorAll('.set-item:not([data-id="' + target + '"])') + .forEach((elem) => { + elem.style.display = "none"; + }); + }); +}); + + +/* + This function is called when a user click on submit button + it validates all user inputs and saves to a cookie. + + - if input having `required` attr and is empty it generates a + error text and insert above the setting element + + - else store setttings to a cookie. +*/ +function submitSettings() { + let form = document.settings; + let stopProceeding = false; + document.querySelectorAll(".errTxt").forEach((e) => e.remove()); + for (let i = 0; i < form.elements.length; i++) { + let input = form.elements[i]; + if (input.value == "" && input.hasAttribute("required")) { + stopProceeding = true; + let elem = input.parentElement.querySelector("[takeInput]"); + let errTxt = document.createElement("p"); + errTxt.classList.add("errTxt"); + errTxt.innerText = "This setting can't be empty!!"; + elem.classList.add("invalid"); + elem.parentElement.insertBefore(errTxt, elem); + let sidebarElement = input.closest(".set-item").getAttribute("data-id"); + document + .querySelector( + `.settings-sidebar .set-name[data-detailId="${sidebarElement}` + ) + .click(); + stopProceeding = true; + } + } + if (!stopProceeding) { + var expiration_date = new Date(); + expiration_date.setFullYear(expiration_date.getFullYear() + 1); + let formData = new FormData(document.querySelector("form")); + for (var [key, value] of formData.entries()) { + document.cookie = `${key}=${value}; expires=${expiration_date.toUTCString()}`; + } + } else { + return false; + } + // On settings saved successfully + alert("Settings saved succssfully!"); + window.location.reload(); +} + + +/* + This function will be called on page ready. + it iterates over saved cookies and if cookie name is + in the list of valid cookies then load it. + + - if cookie is searchEng(type=toggle/checkbox) we deselect + all checkboxes and select those which are stored in cookie. + + - if cookie is of type `select` we deselect default selected + option and then select option which is stored in cookie. +*/ +function loadUserSettings() { + let inputs = ["searchEng", "theme", "color-sch"]; + var keyValuePairs = document.cookie.split(";"); + for (var i = 0; i < keyValuePairs.length; i++) { + var name = keyValuePairs[i].substring(0, keyValuePairs[i].indexOf("=")); + var value = keyValuePairs[i].substring(keyValuePairs[i].indexOf("=") + 1); + name = name.trim(); + if (!inputs.includes(name)) { + continue; + } + let input = document.getElementsByName(name)[0]; + input.value = value; + if (name == "searchEng") { + // Unload all checked engines + document + .querySelectorAll(".searchEng-elem input[type=checkbox]") + .forEach((e) => { + e.checked = false; + }); + value = value.replace(" ", ""); + value.split(",").forEach((val) => { + if (!val) { + return; + } + document + .querySelector(`[data-isCheckbox][data-value="${val}"]`) + .parentElement.querySelector("input").checked = true; + }); + } else { + // Unload all selected options + document + .querySelector( + `[data-input="${name}"] .options span[data-value="${value}"]` + ) + .removeAttribute("selected"); + singleSelectClickHandler( + document.querySelector(`.options span[data-value="${value}"]`) + ); + } + } +} + +// Code where settings are loaded from cookie. +loadUserSettings(); diff --git a/public/static/themes/simple.css b/public/static/themes/simple.css index 17962d0..dfcda52 100644 --- a/public/static/themes/simple.css +++ b/public/static/themes/simple.css @@ -263,41 +263,320 @@ footer { /* Styles for the about page */ - .about-container article{ - font-size: 1.5rem; - color:var(--fg); - padding-bottom: 10px; - } +.about-container article{ + font-size: 1.5rem; + color:var(--fg); + padding-bottom: 10px; +} - .about-container article h1{ - color: var(--2); - font-size: 2.8rem; - } +.about-container article h1{ + color: var(--2); + font-size: 2.8rem; +} - .about-container article div{ - padding-bottom: 15px; - } +.about-container article div{ + padding-bottom: 15px; +} - .about-container a{ - color:var(--3); - } +.about-container a{ + color:var(--3); +} - .about-container article h2{ - color: var(--3); - font-size: 1.8rem; - padding-bottom: 10px; - } +.about-container article h2{ + color: var(--3); + font-size: 1.8rem; + padding-bottom: 10px; +} - .about-container p{ - color:var(--fg); - font-size: 1.6rem; - padding-bottom: 10px; - } +.about-container p{ + color:var(--fg); + font-size: 1.6rem; + padding-bottom: 10px; +} - .about-container h3{ - font-size: 1.5rem; - } +.about-container h3{ + font-size: 1.5rem; +} - .about-container { - width: 80%; - } +.about-container { + width: 80%; +} + +/* css for settings page t */ +.settings { + margin: 2rem; + width: 80%; + height: 100%; + color: var(--fg); +} + +.settings h1 { + color: var(--2); + font-size: 2.5rem; +} + + +.settings hr { + border-color: var(--3); + margin: .3rem 0 1rem 0; +} + +.settings-view { + display: flex; + flex: 1 0 auto; + margin: 1.5rem 0; +} + +.settings-sidebar { + width: 25%; + height: 100%; +} + +.settings-sidebar .set-name { + cursor: pointer; + font-size: 2rem; + display: block; + margin-right: .5rem; + margin-left: -.7rem; + padding: .7rem; + border-radius: 5px; + font-weight: bold; + margin-bottom: .5rem; +} + +.settings-sidebar .set-name:hover, .settings-sidebar .set-name.active { + background-color: rgba(255, 255, 255, 0.15); + +} + +.settings-detail { + border-left: 1.5px solid var(--3); + padding-left: 3rem; +} + +.settings-detail .set-item { + margin: 2rem 0; + margin-top: 0; +} + +.settings-detail .set-name { + font-size: 2rem; + font-weight: bold; + color: var(--4) +} + +.settings-detail .set-desc { + font-size: 1.5rem; + margin-bottom: .5rem; +} + +/* css for custom select */ +.custom-select, .options { + font-size: 1.5rem; + background-color: var(--bg); + width: 250px; + padding: 1rem 1.7rem; + border-radius: 7px; +} + +.custom-select { + position: relative; + vertical-align: middle; + margin: .7rem 0; +} + +.custom-select.invalid { + border: 1px solid red; +} + +.custom-select svg { + float: right; +} + +.options { + display: none; + position: absolute; + left: 0; + margin-top: 1.3rem; + width: 100%; + padding: .7rem 1rem; + cursor: pointer; + z-index: 3; + max-height: 15rem; + overflow-y: auto; +} + +.options span { + display: block; + padding: 1rem; + width: 100%; + border-radius: 5px; + vertical-align: middle; +} + +.options span:hover { + background-color: var(--1); +} + +.selected-multiple-option { + padding: .8rem; + border-radius: 5px; + background-color: var(--bg); + margin: .5rem .3rem; +} + +.selected-multiple-option svg { + width: 1.3rem; + height: 1.3rem; + vertical-align: middle; + margin-bottom: .5rem; + cursor: pointer; +} + +.select-multiple-show { + margin: 1rem 0; + font-size: 1.5rem; +} + +.underlined-text { + font-size: 1.7rem; + cursor: pointer; + margin-bottom: .5rem; + display: block; +} + +.settings .submit-btn { + padding: 1rem 2rem; + font-size: 1.5rem; + background: var(--3); + color: var(--bg); + border-radius: .5rem; + border: 2px solid transparent; + font-weight: bold; + transition: all .1s ease-out; + cursor: pointer; + box-shadow: 5px 5px; +} + +.settings .submit-btn:active { + outline: none; + box-shadow: none; + translate: 5px 5px; +} + +/* Css for error text */ + +.errTxt { + color: white; + background: red; + padding: .5rem; + border-radius: 5px; + font-size: 1.3rem; + width: max-content; +} + +/* Css for toggle element */ + +label, +label::before, +label::after { + transition: 200ms all ease-in-out 50ms; + box-sizing: border-box; + backface-visibility: hidden; +} + +input[type="checkbox"] { + display: none; +} + +/*Button is :CHECKED*/ + +input[type="checkbox"]:checked ~ div[data-isCheckbox] { + background: rgba(73,168,68,1); + box-shadow: 0 0 2px rgba(73,168,68,1); +} + +input[type="checkbox"]:checked ~ div[data-isCheckbox] label { + left: 25px; + transform: rotate(360deg); +} + + +/*shared*/ + +div[data-isCheckbox], +label { + border-radius: 50px; +} + + +/*'un':checked state*/ + +div[data-isCheckbox] { + height: 25px; + width: 50px; + background: rgba(43, 43, 43, 1); + position: relative; + box-shadow: 0 0 2px rgba(43,43,43,1); + +} + +label { + height: 20px; + width: 20px; + background: rgba(255, 255, 255, 1); + position: absolute; + top: 3px; + left: 3px; + cursor: pointer; +} + +label::before { + content: ''; + height: 15px; + width: 3px; + position: absolute; + top: calc(50% - 8px); + left: calc(50% - 1.5px); + transform: rotate(45deg); +} + +label::after { + content: ''; + height: 3px; + width: 15px; + position: absolute; + top: calc(50% - 2.5px); + left: calc(50% - 8px); + transform: rotate(45deg); +} + +label::before, +label::after{ + background: rgba(43,43,43,1); + border-radius: 5px; +} + +/* pesduo class on toggle */ + +input[type="checkbox"]:checked ~ div label::before{ + height: 15px; + top: calc(55% - 8px); + left: calc(60% - 2px); + background: rgba(73,168,68,1); +} + +input[type="checkbox"]:checked ~ div label::after{ + width: 7px; + top: calc(95% - 7px); + left: calc(22.5% - 2px); + background: rgba(73,168,68,1); +} + +.searchEng-elem { + display: flex; + gap: 3rem; + align-items: center; + font-size: 1.5rem; + margin: 1rem 0; +} diff --git a/public/static/ui_plugins.js b/public/static/ui_plugins.js new file mode 100644 index 0000000..53dddb5 --- /dev/null +++ b/public/static/ui_plugins.js @@ -0,0 +1,110 @@ +/* + Select plugin + Note: Only for single select option + Usage example: + ``` + +