[READ-ONLY] Mirror of https://github.com/mrgnw/ananas. learn multiple languages at once ananas.xcc.es
0

Configure Feed

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

fmt

+147 -135
+147 -135
src/routes/languages/+page.svelte
··· 1 1 <script> 2 - import { userStore } from '$lib/stores/user.svelte.js'; 3 - import { getAllLanguages, filterLanguages } from '$lib/utils/languages.js'; 4 - import { flip } from 'svelte/animate'; 2 + import { userStore } from '$lib/stores/user.svelte.js'; 3 + import { getAllLanguages, filterLanguages } from '$lib/utils/languages.js'; 4 + import { flip } from 'svelte/animate'; 5 5 6 - let filter = $state(''); 7 - let { data } = $props(); 8 - let { languages = [], country_languages = [], ip_country } = data; 6 + let filter = $state(''); 7 + let { data } = $props(); 8 + let { languages = [], country_languages = [], ip_country } = data; 9 9 10 - let allLanguages = $state(languages); 10 + let allLanguages = $state(languages); 11 11 12 - // userLanguages: allLanguages that are in userStore.user.selectedLanguages (no filter applied) 13 - let userLanguages = $derived.by(() => 14 - allLanguages.filter(lang => userStore.user.selectedLanguages.includes(lang.code)) 15 - ); 16 - let otherLanguages = $derived.by(() => 17 - allLanguages.filter(lang => !userStore.user.selectedLanguages.includes(lang.code)) 18 - ); 12 + // userLanguages: allLanguages that are in userStore.user.selectedLanguages (no filter applied) 13 + let userLanguages = $derived.by(() => 14 + allLanguages.filter((lang) => (userStore.user?.selectedLanguages ?? []).includes(lang.code)) 15 + ); 16 + let otherLanguages = $derived.by(() => 17 + allLanguages.filter((lang) => !(userStore.user?.selectedLanguages ?? []).includes(lang.code)) 18 + ); 19 19 20 - function formatSpeakers(n) { 21 - if (!n) return ''; 22 - return (n / 1000).toFixed(0) + 'M'; 23 - } 20 + function formatSpeakers(n) { 21 + if (!n) return ''; 22 + return (n / 1000).toFixed(0) + 'M'; 23 + } 24 24 </script> 25 25 26 26 <div class="filter-bar"> 27 - <input 28 - class="language-filter-input" 29 - type="text" 30 - placeholder="Filter languages..." 31 - bind:value={filter} 32 - autocomplete="off" 33 - /> 27 + <input 28 + class="language-filter-input" 29 + type="text" 30 + placeholder="Filter languages..." 31 + bind:value={filter} 32 + autocomplete="off" 33 + /> 34 34 </div> 35 35 36 36 <ul class="languages-list"> 37 - {#each userLanguages as lang (lang.code)} 38 - <li class="language-item selected" 39 - animate:flip={{ duration: 222 }} 40 - onclick={() => userStore.removeLanguage(lang.code)} 41 - tabindex="0" 42 - aria-label={`Remove ${lang.name}`}> 43 - <span class="lang-action">–</span> 44 - <span class="lang-speakers">{formatSpeakers(lang.speakers)}</span> 45 - <span class="lang-label">{lang.name}</span> 46 - {#if lang.nativeName && lang.nativeName !== lang.name} 47 - <span class="lang-native">{lang.nativeName}</span> 48 - {/if} 49 - </li> 50 - {/each} 37 + {#each userLanguages as lang (lang.code)} 38 + <li 39 + class="language-item selected" 40 + animate:flip={{ duration: 222 }} 41 + onclick={() => userStore.removeLanguage(lang.code)} 42 + tabindex="0" 43 + aria-label={`Remove ${lang.name}`} 44 + > 45 + <span class="lang-action">–</span> 46 + <span class="lang-speakers">{formatSpeakers(lang.speakers)}</span> 47 + <span class="lang-label">{lang.name}</span> 48 + {#if lang.nativeName && lang.nativeName !== lang.name} 49 + <span class="lang-native">{lang.nativeName}</span> 50 + {/if} 51 + </li> 52 + {/each} 51 53 52 - {#if userLanguages.length && filterLanguages(otherLanguages, filter).length} 53 - <li class="language-separator" style="margin: 0.5em 0; border-bottom: 1px solid #eee; list-style: none;"></li> 54 - {/if} 54 + {#if userLanguages.length && filterLanguages(otherLanguages, filter).length} 55 + <li 56 + class="language-separator" 57 + style="margin: 0.5em 0; border-bottom: 1px solid #eee; list-style: none;" 58 + ></li> 59 + {/if} 55 60 56 - {#each filterLanguages(otherLanguages, filter) as lang (lang.code)} 57 - <li class="language-item" 58 - animate:flip={{ duration: 222 }} 59 - onclick={() => userStore.addLanguage(lang.code)} 60 - tabindex="0" 61 - aria-label={`Add ${lang.name}`}> 62 - <span class="lang-action">+</span> 63 - <span class="lang-speakers">{formatSpeakers(lang.speakers)}</span> 64 - <span class="lang-label">{lang.name}</span> 65 - {#if lang.nativeName && lang.nativeName !== lang.name} 66 - <span class="lang-native">{lang.nativeName}</span> 67 - {/if} 68 - </li> 69 - {/each} 61 + {#each filterLanguages(otherLanguages, filter) as lang (lang.code)} 62 + <li 63 + class="language-item" 64 + animate:flip={{ duration: 222 }} 65 + onclick={() => userStore.addLanguage(lang.code)} 66 + tabindex="0" 67 + aria-label={`Add ${lang.name}`} 68 + > 69 + <span class="lang-action">+</span> 70 + <span class="lang-speakers">{formatSpeakers(lang.speakers)}</span> 71 + <span class="lang-label">{lang.name}</span> 72 + {#if lang.nativeName && lang.nativeName !== lang.name} 73 + <span class="lang-native">{lang.nativeName}</span> 74 + {/if} 75 + </li> 76 + {/each} 70 77 </ul> 71 78 72 79 <style> 73 - .languages-list { 74 - max-width: 480px; 75 - margin: 0 auto; 76 - padding: 0; 77 - list-style: none; 78 - } 79 - .language-item { 80 - display: flex; 81 - align-items: center; 82 - gap: 0.5em; 83 - padding: 0.15em 0.5em; 84 - border-radius: 5px; 85 - margin-bottom: 0.1em; 86 - font-size: 1em; 87 - min-height: 32px; 88 - transition: background 0.12s, box-shadow 0.12s; 89 - will-change: transform, opacity; 90 - cursor: pointer; 91 - outline: none; 92 - } 93 - .language-item:hover, .language-item:focus { 94 - background: #f6f8fa; 95 - box-shadow: 0 0 0 2px #6366f1; 96 - } 80 + .languages-list { 81 + max-width: 480px; 82 + margin: 0 auto; 83 + padding: 0; 84 + list-style: none; 85 + } 86 + .language-item { 87 + display: flex; 88 + align-items: center; 89 + gap: 0.5em; 90 + padding: 0.15em 0.5em; 91 + border-radius: 5px; 92 + margin-bottom: 0.1em; 93 + font-size: 1em; 94 + min-height: 32px; 95 + transition: 96 + background 0.12s, 97 + box-shadow 0.12s; 98 + will-change: transform, opacity; 99 + cursor: pointer; 100 + outline: none; 101 + } 102 + .language-item:hover, 103 + .language-item:focus { 104 + background: #f6f8fa; 105 + box-shadow: 0 0 0 2px #6366f1; 106 + } 97 107 98 - .lang-label { 99 - font-weight: 600; 100 - margin-left: 0.3em; 101 - } 102 - .lang-native { 103 - color: #888; 104 - margin-left: 0.3em; 105 - font-size: 0.97em; 106 - } 107 - .lang-speakers { 108 - color: #aaa; 109 - font-size: 0.97em; 110 - min-width: 2.5em; 111 - text-align: right; 112 - margin-left: 0.2em; 113 - margin-right: 0.2em; 114 - } 115 - .lang-label { 116 - font-weight: 600; 117 - font-size: 1.05em; 118 - display: block; 119 - } 120 - .lang-native { 121 - display: block; 122 - font-style: italic; 123 - color: #888; 124 - font-size: 0.97em; 125 - margin-top: -0.15em; 126 - margin-bottom: 0.1em; 127 - margin-left: 0.2em; 128 - } 108 + .lang-label { 109 + font-weight: 600; 110 + margin-left: 0.3em; 111 + } 112 + .lang-native { 113 + color: #888; 114 + margin-left: 0.3em; 115 + font-size: 0.97em; 116 + } 117 + .lang-speakers { 118 + color: #aaa; 119 + font-size: 0.97em; 120 + min-width: 2.5em; 121 + text-align: right; 122 + margin-left: 0.2em; 123 + margin-right: 0.2em; 124 + } 125 + .lang-label { 126 + font-weight: 600; 127 + font-size: 1.05em; 128 + display: block; 129 + } 130 + .lang-native { 131 + display: block; 132 + font-style: italic; 133 + color: #888; 134 + font-size: 0.97em; 135 + margin-top: -0.15em; 136 + margin-bottom: 0.1em; 137 + margin-left: 0.2em; 138 + } 129 139 130 - .filter-bar { 131 - display: flex; 132 - align-items: center; 133 - max-width: 420px; 134 - margin: .7rem auto .5rem auto; 135 - padding: 0 0.5rem; 136 - border-bottom: 2px solid #e5e7eb; 137 - background: none; 138 - } 139 - .language-filter-input { 140 - flex: 1; 141 - border: none; 142 - outline: none; 143 - background: none; 144 - font-size: 1.15em; 145 - padding: 0em 0 0em 0; 146 - color: #222; 147 - transition: box-shadow 0.15s, border-color 0.15s; 148 - } 149 - .language-filter-input:focus { 150 - box-shadow: 0 2px 0 0 #6366f1; 151 - border-bottom: 2px solid #6366f1; 152 - } 153 - </style> 140 + .filter-bar { 141 + display: flex; 142 + align-items: center; 143 + max-width: 420px; 144 + margin: 0.7rem auto 0.5rem auto; 145 + padding: 0 0.5rem; 146 + border-bottom: 2px solid #e5e7eb; 147 + background: none; 148 + } 149 + .language-filter-input { 150 + flex: 1; 151 + border: none; 152 + outline: none; 153 + background: none; 154 + font-size: 1.15em; 155 + padding: 0em 0 0em 0; 156 + color: #222; 157 + transition: 158 + box-shadow 0.15s, 159 + border-color 0.15s; 160 + } 161 + .language-filter-input:focus { 162 + box-shadow: 0 2px 0 0 #6366f1; 163 + border-bottom: 2px solid #6366f1; 164 + } 165 + </style>