Merge branch 'master' into stable24

old_stable24
Varun Patil 2022-10-31 23:27:11 -07:00
commit 40e0a92850
7 changed files with 95 additions and 26 deletions

View File

@ -23,7 +23,7 @@ const CTE_FOLDERS = // CTE to get all folders recursively in the given top folde
*PREFIX*filecache f
INNER JOIN *PREFIX*cte_folders c
ON (f.parent = c.fileid
AND f.mimetype = (SELECT `id` FROM `*PREFIX*mimetypes` WHERE `mimetype` = "httpd/unix-directory")
AND f.mimetype = (SELECT `id` FROM `*PREFIX*mimetypes` WHERE `mimetype` = \'httpd/unix-directory\')
AND f.fileid <> :excludedFolderId
)
)';

View File

@ -61,7 +61,7 @@ trait TimelineQueryTags
$query = $this->joinFilecache($query, $folder, true, false);
// GROUP and ORDER by tag name
$query->groupBy('st.name');
$query->groupBy('st.id');
$query->orderBy('st.name', 'ASC');
$query->addOrderBy('st.id'); // tie-breaker

View File

@ -81,6 +81,6 @@ class Version400503Date20221101033144 extends SimpleMigrationStep
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void
{
// Update oc_memories to set objectid equal to fileid for all rows
$this->dbc->executeQuery('UPDATE *PREFIX*memories SET objectid = CAST(fileid AS CHAR)');
$this->dbc->executeQuery('UPDATE *PREFIX*memories SET objectid = CAST(fileid AS CHAR(64))');
}
}

View File

