Skip to content

Commit c0a3d9d

Browse files
hahn-kevmyieye
andauthored
tweak the home page a bit (#1877)
* put space between project rows, make the app bar sticky * use transitions when downloading items, don't show downloaded projects on the server list * don't disable list items when they're skeletons to avoid the destructive bg. Add a shadow to make the items popout more * triple click the icon to enable/disable dev mode * Show loading state for project being downloaded * Remove ButtonListItem --------- Co-authored-by: Tim Haasdyk <[email protected]>
1 parent 8133bd2 commit c0a3d9d

File tree

17 files changed

+263
-347
lines changed

17 files changed

+263
-347
lines changed

backend/FwLite/FwLiteShared/Layout/SvelteLayout.razor

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
}
1717
else
1818
{
19-
<link rel="modulepreload" href="@Assets["_content/FwLiteShared/viewer/svelte-ux.js"]">
2019
<link rel="stylesheet" href="@Assets["_content/FwLiteShared/viewer/main.css"]"/>
2120
<link rel="stylesheet" href="@Assets["_content/FwLiteShared/viewer/webfonts.css"]"/>
2221
}

frontend/src/app.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// <reference types='@sveltejs/kit' />
22

3-
import type { LexAuthUser } from '$lib/user';
3+
import type {LexAuthUser} from '$lib/user';
44
import type {Client} from '@urql/svelte';
55

66
export { }; // for some reason this is required in order to make global changes
@@ -43,5 +43,5 @@ declare global {
4343
'client-error' | 'client-unhandledrejection' |
4444
'server-error-hook' | 'client-error-hook';
4545

46-
function enableDevMode(): void;
46+
function enableDevMode(enable = true): void;
4747
}

frontend/viewer/src/app.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export { }; // for some reason this is required in order to make global changes
22

33
declare global {
4-
function enableDevMode(): void;
4+
function enableDevMode(enable = true): void;
55
function enableShadcn(enable = true): void;
66

77
// waiting on: https://github.com/microsoft/TypeScript/issues/60608

frontend/viewer/src/app.postcss

Lines changed: 1 addition & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
height: max(auto, 100%);
1515
scroll-behavior: smooth;
1616
interpolate-size: allow-keywords;
17+
@apply bg-background text-foreground;
1718
}
1819

1920

@@ -30,30 +31,6 @@
3031
.font-inter {
3132
font-family: 'Inter', sans-serif;
3233
}
33-
/* The search bar dialog is somewhat unique. It should be considered when making global changes here. */
34-
.dialog {
35-
/*using 100vh or dvh still caused the action items to get clipped*/
36-
height: min(1200px, 100%);
37-
width: min(900px, 100%);
38-
@apply md:max-h-[calc(100vh-16px)];
39-
@apply md:max-w-[calc(100vw-30px)]; /* more than the 16px for height, because of scrollbars */
40-
@apply flex flex-col;
41-
@apply max-md:rounded-none;
42-
43-
.actions {
44-
position: sticky;
45-
bottom: 0;
46-
z-index: 2;
47-
}
48-
49-
* {
50-
overscroll-behavior: contain;
51-
}
52-
}
53-
54-
.Checkbox *, label:has(.Switch) {
55-
cursor: pointer;
56-
}
5734

5835
[id^=entry] {
5936
scroll-margin-top: 1rem;
@@ -69,66 +46,6 @@
6946
scroll-margin-bottom: 3.5rem;
7047
}
7148

72-
@layer components {
73-
.collapsible-col {
74-
overflow-x: hidden;
75-
transition: opacity 0.2s ease-out;
76-
}
77-
78-
.side-scroller {
79-
height: calc(var(--space-for-editor, 100vh) - 32px);
80-
transition: height 0.05s ease-out, opacity 0.2s ease-out;
81-
position: sticky;
82-
top: 16px;
83-
}
84-
85-
.collapsible-col.collapse-col {
86-
max-height: 0 !important;
87-
width: 0 !important;
88-
max-width: 0 !important;
89-
opacity: 0 !important;
90-
transition: 0s;
91-
overflow-y: hidden;
92-
}
93-
94-
.text-field-sibling-button {
95-
@apply h-[37.6px] p-1.5 aspect-square text-[0.9em];
96-
}
97-
98-
.key {
99-
display: inline-block;
100-
padding: 0.15em 0.4em;
101-
margin: 0 0.1em;
102-
font-size: 0.8em;
103-
@apply border rounded-md shadow-md;
104-
}
105-
106-
.icon-button-group-container {
107-
.Button {
108-
@apply p-1.5 text-sm min-w-9 min-h-9;
109-
}
110-
}
111-
}
112-
113-
.ListItem > * {
114-
max-width: 100%;
115-
overflow: hidden;
116-
}
117-
118-
.AppBar :has(> [slot="actions"]) {
119-
flex-grow: 0;
120-
}
121-
122-
html:has(.Dialog) {
123-
@apply overflow-hidden;
124-
/* scrollbar-gutter: stable; prevents a page-width resize if there IS a scrollbar,
125-
but it causes a page-width reisze if there ISN'T a scrollbar. */
126-
}
127-
128-
.menu-items .options {
129-
@apply overscroll-contain;
130-
}
131-
13249
.x-ellipsis {
13350
max-width: 100%;
13451
overflow: hidden;

frontend/viewer/src/home/AppBar.svelte

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,24 @@
1212
if (tabTitle) {
1313
document.title = tabTitle;
1414
}
15-
})
15+
});
1616
</script>
17-
<header class="flex items-center relative z-50 gap-2 justify-between px-4 max-md:px-1 min-h-12 bg-primary/50 dark:bg-primary/70 shadow-md">
17+
<header
18+
class="flex items-center z-50 space-x-2 justify-between pr-1 md:pr-3 pl-3 min-h-12 shadow-lg m-3 mb-0 rounded sticky top-3">
1819
{@render title()}
1920
<div class="grow-0"></div>
2021
{@render actions()}
2122
</header>
23+
24+
<style>
25+
header {
26+
/* Stack background colors, so alpha-value only affects the color, not the transparency. */
27+
background:
28+
/*
29+
linear-gradient is just a way to use a solid color and work around: "Only the last background can include a background color."
30+
See: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_backgrounds_and_borders/Using_multiple_backgrounds
31+
*/
32+
linear-gradient(hsl(var(--primary) / 0.4)),
33+
hsl(var(--background));
34+
}
35+
</style>

