| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097 |
- <!doctype html>
- <html lang="en">
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=yes" />
- <meta name="theme-color" content="#f76c5d" />
- <meta name="description" content="ArozOS Photo" />
- <link rel="manifest" crossorigin="use-credentials" href="manifest.json">
- <title>Photo</title>
- <link rel="stylesheet" href="../script/semantic/semantic.min.css">
- <script src="../script/alpine.min.js"></script>
- <script src="../script/jquery.min.js"></script>
- <script src="../script/semantic/semantic.min.js"></script>
- <script src="../script/ao_module.js"></script>
- <script src="constants.js"></script>
- <script src="histogram.js"></script>
- <script src="photo.js"></script>
- <style>
- #main {
- display: flex;
- flex-direction: column;
- height: 100vh;
- }
-
- #menu {
- flex: 0 0 auto;
- width: 100%;
- height: auto;
- margin-left: 0.4em;
- margin-top: 0.4em;
- }
-
- #path-selector {
- display: none;
- }
- #content-area {
- flex: 1;
- display: flex;
- flex-direction: row;
- min-height: 0;
- overflow: hidden;
- }
- #sidebar {
- width: 210px;
- min-width: 210px;
- background: #1e1e1e;
- border-right: 1px solid #333;
- overflow-y: auto;
- overflow-x: hidden;
- display: flex;
- flex-direction: column;
- flex-shrink: 0;
- padding-top: 3em;
- }
- /* Hide the top menu bar on desktop — only needed for mobile toggle */
- @media (min-width: 769px) {
- #menu {
- display: none;
- }
- #sidebar {
- padding-top: 0.4em;
- }
- }
- .sidebar-section {
- padding: 0.75em 0.5em 0.4em 0.5em;
- }
- .sidebar-section-title {
- font-size: 0.68em;
- text-transform: uppercase;
- letter-spacing: 0.12em;
- color: #555;
- padding: 0 0.4em;
- margin-bottom: 0.4em;
- }
- .sidebar-item {
- display: flex;
- align-items: center;
- padding: 0.38em 0.6em;
- border-radius: 5px;
- cursor: pointer;
- color: #aaa;
- font-size: 0.87em;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- text-decoration: none;
- margin: 1px 0.3em;
- user-select: none;
- }
- .sidebar-item:hover {
- background: #2a2a2a;
- color: #fff;
- }
- .sidebar-item.active {
- color: #f76c5d;
- background: rgba(247, 108, 93, 0.12);
- }
- .sidebar-item.disk-root {
- color: #888;
- cursor: default;
- }
- .sidebar-item i {
- flex-shrink: 0;
- margin-right: 0.6em;
- margin-top: -8px;
- width: 1em;
- text-align: center;
- }
- .sidebar-banner {
- margin-top: auto;
- padding: 1em 0.8em;
- border-top: 1px solid #2a2a2a;
- flex-shrink: 0;
- }
- .sidebar-banner img {
- max-width: 100%;
- max-height: 42px;
- opacity: 1;
- }
- .sidebar-divider {
- height: 1px;
- background: #2a2a2a;
- margin: 0.3em 0.8em;
- }
- .sidebar-select {
- width: 100%;
- background: #252525;
- color: #bbb;
- border: 1px solid #3a3a3a;
- border-radius: 5px;
- padding: 0.45em 2em 0.45em 0.7em;
- font-size: 0.87em;
- cursor: pointer;
- outline: none;
- appearance: none;
- -webkit-appearance: none;
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath fill='%23777' d='M0 0l5 6 5-6z'/%3E%3C/svg%3E");
- background-repeat: no-repeat;
- background-position: right 0.7em center;
- background-size: 0.65em;
- box-sizing: border-box;
- transition: border-color 0.15s, color 0.15s;
- }
- .sidebar-select:hover,
- .sidebar-select:focus {
- border-color: #f76c5d;
- color: #fff;
- outline: none;
- }
- .sidebar-select option {
- background: #252525;
- color: #bbb;
- }
- #sidebar-overlay {
- display: none;
- }
- @media (max-width: 768px) {
- /* Keep the top menu bar above the sidebar so the toggle button is always clickable */
- #menu {
- position: relative;
- z-index: 201;
- }
- #sidebar {
- position: fixed;
- top: 0;
- left: 0;
- height: 100%;
- z-index: 200;
- transform: translateX(-100%);
- transition: transform 0.25s ease;
- box-shadow: 3px 0 12px rgba(0,0,0,0.6);
- }
- #sidebar.open {
- transform: translateX(0);
- }
- #sidebar-toggle-btn {
- display: flex !important;
- }
- #sidebar-overlay {
- position: fixed;
- top: 0; left: 0;
- width: 100%; height: 100%;
- background: rgba(0,0,0,0.5);
- z-index: 199;
- }
- }
-
- #display {
- flex: 1;
- margin: 0;
- overflow: hidden;
- background: #1a1a1a;
- min-width: 0;
- }
-
- body {
- overflow: hidden;
- background: #1a1a1a;
- color: #ffffff;
- }
- .imagecard{
- border: 1px solid #444;
- overflow: hidden;
- }
- .imagecard img{
- width: 100%;
- height: 100%;
- object-fit: cover;
- object-position: center;
- }
- #viewbox{
- margin-left: 0px !important;
- margin-top: 0px !important;
- position: relative;
-
- }
- #viewboxContainer{
- height: 100%;
- overflow-y: auto;
- overflow-x: hidden;
- }
- #fadeplate{
- position: absolute;
- top: 0px;
- left: 0px;
- width: 100%;
- height: 100%;
- background: rgba(20,20,20,0.8);
- backdrop-filter: blur(2px);
- }
- #noimg {
- text-align: center;
- margin: 2em auto;
- max-width: 400px;
- color: white;
- }
- #noimg .sub.header {
- color: #cccccc;
- }
- .photo-viewer {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.95);
- z-index: 1000;
- display: none;
- }
- .viewer-content {
- display: flex;
- height: 100%;
- position: relative;
- }
- .viewer-bg {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- z-index: 1;
- }
- .viewer-bg img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- filter: blur(20px);
- }
- .viewer-left, .viewer-right {
- z-index: 2;
- position: relative;
- }
- .viewer-left {
- flex: 1;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 20px;
- overflow: hidden;
- cursor: grab;
- position: relative;
- }
- .viewer-left img {
- max-width: 100%;
- max-height: 100%;
- object-fit: contain;
- transition: transform 0.1s ease-out;
- cursor: auto;
- user-select: none;
- }
- #compressedImage {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- opacity: 1;
- transition: opacity 0.3s ease-out;
- z-index: 2;
- }
- #compressedImage.hidden {
- opacity: 0;
- pointer-events: none;
- }
- .viewer-left img.zoomed {
- max-width: none;
- max-height: none;
- cursor: grab;
- }
- .viewer-left img.dragging {
- cursor: grabbing;
- }
- .viewer-right {
- width: 350px;
- background: rgba(40, 40, 40,0.6);
- padding: 20px;
- color: white;
- overflow-y: auto;
- }
- .info-panel h3 {
- margin-top: 0;
- color: #f76c5d;
- }
- .info-panel h4 {
- margin-top: 20px;
- margin-bottom: 10px;
- color: #f76c5d;
- }
- .info-panel h5 {
- margin-top: 15px;
- margin-bottom: 5px;
- color: #cccccc;
- font-size: 0.9em;
- font-weight: bold;
- text-transform: uppercase;
- letter-spacing: 0.5px;
- }
- .info-panel table {
- width: 100%;
- margin-bottom: 10px;
- }
- .info-panel .exif-section-table {
- margin-bottom: 15px;
- }
- .info-panel td {
- word-wrap: break-word;
- overflow-wrap: break-word;
- hyphens: auto;
- padding: 6px 4px;
- font-size: 0.9em;
- }
- .info-panel td:first-child {
- font-weight: bold;
- width: 120px;
- white-space: nowrap;
- color: #cccccc;
- }
- .info-panel td:last-child {
- word-break: break-all;
- }
- .close-btn {
- position: absolute;
- top: 1em;
- right: 1em;
- background: #222222;
- color: white;
- border: none;
- width: 30px;
- height: 30px;
- border-radius: 50%;
- cursor: pointer;
- font-size: 18px;
- line-height: 1;
- z-index: 500;
- }
- .close-btn.info-panel-close {
- display: none;
- }
- #shooting-params-section .column {
- padding: 0.1em !important;
- }
- #shooting-params-section .propertiesLabel {
- border: 1px solid #c0c0c0 !important;
- border-radius: 0.4em !important;
- margin: 0.1em;
- padding: 0.2em 0.4em;
- }
- #shooting-params-grid{
- margin: 0 !important;
- }
- .show-info-btn {
- position: absolute;
- top: 1em;
- right: 3em;
- background: #222222;
- color: white;
- border: none;
- width: 30px;
- height: 30px;
- border-radius: 50%;
- cursor: pointer;
- font-size: 18px;
- line-height: 1;
- z-index: 500;
- display:none;
- }
- @media (max-width: 768px) {
- .viewer-right {
- position: fixed;
- right: 0;
- backdrop-filter: blur(10px);
- height: 100%;
- overflow-y: auto;
- background-color: rgba(40, 40, 40,0.8);
- display:none;
- }
- .close-btn {
- position: fixed;
- right: 1em;
- }
- .close-btn.info-panel-close {
- display: block;
- }
- .show-info-btn {
- display: block;
- }
- }
- /* Zoom Controls Styles */
- .zoom-controls {
- position: absolute;
- bottom: 20px;
- left: 20px;
- display: flex;
- flex-direction: column;
- gap: 8px;
- z-index: 10;
- }
- .zoom-btn {
- background: rgba(0, 0, 0, 0.7);
- color: white;
- border: 1px solid rgba(255, 255, 255, 0.2);
- width: 40px;
- height: 40px;
- border-radius: 4px;
- cursor: pointer;
- font-size: 20px;
- font-weight: bold;
- transition: all 0.2s;
- display: flex;
- align-items: center;
- justify-content: center;
- backdrop-filter: blur(4px);
- }
- .zoom-btn:hover {
- background: rgba(247, 108, 93, 0.9);
- border-color: #f76c5d;
- }
- .zoom-btn:active {
- transform: scale(0.95);
- }
- .zoom-level-indicator {
- background: rgba(0, 0, 0, 0.7);
- color: white;
- padding: 6px 10px;
- border-radius: 4px;
- font-size: 12px;
- text-align: center;
- backdrop-filter: blur(4px);
- border: 1px solid rgba(255, 255, 255, 0.2);
- }
- /* Navigation Controls Styles */
- .nav-controls {
- position: absolute;
- bottom: 20px;
- right: 20px;
- display: flex;
- flex-direction: row;
- gap: 8px;
- z-index: 10;
- }
- .nav-btn {
- background: rgba(0, 0, 0, 0.7);
- color: white;
- border: 1px solid rgba(255, 255, 255, 0.2);
- width: 40px;
- height: 40px;
- border-radius: 4px;
- cursor: pointer;
- font-size: 20px;
- font-weight: bold;
- transition: all 0.2s;
- display: flex;
- align-items: center;
- justify-content: center;
- backdrop-filter: blur(4px);
- }
- .nav-btn:hover:not(:disabled) {
- background: rgba(247, 108, 93, 0.9);
- border-color: #f76c5d;
- }
- .nav-btn:active:not(:disabled) {
- transform: scale(0.95);
- }
- .nav-btn:disabled {
- opacity: 0.3;
- cursor: not-allowed;
- }
- @media (max-width: 768px) {
- .zoom-snackbar {
- display: none !important;
- }
- .zoom-controls {
- display: none !important;
- }
- .viewer-left {
- cursor: default;
- touch-action: auto;
- }
- .viewer-left img {
- cursor: default;
- }
- }
- /* Loading Progress Indicator */
- .loading-progress {
- position: absolute;
- bottom: 20px;
- left: 50%;
- transform: translateX(-50%);
- background: rgba(0, 0, 0, 0.7);
- color: white;
- padding: 8px 16px;
- border-radius: 4px;
- font-size: 14px;
- backdrop-filter: blur(4px);
- border: 1px solid rgba(255, 255, 255, 0.2);
- z-index: 10;
- display: none;
- }
- </style>
- </head>
- <body>
- <div id="main" x-data='photoListObject()' x-init="init()">
- <div class="ui inverted secondary small menu" id="menu">
- <!-- Mobile sidebar toggle (hidden on desktop via CSS) -->
- <a class="icon item" id="sidebar-toggle-btn" x-on:click="sidebarOpen = !sidebarOpen;" style="display:none;">
- <i class="ui bars large icon"></i>
- </a>
- </div>
- <!-- Mobile sidebar overlay -->
- <div id="sidebar-overlay" x-show="sidebarOpen" x-on:click="sidebarOpen = false;"></div>
- <div id="content-area">
- <!-- Sidebar folder tree -->
- <div id="sidebar" :class="{ 'open': sidebarOpen }">
- <div class="sidebar-section">
- <div class="sidebar-section-title">Sort By</div>
- <div style="padding: 0 0.4em;">
- <select class="sidebar-select" x-model="sortOrder" x-on:change="getFolderInfo();">
- <option value="smart">Natural Order</option>
- <option value="mostRecent">Newest First</option>
- <option value="leastRecent">Oldest First</option>
- <option value="default">Filename A→Z</option>
- <option value="reverse">Filename Z→A</option>
- <option value="smallToLarge">Size Small→Large</option>
- <option value="largeToSmall">Size Large→Small</option>
- <option value="fileTypeAsce">Extension A→Z</option>
- <option value="fileTypeDesc">Extension Z→A</option>
- </select>
- </div>
- </div>
- <div class="sidebar-divider"></div>
- <div class="sidebar-section">
- <div class="sidebar-section-title">Library</div>
- <template x-for="vroot in vroots">
- <a class="sidebar-item" x-on:click="updateRenderingPath(vroot[2]);">
- <i class="ui hdd outline icon"></i>
- <span x-text="vroot[0]"></span>
- </a>
- </template>
- </div>
- <div class="sidebar-divider"></div>
- <div class="sidebar-section">
- <div class="sidebar-section-title">Current Path</div>
- <template x-for="(seg, idx) in getPathSegments()">
- <a class="sidebar-item"
- :class="{ 'active': seg.path === currentPath, 'disk-root': seg.isDiskRoot }"
- :style="{ paddingLeft: (0.6 + seg.depth * 0.75) + 'em' }"
- x-on:click="!seg.isDiskRoot && seg.path !== currentPath && updateRenderingPath(seg.path);">
- <i :class="seg.isDiskRoot ? 'ui hdd outline icon' : 'ui folder icon'"></i>
- <span x-text="seg.name"></span>
- </a>
- </template>
- </div>
- <div x-show="folders.length > 0">
- <div class="sidebar-divider"></div>
- <div class="sidebar-section">
- <div class="sidebar-section-title">Subfolders</div>
- <template x-for="folder in folders">
- <a class="sidebar-item" x-on:click="updateRenderingPath(folder);">
- <i class="ui folder open icon"></i>
- <span x-text="extractFolderName(folder)"></span>
- </a>
- </template>
- </div>
- </div>
- <!-- Banner pinned to bottom of sidebar -->
- <div class="sidebar-banner">
- <img src="img/banner.png" alt="Photo">
- </div>
- </div><!-- /#sidebar -->
- <div id="display">
- <div id="noimg" class="ui basic inverted segment" style="display:none;">
- <h4 class="ui header">
- <div class="content">
- Empty Folder
- <div class="sub header">There are no photo stored in <span x-text="currentPath + '/'"></span></div>
- </div>
- </h4>
- </div>
- <div id="viewboxContainer">
- <div id="viewbox" class="ui six cards viewbox">
- <template x-for="image in images">
- <div class="imagecard" style="cursor: pointer;" x-on:click="showImage($el); ShowModal();" :style="{width: renderSize + 'px', height: renderSize + 'px'}" :filedata="encodeURIComponent(JSON.stringify({'filename':image.filepath.split('/').pop(),'filepath':image.filepath,'filesize':image.filesize}))">
- <a class="image" x-init="updateImageSizes();">
- <img :src="'../system/file_system/loadThumbnail?bytes=true&vpath=' + image.filepath">
- </a>
- </div>
- </template>
- </div>
- <!-- Infinite scroll sentinel -->
- <div id="load-more-indicator" style="text-align:center; padding: 1em; color: #aaa;" x-show="hasMoreImages">
- <i class="loading spinner icon"></i> Loading more photos...
- </div>
- </div>
- </div>
- </div><!-- /#content-area -->
- </div><!-- /#main -->
- <!-- Photo Viewer -->
- <div id="photo-viewer" class="photo-viewer">
-
- <div class="viewer-content">
- <div class="viewer-bg">
- <img id="bg-image" src="" />
- </div>
- <div class="viewer-left">
- <img id="compressedImage" src="" style="display: none;" />
- <img id="fullImage" src="img/loading.png" />
- <button class="close-btn" onclick="closeViewer()">×</button>
- <button class="show-info-btn" onclick="showInfoPanel()">ℹ</button>
-
- <!-- Loading Progress -->
- <div id="loading-progress" class="loading-progress"><i class="loading spinner icon"></i> Loading</div>
-
- <!-- Zoom Controls -->
- <div id="zoom-controls" class="zoom-controls">
- <button class="zoom-btn" onclick="zoomIn()" title="Zoom In">+</button>
- <div class="zoom-level-indicator" id="zoom-level-display">100%</div>
- <button class="zoom-btn" onclick="zoomOut()" title="Zoom Out">−</button>
- </div>
-
- <!-- Navigation Controls -->
- <div id="nav-controls" class="nav-controls">
- <button id="prev-btn" class="nav-btn" onclick="showPreviousImage()" title="Previous Photo">‹</button>
- <button id="next-btn" class="nav-btn" onclick="showNextImage()" title="Next Photo">›</button>
- </div>
- </div>
- <div class="viewer-right">
- <div>
- <button class="close-btn info-panel-close" onclick="hideInfoPanel()">×</button>
- </div>
- <div class="info-panel">
- <h3>Photo Information</h3>
- <table class="ui very basic compact table inverted">
- <tbody>
- <tr>
- <td><strong>Filename:</strong></td>
- <td><span id="info-filename"></span></td>
- </tr>
- <tr>
- <td><strong>Filepath:</strong></td>
- <td><span id="info-filepath"></span></td>
- </tr>
- <tr>
- <td><strong>Image Dimensions:</strong></td>
- <td><span id="info-dimensions">Loading...</span></td>
- </tr>
- </tbody>
- </table>
- <h4>EXIF Data</h4>
- <div id="exif-data">
- <!-- Basic Information -->
- <div id="basic-info-section" style="display: none;">
- <h5>Basic Information</h5>
- <table class="ui very basic compact table inverted exif-section-table">
- <tbody>
- <tr id="format-row" style="display: none;"><td>Format</td><td id="format-value"></td></tr>
- <tr id="dimensions-row" style="display: none;"><td>Dimensions</td><td id="dimensions-value"></td></tr>
- <tr id="pixels-row" style="display: none;"><td>Pixels</td><td id="pixels-value"></td></tr>
- <tr id="color-space-row" style="display: none;"><td>Color Space</td><td id="color-space-value"></td></tr>
- <tr id="shooting-time-row" style="display: none;"><td>Shooting Time</td><td id="shooting-time-value"></td></tr>
- <tr id="software-row" style="display: none;"><td>Software</td><td id="software-value"></td></tr>
- </tbody>
- </table>
- </div>
- <div class="ui divider" id="basic-info-divider" style="display: none;"></div>
- <!-- Shooting Parameters -->
- <div id="shooting-params-section" style="display: none;">
- <h5>Shooting Parameters</h5>
- <div id="shooting-params-grid" class="ui grid">
- <div class="eight wide column" id="focal-length-row" style="display: none;">
- <div class="propertiesLabel">
- <img src="img/focal.svg" alt="Focal Length Icon" style="width: 16px; height: 16px; margin-right: 5px; margin-bottom: -0.2em">
- <span id="focal-length-value"></span>
- </div>
- </div>
- <div class="eight wide column" id="aperture-row" style="display: none;">
- <div class="propertiesLabel">
- <img src="img/aperature.svg" alt="Focal Length Icon" style="width: 16px; height: 16px; margin-right: 5px; margin-bottom: -0.2em">
- <span id="aperture-value"></span>
- </div>
- </div>
- <div class="eight wide column" id="shutter-speed-row" style="display: none;">
- <div class="propertiesLabel">
- <img src="img/shutter_speed.svg" alt="Focal Length Icon" style="width: 16px; height: 16px; margin-right: 5px; margin-bottom: -0.2em">
- <span id="shutter-speed-value"></span>
- </div>
- </div>
- <div class="eight wide column" id="iso-row" style="display: none;">
- <div class="propertiesLabel">
- <img src="img/iso.svg" alt="Focal Length Icon" style="width: 16px; height: 16px; margin-right: 5px; margin-bottom: -0.2em">
- <span>ISO </span><span id="iso-value"></span>
- </div>
- </div>
- <div class="eight wide column" id="ev-row" style="display: none;">
- <div class="propertiesLabel">
- <img src="img/exposure.svg" alt="EV Icon" style="width: 16px; height: 16px; margin-right: 5px; margin-bottom: -0.25em">
- <span>EV </span> <span id="ev-value"></span>
- </div>
- </div>
- </div>
- </div>
- <div class="ui divider" id="shooting-params-divider" style="display: none;"></div>
- <!-- Tone Analysis -->
- <div id="tone-analysis-section" style="display: none;">
- <h5>Tone Analysis</h5>
- <table class="ui very basic compact table inverted exif-section-table">
- <tbody>
- <tr><td>Tone Type</td><td class="tone-type-value">Analyzing...</td></tr>
- <tr><td>Brightness</td><td class="brightness-value">Analyzing...</td></tr>
- <tr><td>Contrast</td><td class="contrast-value">Analyzing...</td></tr>
- <tr><td>Shadow Ratio</td><td class="shadow-ratio-value">Analyzing...</td></tr>
- <tr><td>Highlight Ratio</td><td class="highlight-ratio-value">Analyzing...</td></tr>
- <tr><td colspan="2"><canvas id="histogram-canvas" width="300" height="120" style="width: 100%; height: 120px; background: #222; margin: 10px 0; border-radius: 4px;"></canvas></td></tr>
- </tbody>
- </table>
- </div>
- <div class="ui divider" id="tone-analysis-divider" style="display: none;"></div>
- <!-- Device Information -->
- <div id="device-info-section" style="display: none;">
- <h5>Device Information</h5>
- <table class="ui very basic compact table inverted exif-section-table">
- <tbody>
- <tr id="camera-row" style="display: none;"><td>Camera</td><td id="camera-value"></td></tr>
- <tr id="lens-row" style="display: none;"><td>Lens</td><td id="lens-value"></td></tr>
- <tr id="focal-length-device-row" style="display: none;"><td>Focal Length</td><td id="focal-length-device-value"></td></tr>
- <tr id="max-aperture-row" style="display: none;"><td>Max Aperture</td><td id="max-aperture-value"></td></tr>
- </tbody>
- </table>
- </div>
- <div class="ui divider" id="device-info-divider" style="display: none;"></div>
- <!-- Shooting Mode -->
- <div id="shooting-mode-section" style="display: none;">
- <h5>Shooting Mode</h5>
- <table class="ui very basic compact table inverted exif-section-table">
- <tbody>
- <tr id="exposure-program-row" style="display: none;"><td>Exposure Program</td><td id="exposure-program-value"></td></tr>
- <tr id="exposure-mode-row" style="display: none;"><td>Exposure Mode</td><td id="exposure-mode-value"></td></tr>
- <tr id="metering-mode-row" style="display: none;"><td>Metering Mode</td><td id="metering-mode-value"></td></tr>
- <tr id="white-balance-row" style="display: none;"><td>White Balance</td><td id="white-balance-value"></td></tr>
- <tr id="flash-row" style="display: none;"><td>Flash</td><td id="flash-value"></td></tr>
- <tr id="scene-capture-row" style="display: none;"><td>Scene Capture Type</td><td id="scene-capture-value"></td></tr>
- </tbody>
- </table>
- </div>
- <div class="ui divider" id="shooting-mode-divider" style="display: none;"></div>
- <!-- Technical Parameters -->
- <div id="technical-params-section" style="display: none;">
- <h5>Technical Parameters</h5>
- <table class="ui very basic compact table inverted exif-section-table">
- <tbody>
- <tr id="shutter-speed-tech-row" style="display: none;"><td>Shutter Speed</td><td id="shutter-speed-tech-value"></td></tr>
- <tr id="aperture-value-row" style="display: none;"><td>Aperture Value</td><td id="aperture-value-value"></td></tr>
- <tr id="focal-plane-res-row" style="display: none;"><td>Focal Plane Resolution</td><td id="focal-plane-res-value"></td></tr>
- </tbody>
- </table>
- </div>
- <!-- No EXIF data message -->
- <div id="no-exif-message" style="display: none;">
- <p>No EXIF data available</p>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </body>
- <script>
- //Calculate image size on document ready
- $(window).on("resize ", function() {
- updateImageSizes();
-
- // Show info panel when resizing to desktop width (> 768px)
- if (window.innerWidth > 768) {
- const infoPanel = document.querySelector('.viewer-right');
- if (infoPanel && infoPanel.style.display === 'none') {
- showInfoPanel();
- }
- }
- });
- function hideInfoPanel() {
- document.querySelector('.viewer-right').style.display = 'none';
- }
- function showInfoPanel() {
- document.querySelector('.viewer-right').style.display = 'block';
- }
- // Navigation functionality
- function showPreviousImage() {
- if (typeof prePhoto !== 'undefined' && prePhoto != null) {
- showImage(prePhoto);
- }
- }
- function showNextImage() {
- if (typeof nextPhoto !== 'undefined' && nextPhoto != null) {
- showImage(nextPhoto);
- }
- }
- function updateNavigationButtons() {
- const prevBtn = document.getElementById('prev-btn');
- const nextBtn = document.getElementById('next-btn');
-
- if (prevBtn) {
- prevBtn.disabled = (typeof prePhoto === 'undefined' || prePhoto == null || !$(prePhoto).hasClass("imagecard"));
- }
- if (nextBtn) {
- nextBtn.disabled = (typeof nextPhoto === 'undefined' || nextPhoto == null || !$(nextPhoto).hasClass("imagecard"));
- }
- }
- // Zoom and Pan functionality
- let zoomLevel = 1;
- let panX = 0;
- let panY = 0;
- let isDragging = false;
- let lastMouseX = 0;
- let lastMouseY = 0;
- let initialDistance = 0;
- let initialZoom = 1;
- function resetZoom() {
- zoomLevel = 1;
- panX = 0;
- panY = 0;
- updateImageTransform();
- document.getElementById('fullImage').classList.remove('zoomed');
- }
- function updateImageTransform() {
- const img = document.getElementById('fullImage');
- const controls = document.getElementById('zoom-controls');
- const zoomDisplay = document.getElementById('zoom-level-display');
-
- if (zoomLevel > 1) {
- img.style.transform = `scale(${zoomLevel}) translate(${panX}px, ${panY}px)`;
- img.classList.add('zoomed');
- } else {
- img.style.transform = 'none';
- img.classList.remove('zoomed');
- panX = 0;
- panY = 0;
- }
-
- // Always show zoom controls and update display
- zoomDisplay.textContent = Math.round(zoomLevel * 100) + '%';
- controls.style.display = 'flex';
- }
- function zoomIn() {
- const img = document.getElementById('fullImage');
- const rect = img.getBoundingClientRect();
- const centerX = rect.left + rect.width / 2;
- const centerY = rect.top + rect.height / 2;
- let newZoom = Math.min(3, zoomLevel + 0.2);
- zoomAtPoint(newZoom, centerX, centerY);
- }
- function zoomOut() {
- const img = document.getElementById('fullImage');
- const rect = img.getBoundingClientRect();
- const centerX = rect.left + rect.width / 2;
- const centerY = rect.top + rect.height / 2;
- let newZoom = Math.max(1, zoomLevel - 0.2);
- zoomAtPoint(newZoom, centerX, centerY);
- }
- function zoomAtPoint(scale, centerX, centerY) {
- const img = document.getElementById('fullImage');
- const rect = img.getBoundingClientRect();
- const containerRect = img.parentElement.getBoundingClientRect();
-
- // Calculate the point relative to the image center
- const x = (centerX - rect.left) / rect.width - 0.5;
- const y = (centerY - rect.top) / rect.height - 0.5;
-
- // Calculate the zoom factor
- const newZoom = Math.max(1, Math.min(5, scale));
-
- if (newZoom !== zoomLevel) {
- // Adjust pan to keep the zoom point fixed
- const zoomFactor = newZoom / zoomLevel;
- panX = panX * zoomFactor - x * (newZoom - 1) * 50;
- panY = panY * zoomFactor - y * (newZoom - 1) * 50;
-
- zoomLevel = newZoom;
- constrainPan();
- updateImageTransform();
- }
- }
- function constrainPan() {
- const img = document.getElementById('fullImage');
- const rect = img.getBoundingClientRect();
- const containerRect = img.parentElement.getBoundingClientRect();
-
- const maxPanX = Math.max(0, (rect.width * zoomLevel - containerRect.width) / 2 / zoomLevel * 100);
- const maxPanY = Math.max(0, (rect.height * zoomLevel - containerRect.height) / 2 / zoomLevel * 100);
-
- panX = Math.max(-maxPanX, Math.min(maxPanX, panX));
- panY = Math.max(-maxPanY, Math.min(maxPanY, panY));
- }
- // Initialize zoom and pan when photo viewer is shown
- function initZoomPan() {
- const img = document.getElementById('fullImage');
- const container = document.querySelector('.viewer-left');
- const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
-
- // Reset zoom state
- resetZoom();
- // Only enable custom zoom/pan on desktop
- if (!isMobile) {
- let handleMouseDown = function(e) {
- if (zoomLevel > 1) {
- e.preventDefault();
- isDragging = true;
- lastMouseX = e.clientX;
- lastMouseY = e.clientY;
- img.classList.add('dragging');
- }
- };
- let handleMouseMove = function(e) {
- if (isDragging && zoomLevel > 1) {
- const deltaX = e.clientX - lastMouseX;
- const deltaY = e.clientY - lastMouseY;
-
- panX += deltaX / zoomLevel;
- panY += deltaY / zoomLevel;
-
- constrainPan();
- updateImageTransform();
-
- lastMouseX = e.clientX;
- lastMouseY = e.clientY;
- }
- };
- let handleMouseUp = function() {
- if (isDragging) {
- isDragging = false;
- img.classList.remove('dragging');
- }
- };
- // Remove existing listeners to prevent double triggering
- img.removeEventListener('mousedown', handleMouseDown);
- document.removeEventListener('mousemove', handleMouseMove);
- document.removeEventListener('mouseup', handleMouseUp);
- // Add listeners back
- img.addEventListener('mousedown', handleMouseDown);
- document.addEventListener('mousemove', handleMouseMove);
- document.addEventListener('mouseup', handleMouseUp);
- }
- }
- // Initialize zoom and pan when modal is shown
- const originalShowModal = ShowModal;
- ShowModal = function() {
- originalShowModal();
- // Wait a bit for the image to load, then initialize zoom/pan
- setTimeout(initZoomPan, 100);
- };
-
- </script>
- </html>
|