Skip to content

Commit 457e524

Browse files
committed
feat(client): create Checklist component
1 parent 121d242 commit 457e524

File tree

3 files changed

+134
-94
lines changed

3 files changed

+134
-94
lines changed

apps/client/components.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ declare module '@vue/runtime-core' {
99
export interface GlobalComponents {
1010
BillingFrequencyToggle: typeof import('./src/components/BillingFrequencyToggle.vue')['default']
1111
Checkbox: typeof import('./src/components/Checkbox.vue')['default']
12+
Checklist: typeof import('./src/components/Checklist.vue')['default']
1213
ColorPalette: typeof import('./src/components/ColorPalette.vue')['default']
1314
ColorSquare: typeof import('./src/components/ColorSquare.vue')['default']
1415
Combobox: typeof import('./src/components/Combobox.vue')['default']
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<template>
2+
<q-list>
3+
<slot
4+
v-if="itemIds.length === 0"
5+
name="empty"
6+
></slot>
7+
8+
<q-item
9+
v-for="(itemId, itemIndex) of itemIds"
10+
:key="itemId"
11+
clickable
12+
@click.capture="(event) => selectItem(itemId, event as any)"
13+
v-bind="props.itemProps"
14+
>
15+
<q-item-section
16+
avatar
17+
style="padding-right: 4px"
18+
>
19+
<Checkbox :model-value="finalSelectedItemIds.includes(itemId)" />
20+
</q-item-section>
21+
22+
<slot
23+
name="item"
24+
:item-id="itemId"
25+
:item-index="itemIndex"
26+
></slot>
27+
</q-item>
28+
</q-list>
29+
</template>
30+
31+
<script setup lang="ts">
32+
import type { QItemProps, QListProps } from 'quasar';
33+
34+
const emit = defineEmits(['select', 'unselect']);
35+
36+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
37+
interface Props extends QListProps {
38+
itemIds: string[];
39+
40+
itemProps?: QItemProps;
41+
}
42+
43+
const props = defineProps<Props>();
44+
45+
let lastSelectedItemId: string;
46+
47+
const baseSelectedItemIds = ref(new Set<string>());
48+
49+
const finalSelectedItemIds = computed(() =>
50+
props.itemIds.filter((itemId) => baseSelectedItemIds.value.has(itemId)),
51+
);
52+
53+
function selectItem(itemId: string, event: MouseEvent) {
54+
if (event.shiftKey || internals.mobileAltKey) {
55+
const sourceItemIndex = props.itemIds.indexOf(lastSelectedItemId);
56+
const targetItemIndex = props.itemIds.indexOf(itemId);
57+
58+
if (
59+
sourceItemIndex >= 0 &&
60+
targetItemIndex >= 0 &&
61+
sourceItemIndex !== targetItemIndex
62+
) {
63+
const sign = Math.sign(targetItemIndex - sourceItemIndex);
64+
65+
const add = !baseSelectedItemIds.value.has(itemId);
66+
67+
for (let i = sourceItemIndex; i !== targetItemIndex + sign; i += sign) {
68+
if (add) {
69+
baseSelectedItemIds.value.add(props.itemIds[i]);
70+
emit('select', props.itemIds[i]);
71+
} else {
72+
baseSelectedItemIds.value.delete(props.itemIds[i]);
73+
emit('unselect', props.itemIds[i]);
74+
}
75+
}
76+
}
77+
} else {
78+
if (baseSelectedItemIds.value.has(itemId)) {
79+
baseSelectedItemIds.value.delete(itemId);
80+
emit('unselect', itemId);
81+
} else {
82+
baseSelectedItemIds.value.add(itemId);
83+
emit('select', itemId);
84+
}
85+
}
86+
87+
lastSelectedItemId = itemId;
88+
}
89+
</script>

apps/client/src/layouts/PagesLayout/RightSidebar/PageProperties/VersionHistory.vue

Lines changed: 44 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -4,67 +4,58 @@
44

55
<Gap style="height: 8px" />
66

7-
<q-list
7+
<Checklist
88
style="
99
border-radius: 6px;
1010
height: 220px;
1111
background-color: #383838;
1212
overflow: auto;
1313
"
14+
:item-ids="snapshotInfos.map((snapshotInfo) => snapshotInfo.id)"
15+
@select="(snapshotId) => baseSelectedSnapshotIds.add(snapshotId)"
16+
@unselect="(snapshotId) => baseSelectedSnapshotIds.delete(snapshotId)"
1417
>
15-
<q-item
16-
v-if="snapshotInfos.length === 0"
17-
style="color: #b0b0b0"
18-
>
19-
<q-item-section>
20-
<q-item-label>No versions available</q-item-label>
21-
</q-item-section>
22-
</q-item>
23-
24-
<q-item
25-
v-for="snapshotInfo of snapshotInfos"
26-
:key="snapshotInfo.id"
27-
clickable
28-
@click.capture="
29-
(event) => selectSnapshot(snapshotInfo.id, event as any)
30-
"
31-
>
32-
<q-item-section
33-
avatar
34-
style="padding-right: 4px"
18+
<template #empty>
19+
<q-item style="color: #b0b0b0">
20+
<q-item-section>
21+
<q-item-label>No versions available</q-item-label>
22+
</q-item-section>
23+
</q-item>
24+
</template>
25+
26+
<template #item="{ itemIndex: snapshotIndex }">
27+
<template
28+
v-for="snapshotInfo in [snapshotInfos[snapshotIndex]]"
29+
:key="snapshotInfo.id"
3530
>
36-
<Checkbox
37-
:model-value="finalSelectedSnapshotIds.includes(snapshotInfo.id)"
38-
/>
39-
</q-item-section>
40-
41-
<q-item-section>
42-
<q-item-label>{{
43-
Intl.DateTimeFormat('en', {
44-
dateStyle: 'medium',
45-
timeStyle: 'short',
46-
}).format(snapshotInfo.creationDate)
47-
}}</q-item-label>
48-
49-
<q-item-label
50-
caption
51-
style="display: flex"
52-
>
53-
<div style="flex: 1">
54-
{{
55-
groupMemberNames()(
56-
`${page.react.groupId}:${snapshotInfo.authorId}`,
57-
).get().text
58-
}}
59-
</div>
60-
61-
<Gap style="width: 20px" />
62-
63-
<div>{{ capitalize(snapshotInfo.type) }}</div>
64-
</q-item-label>
65-
</q-item-section>
66-
</q-item>
67-
</q-list>
31+
<q-item-section>
32+
<q-item-label>{{
33+
Intl.DateTimeFormat('en', {
34+
dateStyle: 'medium',
35+
timeStyle: 'short',
36+
}).format(snapshotInfo.creationDate)
37+
}}</q-item-label>
38+
39+
<q-item-label
40+
caption
41+
style="display: flex"
42+
>
43+
<div style="flex: 1">
44+
{{
45+
groupMemberNames()(
46+
`${page.react.groupId}:${snapshotInfo.authorId}`,
47+
).get().text
48+
}}
49+
</div>
50+
51+
<Gap style="width: 20px" />
52+
53+
<div>{{ capitalize(snapshotInfo.type) }}</div>
54+
</q-item-label>
55+
</q-item-section>
56+
</template>
57+
</template>
58+
</Checklist>
6859

6960
<Gap style="height: 16px" />
7061

@@ -121,8 +112,6 @@ const snapshotInfos = computed(
121112
page.value.realtimeCtx.hget('page-snapshots', page.value.id, 'infos') ?? [],
122113
);
123114
124-
let lastSelectedSnapshotId: string;
125-
126115
const baseSelectedSnapshotIds = ref(new Set<string>());
127116
128117
const finalSelectedSnapshotIds = computed(() =>
@@ -133,45 +122,6 @@ const finalSelectedSnapshotIds = computed(() =>
133122
.map((snapshotInfo) => snapshotInfo.id),
134123
);
135124
136-
function selectSnapshot(snapshotId: string, event: MouseEvent) {
137-
if (event.shiftKey || internals.mobileAltKey) {
138-
const snapshotIds = snapshotInfos.value.map((snapshot) => snapshot.id);
139-
140-
const sourceSnapshotIndex = snapshotIds.indexOf(lastSelectedSnapshotId);
141-
const targetSnapshotIndex = snapshotIds.indexOf(snapshotId);
142-
143-
if (
144-
sourceSnapshotIndex >= 0 &&
145-
targetSnapshotIndex >= 0 &&
146-
sourceSnapshotIndex !== targetSnapshotIndex
147-
) {
148-
const sign = Math.sign(targetSnapshotIndex - sourceSnapshotIndex);
149-
150-
const add = !baseSelectedSnapshotIds.value.has(snapshotId);
151-
152-
for (
153-
let i = sourceSnapshotIndex;
154-
i !== targetSnapshotIndex + sign;
155-
i += sign
156-
) {
157-
if (add) {
158-
baseSelectedSnapshotIds.value.add(snapshotIds[i]);
159-
} else {
160-
baseSelectedSnapshotIds.value.delete(snapshotIds[i]);
161-
}
162-
}
163-
}
164-
} else {
165-
if (baseSelectedSnapshotIds.value.has(snapshotId)) {
166-
baseSelectedSnapshotIds.value.delete(snapshotId);
167-
} else {
168-
baseSelectedSnapshotIds.value.add(snapshotId);
169-
}
170-
}
171-
172-
lastSelectedSnapshotId = snapshotId;
173-
}
174-
175125
async function restoreSnapshot(snapshotId: string) {
176126
try {
177127
await asyncDialog({

0 commit comments

Comments
 (0)