[READ-ONLY] Mirror of https://github.com/probablykasper/mr-tagger. Music file tagging app for Mac, Linux and Windows
audio linux macos music tagger tauri windows
0

Configure Feed

Select the types of activity you want to include in your feed.

mr-tagger / src / App.svelte
6.0 kB 241 lines
1<script lang="ts"> 2 import { event } from '@tauri-apps/api' 3 import { checkShortcut, runCmd } from './scripts/helpers' 4 import PageView from './components/Page.svelte' 5 import type { Page } from './components/Page.svelte' 6 import { onDestroy } from 'svelte' 7 import FileDrop from 'svelte-tauri-filedrop' 8 import { fade } from 'svelte/transition' 9 import * as dialog from '@tauri-apps/plugin-dialog' 10 11 type File = { 12 path: string 13 dirty: boolean 14 } 15 type App = { 16 current_index: number 17 files: File[] 18 } 19 let app: App = { 20 current_index: 0, 21 files: [], 22 } 23 async function getApp() { 24 app = await runCmd<App>('get_app') 25 } 26 getApp() 27 28 let page: Page | null = null 29 $: if (app) getPage() 30 async function getPage() { 31 let newPage = await runCmd<Page | null>('get_page') 32 if (!page || !newPage || newPage.path !== page.path) { 33 page = newPage 34 } 35 } 36 37 async function openFiles(paths: string[]) { 38 await runCmd<App>('open_files', { paths }) 39 getApp() 40 } 41 let extensions = [ 42 // 43 'aiff', 44 'mp3', 45 'm4a', 46 'mp4', 47 'm4p', 48 'm4b', 49 'm4r', 50 'm4v', 51 'opus', 52 'wav', 53 ] 54 async function openDialog() { 55 let paths = await dialog.open({ 56 filters: [{ name: 'Audio/Video file', extensions }], 57 multiple: true, 58 directory: false, 59 }) 60 if (typeof paths === 'string') { 61 paths = [paths] 62 } 63 if (paths !== null) { 64 await openFiles(paths) 65 } 66 } 67 async function show(index: number) { 68 if (app.current_index !== index) { 69 await runCmd<App>('show', { index }) 70 getApp() 71 } 72 } 73 async function close(index: number) { 74 if (app.files[index].dirty) { 75 let confirmed = window.confirm('Close without saving?') 76 if (!confirmed) return 77 } 78 await runCmd('close_file', { index }) 79 getApp() 80 } 81 async function saveFile(saveAs: boolean) { 82 await runCmd('save_file', { index: app.current_index, saveAs }) 83 getApp() 84 } 85 async function filesKeydown(e: KeyboardEvent) { 86 if (checkShortcut(e, 'ArrowUp')) { 87 e.preventDefault() 88 if (app.current_index >= 1) { 89 show(app.current_index - 1) 90 } 91 } else if (checkShortcut(e, 'ArrowDown')) { 92 e.preventDefault() 93 if (app.current_index < app.files.length - 1) { 94 show(app.current_index + 1) 95 } 96 } 97 } 98 const unlistenFuture = event.listen('menu', async ({ payload }) => { 99 if (payload === 'Open...') { 100 openDialog() 101 } else if (payload === 'Close') { 102 if (app.files.length === 0) { 103 await runCmd('close_window') 104 } else { 105 close(app.current_index) 106 } 107 } else if (payload === 'Save') { 108 saveFile(false) 109 } else if (payload === 'Save As...') { 110 saveFile(true) 111 } 112 }) 113 onDestroy(async () => { 114 const unlisten = await unlistenFuture 115 unlisten() 116 }) 117</script> 118 119<main> 120 <div class="sidebar"> 121 <div class="topbar"> 122 <button on:click={openDialog}>Open Files</button> 123 </div> 124 <!-- svelte-ignore a11y-no-noninteractive-tabindex --> 125 <div class="files" tabindex="0" on:keydown={filesKeydown}> 126 {#each app.files as file, i} 127 <!-- svelte-ignore a11y-click-events-have-key-events --> 128 <div class="file" class:selected={i === app.current_index} on:click={() => show(i)}> 129 <div class="icon dirty"> 130 {#if file.dirty} 131 <svg width="6" height="6" xmlns="http://www.w3.org/2000/svg"> 132 <circle cx="3" cy="3" r="2.5" /> 133 </svg> 134 {/if} 135 </div> 136 <!-- svelte-ignore a11y-click-events-have-key-events --> 137 <div class="icon x" on:click|stopPropagation={() => close(i)}> 138 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" 139 ><path 140 d="M23.954 21.03l-9.184-9.095 9.092-9.174-2.832-2.807-9.09 9.179-9.176-9.088-2.81 2.81 9.186 9.105-9.095 9.184 2.81 2.81 9.112-9.192 9.18 9.1z" 141 /></svg 142 > 143 </div> 144 {file.path.replace(/^.*[\\/]/, '')} 145 </div> 146 {/each} 147 </div> 148 <FileDrop {extensions} handleFiles={openFiles} let:files> 149 {#if files.length > 0} 150 <div class="dropzone" transition:fade={{ duration: 100 }} /> 151 {/if} 152 </FileDrop> 153 </div> 154 <div class="main"> 155 {#if page} 156 <button on:click={() => saveFile(false)} tabindex="0">Save</button> 157 <PageView {page} on:appRefresh={getApp} /> 158 {/if} 159 </div> 160</main> 161 162<style lang="sass"> 163 :global(body) 164 margin: 0 165 font-family: Arial, Helvetica, sans-serif 166 font-size: 18px 167 background-color: #191B20 168 overflow: hidden 169 user-select: none 170 -webkit-user-select: none 171 main 172 display: flex 173 color: #e6e6e6 174 height: 100vh 175 .main 176 flex-grow: 1 177 width: 0px 178 overflow: auto 179 .sidebar 180 display: flex 181 position: relative 182 flex-direction: column 183 width: 250px 184 height: 100% 185 background-color: #202227 186 border-right: 1px solid rgba(#ffffff, 0.1) 187 font-size: 12px 188 .topbar 189 padding: 8px 190 border-bottom: 1px solid rgba(#ffffff, 0.1) 191 .files 192 overflow-y: auto 193 height: 100% 194 outline: none 195 .file 196 display: flex 197 align-items: center 198 padding: 6px 8px 199 padding-left: 0px 200 cursor: default 201 .icon 202 display: flex 203 align-items: center 204 justify-content: center 205 width: 8px 206 height: 8px 207 flex-shrink: 0 208 padding: 4px 209 margin: 0px 5px 210 border-radius: 2px 211 transition: opacity 180ms ease-out, transform 180ms ease-out 212 &:hover 213 background-color: rgba(#ffffff, 0.15) 214 .icon.x 215 opacity: 0 216 transform: scale(0.5) 217 position: absolute 218 &:hover .icon.x 219 display: flex 220 opacity: 1 221 transform: scale(1) 222 &:hover .icon.dirty 223 opacity: 0 224 transform: scale(0.5) 225 svg 226 fill: #ffffff 227 .file:nth-child(2n) 228 background-color: rgba(#ffffff, 0.05) 229 .file.selected 230 background-color: hsl(147, 0%, 35%) 231 &:focus .file.selected 232 background-color: hsl(147, 70%, 30%) 233 background-color: #103fcb 234 .dropzone 235 position: absolute 236 width: 100% 237 height: 100% 238 top: 0px 239 left: 0px 240 background-color: rgba(#ffffff, 0.2) 241</style>