Skip to content

Commit bfd3a4c

Browse files
csy19ubugeeei
andauthored
feat: timetable (#908)
* add: image * Create timetable.ts * feat: i18n * feat: timetable component * wip feat: timetable page * feat: timetable schedule * wip table内容調整 * feat: PC timetable * feat: sp style * feat: en * Update cspell.config.yaml * fix: lint * wip feat: og * fix: 時間修正・ハンズオンタイトル修正 Keynote(Evan You) 10:11-10:50 Hands-on1 12:50-14:50(セッションタイトルもBeginner Hands-onに) Hands-on2 15:10-17:10(セッションタイトルもIntermediate Hands-onに) ランチタイム明け1つめのセッション時間、終わりを13:40→13:20 * fmt * feat: 戻るボタンを正しく戻るにする composablesに切り出してもいい? * chore: timetableのトラックをstickyに * chore: 学生支援とパネルディスカッションにスピーカーの追加 * update: timetable ja aaaaaa~~~~~~ * en * chore * feat: eventへのリンク * feat: 登壇時間と場所の追加 * Update TimetableCell.vue * chore * fix: 時間微修正 * save * feat: server side i18n * save * save * save * save * save * save * save * save * save * save * feat: server i18n * chore: remove unused --------- Co-authored-by: ubugeeei <ubuge1122@gmail.com>
1 parent 072c55b commit bfd3a4c

File tree

24 files changed

+1921
-20
lines changed

24 files changed

+1921
-20
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,5 @@ logs
2929
# Local Netlify folder
3030
.netlify
3131

32-
app/components/_i18n
32+
app/components/_i18n
33+
server/i18n/generated

app/layouts/default.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@ const menuItems = computed<MenuItemProps[]>(() =>
3737
{
3838
id: HOME_HEADING_ID.timetable,
3939
label: "Timetable",
40-
// TODO:
41-
routeName: localeRoute({ name: "index" }).name,
40+
routeName: localeRoute({ name: "timetable" }).name,
4241
disabled: !import.meta.vfFeatures.timetable,
4342
},
4443
{
@@ -103,6 +102,7 @@ const WIDE_ROUTE_NAMES: RoutesNamesList[] = [
103102
"sponsors",
104103
"sponsors-sponsorId",
105104
"event",
105+
"timetable",
106106
"related-events",
107107
"store",
108108
];
@@ -232,6 +232,7 @@ watch(() => route.hash, async (hash) => {
232232
233233
&.widen-content {
234234
min-width: 960px;
235+
/* min-width: 95%; */
235236
transition: unset;
236237
237238
@media (--mobile) {

app/pages/speaker/[speakerId]/index.vue

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ import {
1313
// eslint-disable-next-line @typescript-eslint/no-unused-vars
1414
useHead,
1515
useSeoMeta,
16+
useRouter,
1617
} from "#imports";
1718
import { VFSection } from "#components";
1819
1920
definePageMeta({ prerender: true });
2021
2122
const route = useRoute("speaker-speakerId");
23+
const router = useRouter();
2224
const { t, locale } = useI18n();
2325
const localeRoute = useLocaleRoute();
2426
@@ -57,6 +59,35 @@ defineOgImage({
5759
color: () => currentSpeaker.value?.color || "default",
5860
},
5961
});
62+
63+
const goBack = () => {
64+
if (window.history.length > 1) {
65+
router.back();
66+
} else {
67+
router.push(localeRoute({ name: "speaker" }));
68+
}
69+
};
70+
71+
// FIXME: Timetableでも使ってるのでどっかに切り出しておきたい
72+
const accentColorName = computed(() => {
73+
switch (currentSpeaker.value?.talkTrack) {
74+
case "hacomono":
75+
return "primary";
76+
case "mates":
77+
return "purple";
78+
case "feature":
79+
return "orange";
80+
case "cyberAgent":
81+
return "navy";
82+
default:
83+
return "primary";
84+
}
85+
});
86+
87+
const trackStyles = computed(() => ({
88+
"--base-color": `var(--color-${accentColorName.value}-base)`,
89+
"--sub-color": `var(--color-${accentColorName.value}-sub)`,
90+
}));
6091
</script>
6192

6293
<template>
@@ -65,16 +96,20 @@ defineOgImage({
6596
<h1>Speaker</h1>
6697

6798
<VFSection :title="currentSpeaker?.type === 'session' ? t('speakers.sessions.information') : t('speakers.lightningTalks.information')">
68-
<!-- TODO: track name -->
69-
<!-- TODO: time -->
99+
<div class="speaker-track" :style="trackStyles">
100+
{{ t(`timetable.track.${currentSpeaker.talkTrack}`) }}
101+
</div>
102+
<div class="speaker-time" :style="trackStyles">
103+
{{ currentSpeaker.talkSchedule }}
104+
</div>
70105

71106
<div class="speaker-information">
72107
<div class="speaker-avatar">
73108
<img :src="currentSpeaker.avatarUrl" :alt="currentSpeaker.name">
74109
</div>
75110
<div class="speaker-details">
76111
<h3 class="session-title">
77-
{{ currentSpeaker.talkTitle || "TBD" }}
112+
{{ currentSpeaker.talkTitle }}
78113
</h3>
79114

80115
<p v-if="currentSpeaker.talkOverview" class="session-overview">
@@ -191,7 +226,7 @@ defineOgImage({
191226
</div>
192227

193228
<div class="back-to-speakers">
194-
<VFButton outlined :link="localeRoute({ name: 'speaker' })">
229+
<VFButton outlined @click="goBack">
195230
{{ t("back") }}
196231
</VFButton>
197232
</div>
@@ -230,6 +265,40 @@ defineOgImage({
230265
}
231266
}
232267
}
268+
.speaker-track{
269+
display: grid;
270+
place-items: center;
271+
width: fit-content;
272+
height: 32px;
273+
padding: 0 8px;
274+
border-radius: 4px;
275+
background-color: var(--base-color);
276+
color: var(--sub-color);
277+
@media (--mobile) {
278+
height: 29px;
279+
font-size: 14px;
280+
}
281+
}
282+
.speaker-time{
283+
display: grid;
284+
place-items: center;
285+
width: fit-content;
286+
height: 28px;
287+
margin-top: 8px;
288+
margin-bottom: 32px;
289+
padding: 0 16px;
290+
font-family: JetBrainsMono-Medium;
291+
font-size: 14px;
292+
border: 1px solid var(--base-color);
293+
border-radius: 100px;
294+
background-color: #fff;
295+
color: var(--base-color);
296+
@media (--mobile) {
297+
height: 25px;
298+
margin-bottom: 24px;
299+
font-size: 12px;
300+
}
301+
}
233302
234303
.speaker-information {
235304
display: grid;
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
<script setup lang="ts">
2+
import { computed } from "vue";
3+
import { useI18n, useLocaleRoute } from "#imports";
4+
import type { TimetableCell } from "~~/i18n/timetable";
5+
import SliderIcon from "~icons/icons/timetable-slider.svg";
6+
7+
const {
8+
type,
9+
title,
10+
sessionStart,
11+
sessionEnd,
12+
speakers,
13+
slide,
14+
track,
15+
link,
16+
} = defineProps <TimetableCell>();
17+
const { t } = useI18n();
18+
const localeRoute = useLocaleRoute();
19+
20+
const accentColorName = computed(() => {
21+
switch (track) {
22+
case "hacomono":
23+
return "primary";
24+
case "mates":
25+
return "purple";
26+
case "feature":
27+
return "orange";
28+
case "cyberAgent":
29+
return "navy";
30+
case "blank":
31+
return "blank";
32+
default:
33+
return "primary";
34+
}
35+
});
36+
37+
const backgroundColor = computed(() => `var(--color-${accentColorName.value}-sub)`);
38+
const color = computed(() => `var(--color-${accentColorName.value}-base)`);
39+
const hoverColor = computed(() => `var(--color-${accentColorName.value}-accent-hover)`);
40+
</script>
41+
42+
<template>
43+
<div class="cell">
44+
<div class="cell-inner">
45+
<template v-if="track && track != 'blank'">
46+
<div class="track">
47+
{{ t(`timetable.track.${track}`) }}
48+
</div>
49+
</template>
50+
<div v-if="sessionStart" class="time">
51+
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
52+
{{ sessionStart }} - {{ sessionEnd }}
53+
</div>
54+
<template v-if="type === 'schedule'">
55+
<div class="schedule-title">
56+
{{ title }}
57+
</div>
58+
</template>
59+
60+
<template v-else>
61+
<div v-if="type === 'event' && link " class="title">
62+
<NuxtLink
63+
:to="{
64+
path: '/event',
65+
query: { session: link },
66+
hash: `#${link}`,
67+
}"
68+
>
69+
{{ title }}
70+
</NuxtLink>
71+
</div>
72+
<div v-else class="title">
73+
{{ title }}
74+
</div>
75+
<div v-if="speakers" class="speakers" :style="{ '--speaker-gap': type ==='lightningTalk' ? '24px' : undefined }">
76+
<template v-for="speaker in speakers" :key="speaker.id">
77+
<div>
78+
<div class="title">
79+
<NuxtLink :to="localeRoute({ name: 'speaker-speakerId', params: { speakerId: speaker.id } }) || '/speakers'">
80+
{{ speaker.talkTitle }}
81+
</NuxtLink>
82+
</div>
83+
84+
<div class="speaker-item">
85+
<div v-if="speaker.avatarUrl" class="avatar">
86+
<img :src="speaker.avatarUrl" :alt="speaker.name">
87+
</div>
88+
<p class="name">
89+
{{ speaker.name }}
90+
</p>
91+
</div>
92+
</div>
93+
</template>
94+
</div>
95+
<!-- eslint-disable-next-line vuejs-accessibility/anchor-has-content -->
96+
<a v-if="slide" :href="slide" class="slide" target="_blank">
97+
<SliderIcon :aria-label="t('timetable.slider')" role="img" />
98+
</a>
99+
</template>
100+
</div>
101+
</div>
102+
</template>
103+
104+
<style scoped>
105+
.cell {
106+
--color-blank-sub: rgba(239, 239, 239, 1);
107+
padding: 16px;
108+
border-radius: 8px;
109+
vertical-align: top;
110+
color: v-bind(color);
111+
background-color: v-bind(backgroundColor);
112+
113+
a {
114+
/* color: inherit; */
115+
&:hover {
116+
color: v-bind(hoverColor);
117+
}
118+
&:has(.name) {
119+
color: var(--color-text-default);
120+
}
121+
}
122+
}
123+
124+
.track {
125+
width: fit-content;
126+
margin-bottom: 8px;
127+
padding: 4px 8px;
128+
border-radius: 4px;
129+
color: v-bind(backgroundColor);
130+
background-color: v-bind(color);
131+
font-size: 12px;
132+
font-family: IBMPlexSansJP-Bold;
133+
134+
}
135+
136+
.time {
137+
display: grid;
138+
place-items: center;
139+
width: fit-content;
140+
height: 25px;
141+
margin-bottom: 8px;
142+
padding: 0 16px;
143+
font-family: JetBrainsMono-Medium;
144+
font-size: 12px;
145+
border: 1px solid v-bind(color);
146+
border-radius: 100px;
147+
background-color: #fff;
148+
}
149+
150+
.title {
151+
color: v-bind(color);
152+
font-family: IBMPlexSansJP-Bold;
153+
font-size: 16px;
154+
text-align: left;
155+
156+
a {
157+
font-family: IBMPlexSansJP-Bold;
158+
color: v-bind(color);
159+
cursor: pointer;
160+
161+
@media (any-hover: hover) {
162+
&:hover {
163+
color: v-bind(hoverColor);
164+
}
165+
}
166+
}
167+
168+
}
169+
.schedule-title {
170+
font-family: IBMPlexSansJP-Bold;
171+
font-size: 16px;
172+
text-align: center;
173+
color: var(--color-text-default);
174+
white-space: pre-wrap;
175+
}
176+
177+
.speakers {
178+
--speaker-gap: 16px;
179+
display: flex;
180+
flex-direction: column;
181+
gap: var(--speaker-gap) 0;
182+
margin-top: 8px;
183+
}
184+
185+
.speaker-item {
186+
display: flex;
187+
align-items: center;
188+
gap: 0 8px;
189+
margin-top: 8px;
190+
191+
.avatar {
192+
width: 48px;
193+
aspect-ratio: 1;
194+
overflow: hidden;
195+
border-radius: 8px;
196+
background-color: #fff;
197+
}
198+
199+
.name {
200+
font-size: 14px;
201+
color: var(--color-text-default);
202+
}
203+
}
204+
205+
.slide {
206+
margin-top: 8px;
207+
208+
svg {
209+
--color-base: v-bind(color);
210+
}
211+
}
212+
</style>

0 commit comments

Comments
 (0)