diff --git a/css/style.css b/css/style.css index bf27801..5b014e2 100644 --- a/css/style.css +++ b/css/style.css @@ -15,6 +15,7 @@ @import url('theme-engine.css'); @import url('weather-screen.css'); @import url('weather-settings.css'); +@import url('web-menu.css'); :root { /* Colors */ diff --git a/css/web-menu.css b/css/web-menu.css new file mode 100644 index 0000000..e760227 --- /dev/null +++ b/css/web-menu.css @@ -0,0 +1,245 @@ +#webMenu { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + margin: 0; + background: var(--panel-bg); + z-index: 0; + overflow: hidden; + backdrop-filter: blur(var(--blur-strength)); + + padding-top: 6vh; + padding-bottom: 6vh; + padding-left: 12vw; + padding-right: 12vw; + + /*Dont increase the geometry by using padding*/ + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + + /*Disable user touch/select on text elements*/ + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + /*Transitions*/ + opacity: 0; + transform: scale(0); + transition: transform var(--transition-speed), + opacity var(--transition-speed), + z-index var(--transition-speed); +} + +@media screen and (max-width: 580px) { + #webMenu { + padding-top: 6vh; + padding-bottom: 0vh; + padding-left: 18vw; + padding-right: 18vw; + } +} + +@media screen and (max-height: 799px) { + #webMenu { + padding-top: 40px; + } +} + +/*Show web menu*/ +.showWebMenu { + transform: scale(1) !important; + opacity: 1 !important; + z-index: 3 !important; +} + +#webMenuContainer { + background: transparent; + max-height: 100%; + + overflow: hidden; +} + +#webMenuSearchBox { + background: var(--base-container); + + text-align: center; + font-family: roboto-bold; + color: var(--panel-color); + + border: none; + border-radius: 6px; + + width: 25%; + height: 36px; + margin: 0; +} + +@media screen and (max-width: 580px) { + #webMenuSearchBox { + width: 50vw; + } +} + +#webMenuSearchBoxContainer { + /*Center horizontally*/ + position: relative; + display: flex; + flex-flow: column wrap; + align-items: center; + + margin-bottom: 20px; +} + +/*Web menu list*/ + +/*UL*/ +#webMenuList { + list-style-type: none; + padding: 0; + margin: 0 auto; + text-align: justify; + + background: transparent; +} + +/*List*/ +#webMenuList li { + /*Align list horizontally*/ + display: inline-block; +} + +@media screen and (max-width: 580px) { + #webMenuList li { + display: inline; + } +} + +#webMenuList li.selected { + background: var(--base-active-bg); + border-radius: var(--rounded-radius); + transition: transform var(--transition-speed); +} + +/*Child of li*/ +.webItem { + background: transparent; + width: 128px; + height: 128px; + margin: 5px; + cursor: pointer; + border-radius: var(--rounded-radius); +} + +.webItem:hover { + background: var(--base-hover-bg); +} + +.webItem:active { + background: var(--base-active-bg); +} + +.webItemFocus { + width: 128px; + height: 128px; + margin: 5px; + border-radius: var(--rounded-radius); + background: var(--base-hover-bg); +} + +.webItemFocus:hover { + background: var(--base-hover-bg); +} + +.webItemFocus:active { + background: var(--base-active-bg); +} + +/*Contains web icon and label*/ +.webItemContainer { + /*Align vertically*/ + margin:0 auto; + position: relative; + top: 50%; + -webkit-transform: translateY(-50%); + + /*Align horizontally*/ + display: flex; + flex-direction: row; + justify-content: center; + + padding: 5px; +} + +/*Web icon container*/ +.webItemIconContainer { + position: relative; + display: flex; + flex-flow: column wrap; + align-items: center; +} + +.webItemIcon { + height: 64px; + width: 64px; + margin-bottom: 0; +} + +/*Web label/name*/ +.webItemName { + text-align: center; + font-size: 11pt; + font-family: roboto; + word-wrap: break-word; + color: var(--base-color); + +} + +#webMenuListContainer { + position: relative; + max-height: 70vh; + + display: flex; + justify-content: center; + + overflow-y: scroll; + /*scrollbar-width: none !important; + -ms-overflow-style: none !important;*/ + + /*Fade transparency*/ + /*-webkit-mask-image: linear-gradient(to bottom, black 85%, transparent 100%);*/ + /*mask-image: linear-gradient(to bottom, black 85%, transparent 100%);*/ +} + +/*Hide scrollbar*/ +/*#webMenuListContainer::-webkit-scrollbar { + display: none; +}*/ + +/*Stretch list item if screen width < 580px*/ +@media screen and (max-width: 580px) { + #webMenuList { + flex-grow: 1; + } + + .webItem { + width: auto; + } + + .webItem:hover { + -ms-transform: scale(1); + -webkit-transform: scale(1); + transform: scale(1); + } + + .webItemFocus { + -ms-transform: scale(1); + -webkit-transform: scale(1); + transform: scale(1); + } +} \ No newline at end of file diff --git a/index.html b/index.html index 38194d0..3952fba 100644 --- a/index.html +++ b/index.html @@ -167,9 +167,6 @@
- - -
@@ -203,7 +200,21 @@
+ +
+
+
+ +
+
+
    + +
+ +
+
+
@@ -219,5 +230,6 @@ + \ No newline at end of file diff --git a/js/animate-dashboard.js b/js/animate-dashboard.js index edfb580..d29df13 100644 --- a/js/animate-dashboard.js +++ b/js/animate-dashboard.js @@ -25,11 +25,11 @@ const hideDashboard = () => { const toggleDashboard = () => { if (rightDashboardVisibility) { - // Hide search box + // Hide dashboard hideDashboard(); } else { - // Show search box + // Show dashboard showDashboard(); } diff --git a/js/dock-buttons.js b/js/dock-buttons.js index 4f2bf45..5e87130 100644 --- a/js/dock-buttons.js +++ b/js/dock-buttons.js @@ -66,8 +66,8 @@ const populateDock = () => { 'Launch', 'launch', () => { - // Toggle web pad - alert('toggle web pad'); + // Toggle web menu + toggleWebMenu(); } ); diff --git a/js/web-menu.js b/js/web-menu.js new file mode 100644 index 0000000..2ad7d56 --- /dev/null +++ b/js/web-menu.js @@ -0,0 +1,341 @@ +var webMenu = document.getElementById("webMenu"); +var webMenuList = document.getElementById("webMenuList"); +var webMenuListContainer = document.getElementById("webMenuListContainer"); +var webMenuSearchBox = document.getElementById("webMenuSearchBox"); + +let webMenuVisibility = false; + +let webItemFocus; +let webListIndex = 0; + +// Create mouse event for passed li +const createMouseUpEvent = (li, url) => { + // Create a callback property for the passed li + li.callback = () => { + window.location.href = encodeURI(url); + } + + // Create onmouseup event for the li + li.onmouseup = () => { + li.callback(); + } +} + +// Sort list alphabetically +const sortList = () => { + Array.from(webMenuList.getElementsByTagName("li")) + .sort((a, b) => a.textContent.localeCompare(b.textContent)) + .forEach(li => webMenuList.appendChild(li)); +} + +// Populate web menu +const populateWebMenu = () => { + + // Generate a list + for (i = 0; i < (webSites.length); i++) { + + var site = webSites[i].site; + var icon = webSites[i].icon; + var url = webSites[i].url; + + var li = document.createElement('li'); + + // Add mouseup event + createMouseUpEvent(li, url); + + // Create an outer div, child of li + let webItemDiv = document.createElement('div') + webItemDiv.className = 'webItem'; + // webItemDiv.tabitemIndex = '1'; + webItemDiv.id = "id" + site; + + // Create a second div, webItemContainer + var webItemContainer = document.createElement('div'); + webItemContainer.className = 'webItemContainer'; + + // Create the innermost div, contains icon and label + var webItemBody = document.createElement('div'); + webItemBody.className = 'webItemBody'; + + // Create div for webItemIcon + var webItemIconContainer = document.createElement('div'); + webItemIconContainer.className = 'webItemIconContainer'; + + var webItemIcon = document.createElement('div'); + webItemIcon.className = 'webItemIcon'; + webItemIcon.style.background = "url('assets/webcons/" + icon + ".svg')"; + webItemIcon.style.backgroundSize = 'cover'; + + // Create webItemName + var webItemName = document.createElement('div'); + webItemName.className = 'webItemName'; + webItemName.innerHTML = site; + + // Append divs with heirarchy + webItemDiv.appendChild(webItemContainer); + webItemContainer.appendChild(webItemBody); + + webItemIconContainer.appendChild(webItemIcon); + webItemBody.appendChild(webItemIconContainer); + webItemBody.appendChild(webItemName); + + li.appendChild(webItemDiv); + webMenuList.appendChild(li); + } + + // Call to sort list + sortList(); +} + +// Fuzzy search +String.prototype.fuzzy = (term, ratio) => { + var string = this.toLowerCase(); + var compare = term.toLowerCase(); + var matches = 0; + + if (string.indexOf(compare) > -1) return true; // covers basic partial matches + for (var i = 0; i < compare.length; i++) { + string.indexOf(compare[i]) > -1 ? matches += 1 : matches -=1; + } + return (matches/this.length >= ratio || term == ""); +}; + +// Search through the list +const filterWebList = () => { + + var input, filter, ul, li, a, i, txtValue; + + input = webMenuSearchBox; + filter = input.value.toUpperCase(); + ul = webMenuList; + li = ul.getElementsByTagName('li'); + + // Loop through all list items, and focus if matches the search query + for (i = 0; i < li.length; i++) { + + a = li[i].getElementsByClassName("webItemName")[0]; + txtValue = a.innerHTML || a.textContent || a.innerText; + + // If an item match, hightlight it and focus + // if (txtValue.toUpperCase().indexOf(filter) !== -1) { + if (txtValue.toUpperCase().fuzzy(filter, 1) === true) { + + // Unselect/Unhightlight old active + var oldWebItemFocus = webItemFocus; + var oldWebItemFocusChild = oldWebItemFocus.querySelector('.webItem'); + oldWebItemFocusChild.classList.remove('webItemFocus'); + + // Update webItemFocus + webItemFocus = li[i]; + + // Update weblistindex + webListIndex = i; + + // Get child + var webItemFocusChild = webItemFocus.querySelector('.webItem'); + // Add webItemFocus class to child + webItemFocusChild.classList.add('webItemFocus'); + + // Scroll focus into active + webItemFocus.scrollIntoView(); + + } + } +} + +// Type event on web mmenu search box +webMenuSearchBox.onkeydown = (event) => { + + // Don't hijack keyboard navigation buttons (up, down, left, right) + if ((event.key === 'ArrowRight') || (event.key === 'ArrowDown') || + (event.key === 'ArrowLeft') || (event.key === 'ArrowUp')) return; + + if (event.key === 'Enter' && webItemFocus) { + // Run the focused li's callback + webItemFocus.callback(); + + // Hide web menu + // webMenuToggle(); + + } else if (event.key === 'Backspace' && webMenuSearchBox.value.length < 1) { + // Hide web menu if backspace is pressed and searchbox value is 0 + // webMenuToggle(); + + } else if ((event.key === 'Escape') || (event.key === 'Alt')) { + // Ignore escape and alt key + return; + } + + // Filter + filterWebList(); +} + +// Reset focus on web menu close +const focusReset = () => { + var oldWebItemFocus = webItemFocus; + var oldWebItemFocusChild = oldWebItemFocus.querySelector('.webItem'); + oldWebItemFocusChild.classList.remove('webItemFocus'); + webListIndex = 0; +} + +// Get first item of ul +const getFirstItem = () => { + var ul = webMenuList; + var li = ul.getElementsByTagName('li'); + + // Focus on first item + webItemFocus = li[0]; + + // Get child + var webItemFocusChildren = webItemFocus.querySelector('.webItem'); + + // Add webItemFocus class + webItemFocusChildren.classList.add('webItemFocus'); +} + +// Show/Hide web menu +// const webMenuToggle = () => { + +// webMenuVisible = !webMenuVisible; + +// hideCenterContainer(); +// rotateProfile(); +// webMenu.classList.toggle("show"); + +// // Clear and unfocus searchbox +// if (!webMenuVisible) { +// webMenuSearchBox.value = ''; +// webMenuSearchBox.blur(); +// filterWebList(); +// webMenuListContainer.scrollTop = 0; + +// focusReset(); +// getFirstItem(); +// } else { +// // Focus +// webMenuSearchBox.focus(); +// } + +// if(weatherVisible && webMenuVisible) { +// weatherToggle(); +// } else if (floatPanelVisible && webMenuVisible) { +// slideDashboard(); +// return; +// } + +// } + + +const showWebMenu = () => { + webMenu.classList.add('showWebMenu'); + webMenuVisibility = !webMenuVisibility; +} + +const hideWebMenu = () => { + webMenu.classList.remove('showWebMenu'); + webMenuVisibility = !webMenuVisibility; +} + +const toggleWebMenu = () => { + + // If profile anim is still running, + // Return to avoid spam + if (profileAnimRunning) return; + + // Rotate profile + rotateProfile(); + + if (webMenuVisibility) { + // Hide web menu + hideWebMenu(); + + } else { + // Show Web menu + showWebMenu(); + } + console.log('toggle web menu'); +} + + + +// Remove class to focused item +const removeClass = (el, className) => { + // Remove webItemFocus class + var oldWebItemFocus = el.querySelector('.webItem'); + oldWebItemFocus.classList.remove('webItemFocus'); +}; + +// Add class to focused item +const addClass = (el, className) => { + var webItemFocusChild = el.querySelector('.webItem'); + + // Add webItemFocus class to child + webItemFocusChild.classList.add('webItemFocus'); + + // Scroll focus into active + webItemFocusChild.scrollIntoView(); +}; + +// Keyboard navigation +webMenu.addEventListener( + 'keydown', + (event) => { + var len = webMenuList.getElementsByTagName('li').length - 1; + // Right and Down + if((event.which === 39) || (event.which === 40)) { + + // Clear web menu searchbox + webMenuSearchBox.value = ''; + webListIndex++; + if (webItemFocus) { + removeClass(webItemFocus, 'webItemFocus'); + next = webMenuList.getElementsByTagName('li')[webListIndex]; + if(typeof next !== undefined && webListIndex <= len) { + webItemFocus = next; + } else { + webListIndex = 0; + webItemFocus = webMenuList.getElementsByTagName('li')[0]; + } + addClass(webItemFocus, 'webItemFocus'); + // console.log(webListIndex); + } else { + webListIndex = 0; + webItemFocus = webMenuList.getElementsByTagName('li')[0]; + addClass(webItemFocus, 'webItemFocus'); + } + } + // Up and left + else if ((event.which === 37) || (event.which === 38)) { + + // Clear web menu searchbox + webMenuSearchBox.value = ''; + if (webItemFocus) { + removeClass(webItemFocus, 'webItemFocus'); + webListIndex--; + // console.log(webListIndex); + next = webMenuList.getElementsByTagName('li')[webListIndex]; + if(typeof next !== undefined && webListIndex >= 0) { + webItemFocus = next; + } else { + webListIndex = len; + webItemFocus = webMenuList.getElementsByTagName('li')[len]; + } + addClass(webItemFocus, 'webItemFocus'); + } else { + webListIndex = 0; + webItemFocus = webMenuList.getElementsByTagName('li')[len]; + addClass(webItemFocus, 'webItemFocus'); + } + } + }, + false +); + +// Populate and get first child +const initWebMenu = () => { + populateWebMenu(); + getFirstItem(); +} + +// Initialize web menu +window.onload = initWebMenu(); \ No newline at end of file