Skip to content

Commit 121d242

Browse files
committed
feat(client): improve page versions
1 parent eeebf32 commit 121d242

File tree

1 file changed

+141
-76
lines changed

1 file changed

+141
-76
lines changed

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

Lines changed: 141 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -21,63 +21,84 @@
2121
</q-item-section>
2222
</q-item>
2323

24-
<div
24+
<q-item
2525
v-for="snapshotInfo of snapshotInfos"
2626
:key="snapshotInfo.id"
27-
class="snapshot"
27+
clickable
28+
@click.capture="
29+
(event) => selectSnapshot(snapshotInfo.id, event as any)
30+
"
2831
>
29-
<q-item
30-
clickable
31-
@click="restoreVersion(snapshotInfo.id)"
32+
<q-item-section
33+
avatar
34+
style="padding-right: 4px"
3235
>
33-
<q-item-section>
34-
<q-item-label>{{
35-
Intl.DateTimeFormat('en', {
36-
dateStyle: 'medium',
37-
timeStyle: 'short',
38-
}).format(snapshotInfo.creationDate)
39-
}}</q-item-label>
40-
41-
<q-item-label
42-
caption
43-
style="display: flex"
44-
>
45-
<div style="flex: 1">
46-
{{
47-
groupMemberNames()(
48-
`${page.react.groupId}:${snapshotInfo.authorId}`,
49-
).get().text
50-
}}
51-
</div>
52-
53-
<Gap style="width: 20px" />
54-
55-
<div>{{ capitalize(snapshotInfo.type) }}</div>
56-
</q-item-label>
57-
</q-item-section>
58-
</q-item>
59-
60-
<DeepBtn
61-
class="delete-btn"
62-
size="12px"
63-
icon="mdi-close"
64-
round
65-
flat
66-
title="Delete page version"
67-
@click="deleteSnapshot(snapshotInfo.id)"
68-
/>
69-
</div>
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>
7067
</q-list>
7168

7269
<Gap style="height: 16px" />
7370

71+
<DeepBtn
72+
label="Restore selected version"
73+
icon="mdi-restore"
74+
color="secondary"
75+
:disable="page.react.readOnly || finalSelectedSnapshotIds.length !== 1"
76+
title="Backup the current version of the page manually"
77+
@click="() => restoreSnapshot(finalSelectedSnapshotIds[0])"
78+
/>
79+
80+
<Gap style="height: 12px" />
81+
82+
<DeepBtn
83+
:label="`Delete selected version${
84+
finalSelectedSnapshotIds.length > 1 ? 's' : ''
85+
}`"
86+
icon="mdi-trash-can"
87+
color="negative"
88+
:disable="page.react.readOnly || finalSelectedSnapshotIds.length === 0"
89+
title="Backup the current version of the page manually"
90+
@click="deleteSelectedSnapshots"
91+
/>
92+
93+
<Gap style="height: 12px" />
94+
7495
<DeepBtn
7596
label="Save current version"
7697
icon="mdi-content-save"
7798
color="secondary"
7899
:disable="page.react.readOnly"
79100
title="Backup the current version of the page manually"
80-
@click="saveVersion"
101+
@click="saveCurrentSnapshot"
81102
/>
82103
</div>
83104
</template>
@@ -100,7 +121,58 @@ const snapshotInfos = computed(
100121
page.value.realtimeCtx.hget('page-snapshots', page.value.id, 'infos') ?? [],
101122
);
102123
103-
async function restoreVersion(snapshotId: string) {
124+
let lastSelectedSnapshotId: string;
125+
126+
const baseSelectedSnapshotIds = ref(new Set<string>());
127+
128+
const finalSelectedSnapshotIds = computed(() =>
129+
snapshotInfos.value
130+
.filter((snapshotInfo) =>
131+
baseSelectedSnapshotIds.value.has(snapshotInfo.id),
132+
)
133+
.map((snapshotInfo) => snapshotInfo.id),
134+
);
135+
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+
175+
async function restoreSnapshot(snapshotId: string) {
104176
try {
105177
await asyncDialog({
106178
title: 'Restore version',
@@ -128,7 +200,7 @@ async function restoreVersion(snapshotId: string) {
128200
}
129201
}
130202
131-
async function saveVersion() {
203+
async function saveCurrentSnapshot() {
132204
try {
133205
await asyncDialog({
134206
title: 'Save version',
@@ -156,50 +228,43 @@ async function saveVersion() {
156228
}
157229
158230
async function deleteSnapshot(snapshotId: string) {
159-
try {
160-
await asyncDialog({
161-
title: 'Delete version',
162-
message: 'Are you sure you want to delete this version?',
231+
await asyncDialog({
232+
title: 'Delete version',
233+
message: 'Are you sure you want to delete this version?',
163234
164-
focus: 'cancel',
235+
focus: 'cancel',
165236
166-
cancel: { label: 'No', flat: true, color: 'primary' },
167-
ok: { label: 'Yes', flat: true, color: 'negative' },
168-
});
237+
cancel: { label: 'No', flat: true, color: 'primary' },
238+
ok: { label: 'Yes', flat: true, color: 'negative' },
239+
});
169240
170-
await deletePageSnapshot(page.value.id, snapshotId);
241+
await deletePageSnapshot(page.value.id, snapshotId);
242+
}
171243
172-
$quasar().notify({
173-
message: 'Version deleted successfully.',
174-
color: 'positive',
175-
});
244+
async function deleteSelectedSnapshots() {
245+
try {
246+
for (const selectedSnapshotId of finalSelectedSnapshotIds.value) {
247+
await deleteSnapshot(selectedSnapshotId);
248+
}
176249
} catch (error: any) {
177250
handleError(error);
178251
}
179252
}
180253
</script>
181254

182255
<style scoped lang="scss">
183-
.snapshot {
184-
position: relative;
185-
186-
> .delete-btn {
187-
position: absolute;
188-
189-
top: 4px;
190-
right: 10px;
256+
.delete-btn {
257+
position: absolute;
191258
192-
opacity: 0;
193-
transition: opacity 0.2s;
259+
top: 4px;
260+
right: 10px;
194261
195-
min-width: 24px;
196-
min-height: 24px;
197-
width: 24px;
198-
height: 24px;
199-
}
200-
}
262+
opacity: 0;
263+
transition: opacity 0.2s;
201264
202-
.snapshot:hover > .delete-btn {
203-
opacity: 1;
265+
min-width: 24px;
266+
min-height: 24px;
267+
width: 24px;
268+
height: 24px;
204269
}
205270
</style>

0 commit comments

Comments
 (0)