|
11 | 11 | useProjectsService, |
12 | 12 | useTroubleshootingService, |
13 | 13 | } from '$lib/services/service-provider'; |
14 | | - import ButtonListItem from '$lib/utils/ButtonListItem.svelte'; |
15 | 14 | import TroubleshootDialog from '$lib/troubleshoot/TroubleshootDialog.svelte'; |
16 | 15 | import ServersList from './ServersList.svelte'; |
17 | 16 | import {t} from 'svelte-i18n-lingui'; |
|
25 | 24 | import ProjectListItem from './ProjectListItem.svelte'; |
26 | 25 | import ListItem from '$lib/components/ListItem.svelte'; |
27 | 26 | import {Input} from '$lib/components/ui/input'; |
| 27 | + import {crossfade} from 'svelte/transition'; |
| 28 | + import {cubicOut} from 'svelte/easing'; |
| 29 | + import {flip} from 'svelte/animate'; |
| 30 | + import {transitionContext} from './transitions'; |
| 31 | + import Anchor from '$lib/components/ui/anchor/anchor.svelte'; |
28 | 32 |
|
29 | 33 | const projectsService = useProjectsService(); |
30 | 34 | const importFwdataService = useImportFwdataService(); |
31 | 35 | const fwLiteConfig = useFwLiteConfig(); |
32 | 36 | const exampleProjectName = 'Example-Project'; |
33 | | -
|
| 37 | + const [send, receive] = crossfade({ |
| 38 | + duration: 500, |
| 39 | + easing: cubicOut, |
| 40 | + }); |
| 41 | + transitionContext.set([send, receive]); |
34 | 42 | function dateTimeProjectSuffix(): string { |
35 | 43 | return new Date() |
36 | 44 | .toISOString() |
|
98 | 106 | const supportsTroubleshooting = useTroubleshootingService(); |
99 | 107 | let troubleshootDialog: TroubleshootDialog | undefined; |
100 | 108 |
|
| 109 | + let clickCount = 0; |
| 110 | + let clickTimeout: ReturnType<typeof setTimeout> | undefined; |
| 111 | +
|
| 112 | + function resetClickCounter() { |
| 113 | + clickCount = 0; |
| 114 | + clearTimeout(clickTimeout); |
| 115 | + } |
| 116 | +
|
| 117 | + function clickIcon() { |
| 118 | + //when a user triple clicks the logo it will enable dev mode, this makes it easier to enable on mobile |
| 119 | + clickCount++; |
| 120 | + clearTimeout(clickTimeout); |
| 121 | +
|
| 122 | + if (clickCount === 3) { |
| 123 | + window.enableDevMode(!$isDev); |
| 124 | + resetClickCounter(); |
| 125 | + } else { |
| 126 | + clickTimeout = setTimeout(resetClickCounter, 500); |
| 127 | + } |
| 128 | + } |
| 129 | +
|
101 | 130 | </script> |
102 | 131 |
|
103 | 132 | <AppBar tabTitle={$t`Dictionaries`}> |
104 | 133 | {#snippet title()} |
105 | 134 | <div class="text-lg flex gap-2 items-center"> |
106 | | - <Icon src={mode.current === 'dark' ? logoLight : logoDark} alt={$t`Lexbox logo`}/> |
| 135 | + <Icon onclick={clickIcon} src={mode.current === 'dark' ? logoLight : logoDark} alt={$t`Lexbox logo`}/> |
107 | 136 | <h3>{$t`Dictionaries`}</h3> |
108 | 137 | </div> |
109 | 138 | {/snippet} |
110 | 139 |
|
111 | 140 | {#snippet actions()} |
112 | | - <div class="flex"> |
| 141 | + <div class="flex gap-1"> |
113 | 142 | {#if import.meta.env.DEV} |
114 | 143 | <Button href="http://localhost:6006/" target="_blank" |
115 | 144 | variant="ghost" size="icon" iconProps={{src: storybookIcon, alt: 'Storybook icon'}}/> |
|
159 | 188 | variant="ghost" |
160 | 189 | onclick={() => refreshProjects()}/> |
161 | 190 | </div> |
162 | | - <div class="shadow rounded"> |
| 191 | + <div> |
163 | 192 | {#each projects.filter((p) => p.crdt) as project, i (project.id ?? i)} |
164 | 193 | {@const server = project.server} |
165 | 194 | {@const loading = deletingProject === project.id} |
166 | | - <ButtonListItem href={`/project/${project.code}`}> |
167 | | - <ProjectListItem icon="i-mdi-book-edit-outline" |
168 | | - {project} |
169 | | - {loading} |
170 | | - subtitle={!server ? $t`Local only` : $t`Synced with ${server.displayName}`} |
171 | | - > |
172 | | - {#snippet actions()} |
173 | | - <div class="flex items-center"> |
174 | | - {#if $isDev} |
175 | | - <Button |
176 | | - icon="i-mdi-delete" |
177 | | - variant="ghost" |
178 | | - title={$t`Delete`} |
179 | | - class="p-2 hover:bg-primary/20" |
180 | | - onclick={(e) => { |
181 | | - e.preventDefault(); |
182 | | - void deleteProject(project); |
183 | | - }} |
184 | | - /> |
185 | | - {/if} |
186 | | - <Icon icon="i-mdi-chevron-right" class="p-2"/> |
187 | | - </div> |
188 | | - {/snippet} |
189 | | - </ProjectListItem> |
190 | | - </ButtonListItem> |
| 195 | + <div out:send={{key: 'project-' + project.code}} in:receive={{key: 'project-' + project.code}}> |
| 196 | + <Anchor href={`/project/${project.code}`}> |
| 197 | + <ProjectListItem icon="i-mdi-book-edit-outline" |
| 198 | + {project} |
| 199 | + {loading} |
| 200 | + subtitle={!server ? $t`Local only` : $t`Synced with ${server.displayName}`} |
| 201 | + > |
| 202 | + {#snippet actions()} |
| 203 | + <div class="flex items-center"> |
| 204 | + {#if $isDev} |
| 205 | + <Button |
| 206 | + icon="i-mdi-delete" |
| 207 | + variant="ghost" |
| 208 | + title={$t`Delete`} |
| 209 | + class="p-2 hover:bg-primary/20" |
| 210 | + onclick={(e) => { |
| 211 | + e.preventDefault(); |
| 212 | + void deleteProject(project); |
| 213 | + }} |
| 214 | + /> |
| 215 | + {/if} |
| 216 | + <Icon icon="i-mdi-chevron-right" class="p-2"/> |
| 217 | + </div> |
| 218 | + {/snippet} |
| 219 | + </ProjectListItem> |
| 220 | + </Anchor> |
| 221 | + </div> |
191 | 222 | {/each} |
192 | 223 | <DevContent> |
193 | | - <ButtonListItem href="/testing/project-view"> |
| 224 | + <Anchor href="/testing/project-view"> |
194 | 225 | <ProjectListItem icon="i-mdi-test-tube" project={{ name: 'Test Project', code: 'Test Project' }}> |
195 | 226 | {#snippet actions()} |
196 | 227 | <Icon icon="i-mdi-chevron-right" class="p-2"/> |
197 | 228 | {/snippet} |
198 | 229 | </ProjectListItem> |
199 | | - </ButtonListItem> |
| 230 | + </Anchor> |
200 | 231 | </DevContent> |
201 | 232 | {#if !projects.some(p => p.name === exampleProjectName) || $isDev} |
202 | | - <ButtonListItem onclick={() => createExampleProject()} disabled={createProjectLoading}> |
203 | | - <ListItem class="dark:bg-muted/50 bg-muted/80 hover:bg-muted/30 hover:dark:bg-muted"> |
204 | | - <span>{$t`Create Example Project`}</span> |
205 | | - {#snippet actions()} |
206 | | - <div class="flex flex-nowrap items-center gap-2"> |
207 | | - {#if $isDev} |
208 | | - <Input |
209 | | - bind:value={customExampleProjectName} |
210 | | - placeholder={$t`Project name...`} |
211 | | - onclick={(e) => e.stopPropagation()} |
212 | | - /> |
213 | | - {/if} |
214 | | - <Icon icon="i-mdi-book-plus-outline" class="p-2"/> |
215 | | - </div> |
216 | | - {/snippet} |
| 233 | + <ListItem onclick={() => createExampleProject()} disabled={createProjectLoading} class="dark:bg-muted/50 bg-muted/80 hover:bg-muted/30 hover:dark:bg-muted"> |
| 234 | + <span>{$t`Create Example Project`}</span> |
| 235 | + {#snippet actions()} |
| 236 | + <div class="flex flex-nowrap items-center gap-2"> |
| 237 | + {#if $isDev} |
| 238 | + <Input |
| 239 | + bind:value={customExampleProjectName} |
| 240 | + placeholder={$t`Project name...`} |
| 241 | + onclick={(e) => e.stopPropagation()} |
| 242 | + /> |
| 243 | + {/if} |
| 244 | + <Icon icon="i-mdi-book-plus-outline" class="p-2"/> |
| 245 | + </div> |
| 246 | + {/snippet} |
217 | 247 |
|
218 | | - </ListItem> |
219 | | - </ButtonListItem> |
| 248 | + </ListItem> |
220 | 249 | {/if} |
221 | 250 | </div> |
222 | 251 | </div> |
223 | 252 | <ServersList localProjects={projects} {refreshProjects}/> |
224 | 253 | {#if projects.some((p) => p.fwdata)} |
225 | 254 | <div> |
226 | 255 | <p class="sub-title">{$t`Classic FieldWorks Projects`}</p> |
227 | | - <div class="shadow rounded"> |
| 256 | + <div> |
228 | 257 | {#each projects.filter((p) => p.fwdata) as project (project.id ?? project.name)} |
229 | | - <ButtonListItem href={`/fwdata/${project.code}`}> |
| 258 | + <Anchor href={`/fwdata/${project.code}`}> |
230 | 259 | <ProjectListItem {project}> |
231 | 260 | {#snippet icon()} |
232 | 261 | <Icon src={flexLogo} alt={$t`FieldWorks logo`}/> |
|
246 | 275 | </DevContent> |
247 | 276 | {/snippet} |
248 | 277 | </ProjectListItem> |
249 | | - </ButtonListItem> |
| 278 | + </Anchor> |
250 | 279 | {/each} |
251 | 280 | </div> |
252 | 281 | </div> |
|
265 | 294 | display: flex; |
266 | 295 | flex-direction: column; |
267 | 296 |
|
268 | | - :global(:is(.ListItem)) { |
269 | | - @apply max-md:!rounded-none; |
270 | | - @apply contrast-[0.95]; |
271 | | - } |
272 | | -
|
273 | 297 | :global(.sub-title) { |
274 | 298 | @apply m-2; |
275 | 299 | @apply text-sm text-muted-foreground; |
|
0 commit comments