From 1938eb100d70fe42f6f0871026124a0bd5abd589 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 23 Oct 2023 22:16:02 +0200 Subject: [PATCH] ui: Add Privacy Panel --- css/Flags/Dark.css | 2 +- css/Pages/Pages.css | 35 ++++++- css/Pages/Settings.css | 72 -------------- css/Pages/Settings/Settings.css | 165 ++++++++++++++++++++++++++++++++ index.html | 30 ++++-- js/UI/Settings/Settings.js | 148 +++++++++++++++++++++------- js/UI/Settings/security.js | 32 +++++++ js/UI/Settings/tiles.js | 59 ++++++++++++ 8 files changed, 430 insertions(+), 113 deletions(-) delete mode 100644 css/Pages/Settings.css create mode 100644 css/Pages/Settings/Settings.css create mode 100644 js/UI/Settings/security.js create mode 100644 js/UI/Settings/tiles.js diff --git a/css/Flags/Dark.css b/css/Flags/Dark.css index da1bcda..589bfcf 100644 --- a/css/Flags/Dark.css +++ b/css/Flags/Dark.css @@ -1,6 +1,6 @@ body.dark { --color: #EEE; - --color2: #EEE9; + --color2: #EEE6; --background: #1118; --bg2: #0008; --hover: #FFF1; diff --git a/css/Pages/Pages.css b/css/Pages/Pages.css index 02e7e0d..4fa4c1a 100644 --- a/css/Pages/Pages.css +++ b/css/Pages/Pages.css @@ -1,6 +1,6 @@ @import url(Home.css); @import url(Services.css); -@import url(Settings.css); +@import url(Settings/Settings.css); .page { position: fixed; @@ -80,3 +80,36 @@ .header .text { font-size: 26px; } + +.sub-switch { + display: flex; + background: var(--bg2); + transition: background .2s; + margin: 16px 24px 0; + z-index: 1; + padding: 4px; + border-radius: 16px; + position: relative; + overflow: hidden; + --id: inherit; +} + +.sub-switch::before { + content: " "; + z-index: -1; + position: absolute; + top: 4px; + left: calc(var(--id) / var(--switches) * 100% + 4px - 4px * var(--id)); + width: calc(100% / var(--switches) - 4px); + height: calc(100% - 8px); + opacity: .25; + background: var(--color2); + border-radius: 12px; + transition: left .3s, background .3s; +} + +.sub-switch div { + padding: 12px; + width: 50%; + cursor: pointer; +} diff --git a/css/Pages/Settings.css b/css/Pages/Settings.css deleted file mode 100644 index d613640..0000000 --- a/css/Pages/Settings.css +++ /dev/null @@ -1,72 +0,0 @@ -.settings { - margin: 32px auto; - padding: 0 16px; -} - -.setting { - background: var(--bg2); - margin: 8px; - padding: 20px; - display: flex; - cursor: pointer; - border-radius: 16px; - align-items: center; - text-align: left; - transition: background .3s; -} - -.setting i { - margin-right: 14px; - font-size: 28px; -} - -.setting .name { - font-size: 16px; -} - -.setting .desc { - opacity: .6; - margin-right: 16px; -} - -.setting .switch { - position: relative; - width: 44px; - min-width: 44px; - height: 24px; - background: #8886; - border-radius: 100px; - margin: 0 4px 0 auto; - transition: border .4s, background .3s; -} - -.setting .switch:after { - content: ""; - position: absolute; - width: 16px; - height: 16px; - background: var(--color); - left: 4px; - top: 50%; - border-radius: 10px; - transform: translateY(-50%); - transition: left .2s, background .3s; -} - -.setting.checked .switch { - background-color: #68F; - border-color: #68F; -} - -.setting.checked .switch:after { - left: calc(100% - 20px); -} - -.warn { - margin: 24px 0 -8px; - color: #F60; -} - -.warn.hidden { - display: none; -} diff --git a/css/Pages/Settings/Settings.css b/css/Pages/Settings/Settings.css new file mode 100644 index 0000000..6a23312 --- /dev/null +++ b/css/Pages/Settings/Settings.css @@ -0,0 +1,165 @@ +.settings { + margin: 32px auto; + padding: 0 16px; +} + +.setting { + background: var(--bg2); + margin: 8px; + padding: 20px; + display: flex; + cursor: pointer; + border-radius: 16px; + align-items: center; + text-align: left; + transition: background .3s; +} + +.setting i { + margin-right: 14px; + font-size: 28px; +} + +.setting .name { + font-size: 16px; +} + +.setting .desc { + opacity: .6; + margin-right: 16px; +} + +.setting .switch { + position: relative; + width: 44px; + min-width: 44px; + height: 24px; + background: #8886; + border-radius: 100px; + margin: 0 4px 0 auto; + transition: border .4s, background .3s; +} + +.setting .switch:after { + content: ""; + position: absolute; + width: 16px; + height: 16px; + background: var(--color); + left: 4px; + top: 50%; + border-radius: 10px; + transform: translateY(-50%); + transition: left .2s, background .3s; +} + +.setting.checked .switch { + background-color: #68F; + border-color: #68F; +} + +.setting.checked .switch:after { + left: calc(100% - 20px); +} + +#no-cookies { + margin: 24px 0 -8px; + color: #F60; +} + +#no-cookies.hidden { + display: none; +} + +.subpages { + position: relative; + transform: translateX(calc(var(--id) * -100%)); + transition: transform .4s, height .4s; +} + +.subpages > div { + position: absolute; + left: calc(var(--n) * 100%); + width: 100%; +} + +#report-boxes { + display: flex; + text-align: left; + flex-wrap: wrap; + margin-bottom: 8px; +} + +#report-boxes i::after { + color: var(--color); + text-shadow: 0 0 48px var(--color); + padding: 16px; + font-size: 28px; + border-radius: 32px; +} + +#report-boxes .icon-https::after { + --color: #0C7; + content: "lock"; +} + +#report-boxes .icon-rocket::after { + --color: #68F; + content: "rocket_launch"; +} + +#report-boxes .prewarn i::after { + --color: #EA0; +} + +#report-boxes .warn i::after { + --color: #F60; +} + +#report-boxes .error i::after { + --color: #F22; +} + +#report-boxes > div { + display: flex; + width: 50%; + min-width: 256px; + flex: 1; + padding: 20px 8px; + align-items: center; +} + +#report-boxes .title { + font-size: 16px; +} + +#report-boxes .subtitle { + opacity: .6; +} + +.security { + display: flex; + align-items: center; + justify-content: space-between; + max-width: 600px; + padding: 24px; + margin: 120px auto 80px; + text-align: right; +} + +.security i { + font-size: 80px; +} + +.security #report-score { + font-size: 48px; +} + +.security #report { + opacity: .6; +} + +.score::after { + content: "%"; + font-size: 18px; +} diff --git a/index.html b/index.html index 41cefe7..179a3cf 100644 --- a/index.html +++ b/index.html @@ -48,16 +48,34 @@ - +
-
- settings -
Settings
+
+
Privacy
+
Settings
+
+
+ + +
+
+ shield +
+
+
+
+
+
+
+ + +
+
WARNING: due to blocked cookies, all settings will be lost after page reload
+
+
-
WARNING: due to blocked cookies, all settings will be lost after page reload
-
diff --git a/js/UI/Settings/Settings.js b/js/UI/Settings/Settings.js index a24e9a6..1c4ecab 100644 --- a/js/UI/Settings/Settings.js +++ b/js/UI/Settings/Settings.js @@ -1,5 +1,8 @@ import App from "../../App"; import * as EVENTS from "./events"; +import { analyzeService } from "./security"; +import * as tiles from "./tiles" + export default class Settings { constructor() { @@ -10,69 +13,148 @@ export default class Settings { init() { this.checkLocalStorage() + this.initSecurityPanel() + this.initSettings() + this.initPager() + } - let darkMode = this.addOnOffTile( + initSettings() { + let darkMode = tiles.addOnOffTile(this.config, "dark_mode", "Dark mode", "Make the colors more appropriate for low-light environments", "dark_mode", EVENTS.onThemeChange ) - document.getElementById("theme-switcher").addEventListener("click", () => { - darkMode.click() - }) - this.addOnOffTile( + tiles.addOnOffTile(this.config, "open_in_new", "Open in new tab", "Clicking on application will open it in a new browser tab", "open_new_tab", EVENTS.onNewTabChange ) - this.addOnOffTile( + tiles.addOnOffTile(this.config, "blur_on", "Enable blur", "Improves UI sweetness but may have a huge impact on performance", "blur", EVENTS.onBlurChange ) - this.addOnOffTile( + tiles.addOnOffTile(this.config, "animation", "Animations", "Show nice and fancy page transitions for improved experience", "animations", EVENTS.onAnimationChange ) + + document.getElementById("theme-switcher").addEventListener("click", () => { + darkMode.click() + }) } - addOnOffTile(icon, name, desc, key, func) { - let item = document.createElement("div") - item.classList.add("setting") - item.innerHTML = ` - ${icon} -
-
${name}
-
${desc}
-
-
` + initPager() { + let switcher = document.getElementById("switch") + let buttons = switcher.children + let subsettings = document.getElementById("subsettings") - let handleState = () => { - let c = item.classList - if (this.config.get(key)) c.add("checked") - else c.remove("checked") + for (let i = 0; i < buttons.length; i++) { + let button = buttons[i] + subsettings.children[i].setAttribute("style", `--n: ${i}`) + + button.addEventListener("click", () => { + let calculatedHeight = subsettings.children[i].offsetHeight + subsettings.style.height = `${calculatedHeight}px` + subsettings.parentNode.setAttribute("style", `--id: ${i}`) + switcher.setAttribute("style", `--switches: ${buttons.length}`) + }) } - let write = () => { - let target_value = !this.config.get(key) - this.config.set(key, target_value) + + buttons[0].click() + } + + initSecurityPanel() { + let stats = { + total: 0, + secure: 0, + thirdParties: 0 + } + for (let service of this.config.getServices()) { + let analysis = analyzeService(service.href) + stats.total++ + stats.secure += analysis.isSecure + stats.thirdParties += analysis.isThirdParty } - let f = () => {func(this.config)} - item.addEventListener("click", write) - item.addEventListener("click", handleState) - if (func) item.addEventListener("click", f) + let secure_pp = 100 * stats.secure / stats.total + let indepencence_pp = 100 * (stats.total - stats.thirdParties) / stats.total + let privacy_score = secure_pp * 0.5 + indepencence_pp * 0.5 - handleState() - if (func && this.config.changed(key)) f() - document.getElementById("settings").appendChild(item) - return item + let privacy_report; + if (privacy_score == 100) { + privacy_report = "Data is sent securely and no third-party services are involved." + } + else if (privacy_score >= 85) { + privacy_report = "Great privacy overall, but at some point it could be better." + } + else if (privacy_score >= 40) { + privacy_report = "Decent privacy. You should pay attention what you're doing." + } + else privacy_report = "Transferring data is insecure and vulnerable to various threats." + + document.getElementById("report-score").innerText = Math.round(privacy_score) + document.getElementById("report").innerText = privacy_report + + const phrase = " of the listed services are using secure connections" + tiles.privacyBox(secure_pp, "icon-https", [ + { + "from": 0, + "name": "No encryption", + "desc": "None" + phrase + }, + { + "from": 0.1, + "name": "Poor encryption", + "desc": "Only some" + phrase + }, + { + "from": 60, + "name": "Fair encryption", + "desc": "Most" + phrase + }, + { + "from": 90, + "name": "Good encryption", + "desc": "Nearly all" + phrase + }, + { + "from": 100, + "name": "Full encryption", + "desc": "All" + phrase + } + ]) + + tiles.privacyBox(indepencence_pp, "icon-rocket", [ + { + "from": 0, + "name": "Not independent", + "desc": "This server lists a lot of 3rd party services" + }, + { + "from": 40, + "name": "Partially independent", + "desc": "This server lists many 3rd party services" + }, + { + "from": 65, + "name": "Mostly independent", + "desc": "This server still lists some 3rd party services" + }, + { + "from": 100, + "name": "Fully independent", + "desc": "This server is free of 3rd party services" + } + ]) } checkLocalStorage() { let warn = document.getElementById("no-cookies").classList if (this.config.storageAvailable) warn.add("hidden") } -} \ No newline at end of file +} diff --git a/js/UI/Settings/security.js b/js/UI/Settings/security.js new file mode 100644 index 0000000..eefcec5 --- /dev/null +++ b/js/UI/Settings/security.js @@ -0,0 +1,32 @@ +export function analyzeService(url) { + let isSiteSecure = ( + window.location.protocol == "https:" || + window.location.hostname == "localhost" + ) + + let domain_base = window.location.hostname + let isSecure = false + let isThirdParty = true + + if (url.startsWith("https://")) { + isSecure = true + } + else if (!["http", "https"].includes(url.split("://")[0])) { + isSecure = isSiteSecure + } + + if (isThirdParty) { + let domain = url.split("://") + if (domain.length > 1) { + domain = domain[1] + domain = domain.split("/")[0] + domain = domain.split(":")[0] + isThirdParty = !domain.includes(domain_base) + } + else { + isThirdParty = false + } + } + + return {isSecure, isThirdParty} +} \ No newline at end of file diff --git a/js/UI/Settings/tiles.js b/js/UI/Settings/tiles.js new file mode 100644 index 0000000..2a535af --- /dev/null +++ b/js/UI/Settings/tiles.js @@ -0,0 +1,59 @@ +export function addOnOffTile(conf, icon, name, desc, key, func) { + let item = document.createElement("div") + item.classList.add("setting") + item.innerHTML = ` + ${icon} +
+
${name}
+
${desc}
+
+
` + + let handleState = () => { + let c = item.classList + if (conf.get(key)) c.add("checked") + else c.remove("checked") + } + let write = () => { + let target_value = !conf.get(key) + conf.set(key, target_value) + } + let f = () => {func(conf)} + + item.addEventListener("click", write) + item.addEventListener("click", handleState) + if (func) item.addEventListener("click", f) + + handleState() + if (func && conf.changed(key)) f() + document.getElementById("settings").appendChild(item) + return item +} + +export function privacyBox(privacyScore, icon_class, levels) { + let name, desc + levels = levels.reverse() + + for (let i in levels) { + let item = levels[i] + if (item.from <= privacyScore) { + name = levels[i].name + desc = levels[i].desc + break + } + } + + let item = document.createElement("div") + item.innerHTML = ` +
+
${name}
+
${desc}
+
` + + if (privacyScore < 30) item.classList.add("error") + else if (privacyScore < 90) item.classList.add("warn") + else if (privacyScore < 100) item.classList.add("prewarn") + + document.getElementById("report-boxes").appendChild(item) + return item +}