Skip to content

Commit 37cc2b4

Browse files
authored
Merge pull request #2084 from AtCoder-NoviSteps/#2083
♻️ Extract common component (#2083)
2 parents ee9e12e + 5ccbbc9 commit 37cc2b4

File tree

3 files changed

+151
-175
lines changed

3 files changed

+151
-175
lines changed
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<script lang="ts">
2+
import { Breadcrumb, BreadcrumbItem } from 'svelte-5-ui-lib';
3+
4+
import HeadingOne from '$lib/components/HeadingOne.svelte';
5+
import WorkBookInputFields from '$lib/components/WorkBooks/WorkBookInputFields.svelte';
6+
import WorkBookTasksTable from '$lib/components/WorkBookTasks/WorkBookTasksTable.svelte';
7+
import TaskSearchBox from '$lib/components/TaskSearchBox.svelte';
8+
import InputFieldWrapper from '$lib/components/InputFieldWrapper.svelte';
9+
import SubmissionButton from '$lib/components/SubmissionButton.svelte';
10+
11+
import { preventEnterKey } from '$lib/actions/prevent_enter_key';
12+
13+
import type { Task, Tasks } from '$lib/types/task';
14+
import type {
15+
WorkBookTaskCreate,
16+
WorkBookTasksCreate,
17+
WorkBookTaskEdit,
18+
WorkBookTasksEdit,
19+
} from '$lib/types/workbook';
20+
21+
interface Props {
22+
pageTitle: string;
23+
breadcrumbTitle: string;
24+
isAdmin: boolean;
25+
// type is any, so we have no choice but to use it.
26+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
27+
superFormObject: any; // superForm object
28+
tasksMapByIds: Map<string, Task>;
29+
submitButtonLabel?: string;
30+
}
31+
32+
let {
33+
pageTitle,
34+
breadcrumbTitle,
35+
isAdmin,
36+
superFormObject,
37+
tasksMapByIds,
38+
submitButtonLabel = '',
39+
}: Props = $props();
40+
41+
const { form, message, errors, enhance } = superFormObject;
42+
43+
let workBookTasksForTable: WorkBookTasksCreate | WorkBookTasksEdit = $derived(
44+
$form.workBookTasks
45+
.map((workBookTask: WorkBookTaskCreate | WorkBookTaskEdit) => {
46+
const task = tasksMapByIds.get(workBookTask.taskId);
47+
48+
if (!task) {
49+
return null;
50+
}
51+
52+
return {
53+
contestId: task.contest_id,
54+
title: task.title,
55+
taskId: workBookTask.taskId,
56+
priority: workBookTask.priority,
57+
comment: workBookTask.comment,
58+
};
59+
})
60+
.filter(
61+
(item: WorkBookTaskCreate | WorkBookTaskEdit): item is NonNullable<typeof item> =>
62+
item !== null,
63+
),
64+
);
65+
66+
const truncateClass = 'min-w-[96px] max-w-[120px] sm:max-w-[300px] lg:max-w-[600px] truncate';
67+
const tasks: Tasks = $derived(Array.from(tasksMapByIds.values()));
68+
</script>
69+
70+
<div class="container mx-auto w-5/6">
71+
<form method="post" use:enhance use:preventEnterKey class="space-y-4">
72+
<HeadingOne title={pageTitle} />
73+
74+
<Breadcrumb aria-label="">
75+
<BreadcrumbItem href="/workbooks" home>問題集</BreadcrumbItem>
76+
77+
<BreadcrumbItem>
78+
<div class={truncateClass}>{breadcrumbTitle}</div>
79+
</BreadcrumbItem>
80+
</Breadcrumb>
81+
82+
<!-- Form for workbook -->
83+
<WorkBookInputFields
84+
bind:authorId={$form.authorId}
85+
bind:workBookTitle={$form.title}
86+
bind:description={$form.description}
87+
bind:editorialUrl={$form.editorialUrl}
88+
bind:isPublished={$form.isPublished}
89+
bind:isOfficial={$form.isOfficial}
90+
bind:isReplenished={$form.isReplenished}
91+
bind:workBookType={$form.workBookType}
92+
{isAdmin}
93+
message={$message}
94+
errors={$errors}
95+
/>
96+
97+
<!-- Search tasks -->
98+
<!-- HACK:
99+
Because the attributes are slightly different, we have no choice
100+
but to separate the data for storing in the database and for creating and editing workbooks.
101+
-->
102+
<div class="space-y-2">
103+
<TaskSearchBox {tasks} bind:workBookTasks={$form.workBookTasks} bind:workBookTasksForTable />
104+
<InputFieldWrapper
105+
inputFieldType="hidden"
106+
inputFieldName="workBookTasks"
107+
inputValue={$form.workBookTasks}
108+
message={$errors.workBookTasks?._errors}
109+
/>
110+
</div>
111+
112+
<!-- Show tasks stored in database and added by search -->
113+
<WorkBookTasksTable
114+
{tasksMapByIds}
115+
bind:workBookTasks={$form.workBookTasks}
116+
bind:workBookTasksForTable
117+
/>
118+
119+
<!-- Create or update button -->
120+
<div class="flex flex-wrap md:justify-center md:items-center">
121+
<SubmissionButton width="w-full md:max-w-md mt-4" labelName={submitButtonLabel} />
122+
</div>
123+
</form>
124+
</div>
Lines changed: 13 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,10 @@
11
<script lang="ts">
2-
import { Breadcrumb, BreadcrumbItem } from 'svelte-5-ui-lib';
32
import { superForm } from 'sveltekit-superforms/client';
43
5-
import {
6-
WorkBookType,
7-
type WorkBookTasksBase,
8-
type WorkBookTaskCreate,
9-
} from '$lib/types/workbook';
10-
import type { Task } from '$lib/types/task';
11-
12-
import { preventEnterKey } from '$lib/actions/prevent_enter_key';
4+
import WorkBookForm from '$lib/components/WorkBook/WorkBookForm.svelte';
135
14-
import HeadingOne from '$lib/components/HeadingOne.svelte';
15-
import WorkBookInputFields from '$lib/components/WorkBooks/WorkBookInputFields.svelte';
16-
import WorkBookTasksTable from '$lib/components/WorkBookTasks/WorkBookTasksTable.svelte';
17-
import TaskSearchBox from '$lib/components/TaskSearchBox.svelte';
18-
import InputFieldWrapper from '$lib/components/InputFieldWrapper.svelte';
19-
import SubmissionButton from '$lib/components/SubmissionButton.svelte';
6+
import { WorkBookType, type WorkBookTasksBase } from '$lib/types/workbook';
7+
import type { Task } from '$lib/types/task';
208
219
let { data } = $props();
2210
@@ -26,75 +14,23 @@
2614
...data.form,
2715
workBookTasks: [] as WorkBookTasksBase,
2816
};
29-
const { form, message, errors, enhance } = superForm(initialData, {
17+
const superFormObject = superForm(initialData, {
3018
dataType: 'json',
3119
});
20+
const { form } = superFormObject;
3221
3322
$form.authorId = data.author.id;
3423
$form.isOfficial = data.isAdmin;
3524
$form.workBookType = $form.isOfficial ? WorkBookType.CURRICULUM : WorkBookType.CREATED_BY_USER;
3625
37-
let workBookTasksForTable: WorkBookTaskCreate[] = $state([]);
38-
39-
$effect((): void => {
40-
workBookTasksForTable = [] as WorkBookTaskCreate[];
41-
});
42-
4326
const tasksMapByIds: Map<string, Task> = data.tasksMapByIds;
4427
</script>
4528

46-
<!-- TODO: 問題集の編集ページのコンポーネントとほぼ共通しているのでリファクタリング -->
47-
<div class="container mx-auto w-5/6">
48-
<form method="post" use:enhance use:preventEnterKey class="space-y-4">
49-
<HeadingOne title="問題集を作成" />
50-
51-
<Breadcrumb aria-label="">
52-
<BreadcrumbItem href="/workbooks" home>問題集</BreadcrumbItem>
53-
<BreadcrumbItem>
54-
<div class="min-w-[96px] max-w-[120px] truncate">問題集を作成</div>
55-
</BreadcrumbItem>
56-
</Breadcrumb>
57-
58-
<WorkBookInputFields
59-
bind:authorId={$form.authorId}
60-
bind:workBookTitle={$form.title}
61-
bind:description={$form.description}
62-
bind:editorialUrl={$form.editorialUrl}
63-
bind:isPublished={$form.isPublished}
64-
bind:isOfficial={$form.isOfficial}
65-
bind:isReplenished={$form.isReplenished}
66-
bind:workBookType={$form.workBookType}
67-
isAdmin={data.isAdmin}
68-
message={$message}
69-
errors={$errors}
70-
/>
71-
72-
<!-- 問題を検索 -->
73-
<!-- HACK: 属性が微妙に異なるため、やむなくデータベースへの保存用と問題集作成・編集用で分けている。 -->
74-
<div class="space-y-2">
75-
<TaskSearchBox
76-
tasks={Array.from(tasksMapByIds.values())}
77-
bind:workBookTasks={$form.workBookTasks}
78-
bind:workBookTasksForTable
79-
/>
80-
<InputFieldWrapper
81-
inputFieldType="hidden"
82-
inputFieldName="workBookTasks"
83-
inputValue={$form.workBookTasks}
84-
message={$errors.workBookTasks?._errors}
85-
/>
86-
</div>
87-
88-
<!-- 問題一覧 -->
89-
<WorkBookTasksTable
90-
{tasksMapByIds}
91-
bind:workBookTasks={$form.workBookTasks}
92-
bind:workBookTasksForTable
93-
/>
94-
95-
<!-- 作成ボタンを追加 -->
96-
<div class="flex flex-wrap md:justify-center md:items-center">
97-
<SubmissionButton width="w-full md:max-w-md mt-4" labelName="作成" />
98-
</div>
99-
</form>
100-
</div>
29+
<WorkBookForm
30+
pageTitle="問題集を作成"
31+
breadcrumbTitle={'問題集を作成'}
32+
isAdmin={data.isAdmin}
33+
{superFormObject}
34+
{tasksMapByIds}
35+
submitButtonLabel="作成"
36+
/>

src/routes/workbooks/edit/[slug]/+page.svelte

Lines changed: 14 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,14 @@
11
<script lang="ts">
2-
import { run } from 'svelte/legacy';
3-
42
import { superForm } from 'sveltekit-superforms/client';
5-
import { Breadcrumb, BreadcrumbItem } from 'svelte-5-ui-lib';
63
7-
import type { WorkBookTasksBase, WorkBookTasksEdit } from '$lib/types/workbook';
8-
import type { Task, Tasks } from '$lib/types/task.js';
4+
import WorkBookForm from '$lib/components/WorkBook/WorkBookForm.svelte';
5+
6+
import type { WorkBookTasksBase } from '$lib/types/workbook';
7+
import type { Task } from '$lib/types/task.js';
98
10-
import { preventEnterKey } from '$lib/actions/prevent_enter_key';
11-
import HeadingOne from '$lib/components/HeadingOne.svelte';
12-
import WorkBookInputFields from '$lib/components/WorkBooks/WorkBookInputFields.svelte';
13-
import WorkBookTasksTable from '$lib/components/WorkBookTasks/WorkBookTasksTable.svelte';
14-
import TaskSearchBox from '$lib/components/TaskSearchBox.svelte';
15-
import InputFieldWrapper from '$lib/components/InputFieldWrapper.svelte';
16-
import SubmissionButton from '$lib/components/SubmissionButton.svelte';
179
import { FORBIDDEN } from '$lib/constants/http-response-status-codes.js';
1810
1911
let { data } = $props();
20-
2112
let canView = $derived(data.status === FORBIDDEN ? false : true);
2213
2314
let workBook = data.workBook;
@@ -28,98 +19,23 @@
2819
...data.form,
2920
workBookTasks: workBook.workBookTasks as WorkBookTasksBase,
3021
};
31-
const { form, message, errors, enhance } = superForm(initialData, {
22+
const superFormObject = superForm(initialData, {
3223
dataType: 'json',
3324
});
3425
35-
const tasks: Tasks = data.tasks;
36-
37-
// データベースに基づいて、問題集の編集用データを作成
26+
// Create data of workbook for edit based on database.
3827
const tasksMapByIds: Map<string, Task> = data.tasksMapByIds;
39-
40-
let workBookTasksForTable: WorkBookTasksEdit = $state([]);
41-
42-
// HACK: $effect だと workBookTasksForTable が更新されない
43-
run(() => {
44-
workBookTasksForTable = $form.workBookTasks
45-
.map((workBookTask) => {
46-
const task = tasksMapByIds.get(workBookTask.taskId);
47-
48-
if (!task) {
49-
return null;
50-
}
51-
52-
return {
53-
contestId: task.contest_id,
54-
title: task.title,
55-
taskId: workBookTask.taskId,
56-
priority: workBookTask.priority,
57-
comment: workBookTask.comment,
58-
};
59-
})
60-
.filter((item): item is NonNullable<typeof item> => item !== null);
61-
});
6228
</script>
6329

6430
{#if canView}
65-
<!-- TODO: 問題集の作成ページのコンポーネントとほぼ共通しているのでリファクタリング -->
66-
<div class="container mx-auto w-5/6">
67-
<form method="post" use:enhance use:preventEnterKey class="space-y-4">
68-
<HeadingOne title="問題集を編集" />
69-
70-
<!-- TODO: コンポーネントとして切り出す -->
71-
<Breadcrumb aria-label="">
72-
<BreadcrumbItem href="/workbooks" home>問題集</BreadcrumbItem>
73-
<BreadcrumbItem>
74-
<div class="min-w-[96px] max-w-[120px] sm:max-w-[300px] lg:max-w-[600px] truncate">
75-
{workBook.title}
76-
</div>
77-
</BreadcrumbItem>
78-
</Breadcrumb>
79-
80-
<WorkBookInputFields
81-
bind:authorId={$form.authorId}
82-
bind:workBookTitle={$form.title}
83-
bind:description={$form.description}
84-
bind:editorialUrl={$form.editorialUrl}
85-
bind:isPublished={$form.isPublished}
86-
bind:isOfficial={$form.isOfficial}
87-
bind:isReplenished={$form.isReplenished}
88-
bind:workBookType={$form.workBookType}
89-
isAdmin={data.loggedInAsAdmin}
90-
message={$message}
91-
errors={$errors}
92-
/>
93-
94-
<!-- 問題を検索 -->
95-
<!-- HACK: 属性が微妙に異なるため、やむなくデータベースへの保存用と問題集作成・編集用で分けている。 -->
96-
<div class="space-y-2">
97-
<TaskSearchBox
98-
{tasks}
99-
bind:workBookTasks={$form.workBookTasks}
100-
bind:workBookTasksForTable
101-
/>
102-
<InputFieldWrapper
103-
inputFieldType="hidden"
104-
inputFieldName="workBookTasks"
105-
inputValue={$form.workBookTasks}
106-
message={$errors.workBookTasks?._errors}
107-
/>
108-
</div>
109-
110-
<!-- データベースに保存されている問題 + 検索で追加した問題を表示 -->
111-
<WorkBookTasksTable
112-
{tasksMapByIds}
113-
bind:workBookTasks={$form.workBookTasks}
114-
bind:workBookTasksForTable
115-
/>
116-
117-
<!-- 更新ボタン -->
118-
<div class="flex flex-wrap md:justify-center md:items-center">
119-
<SubmissionButton width="w-full md:max-w-md mt-4" labelName="更新" />
120-
</div>
121-
</form>
122-
</div>
31+
<WorkBookForm
32+
pageTitle="問題集を編集"
33+
breadcrumbTitle={workBook.title}
34+
isAdmin={data.loggedInAsAdmin}
35+
{superFormObject}
36+
{tasksMapByIds}
37+
submitButtonLabel="更新"
38+
/>
12339
{:else}
12440
<!-- TODO: コンポーネントとして抽出 -->
12541
<h1>{data.status}</h1>

0 commit comments

Comments
 (0)