Launcher: UI rework
Fixed help modal header margins for title and close button. Help menu titles now have same padding from both sides. Scroll fixed in chrome. Problem in Safari.
Change-Id: I2987ea93379e385125e81ec37b9f0bb61a6d7797
diff --git a/core/installer/welcome/launcher-tmpl/launcher.html b/core/installer/welcome/launcher-tmpl/launcher.html
index 91562f5..6d404ad 100644
--- a/core/installer/welcome/launcher-tmpl/launcher.html
+++ b/core/installer/welcome/launcher-tmpl/launcher.html
@@ -5,13 +5,14 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>dodo: Launcher</title>
<link rel="stylesheet" type="text/css" href="/static/pico.2.0.6.min.css">
- <link rel="stylesheet" type="text/css" href="/static/launcher.css?v=0.0.2">
+ <link rel="stylesheet" type="text/css" href="/static/launcher.css?v=0.0.13">
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/hack-font/3.3.0/web/hack.min.css">
</head>
<body class="container-fluid">
<div id="left-panel">
<div class="user-circle">
<div class="circle">
- <p>{{ GetUserInitials .LoggedInUsername }}</p>
+ <p id="user-initial">{{ GetUserInitials .LoggedInUsername }}</p>
<div class="tooltip-user" id="tooltip-user">
<p>{{ .LoggedInUsername }}</p>
<a href="{{ .LogoutURL }}" role="button" id="logout-button">Log Out</a>
@@ -19,28 +20,26 @@
</div>
</div>
<hr class="separator">
- <div class="app-list">
+ <div class="app-list scrollbar-custom">
{{range .AllAppsInfo}}
- <div class="app-icon-tooltip" data-app-id="{{ .Id }}" data-app-url="{{ .URL }}">
- <div class="icon">
- {{.Icon}}
- </div>
- <div class="tooltip">
- <p>{{ .Name }}</p>
- {{ if .DisplayURL }}
- <p>{{ .DisplayURL }}</p>
- {{ end }}
- {{ if .Help }}
- <button class="help-button" id="help-button-{{ CleanAppName .Id }}">Help</button>
- {{ end }}
- </div>
- </div>
+ <div class="app-icon" data-app-id="{{ .Id }}" data-app-url="{{ .URL }}" {{ if not .URL }}data-modal-id="modal-{{ CleanAppName .Id }}"{{ end }}>
+ {{.Icon}}
+ </div>
+ <div class="tooltip">
+ <p>{{ .Name }}</p>
+ {{ if .DisplayURL }}
+ <p>{{ .DisplayURL }}</p>
+ {{ end }}
+ {{ if .Help }}
+ <button class="help-button" id="help-button-{{ CleanAppName .Id }}">Help</button>
+ {{ end }}
+ </div>
<dialog class="app-help-modal" id="modal-{{ CleanAppName .Id }}" close>
<article class="modal-article">
<header>
<h4>{{ .Name }}</h4>
<button class="close-button" id="close-help-{{ CleanAppName .Id }}">
- <svg xmlns="http://www.w3.org/2000/svg" width="1.5em" height="1.5em" viewBox="0 0 32 32"><path fill="black" d="M16 2C8.2 2 2 8.2 2 16s6.2 14 14 14s14-6.2 14-14S23.8 2 16 2m5.4 21L16 17.6L10.6 23L9 21.4l5.4-5.4L9 10.6L10.6 9l5.4 5.4L21.4 9l1.6 1.6l-5.4 5.4l5.4 5.4z"/></svg>
+ <svg xmlns="http://www.w3.org/2000/svg" width="1.5em" height="1.5em" viewBox="0 0 32 32"><path fill="#d4888d" d="M16 2C8.2 2 2 8.2 2 16s6.2 14 14 14s14-6.2 14-14S23.8 2 16 2m5.4 21L16 17.6L10.6 23L9 21.4l5.4-5.4L9 10.6L10.6 9l5.4 5.4L21.4 9l1.6 1.6l-5.4 5.4l5.4 5.4z"/></svg>
</button>
</header>
<div class="app-help-modal-article">
@@ -81,6 +80,6 @@
{{ template "help-content-template" (dict "Help" $h.Children "First" false) }}
{{ end }}
{{ end }}
- <script src="/static/launcher.js?v=0.0.3"></script>
+ <script src="/static/launcher.js?v=0.0.13"></script>
</body>
</html>
diff --git a/core/installer/welcome/static/launcher.css b/core/installer/welcome/static/launcher.css
index bc716c8..d6038f9 100644
--- a/core/installer/welcome/static/launcher.css
+++ b/core/installer/welcome/static/launcher.css
@@ -3,23 +3,32 @@
--pico-color: unset;
}
+:root {
+ --bg: #d6d6d6;
+ --bodyBg: #3a3a3a;
+ --text: #3a3a3a;
+ --formText: #d6d6d6;
+ --button: #7f9f7f;
+ --logo: #d4888d;
+ --fontSize: 14px;
+}
+
body {
margin: 0;
padding: 0;
- font-family: Arial, sans-serif;
+ font-family: Hack, monospace;
display: flex;
height: 100vh;
- padding-left: 10px !important;
- padding-right: 10px !important;
- background-color: black;
+ padding-left: 5px !important;
+ padding-right: 5px !important;
+ background-color: var(--bodyBg);
+ overflow-x: hidden;
+ overflow-y: hidden;
}
#left-panel {
width: 80px;
- background-color: #f0f0f0;
- border-radius: 10px;
- border-width: 1px;
- border-color: black;
+ background-color: var(--bg);
display: flex;
flex-direction: column;
align-items: center;
@@ -31,15 +40,50 @@
display: flex;
flex-direction: column;
align-items: center;
+ overflow-y: auto;
+ overflow-x: hidden;
+ padding-top: 3px;
+ width: 95% !important;
+ /* scrollbar-width: thin;
+ scrollbar-color: var(--bodyBg) var(--bg); */
}
+.scrollbar-custom {
+ scrollbar-width: thin;
+ scrollbar-color: var(--bodyBg) var(--bg);
+}
+
+/* .app-list:hover::-webkit-scrollbar {
+ width: 6px;
+ scrollbar-color: var(--bodyBg) var(--bg);
+} */
+
+.scrollbar-custom::-webkit-scrollbar {
+ width: 6px;
+}
+
+.scrollbar-custom::-webkit-scrollbar-track {
+ background-color: var(--bg) !important;
+}
+
+.scrollbar-custom::-webkit-scrollbar-thumb {
+ background-color: var(--bodyBg) !important;
+ border-radius: 4px !important;
+}
+
+.scrollbar-custom::-webkit-scrollbar-thumb:hover {
+ background-color: var(--bodyBg);
+}
+
+/* .layout-scrollbar::-webkit-scrollbar-thumb:active {
+ background-color: var(--bodyBg);
+} */
+
#right-panel {
flex: 1;
- background-color: #f0f0f0;
- margin: 5px;
+ background-color: none !important;
+ margin: 5px 0 5px 5px;
padding: 2px;
- border-radius: 10px;
- border-color: black;
}
.appFrame {
@@ -49,30 +93,28 @@
border: 0;
}
-.app-icon-tooltip {
- position: relative;
- display: inline-block;
- align-items: flex-start;
+.app-icon {
+ /* position: relative; */
+ /* display: inline-block; */
+ display: flex;
+ flex-direction: column;
+ align-items: center;
justify-content: center;
- cursor: initial;
+ /* cursor: initial; */
width: 80px !important;
height: 50px !important;
margin-bottom: 10px !important;
cursor: pointer !important;
- --pico-background-color: unset !important;
- --pico-color: unset !important;
+ /* --pico-background-color: unset !important;
+ --pico-color: unset !important; */
}
.tooltip {
position: absolute;
width: 200px;
- border-radius: 0 10px 10px 0;
- top: 70%;
- left: 98%;
+ left: 90px;
transform: translateY(-50%);
- background-color: black;
- color: white;
- box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
+ background-color: var(--bodyBg);
padding: 5px;
z-index: 1;
display: flex;
@@ -88,56 +130,45 @@
margin-top: 5px !important;
padding: 0 !important;
border: 0 !important;
- margin-bottom: 5px !important;
+ margin-bottom: 1px !important;
width: 100% !important;
- color: white !important;
+ background-color: var(--button) !important;
+ color: var(--bodyBg) !important;
+ border-radius: 0 !important;
cursor: pointer !important;
font-size: 16px !important;
}
-.icon {
- display: flex;
- justify-content: center;
- align-items: center !important;
-}
-
.tooltip p {
- color: white;
+ color: var(--formText);
margin: 0;
cursor: auto;
+ font-size: var(--fontSize);
}
-.app-icon-tooltip:hover {
+.app-icon:hover {
transform: scale(1.15);
}
-.app-icon-tooltip .background-glow {
- position: absolute;
- top: 0;
- left: 4px;
- right: 4px;
- bottom: 0;
- background: rgba(0, 0, 0, 0);
- pointer-events: none;
- border-radius: 5px;
- box-shadow: 0px 0px 7px 7px black;
-}
-
.modal-left {
- width: 30%;
overflow-y: auto;
float: left;
margin-left: 0px;
+ padding-right: 10px;
background-color: #fbfcfc;
border-radius: 2px;
}
.modal-right {
- /* flex: 1; */
- width: 70%;
+ flex: 1;
+ /* width: 70%; */
overflow-y: auto;
float: right;
margin-left: 2px;
+ color: var(--bg);
+ padding-left: 10px;
+ padding-right: 10px;
+ font-size: 16px !important;
}
.app-help-modal {
@@ -173,6 +204,12 @@
align-items: center;
position: relative;
margin-bottom: 2px !important;
+ background-color: var(--bodyBg) !important;
+}
+
+header h4 {
+ color: var(--formText) !important;
+ padding-left: 10px;
}
.close-button {
@@ -185,7 +222,7 @@
height: 1.5em;
position: absolute;
top: 11px;
- right: 5px;
+ right: 28px;
}
.modal-article {
@@ -194,6 +231,8 @@
min-height: 90% !important;
max-height: 90% !important;
overflow: hidden;
+ padding-left: 5px !important;
+ padding-right: 5px !important;
}
.help-content {
@@ -204,19 +243,21 @@
width: 50px;
height: 50px;
border-radius: 50%;
- background-color: #ccc;
+ background-color: var(--bodyBg);
display: flex;
justify-content: center;
align-items: center;
+ margin-top: 2px;
}
-.circle p {
+#user-initial {
font-size: 24px;
text-align: center;
line-height: 50px;
margin: 0;
position: relative;
display: inline-block;
+ color: var(--logo);
}
.user-circle {
@@ -232,7 +273,7 @@
margin-top: 2px !important;
margin-bottom: 4px !important;
border-width: 2px !important;
- border-color: black !important;
+ border-color: var(--bodyBg) !important;
width: 100% !important;
}
@@ -245,7 +286,7 @@
.modal-left ul li {
list-style: none !important;
- padding-inline-start: 19px !important;
+ padding-inline-start: 10px !important;
margin-bottom: 0px;
font-size: 16px !important;
}
@@ -254,19 +295,17 @@
--pico-text-decoration: none;
cursor: pointer;
}
-
.modal-left ul li a[aria-current] {
color: var(--pico-primary);
}
.tooltip-user {
position: absolute;
- top: 54px;
- left: 90px;
+ top: 45px;
+ left: 85.5px;
transform: translateY(-50%);
width: 234px;
- background-color: black;
- box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
+ background-color: var(--bodyBg);
padding: 5px;
z-index: 1;
display: flex;
@@ -274,7 +313,6 @@
align-items: center;
visibility: hidden;
opacity: 0;
- border-radius: 0 0 10px 0;
cursor: auto;
}
@@ -284,9 +322,11 @@
border: 0 !important;
margin-bottom: 5px !important;
width: 100% !important;
- color: white !important;
cursor: pointer !important;
font-size: 19px !important;
+ border-radius: 0;
+ background-color: var(--button);
+ color: var(--text) !important;
}
.tooltip-user p {
@@ -294,4 +334,5 @@
margin: 0;
cursor: auto;
font-size: 19px;
+ color: var(--logo);
}
diff --git a/core/installer/welcome/static/launcher.js b/core/installer/welcome/static/launcher.js
index cbd35fd..ed54a70 100644
--- a/core/installer/welcome/static/launcher.js
+++ b/core/installer/welcome/static/launcher.js
@@ -1,74 +1,92 @@
+function showTooltip(obj) {
+ obj.style.visibility = 'visible';
+ obj.style.opacity = '1';
+}
+function hideTooltip(obj) {
+ obj.style.visibility = 'hidden';
+ obj.style.opacity = '0';
+}
+
document.addEventListener("DOMContentLoaded", function () {
document.getElementById('appFrame-default').contentDocument.write("Welcome to the dodo: application launcher, think of it as your desktop environment. You can launch applications from left-hand side dock. You should setup VPN clients on your devices, so you can install applications from Application Manager and access your private network. Instructions on how to do that can be viewed by clicking <b>Help</b> button after hovering over <b>Headscale</b> icon in the dock.");
-
- function showTooltip(obj) {
- obj.style.visibility = 'visible';
- obj.style.opacity = '1';
- }
- function hideTooltip(obj) {
- obj.style.visibility = 'hidden';
- obj.style.opacity = '0';
- }
-
+ document.getElementById('appFrame-default').style.backgroundColor = '#d6d6d6';
+ const icons = document.querySelectorAll(".app-icon");
const circle = document.querySelector(".user-circle");
const tooltipUser = document.querySelector("#tooltip-user");
- [
- ['mouseenter', () => showTooltip(tooltipUser)],
- ['mouseleave', () => hideTooltip(tooltipUser)],
- ].forEach(([event, listener]) => {
- circle.addEventListener(event, listener);
+ const initial = document.getElementById('user-initial');
+
+ circle.addEventListener('mouseenter', () => {
+ icons.forEach(icon => {
+ const tooltip = icon.nextElementSibling;
+ hideTooltip(tooltip);
+ });
+ showTooltip(tooltipUser);
+ initial.style.color = "#7f9f7f";
});
- const iframes = {};
- const rightPanel = document.getElementById('right-panel');
+ circle.addEventListener('mouseleave', () => {
+ hideTooltip(tooltipUser);
+ initial.style.color = "#d4888d";
+ });
- function showIframe(appId) {
- document.querySelectorAll('.appFrame').forEach(iframe => {
- iframe.style.display = iframe.id === `appFrame-${appId}` ? 'block' : 'none';
- });
- }
+ let hideTimeout;
+ let activeTooltip;
- function createIframe(appId, appUrl) {
- const iframe = document.createElement('iframe');
- iframe.id = `appFrame-${appId}`;
- iframe.className = 'appFrame';
- iframe.src = appUrl;
- iframe.style.display = 'none';
- rightPanel.appendChild(iframe);
- iframes[appId] = iframe;
- }
-
- const icons = document.querySelectorAll(".app-icon-tooltip");
icons.forEach(function (icon) {
icon.addEventListener("click", function (event) {
event.stopPropagation();
const appUrl = this.getAttribute("data-app-url");
const appId = this.getAttribute("data-app-id");
- if (!appUrl) {
- const modalId = `modal-${this.querySelector('.help-button').id.replace('help-button-', '')}`;
+ const modalId = this.getAttribute("data-modal-id");
+
+ if (!appUrl && modalId) {
openModal(document.getElementById(modalId));
} else {
if (!iframes[appId]) createIframe(appId, appUrl);
showIframe(appId);
- }
- document.querySelectorAll(".app-icon-tooltip .background-glow").forEach((e) => e.remove());
- const glow = document.createElement('div');
- glow.classList.add("background-glow");
- glow.setAttribute("style", "transform: none; transform-origin: 50% 50% 0px;")
- this.appendChild(glow);
+ document.querySelectorAll(".app-icon").forEach((icon) => {
+ icon.style.color = "var(--bodyBg)";
+ });
+ this.style.color = "var(--button)";
+ };
});
- const tooltip = icon.querySelector('.tooltip');
- tooltip.addEventListener("click", function (event) {
- event.stopPropagation();
- });
+
+ const tooltip = icon.nextElementSibling;
[
- ['mouseenter', () => showTooltip(tooltip)],
- ['mouseleave', () => hideTooltip(tooltip)],
- ['focus', () => showTooltip(tooltip)],
- ['blur', () => hideTooltip(tooltip)],
+ ['mouseenter', () => {
+ clearTimeout(hideTimeout);
+ if (activeTooltip && activeTooltip !== tooltip) {
+ hideTooltip(activeTooltip);
+ };
+ const rect = icon.getBoundingClientRect();
+ tooltip.style.top = `${rect.top + 26}px`;
+ showTooltip(tooltip);
+ activeTooltip = tooltip;
+ }],
+ ['mouseleave', () => {
+ hideTimeout = setTimeout(() => {
+ hideTooltip(tooltip);
+ if (activeTooltip === tooltip) {
+ activeTooltip = null;
+ };
+ }, 200);
+ }],
].forEach(([event, listener]) => {
icon.addEventListener(event, listener);
});
+
+ tooltip.addEventListener('mouseenter', () => {
+ clearTimeout(hideTimeout);
+ });
+
+ tooltip.addEventListener('mouseleave', () => {
+ hideTimeout = setTimeout(() => {
+ hideTooltip(tooltip);
+ if (activeTooltip === tooltip) {
+ activeTooltip = null;
+ };
+ }, 200);
+ });
});
let visibleModal = undefined;
@@ -77,6 +95,7 @@
modal.setAttribute("open", true);
visibleModal = modal;
};
+
const closeModal = function (modal) {
modal.removeAttribute("open");
modal.setAttribute("close", true);
@@ -84,6 +103,7 @@
};
const helpButtons = document.querySelectorAll('.help-button');
+
helpButtons.forEach(function (button) {
button.addEventListener('click', function (event) {
event.stopPropagation();
@@ -101,19 +121,33 @@
});
const modalHelpButtons = document.querySelectorAll('.title-menu');
+
modalHelpButtons.forEach(function (button) {
button.addEventListener('click', function (event) {
event.stopPropagation();
const helpTitle = button.getAttribute('id');
const helpTitleId = helpTitle.substring('title-'.length);
const helpContentId = 'help-content-' + helpTitleId;
- const allContentElements = document.querySelectorAll('.help-content');
+ let clDiv = document.getElementById(helpContentId).parentNode;
+ const allContentElements = clDiv.querySelectorAll('.help-content');
+
allContentElements.forEach(function (contentElement) {
contentElement.style.display = "none";
});
- modalHelpButtons.forEach(function (button) {
+
+ let currentHelpTitle = button;
+ while (currentHelpTitle && !currentHelpTitle.classList.contains('modal-left')) {
+ currentHelpTitle = currentHelpTitle.parentNode;
+ if (currentHelpTitle === document.body) {
+ currentHelpTitle = null;
+ break;
+ }
+ }
+
+ currentHelpTitle.querySelectorAll('.title-menu').forEach(function (button) {
button.removeAttribute("aria-current");
});
+
document.getElementById(helpContentId).style.display = 'block';
button.setAttribute("aria-current", "page");
});
@@ -126,13 +160,32 @@
});
document.addEventListener("click", (event) => {
- if (visibleModal === null) return;
+ if (visibleModal === null || visibleModal === undefined) return;
const modalContent = visibleModal.querySelector("article");
const closeButton = visibleModal.querySelector(".close-button");
if (!modalContent.contains(event.target) || closeButton.contains(event.target)) {
closeModal(visibleModal);
}
});
+
+ const iframes = {};
+ const rightPanel = document.getElementById('right-panel');
+
+ function showIframe(appId) {
+ document.querySelectorAll('.appFrame').forEach(iframe => {
+ iframe.style.display = iframe.id === `appFrame-${appId}` ? 'block' : 'none';
+ });
+ };
+
+ function createIframe(appId, appUrl) {
+ const iframe = document.createElement('iframe');
+ iframe.id = `appFrame-${appId}`;
+ iframe.className = 'appFrame';
+ iframe.src = appUrl;
+ iframe.style.display = 'none';
+ rightPanel.appendChild(iframe);
+ iframes[appId] = iframe;
+ };
});
function copyToClipboard(elem, text) {
@@ -140,7 +193,7 @@
elem.setAttribute("data-tooltip", "Copied");
elem.setAttribute("data-placement", "bottom");
setTimeout(() => {
- elem.removeAttribute("data-tooltip");
- elem.removeAttribute("data-placement");
+ elem.removeAttribute("data-tooltip");
+ elem.removeAttribute("data-placement");
}, 500);
-}
+};