diff --git a/src/components/Timeline.vue b/src/components/Timeline.vue
index 2484e273..6fffa9b2 100644
--- a/src/components/Timeline.vue
+++ b/src/components/Timeline.vue
@@ -53,8 +53,12 @@
class="photo-row"
:style="{ height: item.size + 'px', width: rowWidth + 'px' }">
-
+
@@ -331,7 +336,9 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
row.photos[j] = {
flag: this.c.FLAG_PLACEHOLDER,
fileid: Math.random(),
- dispWp: utils.round(100 / this.numCols, 2, true),
+ dispWp: utils.round(1 / this.numCols, 4, true),
+ dispXp: utils.round(j / this.numCols, 4, true),
+ dispY: 0,
};
}
delete row.pct;
@@ -534,6 +541,7 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
// Create header for this day
const head: IHeadRow = {
id: `${day.dayid}-head`,
+ num: -1,
size: 40,
type: IRowType.HEAD,
selected: false,
@@ -642,6 +650,8 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
* @param isAnimating prevents glitches due to height changes
*/
processDay(dayId: number, data: IPhoto[], isAnimating=false) {
+ if (!data) return;
+
const head = this.heads[dayId];
const day = head.day;
this.loadedDays.add(dayId);
@@ -657,6 +667,11 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
day.count = data.length;
day.detail = data;
+ // Reset rows including placeholders
+ for (const row of head.day.rows || []) {
+ row.photos = [];
+ }
+
// Create justified layout with correct params
const justify = justifiedLayout(day.detail.map(p => {
return {
@@ -671,13 +686,6 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
targetRowHeightTolerance: 0.1,
});
- // Reset rows including placeholders
- if (head.day?.rows) {
- for (const row of head.day.rows) {
- row.photos = [];
- }
- }
-
// Check if some rows were added
let addedRows: IRow[] = [];
@@ -735,7 +743,33 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
utils.convertFlags(photo);
// Get aspect ratio
- photo.dispWp = utils.round(100 * jbox.width / this.rowWidth, 2, true);
+ const setPos = () => {
+ photo.dispWp = utils.round(jbox.width / this.rowWidth, 4, true);
+ photo.dispXp = utils.round(jbox.left / this.rowWidth, 4, true);
+ photo.dispY = 0;
+ photo.dispH = 0;
+ photo.dispRowNum = row.num;
+ };
+ if (photo.dispWp !== undefined) { // photo already displayed: animate
+ window.setTimeout(setPos, 50);
+
+ if (photo.dispRowNum !== undefined &&
+ photo.dispRowNum !== row.num &&
+ photo.dispRowNum >= 0 &&
+ photo.dispRowNum < day.rows.length
+ ) { // Row change animation
+ const start = Math.min(photo.dispRowNum, row.num);
+ const end = Math.max(photo.dispRowNum, row.num);
+ const sizeDelta = day.rows.slice(start, end).reduce((acc, r) => {
+ acc += r.size;
+ return acc;
+ }, 0);
+ photo.dispY = sizeDelta * (photo.dispRowNum < row.num ? -1 : 1);
+ photo.dispH = day.rows[photo.dispRowNum].size;
+ }
+ } else {
+ setPos();
+ }
// Move to next index of photo
dataIdx++;
@@ -807,6 +841,7 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
// Create new row
const row = {
id: `${day.dayid}-${day.rows.length}`,
+ num: day.rows.length,
photos: [],
type: rowType,
size: this.rowHeight,
@@ -831,8 +866,6 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
/**
* Delete elements from main view with some animation
- * This function looks horribly slow, probably isn't that bad
- * in all practical scenarios.
*
* This is also going to update day.detail for you and make
* a call to processDay so just pass it the list of ids to
@@ -860,44 +893,11 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
// clear selection at this point
this.selectionManager.clearSelection(delPhotos);
- // Speculate day reflow for animation
- const exitedLeft = new Set();
- for (const day of updatedDays) {
- let nextExit = false;
- for (const row of day.rows) {
- for (const photo of row.photos) {
- if (photo.flag & this.c.FLAG_LEAVING) {
- nextExit = true;
- } else if (nextExit) {
- photo.flag |= this.c.FLAG_EXIT_LEFT;
- exitedLeft.add(photo);
- }
- }
- }
- }
-
- // wait for 200ms
- await new Promise(resolve => setTimeout(resolve, 200));
-
// Reflow all touched days
for (const day of updatedDays) {
const newDetail = day.detail.filter(p => !delPhotosSet.has(p));
this.processDay(day.dayid, newDetail, true);
}
-
- // Enter from right all photos that exited left
- exitedLeft.forEach((photo: any) => {
- photo.flag &= ~this.c.FLAG_EXIT_LEFT;
- photo.flag |= this.c.FLAG_ENTER_RIGHT;
- });
-
- // wait for 200ms
- await new Promise(resolve => setTimeout(resolve, 200));
-
- // Clear enter right flags
- exitedLeft.forEach((photo: any) => {
- photo.flag &= ~this.c.FLAG_ENTER_RIGHT;
- });
}
}
@@ -926,12 +926,14 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
}
.photo-row > .photo {
- display: inline-block;
- position: relative;
+ display: block;
+ position: absolute;
+ top: 0; left: 0;
cursor: pointer;
- vertical-align: top;
height: 100%;
- transition: width 0.2s ease-in-out; // reflow justification
+ transition: width 0.2s ease-in-out,
+ height 0.2s ease-in-out,
+ transform 0.2s ease-in-out; // reflow
}
.head-row {
diff --git a/src/components/frame/Photo.vue b/src/components/frame/Photo.vue
index e160f847..fd5da80d 100644
--- a/src/components/frame/Photo.vue
+++ b/src/components/frame/Photo.vue
@@ -4,8 +4,6 @@
'selected': (data.flag & c.FLAG_SELECTED),
'placeholder': (data.flag & c.FLAG_PLACEHOLDER),
'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),
}">
@@ -70,9 +68,6 @@ export default class Photo extends Mixins(GlobalMixin) {
return undefined;
} else if (this.data.flag & this.c.FLAG_LOAD_FAIL) {
return errorsvg;
- } else if (this.data.flag & this.c.FLAG_FORCE_RELOAD) {
- this.data.flag &= ~this.c.FLAG_FORCE_RELOAD;
- return undefined;
} else {
return this.url;
}
@@ -211,19 +206,6 @@ export default class Photo extends Mixins(GlobalMixin) {
transform: scale(0.9);
opacity: 0;
}
- &.exit-left {
- transition: all 0.2s ease-in;
- transform: translateX(-20%);
- opacity: 0.8;
- }
- &.enter-right {
- animation: enter-right 0.2s ease-out forwards;
- }
-}
-
-@keyframes enter-right {
- from { transform: translateX(20%); opacity: 0.8; }
- to { transform: translateX(0); opacity: 1; }
}
// Distance of icon from border
diff --git a/src/types.ts b/src/types.ts
index f32f7193..039f1248 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -45,6 +45,14 @@ export type IPhoto = {
h?: number;
/** Grid display width percentage */
dispWp?: number;
+ /** Grid display height (forced) */
+ dispH?: number;
+ /** Grid display X percentage */
+ dispXp?: number;
+ /** Grid display Y px */
+ dispY?: number;
+ /** Grid display row id (relative to head) */
+ dispRowNum?: number;
/** Reference to day object */
d?: IDay;
/** Video flag from server */
@@ -84,6 +92,8 @@ export interface ITag extends IPhoto {
export type IRow = {
/** Vue Recycler identifier */
id?: string;
+ /** Row ID from head */
+ num: number;
/** Day ID */
dayId: number;
/** Refrence to day object */