Improve UI
parent
20ff9a4b7d
commit
0845e6fb54
135
src/Timeline.vue
135
src/Timeline.vue
|
@ -14,14 +14,25 @@
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
</h1>
|
</h1>
|
||||||
<div v-else
|
<div v-else
|
||||||
class="photo"
|
class="photo"
|
||||||
v-bind:style="{ height: rowHeight + 'px' }">
|
v-bind:style="{ height: rowHeight + 'px' }">
|
||||||
|
|
||||||
<img v-for="img of item.photos"
|
<img v-for="img of item.photos"
|
||||||
:src="img.src" :key="img.file_id"
|
:src="img.src" :key="img.file_id"
|
||||||
v-bind:style="{ width: rowHeight + 'px', height: rowHeight + 'px' }"/>
|
v-bind:style="{ width: rowHeight + 'px', height: rowHeight + 'px' }"/>
|
||||||
</div>
|
</div>
|
||||||
</RecycleScroller>
|
</RecycleScroller>
|
||||||
|
|
||||||
|
<div ref="timelineScroll" class="timeline-scroll"
|
||||||
|
@mousemove="timelineHover"
|
||||||
|
@click="timelineClick">
|
||||||
|
|
||||||
|
<div v-for="tick of timelineTicks" :key="tick.dayId" class="tick"
|
||||||
|
v-bind:style="{ top: Math.floor(tick.top * timelineHeight / viewHeight) + 'px' }">
|
||||||
|
<span v-if="tick.text">{{ tick.text }}</span>
|
||||||
|
<span v-else class="dash"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -37,9 +48,17 @@ export default {
|
||||||
numCols: 5,
|
numCols: 5,
|
||||||
/** Header rows for dayId key */
|
/** Header rows for dayId key */
|
||||||
heads: {},
|
heads: {},
|
||||||
|
/** Original days response */
|
||||||
|
days: [],
|
||||||
|
|
||||||
/** Computed row height */
|
/** Computed row height */
|
||||||
rowHeight: 100,
|
rowHeight: 100,
|
||||||
|
/** Total height of recycler */
|
||||||
|
viewHeight: 1000,
|
||||||
|
/** Total height of timeline */
|
||||||
|
timelineHeight: 100,
|
||||||
|
/** Computed timeline ticks */
|
||||||
|
timelineTicks: [],
|
||||||
|
|
||||||
/** Current start index */
|
/** Current start index */
|
||||||
currentStart: 0,
|
currentStart: 0,
|
||||||
|
@ -57,13 +76,21 @@ export default {
|
||||||
/** Handle window resize and initialization */
|
/** Handle window resize and initialization */
|
||||||
handleResize() {
|
handleResize() {
|
||||||
let height = this.$refs.container.clientHeight;
|
let height = this.$refs.container.clientHeight;
|
||||||
let width = this.$refs.container.clientWidth;
|
let width = this.$refs.container.clientWidth - 40;
|
||||||
|
this.timelineHeight = this.$refs.timelineScroll.clientHeight;
|
||||||
this.$refs.scroller.$el.style.height = (height - 4) + 'px';
|
this.$refs.scroller.$el.style.height = (height - 4) + 'px';
|
||||||
|
|
||||||
this.numCols = Math.max(4, Math.floor(width / 150));
|
this.numCols = Math.max(4, Math.floor(width / 150));
|
||||||
this.rowHeight = Math.floor(width / this.numCols) - 4;
|
this.rowHeight = Math.floor(width / this.numCols) - 4;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Handle change in rows and view size */
|
||||||
|
handleViewSizeChange() {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.viewHeight = this.$refs.scroller.$refs.wrapper.clientHeight;
|
||||||
|
}, 0);
|
||||||
|
},
|
||||||
|
|
||||||
/** Trigger when recycler view changes */
|
/** Trigger when recycler view changes */
|
||||||
scrollChange(startIndex, endIndex) {
|
scrollChange(startIndex, endIndex) {
|
||||||
if (startIndex === this.currentStart && endIndex === this.currentEnd) {
|
if (startIndex === this.currentStart && endIndex === this.currentEnd) {
|
||||||
|
@ -88,8 +115,8 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
let head = this.heads[item.dayId];
|
let head = this.heads[item.dayId];
|
||||||
if (head && !head.loaded) {
|
if (head && !head.loadedImages) {
|
||||||
head.loaded = true;
|
head.loadedImages = true;
|
||||||
this.fetchDay(item.dayId);
|
this.fetchDay(item.dayId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,8 +126,16 @@ export default {
|
||||||
async fetchDays() {
|
async fetchDays() {
|
||||||
const res = await fetch('/apps/betterphotos/api/days');
|
const res = await fetch('/apps/betterphotos/api/days');
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
this.days = data;
|
||||||
|
|
||||||
|
// Ticks
|
||||||
|
let currTop = 0;
|
||||||
|
let prevYear = new Date().getUTCFullYear();
|
||||||
|
let prevMonth = new Date().getUTCMonth();
|
||||||
|
|
||||||
|
for (const [dayIdx, day] of data.entries()) {
|
||||||
|
day.count = Number(day.count);
|
||||||
|
|
||||||
for (const day of data) {
|
|
||||||
// Nothing here
|
// Nothing here
|
||||||
if (day.count === 0) {
|
if (day.count === 0) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -114,30 +149,49 @@ export default {
|
||||||
dateStr = dateStr.substring(0, dateStr.length - 6);
|
dateStr = dateStr.substring(0, dateStr.length - 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create tick if month changed
|
||||||
|
const dtYear = dateTaken.getUTCFullYear();
|
||||||
|
const dtMonth = dateTaken.getUTCMonth()
|
||||||
|
if (dtMonth !== prevMonth || dtYear !== prevYear) {
|
||||||
|
this.timelineTicks.push({
|
||||||
|
dayId: day.id,
|
||||||
|
top: currTop,
|
||||||
|
text: dtYear === prevYear ? undefined : dtYear,
|
||||||
|
});
|
||||||
|
prevMonth = dtMonth;
|
||||||
|
prevYear = dtYear;
|
||||||
|
}
|
||||||
|
|
||||||
// Add header to list
|
// Add header to list
|
||||||
const head = {
|
const head = {
|
||||||
id: ++this.numRows,
|
id: ++this.numRows,
|
||||||
name: dateStr,
|
name: dateStr,
|
||||||
size: 60,
|
size: 60,
|
||||||
head: true,
|
head: true,
|
||||||
loaded: false,
|
loadedImages: false,
|
||||||
dayId: day.day_id,
|
dayId: day.day_id,
|
||||||
};
|
};
|
||||||
this.heads[day.day_id] = head;
|
this.heads[day.day_id] = head;
|
||||||
this.list.push(head);
|
this.list.push(head);
|
||||||
|
currTop += head.size;
|
||||||
|
|
||||||
// Add rows
|
// Add rows
|
||||||
const nrows = Math.ceil(day.count / this.numCols);
|
const nrows = Math.ceil(day.count / this.numCols);
|
||||||
for (let i = 0; i < nrows; i++) {
|
for (let i = 0; i < nrows; i++) {
|
||||||
this.list.push(this.getBlankRow(day.day_id));
|
const row = this.getBlankRow(day.day_id);
|
||||||
|
this.list.push(row);
|
||||||
|
currTop += row.size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fix view height variable
|
||||||
|
this.handleViewSizeChange();
|
||||||
},
|
},
|
||||||
|
|
||||||
/** Fetch image data for one dayId */
|
/** Fetch image data for one dayId */
|
||||||
async fetchDay(dayId) {
|
async fetchDay(dayId) {
|
||||||
const head = this.heads[dayId];
|
const head = this.heads[dayId];
|
||||||
head.loaded = true;
|
head.loadedImages = true;
|
||||||
|
|
||||||
let data = [];
|
let data = [];
|
||||||
try {
|
try {
|
||||||
|
@ -145,7 +199,7 @@ export default {
|
||||||
data = await res.json();
|
data = await res.json();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
head.loaded = false;
|
head.loadedImages = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get index of header O(n)
|
// Get index of header O(n)
|
||||||
|
@ -189,7 +243,33 @@ export default {
|
||||||
size: this.rowHeight,
|
size: this.rowHeight,
|
||||||
dayId: dayId,
|
dayId: dayId,
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
|
|
||||||
|
/** Handle mouse hover on right timeline */
|
||||||
|
timelineHover(event) {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Handle mouse click on right timeline */
|
||||||
|
timelineClick(event) {
|
||||||
|
this.$refs.scroller.scrollToPosition(this.getTimelinePosition(event));
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Get scroller equivalent position from event */
|
||||||
|
getTimelinePosition(event) {
|
||||||
|
const tH = this.viewHeight;
|
||||||
|
const maxH = this.timelineHeight;
|
||||||
|
return event.offsetY * tH / maxH;
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Scroll to given day Id */
|
||||||
|
scrollToDay(dayId) {
|
||||||
|
const head = this.heads[dayId];
|
||||||
|
if (!head) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$refs.scroller.scrollToPosition(1000);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -197,11 +277,13 @@ export default {
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.container {
|
.container {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scroller {
|
.scroller {
|
||||||
height: 300px;
|
height: 300px;
|
||||||
width: 100%;
|
width: calc(100% + 20px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.photo img {
|
.photo img {
|
||||||
|
@ -214,4 +296,29 @@ export default {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: lighter;
|
font-weight: lighter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timeline-scroll {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 40px;
|
||||||
|
top: 0; right: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: ns-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-scroll .tick {
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: grey;
|
||||||
|
right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-scroll .tick .dash {
|
||||||
|
height: 1px;
|
||||||
|
width: 6px;
|
||||||
|
background-color: grey;
|
||||||
|
opacity: 0.8;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
Loading…
Reference in New Issue