Skip to content

Commit e3a2cae

Browse files
feat(frontend): review launchpad first Satellite design (#2479)
1 parent 3ec6ef0 commit e3a2cae

File tree

10 files changed

+171
-73
lines changed

10 files changed

+171
-73
lines changed

src/frontend/src/lib/components/launchpad/Launchpad.svelte

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import { fade } from 'svelte/transition';
55
import LaunchpadFirstSatellite from '$lib/components/launchpad/LaunchpadFirstSatellite.svelte';
66
import LaunchpadSegments from '$lib/components/launchpad/LaunchpadSegments.svelte';
7-
import ContainerCentered from '$lib/components/ui/ContainerCentered.svelte';
87
import Message from '$lib/components/ui/Message.svelte';
98
import Spinner from '$lib/components/ui/Spinner.svelte';
109
import { satellitesStore } from '$lib/derived/satellites.derived';
@@ -35,11 +34,9 @@
3534
</Message>
3635
</div>
3736
{:else}
38-
<div in:fade>
39-
<ContainerCentered>
40-
<LaunchpadFirstSatellite />
41-
</ContainerCentered>
42-
</div>
37+
<section in:fade>
38+
<LaunchpadFirstSatellite />
39+
</section>
4340
{/if}
4441
{:else if ($satellitesStore?.length ?? 0) >= 1}
4542
<section in:fade>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<script lang="ts">
2+
import { isNullish, nonNullish } from '@dfinity/utils';
3+
import type { Snippet } from 'svelte';
4+
import type { TestId } from '$lib/types/test-id';
5+
import { testId } from '$lib/utils/test.utils';
6+
7+
interface Props {
8+
summary?: Snippet;
9+
children: Snippet;
10+
disabled?: boolean;
11+
row?: boolean;
12+
testId?: TestId;
13+
onclick: () => Promise<void>;
14+
}
15+
16+
let { children, summary, disabled, testId: testIdProp, row = false, onclick }: Props = $props();
17+
</script>
18+
19+
<button class="article" class:row {...testId(testIdProp)} {disabled} {onclick}>
20+
{#if nonNullish(summary)}
21+
<div class="summary">
22+
{@render summary()}
23+
</div>
24+
{/if}
25+
26+
<div class="content" class:only={isNullish(summary)}>
27+
{@render children()}
28+
</div>
29+
</button>
30+
31+
<style lang="scss">
32+
button {
33+
height: 100%;
34+
min-height: 231px;
35+
margin: 0;
36+
}
37+
38+
.summary {
39+
display: flex;
40+
justify-content: space-between;
41+
align-items: center;
42+
padding: var(--padding-4x) var(--padding-4x) var(--padding);
43+
}
44+
45+
.content {
46+
padding: var(--padding-2x) var(--padding-4x) var(--padding);
47+
min-height: 150px;
48+
}
49+
50+
.content,
51+
.summary {
52+
width: 100%;
53+
54+
:global(*::first-letter) {
55+
text-transform: uppercase;
56+
}
57+
}
58+
59+
.only {
60+
height: 100%;
61+
}
62+
63+
.row {
64+
grid-column: 1 / 13;
65+
min-height: 58px;
66+
67+
.content {
68+
display: flex;
69+
align-items: center;
70+
71+
padding: var(--padding-2x) var(--padding-4x);
72+
min-height: auto;
73+
}
74+
}
75+
</style>
Lines changed: 23 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
<script lang="ts">
22
import IconRocket from '$lib/components/icons/IconRocket.svelte';
3+
import LaunchpadButton from '$lib/components/launchpad/LaunchpadButton.svelte';
4+
import LaunchpadHeader from '$lib/components/launchpad/LaunchpadHeader.svelte';
35
import { testIds } from '$lib/constants/test-ids.constants';
46
import { authIdentity } from '$lib/derived/auth.derived';
57
import { missionControlId } from '$lib/derived/console/account.mission-control.derived';
68
import { initSatelliteWizard } from '$lib/services/factory/factory.create.services';
79
import { i18n } from '$lib/stores/app/i18n.store';
8-
import { testId } from '$lib/utils/test.utils';
910
1011
const createSatellite = async () => {
1112
await initSatelliteWizard({
@@ -15,51 +16,36 @@
1516
};
1617
</script>
1718

18-
<button class="primary" onclick={createSatellite} {...testId(testIds.launchpad.launch)}>
19-
{$i18n.satellites.launch}
20-
<IconRocket />
21-
</button>
19+
<LaunchpadHeader withoutGreetingsReturningLabel />
2220

23-
<style lang="scss">
24-
button {
25-
display: flex;
26-
flex-direction: column;
27-
gap: var(--padding-2x);
28-
29-
aspect-ratio: 1/1;
21+
<LaunchpadButton onclick={createSatellite} testId={testIds.launchpad.launch}>
22+
<div class="new">
23+
<IconRocket size="48px" />
3024

31-
max-width: 160px;
32-
max-height: 160px;
25+
<p>{$i18n.satellites.launch_first}</p>
26+
</div>
27+
</LaunchpadButton>
3328

34-
padding: var(--padding);
35-
36-
border-radius: 50%;
29+
<style lang="scss">
30+
@use '../../styles/mixins/fonts';
3731
38-
box-shadow: 3px 3px var(--color-card-contrast);
32+
.new {
33+
display: flex;
34+
justify-content: center;
35+
align-items: center;
36+
flex-direction: column;
3937
40-
animation: push ease-in 750ms 2 forwards;
38+
gap: var(--padding-4x);
4139
42-
&:active {
43-
box-shadow: none;
44-
transform: translateX(3px) translateY(3px);
45-
}
40+
height: 100%;
4641
}
4742
48-
/* -global- */
49-
@keyframes -global-push {
50-
0% {
51-
box-shadow: 3px 3px var(--color-card-contrast);
52-
transform: none;
53-
}
43+
p {
44+
@include fonts.bold(true);
5445
55-
50% {
56-
box-shadow: none;
57-
transform: translateX(3px) translateY(3px);
58-
}
46+
max-width: 150px;
47+
text-align: center;
5948
60-
100% {
61-
box-shadow: 3px 3px var(--color-card-contrast);
62-
transform: none;
63-
}
49+
margin: 0;
6450
}
6551
</style>

src/frontend/src/lib/components/launchpad/LaunchpadGreetings.svelte

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
import IconTerminal from '$lib/components/icons/IconTerminal.svelte';
44
import { i18n } from '$lib/stores/app/i18n.store';
55
6+
interface Props {
7+
withoutReturningLabel?: boolean;
8+
}
9+
10+
let { withoutReturningLabel = false }: Props = $props();
11+
612
const timedGreeting = (): string => {
713
const hour = getHours(new Date());
814
@@ -13,7 +19,10 @@
1319
: $i18n.launchpad.good_evening;
1420
};
1521
16-
const genericGreetings = [$i18n.launchpad.welcome_back, $i18n.launchpad.greetings];
22+
let genericGreetings = $derived([
23+
...(withoutReturningLabel ? [] : [$i18n.launchpad.welcome_back]),
24+
$i18n.launchpad.greetings
25+
]);
1726
1827
let greetings = $derived([...genericGreetings, timedGreeting()]);
1928
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<script lang="ts">
2+
import type { Snippet } from 'svelte';
3+
import LaunchpadGreetings from '$lib/components/launchpad/LaunchpadGreetings.svelte';
4+
import { onIntersection } from '$lib/directives/intersection.directives';
5+
import { onLayoutTitleIntersection } from '$lib/stores/app/layout-intersecting.store';
6+
7+
interface Props {
8+
children?: Snippet;
9+
filter?: string;
10+
withoutGreetingsReturningLabel?: boolean;
11+
}
12+
13+
let { filter = $bindable(''), children, withoutGreetingsReturningLabel }: Props = $props();
14+
15+
const customOnIntersection = (element: HTMLElement) =>
16+
onIntersection(element, {
17+
threshold: 0.8,
18+
rootMargin: '-50px 0px'
19+
});
20+
</script>
21+
22+
<div class="header" onjunoIntersecting={onLayoutTitleIntersection} use:customOnIntersection>
23+
<LaunchpadGreetings withoutReturningLabel={withoutGreetingsReturningLabel} />
24+
25+
{@render children?.()}
26+
</div>
27+
28+
<style lang="scss">
29+
@use '../../styles/mixins/media';
30+
31+
.header {
32+
grid-column: 1 / 13;
33+
34+
@include media.min-width(medium) {
35+
grid-column: 1 / 12;
36+
}
37+
}
38+
</style>

src/frontend/src/lib/components/launchpad/LaunchpadSegments.svelte

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script lang="ts">
22
import LaunchpadAnalytics from '$lib/components/launchpad/LaunchpadAnalytics.svelte';
3+
import LaunchpadHeader from '$lib/components/launchpad/LaunchpadHeader.svelte';
34
import LaunchpadMonitoring from '$lib/components/launchpad/LaunchpadMonitoring.svelte';
45
import LaunchpadSatellite from '$lib/components/launchpad/LaunchpadSatellite.svelte';
56
import LaunchpadToolbar from '$lib/components/launchpad/LaunchpadToolbar.svelte';
@@ -15,7 +16,9 @@
1516
);
1617
</script>
1718

18-
<LaunchpadToolbar bind:filter />
19+
<LaunchpadHeader>
20+
<LaunchpadToolbar bind:filter />
21+
</LaunchpadHeader>
1922

2023
<LaunchpadMonitoring />
2124

src/frontend/src/lib/components/launchpad/LaunchpadToolbar.svelte

Lines changed: 15 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,10 @@
22
import { debounce } from '@dfinity/utils';
33
import { run } from 'svelte/legacy';
44
import AttachActions from '$lib/components/attach-detach/AttachActions.svelte';
5-
import LaunchpadGreetings from '$lib/components/launchpad/LaunchpadGreetings.svelte';
65
import LaunchpadLayout from '$lib/components/launchpad/LaunchpadLayout.svelte';
76
import LaunchpadNewActions from '$lib/components/launchpad/LaunchpadNewActions.svelte';
87
import Input from '$lib/components/ui/Input.svelte';
9-
import { onIntersection } from '$lib/directives/intersection.directives';
108
import { i18n } from '$lib/stores/app/i18n.store';
11-
import { onLayoutTitleIntersection } from '$lib/stores/app/layout-intersecting.store';
129
1310
interface Props {
1411
filter?: string;
@@ -24,36 +21,26 @@
2421
// @ts-expect-error TODO: to be migrated to Svelte v5
2522
(filterInput, debounceUpdateFilter());
2623
});
27-
28-
const customOnIntersection = (element: HTMLElement) =>
29-
onIntersection(element, {
30-
threshold: 0.8,
31-
rootMargin: '-50px 0px'
32-
});
3324
</script>
3425

35-
<div class="header" onjunoIntersecting={onLayoutTitleIntersection} use:customOnIntersection>
36-
<LaunchpadGreetings />
37-
38-
<div role="toolbar">
39-
<div class="filters">
40-
<div class="input">
41-
<Input
42-
name="filter"
43-
inputType="text"
44-
placeholder={$i18n.satellites.search}
45-
spellcheck={false}
46-
bind:value={filterInput}
47-
/>
48-
</div>
49-
50-
<LaunchpadLayout />
51-
52-
<AttachActions />
26+
<div role="toolbar">
27+
<div class="filters">
28+
<div class="input">
29+
<Input
30+
name="filter"
31+
inputType="text"
32+
placeholder={$i18n.satellites.search}
33+
spellcheck={false}
34+
bind:value={filterInput}
35+
/>
5336
</div>
5437

55-
<LaunchpadNewActions />
38+
<LaunchpadLayout />
39+
40+
<AttachActions />
5641
</div>
42+
43+
<LaunchpadNewActions />
5744
</div>
5845

5946
<style lang="scss">

src/frontend/src/lib/i18n/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@
305305
"satellites": {
306306
"title": "Satellites",
307307
"launch": "Launch a new Satellite",
308+
"launch_first": "Launch your first Satellite",
308309
"create": "Create Satellite",
309310
"search": "Search",
310311
"satellite": "Satellite",

src/frontend/src/lib/i18n/zh-cn.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@
305305
"satellites": {
306306
"title": "所有卫星",
307307
"launch": "部署新卫星",
308+
"launch_first": "启动您的第一个 Satellite",
308309
"create": "创建卫星",
309310
"search": "搜索",
310311
"satellite": "卫星",

src/frontend/src/lib/types/i18n.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ interface I18nLaunchpad {
313313
interface I18nSatellites {
314314
title: string;
315315
launch: string;
316+
launch_first: string;
316317
create: string;
317318
search: string;
318319
satellite: string;

0 commit comments

Comments
 (0)