an attempt at a lightweight photo/album viewer

scrobbler tags for each segment

+82 -29
+2 -1
frontend/dist/scrobbler.css
··· 53 53 cursor: pointer; 54 54 } 55 55 56 - #scrobbler-section-title { 56 + #scrobbler-section-title, .scrobbler-tooltip { 57 57 position: absolute; 58 58 top: 0; 59 59 left: -150px; /* Position to the left of the scrobbler */ ··· 64 64 border-radius: 5px; 65 65 display: none; 66 66 text-align: right; 67 + transform: translateY(-1em); 67 68 }
+29 -13
frontend/src/gallery.mjs
··· 55 55 }); 56 56 } 57 57 58 + function getTooltipHtml(contents) { 59 + return ` 60 + <div class="scrobbler-tooltip"> 61 + ${contents} 62 + </div> 63 + ` 64 + } 65 + 58 66 function getSegmentHtml(segment) { 59 67 const images = segment.images; 60 68 var geometry = justifiedLayout(images, config); 61 69 if (geometry.containerHeight < 0) { 62 70 return ` 71 + <h4 style="position:relative;z-index:1000; background:rgba(100,100,100,0.5);"> 72 + ${segment.header}: an error occured<br> 73 + width: ${config.containerWidth}px;<br> 74 + height: ${geometry.containerHeight}px;<br> 75 + numRows: ${geometry.numRows}; 76 + </h4> 63 77 <div> 64 - <h4 style="position:relative;z-index:1000; background:rgba(100,100,100,0.5);"> 65 - ${segment.header}: an error occured<br> 66 - width: ${config.containerWidth}px;<br> 67 - height: ${geometry.containerHeight}px;<br> 68 - numRows: ${geometry.numRows}; 69 - </h4> 70 78 </div>` 71 79 } 72 80 var regions = Array.from(new Set(images.map(i => i.metadata).map(m => m.region))) ··· 184 192 ).sort((a, b) => new Date(a.images[0].timestamp) - new Date(b.images[0].timestamp)); 185 193 minTimestamp = new Date(allSegments[0].images[0].timestamp); 186 194 maxTimestamp = new Date(allSegments[allSegments.length - 1].images[0].timestamp); 187 - setTimeout(() => { 188 - const segments = grid.querySelectorAll('.segment'); 189 - segments.forEach(s => { 190 - segmentOffsets.set(s.id, s.offsetTop); 191 - }); 192 - scrobblerCtrl.updateGalleryMeta(allSegments, segmentOffsets, minTimestamp, maxTimestamp); 193 - }, 1000); 194 195 }); 195 196 grid.addEventListener('section-populated', (e) => { 196 197 const section = e.detail.sectionDiv; 197 198 const segments = section.querySelectorAll('.segment'); 199 + 200 + segments.forEach(s => { 201 + segmentOffsets.set(s.id, s.offsetTop); 202 + }); 203 + scrobblerCtrl.updateGalleryMeta(allSegments, segmentOffsets, minTimestamp, maxTimestamp); 204 + 205 + segments.forEach(s => { 206 + const tooltipText = s.dataset.title; 207 + const tooltip = document.createElement('div'); 208 + tooltip.innerHTML = tooltipText; 209 + tooltip.setAttribute('class', 'scrobbler-tooltip'); 210 + scrobblerContainer.appendChild(tooltip); 211 + scrobblerCtrl.registerTooltip(s, tooltip); 212 + }); 213 + 198 214 const regionTags = [...segments].flatMap(s => s.dataset.regions).flatMap(rs => rs.split(',')).filter(r => r.length !== 0) 199 215 geo.load(regionTags).then(_ => { 200 216 [...segments].forEach(s => {
+51 -15
frontend/src/scrobbler.ts
··· 6 6 private minTimestamp: number | undefined; 7 7 private maxTimestamp: number | undefined; 8 8 9 + private tooltipElements: Array<HTMLElement> = []; 9 10 10 11 constructor( 11 12 private grid: HTMLElement, ··· 30 31 }); 31 32 } 32 33 34 + registerTooltipByDate(tooltipDate: Date, tooltipElement: HTMLElement) { 35 + if (!this.minTimestamp || !this.maxTimestamp) { 36 + console.log("no timestamp data"); 37 + return; 38 + } 39 + const rect = this.scrobblerContainer.getBoundingClientRect(); 40 + const range = this.maxTimestamp - this.minTimestamp; 41 + const pos = tooltipDate.getTime() - this.minTimestamp; 42 + const tooltipOffset = pos * rect.height / range; 43 + tooltipElement.style.top = `${tooltipOffset}px`; 44 + this.tooltipElements.push(tooltipElement); 45 + } 46 + 47 + registerTooltip(referencedElement: HTMLElement, tooltipElement: HTMLElement) { 48 + const rect = this.scrobblerContainer.getBoundingClientRect(); 49 + const range = document.body.scrollHeight; 50 + const pos = this.segmentOffsets.get(referencedElement.id) 51 + const tooltipOffset = pos * rect.height / range; 52 + tooltipElement.style.top = `${tooltipOffset}px`; 53 + this.tooltipElements.push(tooltipElement); 54 + } 55 + 33 56 updateGalleryMeta(allSegments: any, segmentOffsets: any, min: Date, max: Date) { 34 57 this.allSegments = allSegments; 35 58 this.segmentOffsets = segmentOffsets; ··· 61 84 onDragStart(ev: MouseEvent | TouchEvent) { 62 85 this.isDragging = true; 63 86 this.scrobblerSectionTitle.style.display = 'block'; 87 + 88 + // rought calc for how many tooltips will fit 89 + // TODO better margin 90 + const margin = 50; 91 + var vis = 0; 92 + this.tooltipElements 93 + .filter((el, i) => { 94 + const rect = el.style; 95 + const top = parseInt(rect.top.substring(0, rect.top.length - 2)) 96 + if (top > vis) { 97 + vis = top + margin; 98 + return true; 99 + } 100 + return false; 101 + }) 102 + .forEach(t => { 103 + t.style.display = 'block'; 104 + }) 64 105 document.body.style.userSelect = 'none'; 65 106 } 66 107 ··· 92 133 this.scrobblerHandle.style.top = `${handleTop}px`; 93 134 const scrobblerHeight = this.scrobblerContainer.clientHeight; 94 135 const relativePosition = handleTop / scrobblerHeight; 95 - const targetTimestamp = this.minTimestamp + (relativePosition * (this.maxTimestamp - this.minTimestamp)); 96 - let targetSegment; 97 - for (let i = 0; i < this.allSegments.length; i++) { 98 - const segmentTimestamp = new Date(this.allSegments[i].images[0].timestamp).getTime(); 99 - if (segmentTimestamp >= targetTimestamp) { 100 - targetSegment = this.allSegments[i]; 101 - break; 102 - } 103 - } 104 - if (!targetSegment) { 105 - targetSegment = this.allSegments[this.allSegments.length - 1]; 106 - } 107 - this.scrobblerSectionTitle.textContent = targetSegment.header; 108 - this.scrobblerSectionTitle.style.top = `${handleTop}px`; 109 - const scrollTop = this.segmentOffsets.get(targetSegment.segmentId); 136 + const targetOffset = relativePosition * document.body.scrollHeight; 137 + console.log(targetOffset); 138 + const targetSegment = this.segmentOffsets.entries().filter((entry, value) => { 139 + return entry[1] > targetOffset; 140 + }).next(); 141 + console.log(targetSegment) 142 + const scrollTop = (targetSegment.value || ["", 0])[1]; 110 143 if (scrollTop !== undefined) { 111 144 window.scrollTo({ top: scrollTop, behavior: 'auto' }); 112 145 } ··· 117 150 if (!this.isDragging) return; 118 151 this.isDragging = false; 119 152 this.scrobblerSectionTitle.style.display = 'none'; 153 + this.tooltipElements.forEach(t => { 154 + t.style.display = 'none'; 155 + }) 120 156 document.body.style.userSelect = ''; 121 157 } 122 158