diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 328d354..7bb20ad 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -54,6 +54,7 @@ jobs: --exclude "/.npm-cache/" \ --exclude "/node_modules/" \ --exclude "/config/settings.php" \ + --exclude "/log.html" \ --exclude "/files/" \ --exclude "/geo/" \ --exclude "/gaia/" \ diff --git a/lib/Spot.php b/lib/Spot.php index f1afeed..949dfc1 100755 --- a/lib/Spot.php +++ b/lib/Spot.php @@ -325,19 +325,20 @@ class Spot extends Main $asMarkers = [...$asMessages, ...$asGeoMedias]; usort($asMarkers, function($a, $b){return $a['unix_time'] > $b['unix_time'];}); - //Spot Last Update - $asLastUpdate = array(); - $this->addTimeStamp($asLastUpdate, $this->oProject->getLastUpdate()); - $asResult = array( 'markers' => $asMarkers, - 'maps' => $this->oMap->getProjectMaps($this->oProject->getProjectId()), - 'last_update' => $asLastUpdate + 'maps' => $this->oMap->getProjectMaps($this->oProject->getProjectId()) ); return $bInternal?$asResult:self::getJsonResult(true, '', $asResult); } + public function getLastUpdate() { + $asLastUpdate = array(); + $this->addTimeStamp($asLastUpdate, $this->oProject->getLastUpdate()); + return self::getJsonResult(true, '', $asLastUpdate); + } + public function subscribe($sEmail, $sNickName) { $asResult = $this->oUser->addUser($sEmail, $this->oLang->getLanguage(), date_default_timezone_get(), $sNickName); $asUserInfo = $this->oUser->getUserInfo(); diff --git a/lib/index.php b/lib/index.php index df67ef2..7fd6666 100644 --- a/lib/index.php +++ b/lib/index.php @@ -42,6 +42,9 @@ if($sAction!='') case 'markers': $sResult = $oSpot->getMarkers(); break; + case 'last_update': + $sResult = $oSpot->getLastUpdate(); + break; case 'geojson': $sResult = $oSpot->getProjectGeoJson(); break; diff --git a/package-lock.json b/package-lock.json index c037348..4efb597 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2131,12 +2131,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.6.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.2.tgz", - "integrity": "sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw==", + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", "license": "MIT", "dependencies": { - "undici-types": "~7.19.0" + "undici-types": ">=7.24.0 <7.24.7" } }, "node_modules/@types/retry": { @@ -2680,9 +2680,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.29", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz", - "integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==", + "version": "2.10.31", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.31.tgz", + "integrity": "sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -2760,9 +2760,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001792", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", - "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==", + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", "funding": [ { "type": "opencollective", @@ -2797,15 +2797,15 @@ } }, "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "license": "MIT", "dependencies": { - "readdirp": "^4.0.1" + "readdirp": "^5.0.0" }, "engines": { - "node": ">= 14.16.0" + "node": ">= 20.19.0" }, "funding": { "url": "https://paulmillr.com/funding/" @@ -2992,9 +2992,9 @@ } }, "node_modules/css-loader/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3084,9 +3084,9 @@ "license": "ISC" }, "node_modules/electron-to-chromium": { - "version": "1.5.353", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.353.tgz", - "integrity": "sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w==", + "version": "1.5.361", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.361.tgz", + "integrity": "sha512-Q6Hts7N9FnJc5LeGRINFvLhCI9xZmNtTDe5ZbcVezQz7cU4a8Aua3GH1b8J2XY8Al9PF+OCwYqhgsOOheMdvkA==", "license": "ISC" }, "node_modules/emojis-list": { @@ -3099,9 +3099,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.21.5", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.5.tgz", - "integrity": "sha512-mLCNbrQli11K1ySUmuNt4ZUB3OpGIDq4q2vTBTf5cL2lpsRjI9QKqSD0ndjW8FyvcW/Jj46gMe9syyHAsvMa/A==", + "version": "5.21.6", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", + "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -3758,9 +3758,9 @@ } }, "node_modules/kdbush": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", - "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.1.0.tgz", + "integrity": "sha512-e9vurzrXJQrFX6ckpHP3bvj5l+9CnYzkxDNnNQ1h2QTqdWsUAJgXiKdGNcOa1EY85dU8KbQ+z/FdQdB7P+9yfQ==", "license": "ISC" }, "node_modules/kind-of": { @@ -4015,10 +4015,13 @@ "optional": true }, "node_modules/node-releases": { - "version": "2.0.38", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", - "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", - "license": "MIT" + "version": "2.0.45", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.45.tgz", + "integrity": "sha512-iIbHXV9eBB2nB0wa7oTsrrXq+qQt+9SIlx9AX3T96YgobtEQfis5n6TJ6vV+3QP8DwdriEAcGhARaFCu37peBg==", + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/normalize-path": { "version": "3.0.0", @@ -4331,9 +4334,9 @@ } }, "node_modules/postcss": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", - "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "funding": [ { "type": "opencollective", @@ -4350,7 +4353,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -4461,9 +4464,9 @@ "license": "ISC" }, "node_modules/preact": { - "version": "10.29.1", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.1.tgz", - "integrity": "sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg==", + "version": "10.29.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.2.tgz", + "integrity": "sha512-7tNmwg/7mzzAoB/8kSg6Hl37JraAZw3Z3A0JSY7VXlZwo82Xn0G7wKbNNs2qoF4ZEEsQGTwDAroNdqKs1ofJxQ==", "license": "MIT", "funding": { "type": "opencollective", @@ -4483,12 +4486,12 @@ "license": "ISC" }, "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", "license": "MIT", "engines": { - "node": ">= 14.18.0" + "node": ">= 20.19.0" }, "funding": { "type": "individual", @@ -4661,12 +4664,12 @@ } }, "node_modules/sass": { - "version": "1.99.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.99.0.tgz", - "integrity": "sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q==", + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.100.0.tgz", + "integrity": "sha512-B5j0rYMlinhhOo9tjQebMVVn0TfyXAF+wB3b2ggZUuJ/is/Y+7+JGjirAMxHZ9Z3hIP98NPfamlAkBHa1lAaXQ==", "license": "MIT", "dependencies": { - "chokidar": "^4.0.0", + "chokidar": "^5.0.0", "immutable": "^5.1.5", "source-map-js": ">=0.6.2 <2.0.0" }, @@ -4674,7 +4677,7 @@ "sass": "sass.js" }, "engines": { - "node": ">=14.0.0" + "node": ">=20.19.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" @@ -4923,9 +4926,9 @@ } }, "node_modules/terser": { - "version": "5.47.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.47.1.tgz", - "integrity": "sha512-tPbLXTI6ohPASb/1YViL428oEHu6/qv1OxqYnfaonVCFHqx4+wCd95pHrQWsL5X4pl90CTyW9piSAsS2L0VoMw==", + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.48.0.tgz", + "integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==", "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -5035,9 +5038,9 @@ "license": "0BSD" }, "node_modules/undici-types": { - "version": "7.19.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", - "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { @@ -5204,9 +5207,9 @@ } }, "node_modules/webpack": { - "version": "5.107.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.107.0.tgz", - "integrity": "sha512-PSxeHk/dmLYZlnTU+vL1Gej6Evg5RNtl3flhxBresfznFnzxinHMzHKloHnywM/3ouQv7/AlZCswWDIkNSggUA==", + "version": "5.107.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.107.1.tgz", + "integrity": "sha512-mvdIWxj/H6QsfgDdH9djne3a5dYcmEmtsXGESkypaGN5jXjF/b+9KDlmTDQ2TKlFUeA2fI9Y65kihD30JOdB+Q==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.8", diff --git a/src/components/project.vue b/src/components/project.vue index 633c416..43ef053 100644 --- a/src/components/project.vue +++ b/src/components/project.vue @@ -2,31 +2,29 @@ import 'maplibre-gl/dist/maplibre-gl.css'; import { Map, Marker, LngLatBounds, LngLat, Popup, ScaleControl } from 'maplibre-gl'; import { createApp } from 'vue'; -import Simplebar from 'simplebar-vue'; import Lightbox from '@scripts/lightbox'; import SpotIcon from '@components/spotIcon'; import SpotIconStack from '@components/spotIconStack'; -import ProjectPost from '@components/projectPost'; import ProjectPopup from '@components/projectPopup'; -import ProjectNewsletter from '@components/projectNewsletter'; +import ProjectFeed from '@components/projectFeed'; +import ProjectSettings from '@components/projectSettings'; export default { components: { SpotIcon, - ProjectPost, - ProjectNewsletter, - Simplebar + ProjectFeed, + ProjectSettings }, data() { return { - feed: {loading:false, updatable:true, outOfData:false, refIdFirst:0, refIdLast:0, firstChunk:true}, - refreshRate: 60, - lastUpdate: { unix_time: 0, relative_time: '', formatted_time: ''}, - feedPanelOpen: false, - feedSwipe: {x: null, y: null}, - settingsPanelOpen: false, + panels: { + feedOpen: false, + settingsOpen: false + }, + feed: null, + settings: null, track: null, markers: [], markerProps: { @@ -35,29 +33,24 @@ export default { video: {mainClasses: 'media', iconMain: 'marker', iconSub: 'video'}, message: {mainClasses: 'message', iconMain: 'marker', iconSub: 'footprint', iconSubTransform: 'rotate-270'} }, - currProject: null, + project: null, modeHisto: null, - posts: [], baseMaps: [], baseMap: null, map: null, mapInitializing: false, + markerHeight: 32, //FIXME + mapPadding: 16 + 32, //1rem + marker height lightbox: null, hikes: { colors: {}, width: 4 }, popup: {content: null, element: null}, - overview: {id: 0, codename:'overview', name: this.lang.get('project.overview')} + overview: {id: 0, codename:'overview', name: this.lang.get('project.overview')}, }; }, computed: { - projectClasses() { - return [ - this.feedPanelOpen?'with-feed':'', - this.settingsPanelOpen?'with-settings':'' - ].filter(n => n).join(' '); - }, projectOptions() { return [ this.overview, @@ -75,7 +68,7 @@ export default { 'hash.items.0'(newProjectCodename, oldProjectCodename) { if(newProjectCodename != oldProjectCodename) { this.hash.items = [newProjectCodename]; - this.toggleSettingsPanel(false, 'none'); + this.settings.toggle(false, 0); this.init(); } } @@ -113,11 +106,8 @@ export default { }; //Reset values - this.setFeedUpdateTimer(-1); - this.feed = {loading:false, updatable:true, outOfData:false, refIdFirst:0, refIdLast:0, firstChunk:true}; - this.posts = []; this.track = null; - this.currProject = null; + this.project = null; this.removeMapContent(); //Build Map @@ -128,28 +118,27 @@ export default { }, quit() { this.lightbox.end(); - this.$refs.feedSimpleBar?.scrollElement.removeEventListener('scroll', this.onFeedScroll); - this.setFeedUpdateTimer(-1); this.removeMap(); }, async initOverview() { this.modeHisto = true; this.hash.items = [this.overview.codename]; - this.toggleFeedPanel(false, 'none'); + this.feed.toggle(false, 0); await this.initOverviewMap(); }, - async initProject(iProjectId) { - this.currProject = this.projects[iProjectId]; - this.modeHisto = (this.currProject.mode == this.consts.modes.histo); + async initProject(sProjectCodeName) { + this.project = this.projects[sProjectCodeName]; + this.modeHisto = (this.project.mode == this.consts.modes.histo); + await this.$nextTick(); await Promise.all([ - this.initFeed(), + this.feed.init(), this.initProjectMap() ]); //Direct link post action - if(this.hash.items.length == 3) await this.findPost(this.hash.items[1], this.hash.items[2]); + if(this.hash.items.length == 3) await this.feed.findPost(this.hash.items[1], this.hash.items[2]); }, initLightbox() { if(!this.lightbox) { @@ -162,11 +151,11 @@ export default { resizeDuration: 400, hasVideo: true, onMediaChange: async (oMedia) => { - this.hash.items = [this.currProject.codename, 'media', oMedia.id]; + this.hash.items = [this.project.codename, 'media', oMedia.id]; if(oMedia.set == 'post-medias') { - this.goToPost('media', oMedia.id)?.panMapToMarker(); + (await this.feed.goToPost('media', oMedia.id))?.panMapToMarker(); if(!this.lightbox.hasMediaAfterCurrent()) { - await this.getNextFeed(); + await this.feed.getNextFeed(); await this.$nextTick(); this.lightbox.refreshAlbum(); } @@ -176,39 +165,19 @@ export default { }); } }, - async initFeed() { - await this.$nextTick(); - - //Simplebar event - this.$refs.feedSimpleBar?.scrollElement.removeEventListener('scroll', this.onFeedScroll); - this.$refs.feedSimpleBar?.scrollElement.addEventListener('scroll', this.onFeedScroll); - - this.toggleFeedPanel(!this.isMobile(), 'none'); - - //Get first posts batch - await this.getNextFeed(); - if(this.$refs.feedSimpleBar) this.$refs.feedSimpleBar.scrollElement.scrollTop = 0; - - //Start auto-update - if(!this.modeHisto) this.setFeedUpdateTimer(this.refreshRate); - }, async initProjectMap() { [ - { - maps: this.baseMaps, - markers: this.markers, - last_update: this.lastUpdate - }, + {maps: this.baseMaps, markers: this.markers}, this.track ] = await Promise.all([ - this.api.get('markers', {id_project: this.currProject.id}), - this.api.get('geojson', {id_project: this.currProject.id}) + this.api.get('markers', {id_project: this.project.id}), + this.api.get('geojson', {id_project: this.project.id}) ]); await this.initMap({ setCamera: () => { this.map.fitBounds(this.getInitialMapBounds(), { - padding: 20, + padding: this.mapPadding, animate: false, maxZoom: 15 }); @@ -229,7 +198,7 @@ export default { //Center on default project const oDefaultProject = this.projects.getDefaultProject(); - //Adapt zoom to see whole planet + //Get Map / Canvas size const $Canvas = this.map.getCanvas(); const oMapBounds = this.map.getContainer().getBoundingClientRect(); @@ -248,7 +217,7 @@ export default { //Build map if(!this.map) this.addMap(); this.updateMapPadding(); - setCamera(); + setCamera(); //Force wait for load event await new Promise((resolve) => { @@ -363,7 +332,7 @@ export default { 'source': 'track', 'paint': { 'line-opacity': 0, - 'line-width': this.hikes.width + 20 + 'line-width': this.hikes.width + this.mapPadding } }); this.map.on('click', 'track-hitbox', this.openTrackPopup); @@ -435,7 +404,7 @@ export default { this.openPopup({ lnglat: [oProject.longitude, oProject.latitude], options: oProject, - offset: [0, -32] //FIXME + offset: [0, -1 * this.markerHeight] }); }, openMarkerPopup(iMarkerId, sMarkerType) { @@ -443,7 +412,7 @@ export default { this.openPopup({ lnglat: [oMarker.longitude, oMarker.latitude], options: oMarker, - offset: [0, -32] //FIXME + offset: [0, -1 * this.markerHeight] }); }, openTrackPopup(oEvent) { @@ -466,7 +435,7 @@ export default { this.popup.content = createApp(ProjectPopup, { options: options, - project: this.currProject + project: this.project }); this.popup.content .provide('lang', this.lang) @@ -501,7 +470,7 @@ export default { oBounds.extend(new LngLat(oHashMarker.longitude, oHashMarker.latitude)); } else if( //Blog Mode: Fit to last message - this.currProject.mode == this.consts.modes.blog && + this.project.mode == this.consts.modes.blog && this.markers.length > 0 ) { let oLastMsg = this.markers.at(-1); @@ -510,9 +479,7 @@ export default { else { //Pre/Histo Mode: Fit to track for(const iFeatureId in this.track.features) { oBounds = this.track.features[iFeatureId].geometry.coordinates.reduce( - (bounds, coord) => { - return bounds.extend(coord); - }, + (bounds, coord) => bounds.extend(coord), oBounds ); } @@ -520,110 +487,9 @@ export default { return oBounds; }, - - async findPost(sPostType, iPostId) { - let vPost = this.goToPost(sPostType, iPostId); - if(vPost) { - await vPost.executeMainAction(0); - return vPost; - } - else if(!this.feed.outOfData) { - await this.getNextFeed(); - return this.findPost(sPostType, iPostId); - } - else console.log('Missing element ID "'+iPostId+'" of type "'+sPostType+'"'); - return null; - }, - goToPost(sPostType, iPostId) { - let bFound = false; - let avPosts = this.$refs.posts.filter((post) => {return post.postId == sPostType+'-'+iPostId;}); - if(avPosts.length > 0) { - let vPost = avPosts[0]; - this.$refs.feedSimpleBar.scrollElement.scrollTop += Math.round( - vPost.$el.getBoundingClientRect().top - + window.pageYOffset - - parseFloat(getComputedStyle(this.$refs.feedSimpleBar.$el).paddingTop) - ); - - return vPost; - } - }, - async getNextFeed() { - if(!this.feed.outOfData && !this.feed.loading) { - //Get next chunk - this.feed.loading = true; - let aoData = await this.api.get('next_feed', {id_project: this.currProject.id, id: this.feed.refIdLast}); - let iPostCount = Object.keys(aoData.feed).length; - - //Update pointers - this.feed.outOfData = (iPostCount < this.consts.chunk_size); - if(iPostCount > 0) { - this.feed.refIdLast = aoData.ref_id_last; - if(this.feed.firstChunk) this.feed.refIdFirst = aoData.ref_id_first; - } - - //Add posts - this.posts.push(...aoData.feed); - - this.feed.loading = false; - this.feed.firstChunk = false; - } - - return true; - }, - onFeedScroll(oEvent) { - const box = oEvent.currentTarget - const content = box.querySelector('.simplebar-content') - - if ((box.scrollTop + box.clientHeight) / (content?.offsetHeight || 1) >= 0.8) this.getNextFeed(); - }, - onFeedTouchStart(oEvent) { - if(!this.isMobile() || !this.feedPanelOpen || oEvent.touches.length != 1) return; - - const oTouch = oEvent.touches[0]; - this.feedSwipe = {x: oTouch.clientX, y: oTouch.clientY}; - }, - onFeedTouchEnd(oEvent) { - const oTouch = oEvent.changedTouches[0]; - if(!oTouch || this.feedSwipe.x === null) return; - - const iDeltaX = oTouch.clientX - this.feedSwipe.x; - const iDeltaY = oTouch.clientY - this.feedSwipe.y; - - if(iDeltaX > 80 && Math.abs(iDeltaX) > Math.abs(iDeltaY) * 1.5) { - this.toggleFeedPanel(); - } - - this.feedSwipe = {x: null, y: null}; - }, - setFeedUpdateTimer(iSeconds) { - if(typeof this.feedTimer != 'undefined') clearTimeout(this.feedTimer); - if(iSeconds >= 0) this.feedTimer = setTimeout(this.checkNewFeed, iSeconds * 1000); - }, - async checkNewFeed() { - let aoData = await this.api.get('new_feed', {id_project: this.currProject.id, id: this.feed.refIdFirst}); - const aoFeed = aoData.feed || []; - const aoMarkers = aoData.markers || []; - - if(aoFeed.length > 0) { - //Update pointer - this.feed.refIdFirst = aoData.ref_id_first; - - //Add new posts - this.posts.unshift(...aoFeed); - } - - //Add new Markers - if(aoMarkers.length > 0) { - this.markers.push(...aoMarkers); - aoMarkers.forEach(this.addMarker); - } - - //Message Last Update - this.lastUpdate = aoData.last_update; - - //Reschedule - this.setFeedUpdateTimer(this.refreshRate); + addNewMarkers(aoMarkers) { //FIXME Use its own marker update API + this.markers.push(...aoMarkers); + aoMarkers.forEach(this.addMarker); }, panToBetweenPanels(oLngLat, iZoom, iAnimDuration=500) { return new Promise((resolve) => { @@ -643,10 +509,10 @@ export default { getMapPadding() { let bIsMobile = this.isMobile(); return { - top: 0, - bottom: 0, - left: (!bIsMobile && this.settingsPanelOpen)?this.$refs.settings.getBoundingClientRect().width:0, - right: (!bIsMobile && this.feedPanelOpen)?this.$refs.feed.getBoundingClientRect().width:0 + top: this.mapPadding, + bottom: this.mapPadding, + left: this.mapPadding + ((!bIsMobile && this.panels.settingsOpen && this.settings)?this.settings.getWidth():0), + right: this.mapPadding + ((!bIsMobile && this.panels.feedOpen && this.feed)?this.feed.getWidth():0) }; }, updateMapPadding(iDuration=0) { @@ -660,58 +526,31 @@ export default { isMarkerVisible(oLngLat){ return !!this.map && this.map.getBounds().contains(oLngLat); }, - getGoogleMapsLink(asInfo) { - return $('', { - href:'https://www.google.com/maps/place/'+asInfo.lat_dms+'+'+asInfo.lon_dms+'/@'+asInfo.latitude+','+asInfo.longitude+',10z', - title: this.lang.get('map.see_on_google'), - target: '_blank', - rel: 'noreferrer noopener' - }).text(asInfo.lat_dms+' '+asInfo.lon_dms); - }, - toggleFeedPanel(bShow, sMapAction) { - let bOldValue = this.feedPanelOpen; - this.feedPanelOpen = (typeof bShow === 'object' || typeof bShow === 'undefined')?(!this.feedPanelOpen):bShow; + onPanelToggle(sPanel, bNewValue, iAnimDuration=500) { + const sPanelKey = sPanel + 'Open'; + let bOldValue = this.panels[sPanelKey]; + this.panels[sPanelKey] = bNewValue; - if(bOldValue != this.feedPanelOpen && !this.isMobile() && this.map) { - sMapAction = sMapAction || 'panTo'; - switch(sMapAction) { - case 'none': - this.updateMapPadding(); - break; - case 'panTo': - this.updateMapPadding(500); - break; - case 'panToInstant': - this.updateMapPadding(); - break; - } + if(bOldValue != bNewValue) { + //Adjust map center + if(!this.isMobile() && this.map) this.updateMapPadding(iAnimDuration); + + //Open Close panels + this.$el.classList.toggle('with-'+sPanel); } }, - toggleSettingsPanel(bShow, sMapAction) { - let bOldValue = this.settingsPanelOpen; - this.settingsPanelOpen = (typeof bShow === 'object' || typeof bShow === 'undefined')?(!this.settingsPanelOpen):bShow; - - if(bOldValue != this.settingsPanelOpen && !this.isMobile() && this.map) { - sMapAction = sMapAction || 'panTo'; - switch(sMapAction) { - case 'none': - this.updateMapPadding(); - break; - case 'panTo': - this.updateMapPadding(500); - break; - case 'panToInstant': - this.updateMapPadding(); - break; - } - } + setFeed(vPanel) { + this.feed = vPanel; + }, + setSettings(vPanel) { + this.settings = vPanel; } } } diff --git a/src/components/projectFeed.vue b/src/components/projectFeed.vue new file mode 100644 index 0000000..32d5e7c --- /dev/null +++ b/src/components/projectFeed.vue @@ -0,0 +1,216 @@ + + + diff --git a/src/components/projectPost.vue b/src/components/projectPost.vue index 4bd9825..3267304 100644 --- a/src/components/projectPost.vue +++ b/src/components/projectPost.vue @@ -38,6 +38,7 @@ focusZoomLevel: 15 }; }, + inject: ['api', 'lang', 'project', 'feed', 'user', 'map', 'hash', 'consts', 'isMobile'], computed: { postClass() { let sHeaderLess = this.options.headerless?' headerless':''; @@ -59,10 +60,10 @@ return this.mouseOverDrill?null:'footprint'; }, anchorLink() { - return '#'+[this.hash.page, this.project.currProject.codename, this.options.type, this.options.id].join(this.consts.hash_sep); + return '#'+[this.hash.page, this.project.project.codename, this.options.type, this.options.id].join(this.consts.hash_sep); }, modeHisto() { - return (this.project?.currProject?.mode == this.consts.modes.histo); + return (this.project?.project?.mode == this.consts.modes.histo); }, relTime() { return this.modeHisto?(this.options.formatted_time || '').substr(0, 10):this.options.relative_time; @@ -90,7 +91,6 @@ return new LngLat(oRelatedMarker.longitude, oRelatedMarker.latitude); } }, - inject: ['api', 'lang', 'project', 'user', 'map', 'hash', 'consts', 'isMobile'], methods: { copyAnchor() { copyTextToClipboard(this.consts.server+this.anchorLink); @@ -106,8 +106,8 @@ this.popupRequested = true; - if(this.isMobile()) this.project.toggleFeedPanel(false, 'panToInstant'); - this.hash.items = [this.project.currProject.codename, this.options.type, this.options.id]; + if(this.isMobile()) this.feed.toggle(false); + this.hash.items = [this.project.project.codename, this.options.type, this.options.id]; return this.map.panToBetweenPanels(this.relatedMarkerLatLng, this.focusZoomLevel, iAnimDuration).then(() => { this.openMarkerPopup(); @@ -137,14 +137,14 @@ this.api.get( 'add_post', { - id_project: this.project.currProject.id, + id_project: this.project.project.id, name: this.user.name, content: this.postMessage } ) .then(() => { this.postMessage = ''; - this.project.checkNewFeed(); + this.feed.checkNewFeed(); this.sending = false; }) .catch((sDesc) => { diff --git a/src/components/projectSettings.vue b/src/components/projectSettings.vue new file mode 100644 index 0000000..fbf83f0 --- /dev/null +++ b/src/components/projectSettings.vue @@ -0,0 +1,142 @@ + + + diff --git a/src/styles/_mobile.scss b/src/styles/_mobile.scss index e8d39c6..d312867 100644 --- a/src/styles/_mobile.scss +++ b/src/styles/_mobile.scss @@ -10,7 +10,7 @@ display: none !important; } - #projects { + .projects { .map-container { width: calc(#{$panel-width}); max-width: calc(#{$panel-width}); diff --git a/src/styles/_page.project.feed.scss b/src/styles/_page.project.panel.feed.scss similarity index 100% rename from src/styles/_page.project.feed.scss rename to src/styles/_page.project.panel.feed.scss diff --git a/src/styles/_page.project.panel.scss b/src/styles/_page.project.panel.scss index ebb243d..bcd15c0 100644 --- a/src/styles/_page.project.panel.scss +++ b/src/styles/_page.project.panel.scss @@ -6,7 +6,7 @@ $panel-width: 30vw; $panel-width-max: "400px + 3 * #{var.$block-spacing}"; $panel-actual-width: min($panel-width, #{$panel-width-max}); -#projects { +.projects { &.with-feed, &.with-settings { #title { max-width: calc(100vw - var.$block-spacing - $panel-actual-width - (var.$button-width + var.$block-spacing * 2) * 2); diff --git a/src/styles/_page.project.settings.scss b/src/styles/_page.project.panel.settings.scss similarity index 100% rename from src/styles/_page.project.settings.scss rename to src/styles/_page.project.panel.settings.scss diff --git a/src/styles/_page.project.scss b/src/styles/_page.project.scss index 848637c..58d36e9 100644 --- a/src/styles/_page.project.scss +++ b/src/styles/_page.project.scss @@ -3,10 +3,10 @@ @use '@styles/page.project.map' as map; @use '@styles/page.project.panel' as panel; -@use '@styles/page.project.feed' as feed; -@use '@styles/page.project.settings' as settings; +@use '@styles/page.project.panel.feed' as feed; +@use '@styles/page.project.panel.settings' as settings; -#projects { +.projects { --space: #{color.$space}; --horizon: #{color.$horizon}; --track-main: #{color.$main-track};