github.com/Theta-Dev/Talon@v0.0.0-20211018130634-ff179e19fa9a/ui/menu/src/components/Menu.svelte (about) 1 <script lang="ts"> 2 import {openModal} from "svelte-modals" 3 4 import Icon from "./Icon.svelte" 5 import MenuItem from "./MenuItem.svelte" 6 import MenuItemPage from "./MenuItemPage.svelte" 7 import InfoModal from "./InfoModal.svelte" 8 import FloatingButton from "./FloatingButton.svelte" 9 10 import type {Focusable, TalonPage} from "../util/types" 11 import {TalonVisibility} from "../util/types" 12 import PageIcon from "./PageIcon.svelte" 13 import MenuItemInput from "./MenuItemInput.svelte" 14 import {currentPage, currentPageId, pages, rootPath} from "../util/talonData" 15 16 function showSidebar(): void { 17 sidebarShown = true 18 } 19 20 function hideSidebar(): void { 21 sidebarShown = false 22 } 23 24 function isMobile(): boolean { 25 return window.innerWidth < 768 26 } 27 28 function openSearch(): void { 29 searchOpen = true 30 searchInput.focus() 31 } 32 33 function closeSearch() { 34 searchOpen = false 35 searchInput.blur() 36 37 if (displayedPages.length === 0) searchText = "" 38 } 39 40 function clearSearch() { 41 searchText = "" 42 closeSearch() 43 } 44 45 function searchKeypress(e: KeyboardEvent) { 46 switch (e.key) { 47 case "Enter": 48 if (!searchText) { 49 closeSearch() 50 } else if (displayedPages.length) { 51 window.location.href = rootPath + displayedPages[0].path 52 } else { 53 closeSearch() 54 } 55 break 56 case "Escape": 57 clearSearch() 58 break 59 } 60 } 61 62 function openInfo() { 63 openModal(InfoModal) 64 } 65 66 let sidebarShown = !isMobile() 67 let searchInput: Focusable 68 let searchOpen = false 69 let searchText = "" 70 71 let displayedPages: TalonPage[] 72 $: displayedPages = Object.entries(pages) 73 .filter(([id, page]) => { 74 if (id === currentPageId) return false 75 76 if (searchText) { 77 return ( 78 page.visibility !== TalonVisibility.HIDDEN && 79 page.name.toLowerCase().includes(searchText.toLowerCase()) 80 ) 81 } 82 return page.visibility === TalonVisibility.FEATURED 83 }) 84 .map(([, page]) => page) 85 86 </script> 87 88 <style lang="sass"> 89 @use "../style/values" 90 @use "../style/mixin" 91 92 nav 93 position: fixed 94 top: 0 95 right: 0 96 height: 100% 97 98 padding: 1em 0.4em 99 100 display: flex 101 flex-direction: column 102 justify-content: space-between 103 overflow: hidden 104 box-sizing: border-box 105 106 &.hide 107 display: none 108 109 > div 110 flex: 2 1 auto 111 overflow-x: hidden 112 overflow-y: auto 113 114 &:first-child, &:last-child 115 flex: 0 0 auto 116 117 +mixin.hideScrollbar 118 </style> 119 120 <nav class:hide={!sidebarShown}> 121 <div> 122 <MenuItemInput 123 active={searchOpen || Boolean(searchText).valueOf()} 124 on:click={openSearch} 125 on:focusout={closeSearch} 126 on:keyup={searchKeypress} 127 bind:input={searchInput} 128 bind:text={searchText} /> 129 </div> 130 <div> 131 {#each displayedPages as page, i} 132 <MenuItemPage 133 {page} 134 {rootPath} 135 active={searchOpen && searchText && i === 0} /> 136 {/each} 137 </div> 138 <div> 139 {#if currentPage && currentPage.source} 140 <MenuItem 141 text="View source" 142 link={currentPage.source.url} 143 newTab={true} 144 privacy={true}> 145 <Icon iconName={currentPage.source.type} size={40} scale={0.6} /> 146 </MenuItem> 147 {/if} 148 <MenuItem text="Info" on:click={openInfo}> 149 <PageIcon page={currentPage} /> 150 </MenuItem> 151 <MenuItem text="Hide sidebar" on:click={hideSidebar}> 152 <Icon iconName="arrowRight" size={40} scale={0.6} /> 153 </MenuItem> 154 </div> 155 </nav> 156 157 <FloatingButton hide={sidebarShown} on:click={showSidebar} />