Select with head (fix #27)
parent
f522f7c87b
commit
e058316f66
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue