Select with head (fix #27)

pull/62/head
Varun Patil 2022-09-15 10:49:51 -07:00
parent f522f7c87b
commit e058316f66
2 changed files with 119 additions and 33 deletions

View File

@ -14,10 +14,20 @@
@update="scrollChange" @update="scrollChange"
@resize="handleResizeWithDelay" @resize="handleResizeWithDelay"
> >
<h1 v-if="item.type === 0" class="head-row" <div v-if="item.type === 0" class="head-row"
:class="{ 'first': item.id === 1 }"> :class="{
{{ item.name || getHeadName(item) }} 'first': item.id === 1,
</h1> 'selected': item.selected,
}"
>
<div class="icon-checkmark select"
@click="selectHead(item)">
</div>
<span class="name"
@click="selectHead(item)">
{{ item.name || getHeadName(item) }}
</span>
</div>
<div v-else <div v-else
class="photo-row" class="photo-row"
@ -98,7 +108,7 @@
<script lang="ts"> <script lang="ts">
import { Component, Watch, Mixins } from 'vue-property-decorator'; import { Component, Watch, Mixins } from 'vue-property-decorator';
import { IDay, IPhoto, IRow, IRowType, ITick } from "../types"; import { IDay, IHeadRow, IPhoto, IRow, IRowType, ITick } from "../types";
import { NcActions, NcActionButton, NcButton } from '@nextcloud/vue'; import { NcActions, NcActionButton, NcButton } from '@nextcloud/vue';
import { generateUrl } from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
import GlobalMixin from '../mixins/GlobalMixin'; import GlobalMixin from '../mixins/GlobalMixin';
@ -144,7 +154,7 @@ export default class Timeline extends Mixins(GlobalMixin) {
/** Computed number of columns */ /** Computed number of columns */
private numCols = 5; private numCols = 5;
/** Header rows for dayId key */ /** Header rows for dayId key */
private heads: { [dayid: number]: IRow } = {}; private heads: { [dayid: number]: IHeadRow } = {};
/** Original days response */ /** Original days response */
private days: IDay[] = []; private days: IDay[] = [];
@ -440,8 +450,8 @@ export default class Timeline extends Mixins(GlobalMixin) {
/** Process the data for days call including folders */ /** Process the data for days call including folders */
async processDays(data: IDay[]) { async processDays(data: IDay[]) {
const list: IRow[] = []; const list: typeof this.list = [];
const heads: {[dayId: number]: IRow} = {}; const heads: typeof this.heads = {};
// Store the preloads in a separate map. // Store the preloads in a separate map.
// This is required since otherwise the inner detail objects // This is required since otherwise the inner detail objects
@ -472,10 +482,11 @@ export default class Timeline extends Mixins(GlobalMixin) {
} }
// Add header to list // Add header to list
const head = { const head: IHeadRow = {
id: ++this.numRows, id: ++this.numRows,
size: 40, size: 40,
type: IRowType.HEAD, type: IRowType.HEAD,
selected: false,
dayId: day.dayid, dayId: day.dayid,
day: day, day: day,
}; };
@ -840,8 +851,8 @@ export default class Timeline extends Mixins(GlobalMixin) {
} }
/** Add a photo to selection list */ /** Add a photo to selection list */
selectPhoto(photo: IPhoto) { selectPhoto(photo: IPhoto, val?: boolean, noUpdate?: boolean) {
const nval = !this.selection.has(photo); const nval = val ?? !this.selection.has(photo);
if (nval) { if (nval) {
photo.flag |= this.c.FLAG_SELECTED; photo.flag |= this.c.FLAG_SELECTED;
this.selection.add(photo); this.selection.add(photo);
@ -849,23 +860,60 @@ export default class Timeline extends Mixins(GlobalMixin) {
photo.flag &= ~this.c.FLAG_SELECTED; photo.flag &= ~this.c.FLAG_SELECTED;
this.selection.delete(photo); this.selection.delete(photo);
} }
this.$forceUpdate();
if (!noUpdate) {
this.updateHeadSelected(this.heads[photo.d.dayid]);
this.$forceUpdate();
}
} }
/** Clear all selected photos */ /** Clear all selected photos */
clearSelection() { clearSelection() {
const heads = new Set<IHeadRow>();
for (const photo of this.selection) { for (const photo of this.selection) {
photo.flag &= ~this.c.FLAG_SELECTED; photo.flag &= ~this.c.FLAG_SELECTED;
heads.add(this.heads[photo.d.dayid]);
} }
this.selection.clear(); this.selection.clear();
heads.forEach(this.updateHeadSelected);
this.$forceUpdate(); this.$forceUpdate();
} }
/** Select or deselect all photos in a head */
selectHead(head: IHeadRow) {
head.selected = !head.selected;
for (const row of head.day.rows) {
for (const photo of row.photos) {
this.selectPhoto(photo, head.selected, true);
}
}
this.$forceUpdate();
}
/** Check if the day for a photo is selected entirely */
updateHeadSelected(head: IHeadRow) {
let selected = true;
// Check if all photos are selected
for (const row of head.day.rows) {
for (const photo of row.photos) {
if (!(photo.flag & this.c.FLAG_SELECTED)) {
selected = false;
break;
}
}
}
// Update head
head.selected = selected;
}
/** /**
* Download the currently selected files * Download the currently selected files
*/ */
async downloadSelection() { async downloadSelection() {
if (this.selection.size >= 5) { if (this.selection.size >= 100) {
if (!confirm(this.t("memories", "You are about to download a large number of files. Are you sure?"))) { if (!confirm(this.t("memories", "You are about to download a large number of files. Are you sure?"))) {
return; return;
} }
@ -989,27 +1037,59 @@ export default class Timeline extends Mixins(GlobalMixin) {
.recycler { .recycler {
height: 300px; height: 300px;
width: calc(100% + 20px); width: calc(100% + 20px);
}
.photo-row .photo { .photo-row > .photo {
display: inline-block; display: inline-block;
position: relative; position: relative;
cursor: pointer; cursor: pointer;
vertical-align: top; vertical-align: top;
}
.head-row {
height: 40px;
padding-top: 10px;
padding-left: 3px;
font-size: 0.9em;
font-weight: 600;
@include phone {
&.first {
padding-left: 38px;
padding-top: 12px;
}
} }
.head-row { > .select {
height: 40px; position: absolute;
padding-top: 10px; left: 5px; top: 50%;
padding-left: 3px; display: none;
font-size: 0.9em; opacity: 0;
font-weight: 600; transform: translateY(-30%);
transition: opacity 0.2s ease;
background-color: var(--color-background-darker);
border-radius: 50%;
height: 12px; width: 12px;
background-size: 70%;
padding: 5px;
cursor: pointer;
}
> .name {
transition: margin 0.2s ease;
cursor: pointer;
}
@include phone { .hover &, &.selected {
&.first { > .select {
padding-left: 38px; display: inline-block;
padding-top: 12px; opacity: 1;
}
} }
> .name {
margin-left: 25px;
}
}
&.selected > .select {
filter: invert(1);
} }
} }
@ -1028,7 +1108,7 @@ export default class Timeline extends Mixins(GlobalMixin) {
opacity: 1; opacity: 1;
} }
.tick { > .tick {
pointer-events: none; pointer-events: none;
position: absolute; position: absolute;
font-size: 0.75em; font-size: 0.75em;
@ -1055,7 +1135,7 @@ export default class Timeline extends Mixins(GlobalMixin) {
} }
} }
.cursor { > .cursor {
position: absolute; position: absolute;
pointer-events: none; pointer-events: none;
right: 0; right: 0;
@ -1080,7 +1160,7 @@ export default class Timeline extends Mixins(GlobalMixin) {
font-weight: 600; font-weight: 600;
} }
} }
&:hover .cursor.st { &:hover > .cursor.st {
opacity: 1; opacity: 1;
} }
} }
@ -1099,7 +1179,7 @@ export default class Timeline extends Mixins(GlobalMixin) {
display: flex; display: flex;
vertical-align: middle; vertical-align: middle;
.text { > .text {
flex-grow: 1; flex-grow: 1;
line-height: 40px; line-height: 40px;
padding-left: 8px; padding-left: 8px;

View File

@ -58,6 +58,8 @@ export type IRow = {
type: IRowType; type: IRowType;
/** [Head only] Title of the header */ /** [Head only] Title of the header */
name?: string; name?: string;
/** [Head only] Boolean if the entire day is selected */
selected?: boolean;
/** Main list of photo items */ /** Main list of photo items */
photos?: IPhoto[]; photos?: IPhoto[];
/** Height in px of the row */ /** Height in px of the row */
@ -65,6 +67,10 @@ export type IRow = {
/** Count of placeholders to create */ /** Count of placeholders to create */
pct?: number; pct?: number;
} }
export type IHeadRow = IRow & {
type: IRowType.HEAD;
selected: boolean;
}
export enum IRowType { export enum IRowType {
HEAD = 0, HEAD = 0,
PHOTOS = 1, PHOTOS = 1,