[READ-ONLY] Mirror of https://github.com/probablykasper/kadium. App for staying ontop of YouTube channels' uploads kadium.kasper.space
linux macos notifications tauri windows youtube
0

Configure Feed

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

Improve modal behavior

Use modal-svelte, which has some better behaviors

Kasper (Jul 27, 2023, 2:39 AM +0200) 95ebd989 2303ef9c

+165 -220
+10
package-lock.json
··· 19 19 "eslint": "^8.45.0", 20 20 "eslint-config-prettier": "^8.8.0", 21 21 "eslint-plugin-svelte": "^2.32.4", 22 + "modal-svelte": "^1.1.1", 22 23 "postcss": "^8.4.27", 23 24 "prettier": "^3.0.0", 24 25 "prettier-plugin-svelte": "^3.0.1", ··· 2671 2672 }, 2672 2673 "bin": { 2673 2674 "mkdirp": "bin/cmd.js" 2675 + } 2676 + }, 2677 + "node_modules/modal-svelte": { 2678 + "version": "1.1.1", 2679 + "resolved": "https://registry.npmjs.org/modal-svelte/-/modal-svelte-1.1.1.tgz", 2680 + "integrity": "sha512-O3+ZP3j+Q9CEQ8j1XQ+0WJaniWziwTtXbJb6gPc7gp+9qO4VYnSO+z2KtEdK8FDdu3OqGoBrtU5o4zqZXzJZmA==", 2681 + "dev": true, 2682 + "peerDependencies": { 2683 + "svelte": "^3.0.0 || ^4.0.0" 2674 2684 } 2675 2685 }, 2676 2686 "node_modules/mri": {
+1
package.json
··· 25 25 "eslint": "^8.45.0", 26 26 "eslint-config-prettier": "^8.8.0", 27 27 "eslint-plugin-svelte": "^2.32.4", 28 + "modal-svelte": "^1.1.1", 28 29 "postcss": "^8.4.27", 29 30 "prettier": "^3.0.0", 30 31 "prettier-plugin-svelte": "^3.0.1",
+1 -1
src/App.svelte
··· 102 102 /> 103 103 104 104 {#if getStartedWasSeen} 105 - <GetStarted visible={true} /> 105 + <GetStarted /> 106 106 {/if} 107 107 {:else if error} 108 108 Error loading.
+3 -6
src/lib/Link.svelte
··· 11 11 {#if href === ''} 12 12 <button type="button" on:click><slot /></button> 13 13 {:else} 14 - <a {href} {target}><button type="button" on:click><slot /></button></a> 14 + <a {href} {target}><slot /></a> 15 15 {/if} 16 16 17 17 <style lang="sass"> 18 18 button 19 19 font-size: 13px 20 20 margin: 0px 21 - color: hsl(210, 100%, 55%) 22 21 background-color: transparent 23 22 padding: 0px 24 23 border: none 25 - user-select: none 26 24 cursor: default 25 + a, button 26 + color: hsl(210, 100%, 55%) 27 27 &:hover 28 28 color: hsl(210, 100%, 45%) 29 29 a 30 - color: inherit 31 30 text-decoration: none 32 31 font-size: inherit 33 - button 34 - font-size: inherit 35 32 </style>
-60
src/lib/Modal.svelte
··· 1 - <script lang="ts"> 2 - export let visible = false 3 - let modalBg: HTMLDivElement 4 - let lastFocus: Element | null 5 - $: if (visible) { 6 - lastFocus = document.activeElement 7 - } else if (lastFocus instanceof HTMLElement) { 8 - lastFocus.focus() 9 - } 10 - $: if (visible && modalBg) { 11 - modalBg.focus() 12 - const firstInput = modalBg.querySelector('input, textarea') 13 - if (firstInput instanceof HTMLElement) { 14 - firstInput.focus() 15 - } 16 - } 17 - function close() { 18 - visible = false 19 - } 20 - </script> 21 - 22 - {#if visible} 23 - <!-- svelte-ignore a11y-no-static-element-interactions --> 24 - <div class="modal-bg" on:click|self={close} tabindex="-1" on:keydown bind:this={modalBg}> 25 - <div class="box"> 26 - <div class="spacer"> 27 - <slot {close} /> 28 - </div> 29 - </div> 30 - </div> 31 - {/if} 32 - 33 - <style lang="sass"> 34 - .modal-bg 35 - position: fixed 36 - width: 100% 37 - height: 100% 38 - top: 0px 39 - left: 0px 40 - display: flex 41 - align-items: center 42 - justify-content: center 43 - z-index: 100 44 - padding: 20px 45 - box-sizing: border-box 46 - background-color: rgba(#000000, 0.5) 47 - .box 48 - background-color: hsl(220, 18%, 11%) 49 - border: 1px solid hsla(0, 0%, 100%, 0.1) 50 - min-width: 300px 51 - max-width: 100% 52 - max-height: 100% 53 - box-sizing: border-box 54 - border-radius: 7px 55 - box-shadow: 0px 0px 30px 0px rgba(#000000, 0.5) 56 - overflow: auto 57 - .spacer 58 - margin: 18px 59 - position: relative 60 - </style>
+1
src/lib/Nav.svelte
··· 70 70 background-color: hsl(220, 17%, 10%) 71 71 border-bottom: 1px solid hsla(0, 0%, 50%, 0.08) 72 72 a 73 + cursor: default 73 74 background-color: transparent 74 75 border: none 75 76 display: inline-block
+41 -44
src/modals/Channel.svelte
··· 1 1 <script lang="ts"> 2 2 import { loadSettings } from '../lib/data' 3 - import Modal from '../lib/Modal.svelte' 3 + import Modal from 'modal-svelte' 4 4 import { DateInput } from 'date-picker-svelte' 5 5 import Button from '../lib/Button.svelte' 6 6 import type { Channel } from '../../bindings' ··· 68 68 datePopupVisible = false 69 69 }, 10) 70 70 } 71 - 72 - function keydown(e: KeyboardEvent) { 73 - if (e.key === 'Escape' && e.target) { 74 - visible = false 75 - e.preventDefault() 76 - } 77 - } 78 71 </script> 79 72 80 - <Modal bind:visible on:keydown={keydown}> 81 - <form class="content" on:submit|preventDefault={submit}> 82 - {#if editIndex === null} 83 - <h3>Add Channel</h3> 84 - {:else} 85 - <h3>Edit Channel</h3> 86 - {/if} 73 + {#if visible} 74 + <Modal onCancel={() => (visible = false)} noCloseIcon> 75 + <form class="content" on:submit|preventDefault={submit}> 76 + {#if editIndex === null} 77 + <h3>Add Channel</h3> 78 + {:else} 79 + <h3>Edit Channel</h3> 80 + {/if} 87 81 88 - {#if editIndex === null} 89 - <p>Channel or Video URL</p> 90 - <input 91 - type="text" 92 - placeholder="https://www.youtube.com/channel/UCeTncCK57upn3lPn6PX18Ng" 93 - bind:value={url} 94 - /> 95 - {/if} 82 + {#if editIndex === null} 83 + <p>Channel or Video URL</p> 84 + <input 85 + type="text" 86 + placeholder="https://www.youtube.com/channel/UCeTncCK57upn3lPn6PX18Ng" 87 + bind:value={url} 88 + /> 89 + {/if} 96 90 97 - <p>Refresh rate (minutes)</p> 98 - <p class="sub"> 99 - Channels with identical refresh rates are grouped together in batches, so it's recommended to 100 - use only a few different refresh rates 101 - </p> 102 - <input type="number" bind:value={refreshRateMinutes} /> 91 + <p>Refresh rate (minutes)</p> 92 + <p class="sub"> 93 + Channels with identical refresh rates are grouped together in batches, so it's recommended 94 + to use only a few different refresh rates 95 + </p> 96 + <input type="number" bind:value={refreshRateMinutes} /> 103 97 104 - <p>Check for videos after</p> 105 - <div class="date-picker"> 106 - <DateInput bind:value={fromTime} bind:visible={datePopupVisible} /> 107 - </div> 98 + <p>Check for videos after</p> 99 + <div class="date-picker"> 100 + <DateInput bind:value={fromTime} bind:visible={datePopupVisible} /> 101 + </div> 108 102 109 - <div class="buttons"> 110 - {#if editIndex !== null} 111 - <Button danger on:click={onDelete}>Delete</Button> 112 - {/if} 113 - <div class="max-spacer" /> 114 - <Button secondary on:click={() => (visible = false)}>Cancel</Button> 115 - <div class="spacer" /> 116 - <Button type="submit">{editIndex === null ? 'Add' : 'Save'}</Button> 117 - </div> 118 - </form> 119 - </Modal> 103 + <div class="buttons"> 104 + {#if editIndex !== null} 105 + <Button danger on:click={onDelete}>Delete</Button> 106 + {/if} 107 + <div class="max-spacer" /> 108 + <Button secondary on:click={() => (visible = false)}>Cancel</Button> 109 + <div class="spacer" /> 110 + <Button type="submit">{editIndex === null ? 'Add' : 'Save'}</Button> 111 + </div> 112 + </form> 113 + </Modal> 114 + {/if} 120 115 121 116 <style lang="sass"> 117 + h3 118 + margin-top: 0px 122 119 .content 123 120 max-width: 440px 124 121 .date-picker
+21 -24
src/modals/GetStarted.svelte
··· 1 1 <script lang="ts"> 2 2 import { settingsOpen } from '../lib/data' 3 - import Modal from '../lib/Modal.svelte' 3 + import Modal from 'modal-svelte' 4 4 import Button from '../lib/Button.svelte' 5 5 import Link from '../lib/Link.svelte' 6 6 7 - export let visible = false 8 - 9 - function keydown(e: KeyboardEvent) { 10 - if (e.key === 'Escape' || (e.key === 'Enter' && e.target)) { 11 - visible = false 12 - e.preventDefault() 13 - } 14 - } 7 + export let visible = true 15 8 </script> 16 9 17 - <Modal bind:visible on:keydown={keydown}> 18 - <main> 19 - <h3>Get Started</h3> 20 - <p>You can add a channel on the Channels page.</p> 21 - <p> 22 - Consider opening the 23 - <Link href="/" on:click={() => ($settingsOpen = true)}>settings</Link> 24 - to see how to get your own API key. Kadium has a default one, but it's vulnerable to abuse and 25 - could run out of quota. 26 - </p> 27 - <div class="buttons"> 28 - <Button type="submit" on:click={() => (visible = false)}>Whatever</Button> 29 - </div> 30 - </main> 31 - </Modal> 10 + {#if visible} 11 + <Modal onCancel={() => (visible = false)} form={() => (visible = false)} noCloseIcon> 12 + <main> 13 + <h3>Get Started</h3> 14 + <p>You can add a channel on the Channels page.</p> 15 + <p> 16 + Consider opening the 17 + <Link href="/" on:click={() => ($settingsOpen = true)}>settings</Link> 18 + to see how to get your own API key. Kadium has a default one, but it's vulnerable to abuse and 19 + could run out of quota. 20 + </p> 21 + <div class="buttons"> 22 + <Button type="submit" on:click={() => (visible = false)}>Whatever</Button> 23 + </div> 24 + </main> 25 + </Modal> 26 + {/if} 32 27 33 28 <style lang="sass"> 34 29 main 35 30 max-width: 400px 31 + h3 32 + margin-top: 0px 36 33 p 37 34 font-size: 14px 38 35 margin-top: 5px
+87 -85
src/modals/Settings.svelte
··· 1 1 <script lang="ts"> 2 2 import Button from '../lib/Button.svelte' 3 3 import { loadSettings } from '../lib/data' 4 - import Modal from '../lib/Modal.svelte' 4 + import Modal from 'modal-svelte' 5 5 import Link from '../lib/Link.svelte' 6 6 import Switch from '../lib/Switch.svelte' 7 7 import commands from '../lib/commands' ··· 11 11 export let checkInBackground: boolean 12 12 13 13 export let visible = false 14 + let keyGuideVisible = false 14 15 15 16 async function setGeneralSettings() { 16 17 await commands.setGeneralSettings(apiKey, maxConcurrentRequests, checkInBackground) 17 18 await loadSettings() 18 19 visible = false 19 20 } 20 - 21 - function keydown(e: KeyboardEvent) { 22 - if (e.key === 'Escape' && e.target) { 23 - visible = false 24 - e.preventDefault() 25 - } 26 - } 27 - 28 - let keyGuideVisible = false 29 - function keyGuideKeydown(e: KeyboardEvent) { 30 - if (e.key === 'Escape') { 31 - keyGuideVisible = false 32 - e.preventDefault() 33 - } 34 - } 35 21 </script> 36 22 37 - <Modal bind:visible on:keydown={keydown}> 38 - <form class="page" on:submit|preventDefault={setGeneralSettings}> 39 - <h3>Settings</h3> 40 - <p>API Key</p> 41 - <p class="sub"> 42 - Kadium has a default API key, but it's vulnerable to abuse and could run out of quota. 43 - <Link on:click={() => (keyGuideVisible = true)}> 44 - <div>Get your own key</div> 45 - </Link> 46 - </p> 47 - <input class="textbox" type="text" bind:value={apiKey} placeholder="AIzaSyNq5Y9knL..." /> 48 - <div class="toggle-row"> 49 - <label for="check-in-background"> 50 - <p>Check for new videos automatically</p> 51 - </label> 52 - <Switch id="check-in-background" bind:checked={checkInBackground} /> 53 - </div> 54 - <div class="buttons"> 55 - <Button secondary on:click={() => (visible = false)}>Cancel</Button> 56 - <div class="spacer" /> 57 - <Button type="submit">Save</Button> 58 - </div> 59 - </form> 60 - </Modal> 23 + {#if visible} 24 + <Modal 25 + onCancel={() => { 26 + visible = false 27 + }} 28 + let:focus 29 + noCloseIcon 30 + > 31 + <form class="page" on:submit|preventDefault={setGeneralSettings}> 32 + <h3>Settings</h3> 33 + <p>API Key</p> 34 + <p class="sub"> 35 + Kadium has a default API key, but it's vulnerable to abuse and could run out of quota. 36 + <Link on:click={() => (keyGuideVisible = true)}> 37 + <div>Get your own key</div> 38 + </Link> 39 + </p> 40 + <input 41 + class="textbox" 42 + type="text" 43 + bind:value={apiKey} 44 + placeholder="AIzaSyNq5Y9knL..." 45 + use:focus 46 + /> 47 + <div class="toggle-row"> 48 + <label for="check-in-background"> 49 + <p>Check for new videos automatically</p> 50 + </label> 51 + <Switch id="check-in-background" bind:checked={checkInBackground} /> 52 + </div> 53 + <div class="buttons"> 54 + <Button secondary on:click={() => (visible = false)}>Cancel</Button> 55 + <div class="spacer" /> 56 + <Button type="submit">Save</Button> 57 + </div> 58 + </form> 59 + </Modal> 60 + {/if} 61 61 62 - <Modal bind:visible={keyGuideVisible} on:keydown={keyGuideKeydown}> 63 - <form class="guide-page" on:submit|preventDefault={() => (keyGuideVisible = false)}> 64 - <h3>Create an API key</h3> 65 - <ol> 66 - <li> 67 - Go to the <Link href="https://console.cloud.google.com/apis/dashboard" 68 - >Google APIs & Services 69 - </Link> website. 70 - </li> 71 - <li> 72 - <Link href="https://console.cloud.google.com/projectcreate">Create a new project</Link>. Set 73 - the project name to <b>my-kadium</b> and click <b>CREATE</b>. 74 - </li> 75 - <li> 76 - Make sure you have the <b>my-kadium</b> project selected in the top-left project dropdown menu. 77 - </li> 78 - <li> 79 - Go to the <Link 80 - href="https://console.cloud.google.com/apis/library/youtube.googleapis.com?project=mykadium" 81 - >YouTube Data API v3</Link 82 - > 83 - page and click <b>ENABLE</b>. 84 - </li> 85 - <li> 86 - On the <Link href="https://console.cloud.google.com/apis/credentials?project=mykadium" 87 - >Credentials</Link 88 - > 89 - page, click <b>CREATE CREDENTIALS</b>, then <b>API key</b>. 90 - </li> 91 - <li>You should see your API key!</li> 92 - <li>Optionally restrict the API key to the YouTube API:</li> 93 - <ul> 62 + {#if keyGuideVisible} 63 + <Modal onCancel={() => (keyGuideVisible = false)} noCloseIcon> 64 + <form class="guide-page" on:submit|preventDefault={() => (keyGuideVisible = false)}> 65 + <h3>Create an API key</h3> 66 + <ol> 67 + <li> 68 + Go to the <Link href="https://console.cloud.google.com/apis/dashboard" 69 + >Google APIs & Services 70 + </Link> website. 71 + </li> 72 + <li> 73 + <Link href="https://console.cloud.google.com/projectcreate">Create a new project</Link>. 74 + Set the project name to <b>my-kadium</b> and click <b>CREATE</b>. 75 + </li> 94 76 <li> 95 - Open your API key from the 96 - <Link href="https://console.cloud.google.com/apis/credentials?project=mykadium" 77 + Make sure you have the <b>my-kadium</b> project selected in the top-left project dropdown menu. 78 + </li> 79 + <li> 80 + Go to the <Link 81 + href="https://console.cloud.google.com/apis/library/youtube.googleapis.com?project=mykadium" 82 + >YouTube Data API v3</Link 83 + > 84 + page and click <b>ENABLE</b>. 85 + </li> 86 + <li> 87 + On the <Link href="https://console.cloud.google.com/apis/credentials?project=mykadium" 97 88 >Credentials</Link 98 - > page. 89 + > 90 + page, click <b>CREATE CREDENTIALS</b>, then <b>API key</b>. 99 91 </li> 100 - <li>Under <b>API restrictions</b>, select <b>Restrict key</b>.</li> 101 - <li>In the dropdown, select <b>YouTube Data API v3</b>.</li> 102 - <li>Press <b>SAVE</b>.</li> 103 - </ul> 104 - </ol> 105 - <div class="right"> 106 - <Button type="submit">Oh okay</Button> 107 - </div> 108 - </form> 109 - </Modal> 92 + <li>You should see your API key!</li> 93 + <li>Optionally restrict the API key to the YouTube API:</li> 94 + <ul> 95 + <li> 96 + Open your API key from the 97 + <Link href="https://console.cloud.google.com/apis/credentials?project=mykadium" 98 + >Credentials</Link 99 + > page. 100 + </li> 101 + <li>Under <b>API restrictions</b>, select <b>Restrict key</b>.</li> 102 + <li>In the dropdown, select <b>YouTube Data API v3</b>.</li> 103 + <li>Press <b>SAVE</b>.</li> 104 + </ul> 105 + </ol> 106 + <div class="right"> 107 + <Button type="submit">Oh okay</Button> 108 + </div> 109 + </form> 110 + </Modal> 111 + {/if} 110 112 111 113 <style lang="sass"> 112 114 .guide-page