big: transform position
parent
61737e4830
commit
644377d12a
|
@ -53,8 +53,12 @@
|
||||||
class="photo-row"
|
class="photo-row"
|
||||||
:style="{ height: item.size + 'px', width: rowWidth + 'px' }">
|
:style="{ height: item.size + 'px', width: rowWidth + 'px' }">
|
||||||
|
|
||||||
<div class="photo" v-for="photo in item.photos" :key="photo.fileid"
|
<div class="photo" v-for="photo of item.photos" :key="photo.fileid"
|
||||||
:style="{ width: photo.dispWp + '%' }">
|
:style="{
|
||||||
|
height: photo.dispH ? photo.dispH + 'px' : undefined,
|
||||||
|
width: photo.dispWp * rowWidth + 'px',
|
||||||
|
transform: 'translateX(' + photo.dispXp * rowWidth + 'px) translateY(' + photo.dispY + 'px)',
|
||||||
|
}">
|
||||||
|
|
||||||
<Folder v-if="photo.flag & c.FLAG_IS_FOLDER"
|
<Folder v-if="photo.flag & c.FLAG_IS_FOLDER"
|
||||||
:data="photo"
|
:data="photo"
|
||||||
|
@ -67,6 +71,7 @@
|
||||||
<Photo v-else
|
<Photo v-else
|
||||||
:data="photo"
|
:data="photo"
|
||||||
:day="item.day"
|
:day="item.day"
|
||||||
|
:key="photo.fileid"
|
||||||
@select="selectionManager.selectPhoto"
|
@select="selectionManager.selectPhoto"
|
||||||
@delete="deleteFromViewWithAnimation"
|
@delete="deleteFromViewWithAnimation"
|
||||||
@clickImg="clickPhoto" />
|
@clickImg="clickPhoto" />
|
||||||
|
@ -331,7 +336,9 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
||||||
row.photos[j] = {
|
row.photos[j] = {
|
||||||
flag: this.c.FLAG_PLACEHOLDER,
|
flag: this.c.FLAG_PLACEHOLDER,
|
||||||
fileid: Math.random(),
|
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;
|
delete row.pct;
|
||||||
|
@ -534,6 +541,7 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
||||||
// Create header for this day
|
// Create header for this day
|
||||||
const head: IHeadRow = {
|
const head: IHeadRow = {
|
||||||
id: `${day.dayid}-head`,
|
id: `${day.dayid}-head`,
|
||||||
|
num: -1,
|
||||||
size: 40,
|
size: 40,
|
||||||
type: IRowType.HEAD,
|
type: IRowType.HEAD,
|
||||||
selected: false,
|
selected: false,
|
||||||
|
@ -642,6 +650,8 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
||||||
* @param isAnimating prevents glitches due to height changes
|
* @param isAnimating prevents glitches due to height changes
|
||||||
*/
|
*/
|
||||||
processDay(dayId: number, data: IPhoto[], isAnimating=false) {
|
processDay(dayId: number, data: IPhoto[], isAnimating=false) {
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
const head = this.heads[dayId];
|
const head = this.heads[dayId];
|
||||||
const day = head.day;
|
const day = head.day;
|
||||||
this.loadedDays.add(dayId);
|
this.loadedDays.add(dayId);
|
||||||
|
@ -657,6 +667,11 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
||||||
day.count = data.length;
|
day.count = data.length;
|
||||||
day.detail = data;
|
day.detail = data;
|
||||||
|
|
||||||
|
// Reset rows including placeholders
|
||||||
|
for (const row of head.day.rows || []) {
|
||||||
|
row.photos = [];
|
||||||
|
}
|
||||||
|
|
||||||
// Create justified layout with correct params
|
// Create justified layout with correct params
|
||||||
const justify = justifiedLayout(day.detail.map(p => {
|
const justify = justifiedLayout(day.detail.map(p => {
|
||||||
return {
|
return {
|
||||||
|
@ -671,13 +686,6 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
||||||
targetRowHeightTolerance: 0.1,
|
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
|
// Check if some rows were added
|
||||||
let addedRows: IRow[] = [];
|
let addedRows: IRow[] = [];
|
||||||
|
|
||||||
|
@ -735,7 +743,33 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
||||||
utils.convertFlags(photo);
|
utils.convertFlags(photo);
|
||||||
|
|
||||||
// Get aspect ratio
|
// 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
|
// Move to next index of photo
|
||||||
dataIdx++;
|
dataIdx++;
|
||||||
|
@ -807,6 +841,7 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
||||||
// Create new row
|
// Create new row
|
||||||
const row = {
|
const row = {
|
||||||
id: `${day.dayid}-${day.rows.length}`,
|
id: `${day.dayid}-${day.rows.length}`,
|
||||||
|
num: day.rows.length,
|
||||||
photos: [],
|
photos: [],
|
||||||
type: rowType,
|
type: rowType,
|
||||||
size: this.rowHeight,
|
size: this.rowHeight,
|
||||||
|
@ -831,8 +866,6 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete elements from main view with some animation
|
* 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
|
* 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
|
* 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
|
// clear selection at this point
|
||||||
this.selectionManager.clearSelection(delPhotos);
|
this.selectionManager.clearSelection(delPhotos);
|
||||||
|
|
||||||
// Speculate day reflow for animation
|
|
||||||
const exitedLeft = new Set<IPhoto>();
|
|
||||||
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
|
// Reflow all touched days
|
||||||
for (const day of updatedDays) {
|
for (const day of updatedDays) {
|
||||||
const newDetail = day.detail.filter(p => !delPhotosSet.has(p));
|
const newDetail = day.detail.filter(p => !delPhotosSet.has(p));
|
||||||
this.processDay(day.dayid, newDetail, true);
|
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;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -926,12 +926,14 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
.photo-row > .photo {
|
.photo-row > .photo {
|
||||||
display: inline-block;
|
display: block;
|
||||||
position: relative;
|
position: absolute;
|
||||||
|
top: 0; left: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
vertical-align: top;
|
|
||||||
height: 100%;
|
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 {
|
.head-row {
|
||||||
|
|
|
@ -4,8 +4,6 @@
|
||||||
'selected': (data.flag & c.FLAG_SELECTED),
|
'selected': (data.flag & c.FLAG_SELECTED),
|
||||||
'placeholder': (data.flag & c.FLAG_PLACEHOLDER),
|
'placeholder': (data.flag & c.FLAG_PLACEHOLDER),
|
||||||
'leaving': (data.flag & c.FLAG_LEAVING),
|
'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),
|
'error': (data.flag & c.FLAG_LOAD_FAIL),
|
||||||
}">
|
}">
|
||||||
|
|
||||||
|
@ -70,9 +68,6 @@ export default class Photo extends Mixins(GlobalMixin) {
|
||||||
return undefined;
|
return undefined;
|
||||||
} else if (this.data.flag & this.c.FLAG_LOAD_FAIL) {
|
} else if (this.data.flag & this.c.FLAG_LOAD_FAIL) {
|
||||||
return errorsvg;
|
return errorsvg;
|
||||||
} else if (this.data.flag & this.c.FLAG_FORCE_RELOAD) {
|
|
||||||
this.data.flag &= ~this.c.FLAG_FORCE_RELOAD;
|
|
||||||
return undefined;
|
|
||||||
} else {
|
} else {
|
||||||
return this.url;
|
return this.url;
|
||||||
}
|
}
|
||||||
|
@ -211,19 +206,6 @@ export default class Photo extends Mixins(GlobalMixin) {
|
||||||
transform: scale(0.9);
|
transform: scale(0.9);
|
||||||
opacity: 0;
|
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
|
// Distance of icon from border
|
||||||
|
|
10
src/types.ts
10
src/types.ts
|
@ -45,6 +45,14 @@ export type IPhoto = {
|
||||||
h?: number;
|
h?: number;
|
||||||
/** Grid display width percentage */
|
/** Grid display width percentage */
|
||||||
dispWp?: number;
|
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 */
|
/** Reference to day object */
|
||||||
d?: IDay;
|
d?: IDay;
|
||||||
/** Video flag from server */
|
/** Video flag from server */
|
||||||
|
@ -84,6 +92,8 @@ export interface ITag extends IPhoto {
|
||||||
export type IRow = {
|
export type IRow = {
|
||||||
/** Vue Recycler identifier */
|
/** Vue Recycler identifier */
|
||||||
id?: string;
|
id?: string;
|
||||||
|
/** Row ID from head */
|
||||||
|
num: number;
|
||||||
/** Day ID */
|
/** Day ID */
|
||||||
dayId: number;
|
dayId: number;
|
||||||
/** Refrence to day object */
|
/** Refrence to day object */
|
||||||
|
|
Loading…
Reference in New Issue