@ -270,6 +270,13 @@ body {
max-height: 100vh;
}
// Top bar is above everything else on mobile
#content-vue.has-top-bar {
@media (max-width: 1024px) {
z-index: 3000;
}
}
// Patch viewer to remove the title and
// make the image fill the entire screen
.viewer {

View File

@ -1,7 +1,7 @@
<template>
<div>
<div v-if="selection.size > 0" class="top-bar">
<NcActions>
<div v-if="show" class="top-bar">
<NcActions :inline="1">
<NcActionButton
:aria-label="t('memories', 'Cancel')"
@click="clearSelection()"
@ -47,7 +47,7 @@
</template>
<script lang="ts">
import { Component, Emit, Mixins, Prop } from "vue-property-decorator";
import { Component, Emit, Mixins, Prop, Watch } from "vue-property-decorator";
import GlobalMixin from "../mixins/GlobalMixin";
import UserConfig from "../mixins/UserConfig";
@ -88,10 +88,11 @@ type Selection = Map<number, IPhoto>;
CloseIcon,
},
})
export default class SelectionHandler extends Mixins(GlobalMixin, UserConfig) {
@Prop() public selection: Selection;
export default class SelectionManager extends Mixins(GlobalMixin, UserConfig) {
@Prop() public heads: { [dayid: number]: IHeadRow };
private show = false;
private readonly selection!: Selection;
private readonly defaultActions: ISelectionAction[];
@Emit("refresh")
@ -106,6 +107,8 @@ export default class SelectionHandler extends Mixins(GlobalMixin, UserConfig) {
constructor() {
super();
this.selection = new Map<number, IPhoto>();
// Make default actions
this.defaultActions = [
{
@ -177,6 +180,29 @@ export default class SelectionHandler extends Mixins(GlobalMixin, UserConfig) {
];
}
@Watch("show")
onShowChange() {
const elem = document.getElementById("content-vue");
const klass = "has-top-bar";
if (this.show) {
elem.classList.add(klass);
} else {
elem.classList.remove(klass);
}
}
private selectionChanged() {
this.show = this.selection.size > 0;
}
/** Is this fileid (or anything if not specified) selected */
public has(fileid?: number) {
if (fileid === undefined) {
return this.selection.size > 0;
}
return this.selection.has(fileid);
}
/** Click on an action */
private async click(action: ISelectionAction) {
try {
@ -191,7 +217,9 @@ export default class SelectionHandler extends Mixins(GlobalMixin, UserConfig) {
/** Get the actions list */
private getActions(): ISelectionAction[] {
return this.defaultActions.filter((a) => (!a.if || a.if(this)) && (!this.routeIsPublic() || a.allowPublic));
return this.defaultActions.filter(
(a) => (!a.if || a.if(this)) && (!this.routeIsPublic() || a.allowPublic)
);
}
/** Clear all selected photos */
@ -202,6 +230,7 @@ export default class SelectionHandler extends Mixins(GlobalMixin, UserConfig) {
photo.flag &= ~this.c.FLAG_SELECTED;
heads.add(this.heads[photo.d.dayid]);
this.selection.delete(photo.fileid);
this.selectionChanged();
});
heads.forEach(this.updateHeadSelected);
this.$forceUpdate();
@ -239,9 +268,17 @@ export default class SelectionHandler extends Mixins(GlobalMixin, UserConfig) {
if (nval) {
photo.flag |= this.c.FLAG_SELECTED;
this.selection.set(photo.fileid, photo);
this.selectionChanged();
} else {
photo.flag &= ~this.c.FLAG_SELECTED;
this.selection.delete(photo.fileid);
// Only do this if the photo in the selection set is this one.
// The problem arises when there are duplicates (e.g. face rect)
// in the list, which creates an inconsistent state if we do this.
if (this.selection.get(photo.fileid) === photo) {
this.selection.delete(photo.fileid);
this.selectionChanged();
}
}
if (!noUpdate) {
@ -509,11 +546,11 @@ export default class SelectionHandler extends Mixins(GlobalMixin, UserConfig) {
right: 60px;
padding: 8px;
width: 400px;
max-width: calc(100vw - 30px);
max-width: 100vw;
background-color: var(--color-main-background);
box-shadow: 0 0 2px gray;
border-radius: 10px;
opacity: 0.95;
opacity: 0.97;
display: flex;
vertical-align: middle;
z-index: 100;
@ -524,9 +561,17 @@ export default class SelectionHandler extends Mixins(GlobalMixin, UserConfig) {
padding-left: 8px;
}
@media (max-width: 768px) {
top: 35px;
right: 15px;
@media (max-width: 1024px) {
// sidebar is hidden below this point
top: 0;
left: 0;
right: unset;
position: fixed;
width: 100vw;
border-radius: 0px;
opacity: 1;
padding-top: 3px;
padding-bottom: 3px;
}
}
</style>

View File

@ -73,7 +73,7 @@
<div
class="photo"
v-for="photo of item.photos"
:key="photo.fileid"
:key="photo.key || photo.fileid"
:style="{
height: photo.dispH + 'px',
width: photo.dispW + 'px',
@ -116,7 +116,6 @@
<SelectionManager
ref="selectionManager"
:selection="selection"
:heads="heads"
@refresh="softRefresh"
@delete="deleteFromViewWithAnimation"
@ -153,7 +152,7 @@ import TopMatter from "./top-matter/TopMatter.vue";
const SCROLL_LOAD_DELAY = 100; // Delay in loading data when scrolling
const DESKTOP_ROW_HEIGHT = 200; // Height of row on desktop
const MOBILE_NUM_COLS = 3; // Number of columns on phone
const MOBILE_ROW_HEIGHT = 120; // Approx row height on mobile
@Component({
components: {
@ -204,8 +203,6 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
private fetchDayQueue = [] as number[];
/** Timer to load day call */
private fetchDayTimer = null as number | null;
/** Set of selected file ids */
private selection = new Map<number, IPhoto>();
/** State for request cancellations */
private state = Math.random();
@ -255,8 +252,12 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
return window.innerWidth <= 768;
}
isMobileLayout() {
return window.innerWidth <= 600;
}
allowBreakout() {
return this.isMobile() && !this.config_squareThumbs;
return this.isMobileLayout() && !this.config_squareThumbs;
}
/** Create new state */
@ -353,14 +354,13 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
return;
}
if (this.isMobile()) {
if (this.isMobileLayout()) {
// Mobile
this.numCols = MOBILE_NUM_COLS;
this.numCols = Math.max(3, Math.floor(this.rowWidth / MOBILE_ROW_HEIGHT));
this.rowHeight = Math.floor(this.rowWidth / this.numCols);
} else {
// Desktop
if (this.config_squareThumbs) {
// Set columns first, then height
this.numCols = Math.max(
3,
Math.floor(this.rowWidth / DESKTOP_ROW_HEIGHT)
@ -883,7 +883,7 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
}
// Force all to square
const squareMode = this.isMobile() || this.config_squareThumbs;
const squareMode = this.isMobileLayout() || this.config_squareThumbs;
// Create justified layout with correct params
const justify = getLayout(
@ -924,6 +924,9 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
let rowIdx = headIdx + 1;
let rowY = headY + head.size;
// Duplicate detection, e.g. for face rects
const seen = new Map<number, number>();
// Previous justified row
let prevJustifyTop = justify[0]?.top || 0;
@ -1010,6 +1013,18 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
// Move to next index of photo
dataIdx++;
// Duplicate detection.
// These may be valid, e.g. in face rects. All we need to have
// is a unique Vue key for the v-for loop.
if (seen.has(photo.fileid)) {
const val = seen.get(photo.fileid);
photo.key = `${photo.fileid}-${val}`;
seen.set(photo.fileid, val + 1);
} else {
photo.key = null;
seen.set(photo.fileid, 1);
}
// Add photo to row
row.photos.push(photo);
}
@ -1097,7 +1112,7 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
clickPhoto(photo: IPhoto) {
if (photo.flag & this.c.FLAG_PLACEHOLDER) return;
if (this.selection.size > 0) {
if (this.selectionManager.has()) {
// selection mode
this.selectionManager.selectPhoto(photo);
} else {

View File

@ -35,6 +35,8 @@ export type IDay = {
export type IPhoto = {
/** Nextcloud ID of file */
fileid: number;
/** Vue key if duplicates present (otherwise use fileid) */
key?: string;
/** Etag from server */
etag?: string;
/** Path to file */