big: switch to justified layout
parent
4e98e93d6e
commit
e298ef97fa
|
@ -12,6 +12,7 @@
|
|||
"@nextcloud/l10n": "^1.6.0",
|
||||
"@nextcloud/paths": "^2.1.0",
|
||||
"@nextcloud/vue": "^7.0.0",
|
||||
"justified-layout": "^4.1.0",
|
||||
"moment": "^2.29.4",
|
||||
"path-posix": "^1.0.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
|
@ -7430,6 +7431,11 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/justified-layout": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/justified-layout/-/justified-layout-4.1.0.tgz",
|
||||
"integrity": "sha512-M5FimNMXgiOYerVRGsXZ2YK9YNCaTtwtYp7Hb2308U1Q9TXXHx5G0p08mcVR5O53qf8bWY4NJcPBxE6zuayXSg=="
|
||||
},
|
||||
"node_modules/kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
|
@ -17701,6 +17707,11 @@
|
|||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"justified-layout": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/justified-layout/-/justified-layout-4.1.0.tgz",
|
||||
"integrity": "sha512-M5FimNMXgiOYerVRGsXZ2YK9YNCaTtwtYp7Hb2308U1Q9TXXHx5G0p08mcVR5O53qf8bWY4NJcPBxE6zuayXSg=="
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
"@nextcloud/l10n": "^1.6.0",
|
||||
"@nextcloud/paths": "^2.1.0",
|
||||
"@nextcloud/vue": "^7.0.0",
|
||||
"justified-layout": "^4.1.0",
|
||||
"moment": "^2.29.4",
|
||||
"path-posix": "^1.0.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
|
|
|
@ -119,6 +119,7 @@ export default class App extends Mixins(GlobalMixin, UserConfig) {
|
|||
padding: 0px;
|
||||
|
||||
// Get rid of padding on img-outer (1px on mobile)
|
||||
// Also need to make sure we don't end up with a scrollbar -- see below
|
||||
margin-left: -1px;
|
||||
width: calc(100% + 3px); // 1px extra here because ... reasons
|
||||
}
|
||||
|
@ -139,6 +140,12 @@ body {
|
|||
width: calc(100% - var(--body-container-margin)*1); // was *2
|
||||
}
|
||||
|
||||
// Hide horizontal scrollbar on mobile
|
||||
// For the padding removal above
|
||||
#app-content-vue {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
// Fill all available space
|
||||
.fill-block {
|
||||
width: 100%;
|
||||
|
|
|
@ -72,6 +72,8 @@ export default class ScrollerManager extends Mixins(GlobalMixin) {
|
|||
private scrollingRecyclerTimer = null as number | null;
|
||||
/** View size reflow timer */
|
||||
private reflowRequest = false;
|
||||
/** Tick adjust timer */
|
||||
private adjustTimer = null as number | null;
|
||||
|
||||
/** Get the visible ticks */
|
||||
get visibleTicks() {
|
||||
|
@ -91,7 +93,7 @@ export default class ScrollerManager extends Mixins(GlobalMixin) {
|
|||
|
||||
/** Recycler scroll event, must be called by timeline */
|
||||
public recyclerScrolled(event?: any) {
|
||||
this.cursorY = event ? event.target.scrollTop * this.height / this.recyclerHeight : 0;
|
||||
this.cursorY = utils.roundHalf(event ? event.target.scrollTop * this.height / this.recyclerHeight : 0);
|
||||
this.moveHoverCursor(this.cursorY);
|
||||
|
||||
if (this.scrollingRecyclerTimer) window.clearTimeout(this.scrollingRecyclerTimer);
|
||||
|
@ -114,22 +116,77 @@ export default class ScrollerManager extends Mixins(GlobalMixin) {
|
|||
this.reflowRequest = false;
|
||||
}
|
||||
|
||||
private setTickTop(tick: ITick) {
|
||||
const extraY = this.recyclerBefore?.clientHeight || 0;
|
||||
tick.topF = (extraY + tick.y) * (this.height / this.recyclerHeight);
|
||||
tick.top = utils.roundHalf(tick.topF);
|
||||
}
|
||||
|
||||
/** Re-create tick data */
|
||||
private reflowNow() {
|
||||
// Refresh height of recycler
|
||||
this.recyclerHeight = this.recycler.$refs.wrapper.clientHeight;
|
||||
|
||||
// Recreate ticks data
|
||||
this.recreate();
|
||||
|
||||
// Get height of recycler
|
||||
this.recyclerHeight = this.recycler.$refs.wrapper.clientHeight;
|
||||
// Recompute which ticks are visible
|
||||
this.computeVisibleTicks();
|
||||
}
|
||||
|
||||
// Static extra height at top (before slot)
|
||||
const extraY = this.recyclerBefore?.clientHeight || 0;
|
||||
/** Recreate from scratch */
|
||||
private recreate() {
|
||||
// Clear
|
||||
this.ticks = [];
|
||||
|
||||
// Compute tick positions
|
||||
for (const tick of this.ticks) {
|
||||
tick.top = (extraY + tick.y) * (this.height / this.recyclerHeight);
|
||||
// Ticks
|
||||
let y = 0;
|
||||
let prevYear = 9999;
|
||||
let prevMonth = 0;
|
||||
const thisYear = new Date().getFullYear();
|
||||
|
||||
// Get a new tick
|
||||
const getTick = (dayId: number, text?: string | number): ITick => {
|
||||
const tick = {
|
||||
dayId,
|
||||
y: y,
|
||||
text,
|
||||
topF: 0,
|
||||
top: 0,
|
||||
s: false,
|
||||
};
|
||||
this.setTickTop(tick);
|
||||
return tick;
|
||||
}
|
||||
|
||||
// Itearte over rows
|
||||
for (const row of this.rows) {
|
||||
if (row.type === IRowType.HEAD) {
|
||||
if (this.TagDayIDValueSet.has(row.dayId)) {
|
||||
// Blank tick
|
||||
this.ticks.push(getTick(row.dayId));
|
||||
} else {
|
||||
// Make date string
|
||||
const dateTaken = utils.dayIdToDate(row.dayId);
|
||||
|
||||
// Create tick if month changed
|
||||
const dtYear = dateTaken.getUTCFullYear();
|
||||
const dtMonth = dateTaken.getUTCMonth()
|
||||
if (Number.isInteger(row.dayId) && (dtMonth !== prevMonth || dtYear !== prevYear)) {
|
||||
const text = (dtYear === prevYear || dtYear === thisYear) ? undefined : dtYear;
|
||||
this.ticks.push(getTick(row.dayId, text));
|
||||
}
|
||||
prevMonth = dtMonth;
|
||||
prevYear = dtYear;
|
||||
}
|
||||
}
|
||||
|
||||
y += row.size;
|
||||
}
|
||||
}
|
||||
|
||||
/** Mark ticks as visible or invisible */
|
||||
private computeVisibleTicks() {
|
||||
// Do another pass to figure out which points are visible
|
||||
// This is not as bad as it looks, it's actually 12*O(n)
|
||||
// because there are only 12 months in a year
|
||||
|
@ -181,47 +238,45 @@ export default class ScrollerManager extends Mixins(GlobalMixin) {
|
|||
}
|
||||
}
|
||||
|
||||
/** Recreate from scratch */
|
||||
private recreate() {
|
||||
// Clear
|
||||
this.ticks = [];
|
||||
/**
|
||||
* Update tick positions without truncating the list
|
||||
* This is much cheaper than reflowing the whole thing
|
||||
*/
|
||||
public adjust() {
|
||||
if (this.adjustTimer) return;
|
||||
this.adjustTimer = window.setTimeout(() => {
|
||||
this.adjustTimer = null;
|
||||
this.adjustNow();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// Ticks
|
||||
/** Do adjustment synchrnously */
|
||||
private adjustNow() {
|
||||
// Refresh height of recycler
|
||||
this.recyclerHeight = this.recycler.$refs.wrapper.clientHeight;
|
||||
|
||||
// Start with the first tick. Walk over all rows counting the
|
||||
// y position. When you hit a row with the tick, update y and
|
||||
// top values and move to the next visible tick.
|
||||
let tickId = 0; // regardless of whether it's visible or not
|
||||
let y = 0;
|
||||
let prevYear = 9999;
|
||||
let prevMonth = 0;
|
||||
const thisYear = new Date().getFullYear();
|
||||
|
||||
// Get a new tick
|
||||
const getTick = (dayId: number, text?: string | number): ITick => {
|
||||
return {
|
||||
dayId,
|
||||
y: y,
|
||||
text,
|
||||
top: 0,
|
||||
s: false,
|
||||
};
|
||||
}
|
||||
|
||||
// Itearte over rows
|
||||
for (const row of this.rows) {
|
||||
if (row.type === IRowType.HEAD) {
|
||||
if (this.TagDayIDValueSet.has(row.dayId)) {
|
||||
// Blank tick
|
||||
this.ticks.push(getTick(row.dayId));
|
||||
} else {
|
||||
// Make date string
|
||||
const dateTaken = utils.dayIdToDate(row.dayId);
|
||||
// Check if tick is valid
|
||||
if (tickId >= this.ticks.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create tick if month changed
|
||||
const dtYear = dateTaken.getUTCFullYear();
|
||||
const dtMonth = dateTaken.getUTCMonth()
|
||||
if (Number.isInteger(row.dayId) && (dtMonth !== prevMonth || dtYear !== prevYear)) {
|
||||
const text = (dtYear === prevYear || dtYear === thisYear) ? undefined : dtYear;
|
||||
this.ticks.push(getTick(row.dayId, text));
|
||||
}
|
||||
prevMonth = dtMonth;
|
||||
prevYear = dtYear;
|
||||
// Check if we hit the next tick
|
||||
const tick = this.ticks[tickId];
|
||||
if (tick.dayId === row.dayId) {
|
||||
tick.y = y;
|
||||
this.setTickTop(tick);
|
||||
|
||||
// Get the next visible tick
|
||||
tickId++;
|
||||
while (tickId < this.ticks.length && !this.ticks[tickId].s) {
|
||||
tickId++;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -231,10 +286,10 @@ export default class ScrollerManager extends Mixins(GlobalMixin) {
|
|||
|
||||
/** Change actual position of the hover cursor */
|
||||
private moveHoverCursor(y: number) {
|
||||
this.hoverCursorY = y;
|
||||
this.hoverCursorY = utils.roundHalf(y);
|
||||
|
||||
// Get index of previous tick
|
||||
let idx = utils.binarySearch(this.ticks, y, 'top');
|
||||
let idx = utils.binarySearch(this.ticks, y, 'topF');
|
||||
if (idx === 0) {
|
||||
// use this tick
|
||||
} else if (idx >= 1 && idx <= this.ticks.length) {
|
||||
|
|
|
@ -51,10 +51,10 @@
|
|||
|
||||
<div v-else
|
||||
class="photo-row"
|
||||
:style="{ height: item.size + 'px' }">
|
||||
:style="{ height: item.size + 'px', width: rowWidth + 'px' }">
|
||||
|
||||
<div class="photo" v-for="(photo, index) in item.photos" :key="index"
|
||||
:style="{ width: rowHeight + 'px' }">
|
||||
<div class="photo" v-for="(photo, index) in item.photos" :key="photo.fileid"
|
||||
:style="{ width: (photo.dispWp * 100) + '%' }">
|
||||
|
||||
<Folder v-if="photo.flag & c.FLAG_IS_FOLDER"
|
||||
:data="photo"
|
||||
|
@ -102,6 +102,7 @@ import moment from 'moment';
|
|||
|
||||
import * as dav from "../services/DavRequests";
|
||||
import * as utils from "../services/Utils";
|
||||
import justifiedLayout from "justified-layout";
|
||||
import axios from '@nextcloud/axios'
|
||||
import Folder from "./frame/Folder.vue";
|
||||
import Tag from "./frame/Tag.vue";
|
||||
|
@ -117,8 +118,8 @@ import PeopleIcon from 'vue-material-design-icons/AccountMultiple.vue';
|
|||
import ImageMultipleIcon from 'vue-material-design-icons/ImageMultiple.vue';
|
||||
|
||||
const SCROLL_LOAD_DELAY = 100; // Delay in loading data when scrolling
|
||||
const MAX_PHOTO_WIDTH = 175; // Max width of a photo
|
||||
const MIN_COLS = 3; // Min number of columns (on phone, e.g.)
|
||||
const DESKTOP_ROW_HEIGHT = 200; // Height of row on desktop
|
||||
const MOBILE_NUM_COLS = 3; // Number of columns on phone
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
|
@ -144,7 +145,9 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
/** Counter of rows */
|
||||
private numRows = 0;
|
||||
/** Computed number of columns */
|
||||
private numCols = 5;
|
||||
private numCols = 0;
|
||||
/** Keep all images square */
|
||||
private squareMode = false;
|
||||
/** Header rows for dayId key */
|
||||
private heads: { [dayid: number]: IHeadRow } = {};
|
||||
/** Original days response */
|
||||
|
@ -152,6 +155,8 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
|
||||
/** Computed row height */
|
||||
private rowHeight = 100;
|
||||
/** Computed row width */
|
||||
private rowWidth = 100;
|
||||
|
||||
/** Current start index */
|
||||
private currentStart = 0;
|
||||
|
@ -264,7 +269,7 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
// Size of outer container
|
||||
const e = this.$refs.container as Element;
|
||||
let height = e.clientHeight;
|
||||
let width = e.clientWidth;
|
||||
this.rowWidth = e.clientWidth;
|
||||
|
||||
// Scroller spans the container height
|
||||
this.scrollerHeight = height;
|
||||
|
@ -277,22 +282,21 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
const recycler = this.$refs.recycler as any;
|
||||
recycler.$el.style.height = (height - tmHeight - 4) + 'px';
|
||||
|
||||
// Desktop scroller width
|
||||
if (window.innerWidth > 768) {
|
||||
width -= 40;
|
||||
if (window.innerWidth <= 768) {
|
||||
// Mobile
|
||||
this.numCols = MOBILE_NUM_COLS;
|
||||
this.rowHeight = this.rowWidth / this.numCols;
|
||||
this.squareMode = true;
|
||||
} else {
|
||||
// Desktop
|
||||
this.rowWidth -= 40;
|
||||
this.rowHeight = DESKTOP_ROW_HEIGHT;
|
||||
this.squareMode = false;
|
||||
|
||||
// As a heuristic, assume all images are 4:3 landscape
|
||||
this.numCols = Math.floor(this.rowWidth / (this.rowHeight * 4 / 3));
|
||||
}
|
||||
|
||||
if (this.days.length === 0) {
|
||||
// Don't change cols if already initialized
|
||||
this.numCols = Math.max(MIN_COLS, Math.floor(width / MAX_PHOTO_WIDTH));
|
||||
}
|
||||
|
||||
this.rowHeight = Math.floor(width / this.numCols);
|
||||
|
||||
// Set heights of rows
|
||||
this.list.filter(r => r.type !== IRowType.HEAD).forEach(row => {
|
||||
row.size = this.rowHeight;
|
||||
});
|
||||
this.scrollerManager.reflow();
|
||||
}
|
||||
|
||||
|
@ -322,9 +326,12 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
if (row.pct && !row.photos.length) {
|
||||
row.photos = new Array(row.pct);
|
||||
for (let j = 0; j < row.pct; j++) {
|
||||
// Any row that has placeholders has ONLY placeholders
|
||||
// so we can calculate the display width
|
||||
row.photos[j] = {
|
||||
flag: this.c.FLAG_PLACEHOLDER,
|
||||
fileid: Math.random(),
|
||||
dispWp: 1 / this.numCols,
|
||||
};
|
||||
}
|
||||
delete row.pct;
|
||||
|
@ -374,6 +381,18 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
}
|
||||
}
|
||||
|
||||
/** Store the current scroll position to restore later */
|
||||
private getScrollY() {
|
||||
const recycler = this.$refs.recycler as any;
|
||||
return recycler.$el.scrollTop
|
||||
}
|
||||
|
||||
/** Restore the stored scroll position */
|
||||
private setScrollY(y: number) {
|
||||
const recycler = this.$refs.recycler as any;
|
||||
recycler.scrollToPosition(y);
|
||||
}
|
||||
|
||||
/** Get query string for API calls */
|
||||
appendQuery(url: string) {
|
||||
const query = new URLSearchParams();
|
||||
|
@ -614,6 +633,19 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
const dayId = day.dayid;
|
||||
const data = day.detail;
|
||||
|
||||
// Create justified layout with correct params
|
||||
const justify = justifiedLayout(day.detail.map(p => {
|
||||
return {
|
||||
width: (this.squareMode ? null : p.w) || this.rowHeight,
|
||||
height: (this.squareMode ? null : p.h) || this.rowHeight,
|
||||
};
|
||||
}), {
|
||||
containerWidth: this.rowWidth,
|
||||
containerPadding: 0,
|
||||
boxSpacing: 0,
|
||||
targetRowHeight: this.rowHeight,
|
||||
});
|
||||
|
||||
const head = this.heads[dayId];
|
||||
this.loadedDays.add(dayId);
|
||||
|
||||
|
@ -625,30 +657,46 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
}
|
||||
head.day.rows.clear();
|
||||
|
||||
// Check if some row was added
|
||||
let addedRow = false;
|
||||
// Check if some rows were added
|
||||
let addedRows: IRow[] = [];
|
||||
|
||||
// Check if row height changed
|
||||
let rowSizeDelta = 0;
|
||||
|
||||
// Get index of header O(n)
|
||||
const headIdx = this.list.findIndex(item => item.id === head.id);
|
||||
let rowIdx = headIdx + 1;
|
||||
|
||||
// Store the scroll position in case we change any rows
|
||||
const scrollY = this.getScrollY();
|
||||
|
||||
// Previous justified row
|
||||
let prevJustifyTop = justify.boxes[0]?.top || 0;
|
||||
|
||||
// Add all rows
|
||||
let dataIdx = 0;
|
||||
while (dataIdx < data.length) {
|
||||
// Check if we ran out of rows
|
||||
if (rowIdx >= this.list.length || this.list[rowIdx].type === IRowType.HEAD) {
|
||||
addedRow = true;
|
||||
this.list.splice(rowIdx, 0, this.getBlankRow(day));
|
||||
const newRow = this.getBlankRow(day);
|
||||
addedRows.push(newRow);
|
||||
rowSizeDelta += newRow.size;
|
||||
this.list.splice(rowIdx, 0, newRow);
|
||||
}
|
||||
|
||||
const row = this.list[rowIdx];
|
||||
|
||||
// Go to the next row
|
||||
if (row.photos.length >= this.numCols) {
|
||||
const jbox = justify.boxes[dataIdx];
|
||||
if (jbox.top !== prevJustifyTop) {
|
||||
prevJustifyTop = jbox.top;
|
||||
rowIdx++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set row height
|
||||
const row = this.list[rowIdx];
|
||||
rowSizeDelta += jbox.height - row.size;
|
||||
row.size = jbox.height;
|
||||
|
||||
// Add the photo to the row
|
||||
const photo = data[dataIdx];
|
||||
if (typeof photo.flag === "undefined") {
|
||||
|
@ -678,6 +726,9 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
delete photo.istag;
|
||||
}
|
||||
|
||||
// Get aspect ratio
|
||||
photo.dispWp = jbox.width / this.rowWidth;
|
||||
|
||||
// Move to next index of photo
|
||||
dataIdx++;
|
||||
|
||||
|
@ -695,10 +746,15 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
head.day.rows.add(row);
|
||||
}
|
||||
|
||||
// Rows that were removed
|
||||
const removedRows: IRow[] = [];
|
||||
let headRemoved = false;
|
||||
|
||||
// No rows, splice everything including the header
|
||||
if (head.day.rows.size === 0) {
|
||||
this.list.splice(headIdx, 1);
|
||||
removedRows.push(...this.list.splice(headIdx, 1));
|
||||
rowIdx = headIdx - 1;
|
||||
headRemoved = true;
|
||||
delete this.heads[dayId];
|
||||
}
|
||||
|
||||
|
@ -708,14 +764,34 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
spliceCount++;
|
||||
}
|
||||
if (spliceCount > 0) {
|
||||
this.list.splice(rowIdx + 1, spliceCount);
|
||||
removedRows.push(...this.list.splice(rowIdx + 1, spliceCount));
|
||||
}
|
||||
|
||||
// Update size delta for removed rows
|
||||
for (const row of removedRows) {
|
||||
rowSizeDelta -= row.size;
|
||||
}
|
||||
|
||||
// This will be true even if the head is being spliced
|
||||
// because one row is always removed in that case
|
||||
// So just reflow the timeline here
|
||||
if (addedRow || spliceCount > 0) {
|
||||
this.scrollerManager.reflow();
|
||||
if (rowSizeDelta !== 0) {
|
||||
if (headRemoved) {
|
||||
// If the head was removed, that warrants a reflow
|
||||
// since months or years might disappear!
|
||||
this.scrollerManager.reflow();
|
||||
} else {
|
||||
// Otherwise just adjust the visible ticks
|
||||
this.scrollerManager.adjust();
|
||||
}
|
||||
|
||||
// Scroll to the same actual position if the added rows
|
||||
// were above the current scroll position
|
||||
const recycler: any = this.$refs.recycler;
|
||||
const midIndex = (recycler.$_startIndex + recycler.$_endIndex) / 2;
|
||||
if (midIndex > headIdx) {
|
||||
this.setScrollY(scrollY + rowSizeDelta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -815,9 +891,6 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
exitedLeft.forEach((photo: any) => {
|
||||
photo.flag &= ~this.c.FLAG_ENTER_RIGHT;
|
||||
});
|
||||
|
||||
// Reflow timeline
|
||||
this.scrollerManager.reflow();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
'p-load-fail': info.flag & c.FLAG_LOAD_FAIL,
|
||||
}"
|
||||
:key="'fpreview-' + info.fileid"
|
||||
:src="getPreviewUrl(info.fileid, info.etag)"
|
||||
:src="getPreviewUrl(info.fileid, info.etag, true, 256)"
|
||||
@load="info.flag |= c.FLAG_LOADED"
|
||||
@error="info.flag |= c.FLAG_LOAD_FAIL" />
|
||||
</div>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
'leaving': (data.flag & c.FLAG_LEAVING),
|
||||
'exit-left': (data.flag & c.FLAG_EXIT_LEFT),
|
||||
'enter-right': (data.flag & c.FLAG_ENTER_RIGHT),
|
||||
'error': (data.flag & c.FLAG_LOAD_FAIL),
|
||||
}">
|
||||
|
||||
<Check :size="15" class="select"
|
||||
|
@ -81,7 +82,7 @@ export default class Photo extends Mixins(GlobalMixin) {
|
|||
|
||||
/** Get url of the photo */
|
||||
get url() {
|
||||
return getPreviewUrl(this.data.fileid, this.data.etag)
|
||||
return getPreviewUrl(this.data.fileid, this.data.etag, false, 512)
|
||||
}
|
||||
|
||||
/** Image loaded successfully */
|
||||
|
@ -276,7 +277,8 @@ div.img-outer {
|
|||
user-select: none;
|
||||
|
||||
.selected > & { box-shadow: 0 0 3px 2px var(--color-primary); }
|
||||
.p-loading > & { display: none; }
|
||||
.p-outer.p-loading > & { display: none; }
|
||||
.p-outer.error & { object-fit: contain; }
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -18,7 +18,7 @@
|
|||
'p-load-fail': info.flag & c.FLAG_LOAD_FAIL,
|
||||
}"
|
||||
:key="'fpreview-' + info.fileid"
|
||||
:src="getPreviewUrl(info.fileid, info.etag)"
|
||||
:src="getPreviewUrl(info.fileid, info.etag, true, 256)"
|
||||
:style="getCoverStyle(info)"
|
||||
@load="info.flag |= c.FLAG_LOADED"
|
||||
@error="info.flag |= c.FLAG_LOAD_FAIL" />
|
||||
|
@ -71,9 +71,9 @@ export default class Tag extends Mixins(GlobalMixin) {
|
|||
|
||||
getPreviewUrl(fileid: number, etag: string) {
|
||||
if (this.isFace) {
|
||||
return generateUrl(`/core/preview?fileId=${fileid}&c=${etag}&x=2048&y=2048&forceIcon=0&a=1`);
|
||||
return getPreviewUrl(fileid, etag, false, 2048);
|
||||
}
|
||||
return getPreviewUrl(fileid, etag);
|
||||
return getPreviewUrl(fileid, etag, true, 256);
|
||||
}
|
||||
|
||||
get isFace() {
|
||||
|
|
|
@ -123,8 +123,9 @@
|
|||
return fileInfo
|
||||
}
|
||||
|
||||
const getPreviewUrl = function(fileid: number, etag: string): string {
|
||||
return generateUrl(`/core/preview?fileId=${fileid}&c=${etag}&x=250&y=250&forceIcon=0&a=0`);
|
||||
const getPreviewUrl = function(fileid: number, etag: string, square: boolean, size: number): string {
|
||||
const a = square ? '0' : '1'
|
||||
return generateUrl(`/core/preview?fileId=${fileid}&c=${etag}&x=${size}&y=${size}&forceIcon=0&a=${a}`);
|
||||
}
|
||||
|
||||
export { encodeFilePath, extractFilePaths, sortCompare, genFileInfo, getPreviewUrl }
|
|
@ -90,6 +90,24 @@ export function binarySearch(arr: any, elem: any, key?: string) {
|
|||
return minIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Round a number to N decimal places
|
||||
* @param num Number to round
|
||||
* @param places Number of decimal places
|
||||
*/
|
||||
export function round(num: number, places: number) {
|
||||
const pow = Math.pow(10, places);
|
||||
return Math.round(num * pow) / pow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Round to nearest 0.5. Useful for pixels.
|
||||
* @param num Number to round
|
||||
*/
|
||||
export function roundHalf(num: number) {
|
||||
return Math.round(num * 2) / 2;
|
||||
}
|
||||
|
||||
/** Global constants */
|
||||
export const constants = {
|
||||
c: {
|
||||
|
|
|
@ -43,6 +43,8 @@ export type IPhoto = {
|
|||
w?: number;
|
||||
/** Height of full image */
|
||||
h?: number;
|
||||
/** Grid display width percentage */
|
||||
dispWp?: number;
|
||||
/** Reference to day object */
|
||||
d?: IDay;
|
||||
/** Video flag from server */
|
||||
|
@ -114,6 +116,8 @@ export type ITick = {
|
|||
/** Day ID */
|
||||
dayId: number;
|
||||
/** Display top position */
|
||||
topF: number;
|
||||
/** Display top position (truncated to 1 decimal pt) */
|
||||
top: number;
|
||||
/** Y coordinate on recycler */
|
||||
y: number;
|
||||
|
|
Loading…
Reference in New Issue