[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.

Design and download button

Kasper (Jun 26, 2022, 2:23 PM +0200) 36fcecde 4e853e08

+372 -80
+3 -2
README.md
··· 9 9 </p> 10 10 <br/> 11 11 12 - ![Screenshot 1](assets/screenshot-1.png) 13 - ![Screenshot 2](assets/screenshot-2.png) 12 + ![Screenshot 1](website/static/screenshot-1.webp) 13 + 14 + ![Screenshot 2](website/static/screenshot-2.webp) 14 15 15 16 ## Dev instructions 16 17
assets/screenshot-1.png

This is a binary file and will not be displayed.

assets/screenshot-2.png

This is a binary file and will not be displayed.

+42 -50
package-lock.json
··· 786 786 "node": ">=8" 787 787 } 788 788 }, 789 + "node_modules/bowser": { 790 + "version": "2.11.0", 791 + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", 792 + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", 793 + "dev": true 794 + }, 789 795 "node_modules/brace-expansion": { 790 796 "version": "1.1.11", 791 797 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", ··· 3317 3323 }, 3318 3324 "website": { 3319 3325 "devDependencies": { 3320 - "@sveltejs/adapter-static": "^1.0.0-next.28", 3321 - "@sveltejs/kit": "^1.0.0-next.288", 3326 + "@sveltejs/adapter-static": "1.0.0-next.34", 3327 + "@sveltejs/kit": "1.0.0-next.354", 3322 3328 "@tailwindcss/forms": "^0.5.2", 3323 - "@typescript-eslint/eslint-plugin": "^5.13.0", 3324 - "@typescript-eslint/parser": "^5.13.0", 3325 - "eslint": "^8.10.0", 3326 - "eslint-config-prettier": "^8.4.0", 3327 - "eslint-plugin-svelte3": "^3.4.1", 3328 - "prettier": "^2.5.1", 3329 - "prettier-plugin-svelte": "^2.6.0", 3329 + "@typescript-eslint/eslint-plugin": "^5.29.0", 3330 + "@typescript-eslint/parser": "^5.29.0", 3331 + "bowser": "^2.11.0", 3332 + "eslint": "^8.18.0", 3333 + "eslint-config-prettier": "^8.5.0", 3334 + "eslint-plugin-svelte3": "^4.0.0", 3335 + "prettier": "^2.7.1", 3336 + "prettier-plugin-svelte": "^2.7.0", 3330 3337 "prettier-plugin-tailwindcss": "^0.1.11", 3331 - "sass": "^1.49.9", 3332 - "svelte": "^3.46.4", 3333 - "svelte-check": "^2.4.5", 3334 - "svelte-preprocess": "^4.10.4", 3338 + "sass": "^1.53.0", 3339 + "svelte": "^3.48.0", 3340 + "svelte-check": "^2.7.2", 3341 + "svelte-preprocess": "^4.10.7", 3335 3342 "tailwindcss": "^3.1.4", 3336 - "typescript": "^4.5.5" 3337 - } 3338 - }, 3339 - "website/node_modules/eslint-plugin-svelte3": { 3340 - "version": "3.4.1", 3341 - "resolved": "https://registry.npmjs.org/eslint-plugin-svelte3/-/eslint-plugin-svelte3-3.4.1.tgz", 3342 - "integrity": "sha512-7p59WG8qV8L6wLdl4d/c3mdjkgVglQCdv5XOTk/iNPBKXuuV+Q0eFP5Wa6iJd/G2M1qR3BkLPEzaANOqKAZczw==", 3343 - "dev": true, 3344 - "engines": { 3345 - "node": ">=10" 3346 - }, 3347 - "peerDependencies": { 3348 - "eslint": ">=6.0.0", 3349 - "svelte": "^3.2.0" 3343 + "typescript": "^4.7.4" 3350 3344 } 3351 3345 } 3352 3346 }, ··· 3815 3809 "version": "2.2.0", 3816 3810 "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 3817 3811 "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 3812 + "dev": true 3813 + }, 3814 + "bowser": { 3815 + "version": "2.11.0", 3816 + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", 3817 + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", 3818 3818 "dev": true 3819 3819 }, 3820 3820 "brace-expansion": { ··· 5441 5441 "website": { 5442 5442 "version": "file:website", 5443 5443 "requires": { 5444 - "@sveltejs/adapter-static": "^1.0.0-next.28", 5445 - "@sveltejs/kit": "^1.0.0-next.288", 5444 + "@sveltejs/adapter-static": "1.0.0-next.34", 5445 + "@sveltejs/kit": "1.0.0-next.354", 5446 5446 "@tailwindcss/forms": "^0.5.2", 5447 - "@typescript-eslint/eslint-plugin": "^5.13.0", 5448 - "@typescript-eslint/parser": "^5.13.0", 5449 - "eslint": "^8.10.0", 5450 - "eslint-config-prettier": "^8.4.0", 5451 - "eslint-plugin-svelte3": "^3.4.1", 5452 - "prettier": "^2.5.1", 5453 - "prettier-plugin-svelte": "^2.6.0", 5447 + "@typescript-eslint/eslint-plugin": "^5.29.0", 5448 + "@typescript-eslint/parser": "^5.29.0", 5449 + "bowser": "^2.11.0", 5450 + "eslint": "^8.18.0", 5451 + "eslint-config-prettier": "^8.5.0", 5452 + "eslint-plugin-svelte3": "^4.0.0", 5453 + "prettier": "^2.7.1", 5454 + "prettier-plugin-svelte": "^2.7.0", 5454 5455 "prettier-plugin-tailwindcss": "^0.1.11", 5455 - "sass": "^1.49.9", 5456 - "svelte": "^3.46.4", 5457 - "svelte-check": "^2.4.5", 5458 - "svelte-preprocess": "^4.10.4", 5456 + "sass": "^1.53.0", 5457 + "svelte": "^3.48.0", 5458 + "svelte-check": "^2.7.2", 5459 + "svelte-preprocess": "^4.10.7", 5459 5460 "tailwindcss": "^3.1.4", 5460 - "typescript": "^4.5.5" 5461 - }, 5462 - "dependencies": { 5463 - "eslint-plugin-svelte3": { 5464 - "version": "3.4.1", 5465 - "resolved": "https://registry.npmjs.org/eslint-plugin-svelte3/-/eslint-plugin-svelte3-3.4.1.tgz", 5466 - "integrity": "sha512-7p59WG8qV8L6wLdl4d/c3mdjkgVglQCdv5XOTk/iNPBKXuuV+Q0eFP5Wa6iJd/G2M1qR3BkLPEzaANOqKAZczw==", 5467 - "dev": true, 5468 - "requires": {} 5469 - } 5461 + "typescript": "^4.7.4" 5470 5462 } 5471 5463 }, 5472 5464 "which": {
+14
updates.json
··· 1 + { 2 + "changelogDefault": "CHANGELOG.md", 3 + "versions": { 4 + "1.0.2": { 5 + "update_from": "1.0.1", 6 + "macos": { 7 + "version": "^10.13", 8 + "arch": "x86_64" 9 + } 10 + }, 11 + 12 + "1.0.1": {} 13 + } 14 + }
+15 -14
website/package.json
··· 9 9 "format": "prettier --plugin-search-dir=. --write src *.js && eslint --fix src *.js" 10 10 }, 11 11 "devDependencies": { 12 - "@sveltejs/adapter-static": "^1.0.0-next.28", 13 - "@sveltejs/kit": "^1.0.0-next.288", 12 + "@sveltejs/adapter-static": "1.0.0-next.34", 13 + "@sveltejs/kit": "1.0.0-next.354", 14 14 "@tailwindcss/forms": "^0.5.2", 15 - "@typescript-eslint/eslint-plugin": "^5.13.0", 16 - "@typescript-eslint/parser": "^5.13.0", 17 - "eslint": "^8.10.0", 18 - "eslint-config-prettier": "^8.4.0", 19 - "eslint-plugin-svelte3": "^3.4.1", 20 - "prettier": "^2.5.1", 21 - "prettier-plugin-svelte": "^2.6.0", 15 + "@typescript-eslint/eslint-plugin": "^5.29.0", 16 + "@typescript-eslint/parser": "^5.29.0", 17 + "bowser": "^2.11.0", 18 + "eslint": "^8.18.0", 19 + "eslint-config-prettier": "^8.5.0", 20 + "eslint-plugin-svelte3": "^4.0.0", 21 + "prettier": "^2.7.1", 22 + "prettier-plugin-svelte": "^2.7.0", 22 23 "prettier-plugin-tailwindcss": "^0.1.11", 23 - "sass": "^1.49.9", 24 - "svelte": "^3.46.4", 25 - "svelte-check": "^2.4.5", 26 - "svelte-preprocess": "^4.10.4", 24 + "sass": "^1.53.0", 25 + "svelte": "^3.48.0", 26 + "svelte-check": "^2.7.2", 27 + "svelte-preprocess": "^4.10.7", 27 28 "tailwindcss": "^3.1.4", 28 - "typescript": "^4.5.5" 29 + "typescript": "^4.7.4" 29 30 }, 30 31 "prettier": { 31 32 "printWidth": 100,
+3
website/src/hooks.js
··· 1 + export async function handle({ event, resolve }) { 2 + return resolve(event, { ssr: false }) 3 + }
+30
website/src/routes/_ButtonPopup.svelte
··· 1 + <script lang="ts"> 2 + export let isOpen = false 3 + 4 + function toggle() { 5 + isOpen = !isOpen 6 + } 7 + 8 + let parent: HTMLDivElement 9 + function focusout(e: FocusEvent) { 10 + if (parent.contains(document.activeElement)) { 11 + return 12 + } else if (e.relatedTarget === null) { 13 + isOpen = false 14 + } else if (e.relatedTarget instanceof HTMLElement) { 15 + const stayingInParent = parent.contains(e.relatedTarget) 16 + if (!stayingInParent && !e.relatedTarget.isSameNode(parent)) { 17 + isOpen = false 18 + } 19 + } 20 + } 21 + </script> 22 + 23 + <div class="relative" bind:this={parent} on:focusout={focusout} tabindex="-1"> 24 + <slot {toggle} {isOpen} /> 25 + {#if isOpen} 26 + <div class="absolute z-10 w-full divide-y divide-gray-100 text-sm text-gray-700 shadow"> 27 + <slot name="popup" /> 28 + </div> 29 + {/if} 30 + </div>
+66
website/src/routes/_Revealed.svelte
··· 1 + <script lang="ts"> 2 + import { onMount, tick } from 'svelte' 3 + 4 + export let loaded = false 5 + 6 + type Options = { 7 + repeat?: boolean 8 + duration?: number 9 + delay?: number 10 + threshold?: number 11 + opacity?: number 12 + x?: number 13 + y?: number 14 + scale?: number 15 + } 16 + export let options: Options = {} 17 + $: baseStyle = options.duration == null ? '' : `transition-duration: ${options.duration}ms;` 18 + $: baseStyle += options.delay == null ? '' : `transition-delay: ${options.delay}ms;` 19 + 20 + $: style = baseStyle || null 21 + $: if (loaded) { 22 + style = baseStyle || null 23 + } else { 24 + if (options.opacity != null) { 25 + style += `opacity: ${options.opacity};` 26 + } 27 + 28 + let transform = '' 29 + if (options.x) transform += `translateX(${options.x}px)` 30 + if (options.y) transform += `translateY(${options.y}px)` 31 + if (options.scale) transform += `scale(${options.scale})` 32 + if (transform !== '') { 33 + style += `transform: ${transform};` 34 + } 35 + } 36 + 37 + let classes = '' 38 + export { classes as class } 39 + 40 + $: currentClass = classes + (loaded === true ? '' : ' ') 41 + 42 + let observer: IntersectionObserver 43 + let element: HTMLElement 44 + 45 + onMount(async () => { 46 + await tick() 47 + if (typeof IntersectionObserver === 'undefined') { 48 + return 49 + } 50 + observer = new IntersectionObserver((entries) => { 51 + entries.forEach((entry) => { 52 + if (entry.isIntersecting) { 53 + loaded = true 54 + } else if (options.repeat) { 55 + loaded = false 56 + } 57 + }) 58 + }, {}) 59 + observer.observe(element) 60 + return () => observer.unobserve(element) 61 + }) 62 + </script> 63 + 64 + <div bind:this={element} class={currentClass} style={style === null ? '' : style}> 65 + <slot {loaded} /> 66 + </div>
+192 -8
website/src/routes/index.svelte
··· 1 1 <script lang="ts"> 2 + import { scale } from 'svelte/transition' 3 + import ButtonPopup from './_ButtonPopup.svelte' 4 + import Revealed from './_Revealed.svelte' 5 + import Bowser from 'bowser' 6 + import { onMount } from 'svelte' 7 + import { Octokit } from '/skypack/octokit@1.x' 8 + 9 + type Version = { 10 + os: string 11 + arch: string 12 + ending: string 13 + } 14 + const macOS: Version = { 15 + os: 'macOS', 16 + arch: 'x64', 17 + ending: '.dmg', 18 + } 19 + const windows: Version = { 20 + os: 'Windows', 21 + arch: 'x64', 22 + ending: '.msi', 23 + } 24 + const linuxDeb: Version = { 25 + os: 'Linux .deb', 26 + arch: 'x64', 27 + ending: '.deb', 28 + } 29 + const linuxAppImage: Version = { 30 + os: 'Linux AppImage', 31 + arch: 'x64', 32 + ending: '.appimage', 33 + } 34 + const versionList: Version[] = [macOS, windows, linuxDeb, linuxAppImage] 35 + let suggestedVersion = windows 36 + onMount(() => { 37 + const browser = Bowser.getParser(window.navigator.userAgent) 38 + const osName = browser.getOSName() 39 + if (osName === 'macOS' || osName === 'iOS') { 40 + suggestedVersion = macOS 41 + } else if (osName === 'Windows') { 42 + suggestedVersion = windows 43 + } else if (osName === 'Linux' || osName === 'Chrome OS') { 44 + suggestedVersion = linuxDeb 45 + } 46 + }) 47 + 48 + async function download(version: Version) { 49 + const octokit = new Octokit() 50 + try { 51 + const { data } = await octokit.rest.repos.getLatestRelease({ 52 + owner: 'probablykasper', 53 + repo: 'kadium', 54 + }) 55 + for (const asset of data.assets) { 56 + if (asset.name.toLowerCase().endsWith(version.ending)) { 57 + console.log(asset) 58 + window.open(asset.browser_download_url, '_self') 59 + return 60 + } 61 + } 62 + } catch (e) { 63 + console.error(e) 64 + } 65 + } 2 66 </script> 3 67 4 - <h1 class="mt-8 mb-2 inline-block text-7xl font-extrabold">Kadium</h1> 5 - <p class="text-md text-blue-100 opacity-60"> 6 - An app for staying ontop of YouTube channels' uploads 7 - </p> 68 + <Revealed 69 + class="mt-32 mb-4 transition-all ease-out" 70 + options={{ opacity: 0, scale: 0.9, duration: 1000 }} 71 + > 72 + <h1 class="inline-block text-7xl font-extrabold">Kadium</h1> 73 + </Revealed> 74 + <Revealed 75 + class="mb-8 transition-all ease-out" 76 + options={{ opacity: 0, y: -10, duration: 750, delay: 500 }} 77 + > 78 + <p class="text-xl text-blue-100 opacity-60 transition-all duration-500 ease-out"> 79 + An app for staying ontop of YouTube channels' uploads 80 + </p> 81 + </Revealed> 82 + 83 + <Revealed 84 + class="mx-auto mb-24 inline-block transition-all ease-out" 85 + options={{ opacity: 0, scale: 0.9, duration: 750, delay: 700 }} 86 + > 87 + <ButtonPopup let:toggle let:isOpen> 88 + <div 89 + class="relative flex h-9 cursor-default items-center border border-white border-opacity-10 bg-white bg-opacity-5 text-base font-medium transition-all duration-300 ease-in-out hover:border-opacity-20" 90 + class:rounded-2xl={!isOpen} 91 + class:rounded-lg={isOpen} 92 + > 93 + <button 94 + class="group relative flex h-full items-center pr-4 pl-5 text-white text-opacity-70 outline-none transition-all duration-300 hover:text-opacity-100" 95 + on:click={() => download(suggestedVersion)} 96 + > 97 + <div 98 + class="opacity-0 transition-all duration-700 ease-out group-hover:opacity-40 group-focus:opacity-40" 99 + > 100 + <div 101 + class="gradient gradient-3 scale-80 absolute inset-0 -z-10 transition-all duration-700 ease-out group-hover:scale-100 group-hover:blur-md group-focus:scale-100 group-focus:blur-md" 102 + /> 103 + </div> 104 + Download for {suggestedVersion.os} 105 + </button> 106 + <div class="h-5 border-l border-white border-opacity-30" /> 107 + <button 108 + class="group relative h-full pl-4 pr-5 text-white text-opacity-70 outline-none transition-all duration-300 hover:text-opacity-100" 109 + on:click={toggle} 110 + > 111 + <div 112 + class="opacity-0 transition-all duration-700 ease-out group-hover:opacity-40 group-focus:opacity-40" 113 + > 114 + <div 115 + class="gradient gradient-3 scale-80 absolute inset-0 -z-10 transition-all duration-700 ease-out group-hover:scale-100 group-hover:blur-md group-focus:scale-100 group-focus:blur-md" 116 + /> 117 + </div> 118 + <svg 119 + fill="currentColor" 120 + xmlns="http://www.w3.org/2000/svg" 121 + width="12" 122 + height="12" 123 + viewBox="0 0 24 24" 124 + ><path d="M0 7.33l2.829-2.83 9.175 9.339 9.167-9.339 2.829 2.83-11.996 12.17z" /></svg 125 + > 126 + </button> 127 + </div> 128 + <div 129 + slot="popup" 130 + class="mt-0.5 w-full divide-y divide-white divide-opacity-10 rounded-xl border border-white border-opacity-20 bg-white bg-opacity-5 text-white backdrop-blur-sm will-change-contents" 131 + transition:scale={{ start: 0.9, opacity: 0, duration: 300 }} 132 + > 133 + {#each versionList as version} 134 + <button 135 + class="h-9 w-full bg-white bg-opacity-0 px-5 text-left outline-none hover:bg-opacity-5 focus:bg-opacity-5" 136 + on:click={() => download(version)} 137 + > 138 + {version.os} <span class="opacity-70">{version.arch}</span> 139 + </button> 140 + {/each} 141 + </div> 142 + </ButtonPopup> 143 + </Revealed> 144 + 145 + <Revealed 146 + class="relative mx-auto mb-24 max-w-5xl px-6 sm:px-8" 147 + options={{ opacity: 0, y: 10, duration: 1000, delay: 600 }} 148 + > 149 + <div class="gradient gradient-1 show-gradient absolute inset-0 -z-10 blur-[160px]" /> 150 + <div class="relative"> 151 + <div class="ripple absolute inset-0 -z-10 rounded-[5px]" /> 152 + <img src="/screenshot-1.webp" alt="" /> 153 + </div> 154 + </Revealed> 155 + <Revealed 156 + class="relative mx-auto mb-24 max-w-5xl px-6 sm:px-8" 157 + options={{ opacity: 0, y: 10, duration: 1000 }} 158 + > 159 + <div class="gradient gradient-2 absolute inset-0 -z-10 blur-[160px]" /> 160 + <div class="relative"> 161 + <div class="ripple absolute inset-0 -z-10 rounded-[5px]" /> 162 + <img src="/screenshot-2.webp" alt="" /> 163 + </div> 164 + </Revealed> 8 165 9 166 <style lang="sass"> 167 + :global(html) 168 + background-color: #000000 10 169 :global(body) 11 - margin: 100px 0px 12 170 font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji 13 171 font-size: 18px 14 - background-color: #111318 15 172 color: #f2f2f2 16 173 text-align: center 17 174 h1 18 - background: linear-gradient(130deg,#0FA0BD 10%,#0D3BF2 90%) 19 - -webkit-background-clip: text 175 + background: linear-gradient(130deg,#09cff6 10%,#3159f6 90%) 20 176 background-clip: text 177 + text-fill-color: transparent 21 178 -webkit-text-fill-color: transparent 179 + .gradient 180 + will-change: contents 181 + &.gradient-1 182 + background: conic-gradient(from 230.29deg at 50% 50%,#2400ff 0deg,#199af5 90deg,#691eff 180deg,#00d4ff 270deg,#1fff8b 360deg) 183 + &.gradient-2 184 + background: conic-gradient(from 230.29deg at 50% 50%,#311bf3 0deg,#f5198f 90deg,#691eff 180deg,#2400ff 270deg,#199af5 360deg) 185 + &.gradient-3 186 + background: linear-gradient(130deg,#09cff6 10%,#3159f6 90%) 187 + .show-gradient 188 + animation: show-gradient 3s ease-in-out forwards 189 + opacity: 0 190 + animation-delay: 500ms 191 + @keyframes show-gradient 192 + 0% 193 + opacity: 0 194 + 50% 195 + opacity: 0.9 196 + 100% 197 + opacity: 0.7 198 + .ripple 199 + // animation: ping 3s cubic-bezier(0, 0, 0.2, 1) infinite 200 + box-shadow: 0px 0px 0px 1px hsla(0, 0%, 100%, 0.1), 0px 0px 0px 4px hsla(0, 0%, 100%, 0.15) 201 + // @keyframes ping 202 + // 0% 203 + // box-shadow: 0px 0px 0px 1px hsla(0, 0%, 100%, 0.1), 0px 0px 0px 0px hsla(0, 0%, 100%, 0.15) 204 + // 100% 205 + // box-shadow: 0px 0px 0px 1px hsla(0, 100%, 100%, 0.1), 0px 0px 0px 9px hsla(0, 0%, 100%, 0) 22 206 </style>
website/static/screenshot-1.webp

This is a binary file and will not be displayed.

website/static/screenshot-2.webp

This is a binary file and will not be displayed.

+7
website/svelte.config.js
··· 10 10 prerender: { 11 11 default: true, 12 12 }, 13 + vite: { 14 + resolve: { 15 + alias: { 16 + '/skypack/': 'https://cdn.skypack.dev/', 17 + }, 18 + }, 19 + }, 13 20 }, 14 21 } 15 22
-6
website/tailwind.config.cjs
··· 1 - const defaultTheme = require('tailwindcss/defaultTheme') 2 - 3 1 const config = { 4 2 content: ['./src/**/*.{html,js,svelte,ts}'], 5 3 ··· 7 5 extend: { 8 6 transitionTimingFunction: { 9 7 md: 'cubic-bezier(0.4, 0.0, 0.2, 1)', 10 - }, 11 - screens: { 12 - xs: '440px', 13 - ...defaultTheme.screens, 14 8 }, 15 9 }, 16 10 },