frontend/viewer/src/home/HomeView.svelte

Lines changed: 81 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
useProjectsService,
1212
useTroubleshootingService,
1313
} from '$lib/services/service-provider';
14-
import ButtonListItem from '$lib/utils/ButtonListItem.svelte';
1514
import TroubleshootDialog from '$lib/troubleshoot/TroubleshootDialog.svelte';
1615
import ServersList from './ServersList.svelte';
1716
import {t} from 'svelte-i18n-lingui';
@@ -25,12 +24,21 @@
2524
import ProjectListItem from './ProjectListItem.svelte';
2625
import ListItem from '$lib/components/ListItem.svelte';
2726
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';
2832
2933
const projectsService = useProjectsService();
3034
const importFwdataService = useImportFwdataService();
3135
const fwLiteConfig = useFwLiteConfig();
3236
const exampleProjectName = 'Example-Project';
33-
37+
const [send, receive] = crossfade({
38+
duration: 500,
39+
easing: cubicOut,
40+
});
41+
transitionContext.set([send, receive]);
3442
function dateTimeProjectSuffix(): string {
3543
return new Date()
3644
.toISOString()
@@ -98,18 +106,39 @@
98106
const supportsTroubleshooting = useTroubleshootingService();
99107
let troubleshootDialog: TroubleshootDialog | undefined;
100108
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+
101130
</script>
102131

103132
<AppBar tabTitle={$t`Dictionaries`}>
104133
{#snippet title()}
105134
<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`}/>
107136
<h3>{$t`Dictionaries`}</h3>
108137
</div>
109138
{/snippet}
110139

111140
{#snippet actions()}
112-
<div class="flex">
141+
<div class="flex gap-1">
113142
{#if import.meta.env.DEV}
114143
<Button href="http://localhost:6006/" target="_blank"
115144
variant="ghost" size="icon" iconProps={{src: storybookIcon, alt: 'Storybook icon'}}/>
@@ -159,74 +188,74 @@
159188
variant="ghost"
160189
onclick={() => refreshProjects()}/>
161190
</div>
162-
<div class="shadow rounded">
191+
<div>
163192
{#each projects.filter((p) => p.crdt) as project, i (project.id ?? i)}
164193
{@const server = project.server}
165194
{@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>
191222
{/each}
192223
<DevContent>
193-
<ButtonListItem href="/testing/project-view">
224+
<Anchor href="/testing/project-view">
194225
<ProjectListItem icon="i-mdi-test-tube" project={{ name: 'Test Project', code: 'Test Project' }}>
195226
{#snippet actions()}
196227
<Icon icon="i-mdi-chevron-right" class="p-2"/>
197228
{/snippet}
198229
</ProjectListItem>
199-
</ButtonListItem>
230+
</Anchor>
200231
</DevContent>
201232
{#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}
217247

218-
</ListItem>
219-
</ButtonListItem>
248+
</ListItem>
220249
{/if}
221250
</div>
222251
</div>
223252
<ServersList localProjects={projects} {refreshProjects}/>
224253
{#if projects.some((p) => p.fwdata)}
225254
<div>
226255
<p class="sub-title">{$t`Classic FieldWorks Projects`}</p>
227-
<div class="shadow rounded">
256+
<div>
228257
{#each projects.filter((p) => p.fwdata) as project (project.id ?? project.name)}
229-
<ButtonListItem href={`/fwdata/${project.code}`}>
258+
<Anchor href={`/fwdata/${project.code}`}>
230259
<ProjectListItem {project}>
231260
{#snippet icon()}
232261
<Icon src={flexLogo} alt={$t`FieldWorks logo`}/>
@@ -246,7 +275,7 @@
246275
</DevContent>
247276
{/snippet}
248277
</ProjectListItem>
249-
</ButtonListItem>
278+
</Anchor>
250279
{/each}
251280
</div>
252281
</div>
@@ -265,11 +294,6 @@
265294
display: flex;
266295
flex-direction: column;
267296
268-
:global(:is(.ListItem)) {
269-
@apply max-md:!rounded-none;
270-
@apply contrast-[0.95];
271-
}
272-
273297
:global(.sub-title) {
274298
@apply m-2;
275299
@apply text-sm text-muted-foreground;

0 commit comments

Comments
 (0)