diff --git a/lib/Project.php b/lib/Project.php
index e3a9c25..04754de 100644
--- a/lib/Project.php
+++ b/lib/Project.php
@@ -120,6 +120,7 @@ class Project extends PhpObject {
public function getProjects($iProjectId=0) {
$bSpecificProj = ($iProjectId > 0);
+ $sDefaultProjectCodeName = $this->getProjectCodeName();
$asInfo = array(
'select'=> array(
Db::getId(self::PROJ_TABLE)." AS id",
@@ -145,6 +146,7 @@ class Project extends PhpObject {
//$asProject['geofilepath'] = Spot::addTimestampToFilePath(GeoJson::getDistFilePath($sCodeName));
$asProject['gpxfilepath'] = Spot::addTimestampToFilePath(Gpx::getDistFilePath($sCodeName));
$asProject['codename'] = $sCodeName;
+ $asProject['default'] = ($sCodeName == $sDefaultProjectCodeName);
}
return $bSpecificProj?$asProject:$asProjects;
}
diff --git a/lib/Spot.php b/lib/Spot.php
index 9acccce..bfc500b 100755
--- a/lib/Spot.php
+++ b/lib/Spot.php
@@ -173,16 +173,16 @@ class Spot extends Main
return parent::getMainPage(
array(
- 'vars' => array(
- 'default_project_codename' => $this->oProject->getProjectCodeName(),
- 'projects' => $this->oProject->getProjects(),
- 'user' => $this->oUser->getUserInfo()
- ),
+ 'projects' => $this->oProject->getProjects(),
+ 'user' => $this->oUser->getUserInfo(),
'consts' => array(
'modes' => Project::MODES,
'clearances' => User::CLEARANCES,
'default_timezone' => Settings::TIMEZONE,
- 'chunk_size' => self::FEED_CHUNK_SIZE
+ 'chunk_size' => self::FEED_CHUNK_SIZE,
+ 'hash_sep' => '-',
+ 'title' => 'Spotty',
+ 'default_page' => 'project'
)
),
self::MAIN_PAGE,
diff --git a/src/Spot.vue b/src/Spot.vue
index 3422e8b..6e20722 100644
--- a/src/Spot.vue
+++ b/src/Spot.vue
@@ -13,59 +13,59 @@ export default {
data() {
return {
hash: {page: '', items: []},
- consts: this.spot.consts,
- user: this.spot.vars('user'),
- routes: aoRoutes
+ consts: this.appConfig.consts,
+ mobile: false
};
},
provide() {
return {
hash: this.hash,
- projects: this.spot.vars('projects'),
consts: this.consts,
- user: this.user
+ isMobile: () => this.isMobile()
};
},
computed: {
route() {
- return this.routes[this.hash.page];
+ return aoRoutes[this.hash.page];
},
hashSnapshot() {
return JSON.stringify(this.hash);
}
},
- inject: ['spot'],
+ inject: ['appConfig'],
created() {
- //User
- this.user.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || this.consts.default_timezone;
-
//Set initial page
let asInitHash = this.getBrowserHash();
- if(!asInitHash.page) asInitHash.page = this.spot.consts.default_page;
+ if(!asInitHash.page) asInitHash.page = this.consts.default_page;
this.setVarHash(asInitHash);
},
mounted() {
//Catch browser hash change
window.addEventListener('hashchange', this.onBrowserHashChange);
+ window.addEventListener('resize', this.updateMobile);
+ this.updateMobile();
},
watch: {
hashSnapshot(jNewHash, jOldHash) {
const asNewHash = JSON.parse(jNewHash);
- const asOldHash = JSON.parse(jOldHash);
- this.spot.vars('page', this.hash.page); //FIXME remove
-
//Sync variable -> #hash
if(asNewHash != this.getBrowserHash()) {
this.setBrowserHash(asNewHash.page, asNewHash.items);
}
- //Same Page change
- if(asNewHash != asOldHash && asNewHash.page == asOldHash.page) {
- this.spot.onSamePageMove(asNewHash, asOldHash);
- }
+ this.setPageTitle(asNewHash.page);
}
},
methods: {
+ isMobile() {
+ return this.mobile;
+ },
+ updateMobile() {
+ this.mobile = getComputedStyle(this.$refs.mobile).display !== 'none';
+ },
+ setPageTitle(sTitle) {
+ document.title = this.consts.title + ' - ' + sTitle.trim();
+ },
setVarHash(asHash) {
this.hash.page = asHash.page || '';
this.hash.items = Array.isArray(asHash.items) ? [...asHash.items.filter(n => n)] : [];
@@ -73,22 +73,22 @@ export default {
onBrowserHashChange() { //Sync #hash -> variable
let asHash = this.getBrowserHash();
if(asHash != this.hash) this.setVarHash(asHash);
- this.spot.vars('page', this.hash.page); //FIXME remove
},
getBrowserHash() {
let sHash = window.location.hash.slice(1);
- let asHash = sHash.split(this.spot.consts.hash_sep).filter(n => n);
+ let asHash = sHash.split(this.consts.hash_sep).filter(n => n);
let sPage = asHash.shift() || '';
return {page: sPage, items: asHash};
},
setBrowserHash(sPage = '', asItems = []) {
if(typeof asItems == 'string' && asItems != '') asItems = [asItems];
- const sItems = (asItems.length > 0)?(this.spot.consts.hash_sep + asItems.join(this.spot.consts.hash_sep)):'';
+ const sItems = (asItems.length > 0)?(this.consts.hash_sep + asItems.join(this.consts.hash_sep)):'';
window.location.hash = '#' + sPage + sItems;
}
},
beforeUnmount() {
- window.removeEventListener('hashchange', this.onBrowserHashChange)
+ window.removeEventListener('hashchange', this.onBrowserHashChange);
+ window.removeEventListener('resize', this.updateMobile);
}
}
@@ -96,5 +96,5 @@ export default {
-
+
diff --git a/src/components/admin.vue b/src/components/admin.vue
index 9b0e082..1203789 100644
--- a/src/components/admin.vue
+++ b/src/components/admin.vue
@@ -9,38 +9,37 @@ export default {
SpotButton,
AdminInput
},
- inject: ['spot', 'lang'],
- data() {
- return {
- elems: {},
- feedbacks: []
- };
- },
- mounted() {
- this.setEvents();
- this.setProjects();
- },
+ inject: ['api', 'lang'],
+ data() {
+ return {
+ elems: {},
+ feedbacks: [],
+ saveTimer: null
+ };
+ },
+ beforeUnmount() {
+ if(this.saveTimer) clearTimeout(this.saveTimer);
+ },
+ mounted() {
+ this.setProjects();
+ },
methods: {
l(id) {
- return this.lang.get(id);
+ return this.lang.get(id);
},
- setEvents() {
- this.spot.addPage('admin', {
- onFeedback: (sType, sMsg, asContext) => {
- delete asContext.a;
- delete asContext.t;
- sMsg += ' (';
- for(const [sKey, sElem] of Object.entries(asContext)) {
- sMsg += sKey+'='+sElem+' / ' ;
- }
- sMsg = sMsg.slice(0, -3)+')';
+ addFeedback(sType, sMsg, asContext = {}) {
+ delete asContext.a;
+ delete asContext.t;
+ sMsg += ' (';
+ for(const [sKey, sElem] of Object.entries(asContext)) {
+ sMsg += sKey+'='+sElem+' / ' ;
+ }
+ sMsg = sMsg.slice(0, -3)+')';
- this.feedbacks.push({type:sType, msg:sMsg});
- }
- });
+ this.feedbacks.push({type:sType, msg:sMsg});
},
async setProjects() {
- let aoElemTypes = await this.spot.get2('admin_get');
+ let aoElemTypes = await this.api.get('admin_get');
for(const [sType, aoElems] of Object.entries(aoElemTypes)) {
this.elems[sType] = {};
@@ -51,17 +50,17 @@ export default {
}
},
createElem(sType) {
- this.spot.get2('admin_create', {type: sType})
+ this.api.get('admin_create', {type: sType})
.then((aoNewElemTypes) => {
for(const [sType, aoNewElems] of Object.entries(aoNewElemTypes)) {
for(const [iKey, oNewElem] of Object.entries(aoNewElems)) {
oNewElem.type = sType;
this.elems[sType][oNewElem.id] = oNewElem;
- this.spot.onFeedback('success', this.lang.get('admin_create_success'), {'create':sType});
+ this.addFeedback('success', this.lang.get('admin_create_success'), {'create':sType});
}
}
})
- .catch((sMsg) => {this.spot.onFeedback('error', sMsg, {'create':sType});});
+ .catch((sMsg) => {this.addFeedback('error', sMsg, {'create':sType});});
},
deleteElem(oElem) {
const asInputs = {
@@ -69,23 +68,20 @@ export default {
id: oElem.id
};
- this.spot.get(
- 'admin_delete',
- (asData) => {
- delete this.elems[asInputs.type][asInputs.id];
- this.spot.onFeedback('success', this.lang.get('admin_delete_success'), asInputs);
- },
- asInputs,
- (sError) => {
- this.spot.onFeedback('error', sError, asInputs);
- }
- );
- },
- updateElem(oElem, oEvent) {
- if(typeof this.spot.tmp('wait') != 'undefined') clearTimeout(this.spot.tmp('wait'));
-
- let sOldVal = this.elems[oElem.type][oElem.id][oEvent.target.name];
- let sNewVal = oEvent.target.value;
+ this.api.get('admin_delete', asInputs)
+ .then((asData) => {
+ delete this.elems[asInputs.type][asInputs.id];
+ this.addFeedback('success', this.lang.get('admin_delete_success'), asInputs);
+ })
+ .catch((sError) => {
+ this.addFeedback('error', sError, asInputs);
+ });
+ },
+ updateElem(oElem, oEvent) {
+ if(this.saveTimer) clearTimeout(this.saveTimer);
+
+ let sOldVal = this.elems[oElem.type][oElem.id][oEvent.target.name];
+ let sNewVal = oEvent.target.value;
if(sOldVal != sNewVal) {
let asInputs = {
type: oElem.type,
@@ -94,25 +90,25 @@ export default {
value: sNewVal
};
- this.spot.get2('admin_set', asInputs)
+ this.api.get('admin_set', asInputs)
.then((asData) => {
this.elems[oElem.type][oElem.id][oEvent.target.name] = sNewVal;
- this.spot.onFeedback('success', this.lang.get('admin_save_success'), asInputs);
+ this.addFeedback('success', this.lang.get('admin_save_success'), asInputs);
})
.catch((sError) => {
oEvent.target.value = sOldVal;
- this.spot.onFeedback('error', sError, asInputs);
+ this.addFeedback('error', sError, asInputs);
});
}
- },
- queue(oElem, oEvent) {
- if(typeof this.spot.tmp('wait') != 'undefined') clearTimeout(this.spot.tmp('wait'));
- this.spot.tmp('wait', setTimeout(() => {this.updateElem(oElem, oEvent);}, 2000));
- },
+ },
+ queue(oElem, oEvent) {
+ if(this.saveTimer) clearTimeout(this.saveTimer);
+ this.saveTimer = setTimeout(() => {this.updateElem(oElem, oEvent);}, 2000);
+ },
updateProject() {
- this.spot.get2('update_project')
- .then((asData, sMsg) => {this.spot.onFeedback('success', sMsg, {'update':'project'});})
- .catch((sMsg) => {this.spot.onFeedback('error', sMsg, {'update':'project'});});
+ this.api.get('update_project')
+ .then((asData, sMsg) => {this.addFeedback('success', sMsg, {'update':'project'});})
+ .catch((sMsg) => {this.addFeedback('error', sMsg, {'update':'project'});});
}
}
}
@@ -232,4 +228,4 @@ export default {
{{ feedback.msg }}
-
+
diff --git a/src/components/project.vue b/src/components/project.vue
index 0845de1..5fb2ec5 100644
--- a/src/components/project.vue
+++ b/src/components/project.vue
@@ -22,7 +22,7 @@ export default {
},
data() {
return {
- server: this.spot.consts.server,
+ server: this.consts.server,
feed: {loading:false, updatable:true, outOfData:false, refIdFirst:0, refIdLast:0, firstChunk:true},
refreshRate: 60,
lastUpdate: { unix_time: 0, relative_time: '', formatted_time: ''},
@@ -63,9 +63,6 @@ export default {
},
nlAction() {
return this.subscribed?'unsubscribe':'subscribe';
- },
- mobile() {
- return this.spot.isMobile();
}
},
watch: {
@@ -75,6 +72,12 @@ export default {
if(sNewBaseMap && this.map.getLayer(sNewBaseMap)) this.map.setLayoutProperty(sNewBaseMap, 'visibility', 'visible');
}
},
+ 'hash.items.0'(newProjectCodename, oldProjectCodename) {
+ if(newProjectCodename && newProjectCodename != oldProjectCodename) {
+ this.hash.items = [newProjectCodename];
+ this.init();
+ }
+ }
},
provide() {
return {
@@ -87,42 +90,28 @@ export default {
project: this
};
},
- inject: ['spot', 'lang', 'hash', 'projects', 'user'],
+ inject: ['api', 'lang', 'hash', 'projects', 'user', 'consts', 'isMobile'],
beforeMount() {
- if(this.hash.items.length == 0) this.hash.items[0] = this.spot.vars('default_project_codename');
+ if(this.hash.items.length == 0) this.hash.items[0] = this.projects.getDefaultCodeName();
},
mounted() {
- this.spot.addPage('project', {
- onResize: () => {
- //this.spot.tmp('map_offset', -1 * (this.feedPanelOpen?getOuterWidth(this.$refs.feed):0) / getOuterWidth(window));
-
- /* TODO
- if(typeof this.spot.tmp('elev') != 'undefined' && this.spot.tmp('elev')._showState) {
- this.spot.tmp('elev').resize({width:this.getElevWidth()});
- }
- */
- },
- onSamePageMove: (asNewHash, asOldHash) => {
- //this.toggleSettingsPanel(false);
- //this.quit();
-
- //Check for project change
- if(asNewHash.items[0] != asOldHash.items[0]) {
- this.hash.items = [asNewHash.items[0]];
- this.init();
- }
- },
- onQuitPage: () => {
- this.quit();
- }
- });
-
+ window.addEventListener('resize', this.handleResize);
this.init();
},
beforeUnmount() {
+ window.removeEventListener('resize', this.handleResize);
this.quit();
},
methods: {
+ handleResize() {
+ //this.spot.tmp('map_offset', -1 * (this.feedPanelOpen?getOuterWidth(this.$refs.feed):0) / getOuterWidth(window));
+
+ /* TODO
+ if(typeof this.spot.tmp('elev') != 'undefined' && this.spot.tmp('elev')._showState) {
+ this.spot.tmp('elev').resize({width:this.getElevWidth()});
+ }
+ */
+ },
async init() {
let bFirstLoad = (typeof this.currProject.codename == 'undefined');
this.initProject();
@@ -142,9 +131,25 @@ export default {
this.setFeedUpdateTimer(-1);
this.map.remove();
},
+ getNaturalDuration(iHours) {
+ var iTimeMinutes = 0, iTimeHours = 0, iTimeDays = Math.floor(iHours/8); //8 hours a day
+ if(iTimeDays > 1) iTimeDays = Math.round(iTimeDays * 2) / 2; //Round down to the closest half day
+ else {
+ iTimeDays = 0;
+ iTimeHours = Math.floor(iHours);
+ iHours -= iTimeHours;
+
+ iTimeMinutes = Math.floor(iHours * 4) * 15; //Round down to the closest 15 minutes
+ }
+ return '~ '
+ +(iTimeDays>0?(iTimeDays+(iTimeDays%2==0?'':'½')+' '+this.lang.get(iTimeDays>1?'unit_days':'unit_day')):'') //Days
+ +((iTimeHours>0 || iTimeDays==0)?iTimeHours+this.lang.get('unit_hour'):'') //Hours
+ +((iTimeDays>0 || iTimeMinutes==0)?'':iTimeMinutes); //Minutes
+
+ },
initProject() {
this.currProject = this.projects[this.hash.items[0]];
- this.modeHisto = (this.currProject.mode == this.spot.consts.modes.histo);
+ this.modeHisto = (this.currProject.mode == this.consts.modes.histo);
this.feed = {loading:false, updatable:true, outOfData:false, refIdFirst:0, refIdLast:0, firstChunk:true};
this.posts = [];
//this.baseMap = null;
@@ -185,8 +190,8 @@ export default {
},
async initMap() {
//Start async calls
- const oMarkersPromise = this.spot.get2('markers', {id_project: this.currProject.id});
- const oTrackPromise = this.spot.get2('geojson', {id_project: this.currProject.id});
+ const oMarkersPromise = this.api.get('markers', {id_project: this.currProject.id});
+ const oTrackPromise = this.api.get('geojson', {id_project: this.currProject.id});
//Build Map
if(this.map) this.map.remove();
@@ -324,11 +329,11 @@ export default {
});
},
async positionMap(oTrack) {
- let bOpenFeedPanel = !this.mobile;
+ let bOpenFeedPanel = !this.isMobile();
let oBounds = new LngLatBounds();
if( //Blog Mode: Fit to last message
- this.currProject.mode == this.spot.consts.modes.blog &&
+ this.currProject.mode == this.consts.modes.blog &&
this.markers.messages.length > 0 &&
this.hash.items[2] != 'message'
) {
@@ -399,7 +404,11 @@ export default {
medias: [oMedia],
project: this.currProject
});
- this.popup.content.provide('spot', this.spot).provide('lang', this.lang).mount($Popup);
+ this.popup.content
+ .provide('spot', this.spot)
+ .provide('lang', this.lang)
+ .provide('consts', this.consts)
+ .mount($Popup);
},
openMarkerPopup(oFeature) {
this.closeMarkerPopup();
@@ -434,7 +443,11 @@ export default {
medias: JSON.parse(oFeature.properties.medias || '[]'),
project: this.currProject
});
- this.popup.content.provide('spot', this.spot).provide('lang', this.lang).mount($Popup);
+ this.popup.content
+ .provide('spot', this.spot)
+ .provide('lang', this.lang)
+ .provide('consts', this.consts)
+ .mount($Popup);
},
closeMarkerPopup() {
if(this.popup.content) {
@@ -450,11 +463,11 @@ export default {
if(!this.feed.outOfData && !this.feed.loading) {
//Get next chunk
this.feed.loading = true;
- let aoData = await this.spot.get2('next_feed', {id_project: this.currProject.id, id: this.feed.refIdLast});
+ 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.spot.consts.chunk_size);
+ 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;
@@ -480,7 +493,7 @@ export default {
if(iSeconds >= 0) this.feedTimer = setTimeout(this.checkNewFeed, iSeconds * 1000);
},
async checkNewFeed() {
- let aoData = await this.spot.get2('new_feed', {id_project: this.currProject.id, id: this.feed.refIdFirst});
+ let aoData = await this.api.get('new_feed', {id_project: this.currProject.id, id: this.feed.refIdFirst});
if(Object.keys(aoData.feed).length > 0) {
//Update pointer
@@ -533,10 +546,10 @@ export default {
var regexEmail = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if(!regexEmail.test(this.user.email)) this.nlFeedbacks.push({type:'error', 'msg':this.lang.get('nl_invalid_email')});
else {
- this.spot.get2(this.nlAction, {'email': this.user.email, 'name': this.user.name}, this.nlLoading)
+ this.api.get(this.nlAction, {'email': this.user.email, 'name': this.user.name})
.then((asUser, sDesc) => {
this.nlFeedbacks.push('success', sDesc);
- this.user = asUser;
+ Object.assign(this.user, asUser);
})
.catch((sDesc) => {this.nlFeedbacks.push('error', sDesc);});
}
@@ -545,8 +558,8 @@ export default {
let bOldValue = this.feedPanelOpen;
this.feedPanelOpen = (typeof bShow === 'object' || typeof bShow === 'undefined')?(!this.feedPanelOpen):bShow;
- if(bOldValue != this.feedPanelOpen && !this.mobile) {
- this.spot.onResize();
+ if(bOldValue != this.feedPanelOpen && !this.isMobile()) {
+ this.handleResize();
sMapAction = sMapAction || 'panTo';
switch(sMapAction) {
@@ -579,8 +592,8 @@ export default {
let bOldValue = this.settingsPanelOpen;
this.settingsPanelOpen = (typeof bShow === 'object' || typeof bShow === 'undefined')?(!this.settingsPanelOpen):bShow;
- if(bOldValue != this.settingsPanelOpen && !this.mobile) {
- this.spot.onResize();
+ if(bOldValue != this.settingsPanelOpen && !this.isMobile()) {
+ this.handleResize();
sMapAction = sMapAction || 'panTo';
switch(sMapAction) {
@@ -640,7 +653,7 @@ export default {
{{ lang.get(subscribed?'nl_subscribed_desc':'nl_unsubscribed_desc') }}
-
+
@@ -692,16 +705,16 @@ export default {
{{ lang.get('credits_license') }}
-
+
-
+
{{ lang.get('track_'+hikeType) }}
-
@@ -718,7 +731,7 @@ export default {
-
diff --git a/src/components/projectPopup.vue b/src/components/projectPopup.vue
index 928f0c2..dd715d7 100644
--- a/src/components/projectPopup.vue
+++ b/src/components/projectPopup.vue
@@ -18,7 +18,7 @@ export default {
medias: Array,
project: Object
},
- inject: ['spot', 'lang'],
+ inject: ['lang', 'consts'],
computed: {
timeIcon() {
return (this.type == 'media')?'image-shot':'time';
@@ -45,7 +45,7 @@ export default {
- ({{ options.relative_time }})
+ ({{ options.relative_time }})
diff --git a/src/components/projectPost.vue b/src/components/projectPost.vue
index b60294e..0c0095a 100644
--- a/src/components/projectPost.vue
+++ b/src/components/projectPost.vue
@@ -52,10 +52,10 @@
return this.options.displayed_id?(this.lang.get('counter', this.options.displayed_id)):'';
},
anchorLink() {
- return '#'+[this.hash.page, this.hash.items[0], this.options.type, this.options.id].join(this.spot.consts.hash_sep);
+ return '#'+[this.hash.page, this.hash.items[0], this.options.type, this.options.id].join(this.consts.hash_sep);
},
modeHisto() {
- return (this.project.currProject.mode == this.spot.consts.modes.histo);
+ return (this.project.currProject.mode == this.consts.modes.histo);
},
relTime() {
return this.modeHisto?(this.options.formatted_time || '').substr(0, 10):this.options.relative_time;
@@ -69,10 +69,10 @@
}
},
- inject: ['spot', 'lang', 'project', 'user', 'map', 'hash'],
+ inject: ['api', 'lang', 'project', 'user', 'map', 'hash', 'consts', 'isMobile'],
methods: {
copyAnchor() {
- copyTextToClipboard(this.spot.consts.server+this.anchorLink);
+ copyTextToClipboard(this.consts.server+this.anchorLink);
this.anchorTitle = this.lang.get('link_copied');
this.anchorIcon = 'copied';
setTimeout(()=>{ //TODO animation
@@ -82,7 +82,7 @@
},
panMapToMessage() {
this.popupRequested = true;
- if(this.spot.isMobile()) this.project.toggleFeedPanel(false, 'panToInstant');
+ if(this.isMobile()) this.project.toggleFeedPanel(false, 'panToInstant');
this.map.panToBetweenPanels(
this.lngLat,
this.focusZoomLevel,
@@ -100,7 +100,7 @@
send() {
if(this.postMessage != '' && this.user.name != '') {
this.sending = true;
- this.spot.get2(
+ this.api.get(
'add_post',
{
id_project: this.project.currProject.id,
diff --git a/src/components/upload.vue b/src/components/upload.vue
index ea8080f..6fc3493 100644
--- a/src/components/upload.vue
+++ b/src/components/upload.vue
@@ -10,10 +10,10 @@ import SpotButton from './spotButton.vue';
export default {
name: 'upload',
components: { SpotButton, SpotIcon },
- inject: ['spot', 'lang', 'projects', 'consts', 'user'],
+ inject: ['api', 'lang', 'projects', 'consts', 'user'],
data() {
return {
- project: this.projects[this.spot.vars('default_project_codename')],
+ project: this.projects.getDefaultProject(),
files: [],
logs: [],
progress: 0,
@@ -21,8 +21,6 @@ export default {
};
},
mounted() {
- this.spot.addPage('upload', {});
-
if(!this.project.editable) {
this.logs = [this.lang.get('upload_mode_archived', [this.project.name])];
return;
@@ -87,7 +85,10 @@ export default {
event.target.value = '';
},
addComment(oFile) {
- this.spot.get2('add_comment', {id: oFile.id, content: oFile.content})
+ this.api.get('add_comment', {
+ id: oFile.id,
+ content: oFile.content
+ })
.then((asData) => {this.logs.push(this.lang.get('media_comment_update', asData.filename));})
.catch((sMsgId) => {this.logs.push(this.lang.get(sMsgId));});
},
@@ -97,7 +98,11 @@ export default {
navigator.geolocation.getCurrentPosition(
(position) => {
this.logs.push('Sending position...');
- this.spot.get2('add_position', {'latitude':position.coords.latitude, 'longitude':position.coords.longitude, 'timestamp':Math.round(position.timestamp / 1000)})
+ this.api.get('add_position', {
+ 'latitude': position.coords.latitude,
+ 'longitude': position.coords.longitude,
+ 'timestamp': Math.round(position.timestamp / 1000)
+ })
.then((asData) => {this.logs.push(this.lang.get('success'));})
.catch((sMsgId) => {this.logs.push(this.lang.get(sMsgId));});
},
@@ -122,20 +127,20 @@ export default {
-
{{ lang.get('upload_pos_title') }}
-
-
+
+
diff --git a/src/scripts/api.js b/src/scripts/api.js
new file mode 100644
index 0000000..a405e94
--- /dev/null
+++ b/src/scripts/api.js
@@ -0,0 +1,39 @@
+export default class Api {
+
+ constructor({server, processPage, timezone, errorCode, lang}) {
+ this.server = server;
+ this.processPage = processPage;
+ this.timezone = timezone;
+ this.errorCode = errorCode;
+ this.lang = lang;
+ }
+
+ async get(action, params = {}) {
+ const requestParams = {
+ ...params,
+ a: action,
+ t: this.timezone
+ };
+
+ const url = new URL(this.processPage, this.server);
+ url.search = new URLSearchParams(requestParams).toString();
+
+ const request = await fetch(url, {
+ method: 'GET',
+ headers: {'Content-Type': 'application/json'}
+ });
+
+ if(!request.ok) {
+ throw new Error('Error HTTP ' + request.status + ': ' + request.statusText);
+ }
+
+ const response = await request.json();
+ response.desc = this.lang.parse(response.desc);
+
+ if(response.result == this.errorCode) {
+ throw response.desc;
+ }
+
+ return response.data;
+ }
+}
diff --git a/src/scripts/app.js b/src/scripts/app.js
index 3156d10..a411674 100644
--- a/src/scripts/app.js
+++ b/src/scripts/app.js
@@ -1,8 +1,12 @@
//Librairies
+import Api from './api.js';
import Lang from './lang.js';
-import Spot from './spot.js';
+import Projects from './projects.js';
+import User from './user.js';
import { createApp } from 'vue';
-import SpotVue from '../Spot.vue';
+
+//Main template
+import Spot from '../Spot.vue';
//Style
import Css from './../styles/spot.scss';
@@ -11,11 +15,22 @@ import Css from './../styles/spot.scss';
const appConfig = JSON.parse(document.getElementById('app-config').textContent);
//Instances
+const oProjects = new Projects(appConfig.projects);
+const oUser = new User(appConfig.user, appConfig.consts.default_timezone);
const oLang = new Lang({translations: appConfig.consts.lang, prefix: appConfig.consts.lang_prefix});
-const oSpot = new Spot(appConfig, oLang);
+const oApi = new Api({
+ server: appConfig.consts.server,
+ processPage: appConfig.consts.process_page,
+ timezone: oUser.timezone,
+ errorCode: appConfig.consts.error,
+ lang: oLang
+});
//Mount app
-const oSpotVue = createApp(SpotVue);
-oSpotVue.provide('spot', oSpot);
-oSpotVue.provide('lang', oLang);
-oSpotVue.mount('#container');
+const oSpot = createApp(Spot);
+oSpot.provide('appConfig', appConfig);
+oSpot.provide('api', oApi);
+oSpot.provide('lang', oLang);
+oSpot.provide('projects', oProjects);
+oSpot.provide('user', oUser);
+oSpot.mount('#container');
diff --git a/src/scripts/projects.js b/src/scripts/projects.js
new file mode 100644
index 0000000..60875f0
--- /dev/null
+++ b/src/scripts/projects.js
@@ -0,0 +1,17 @@
+export default class Projects {
+
+ constructor(asProjects = {}) {
+ Object.assign(this, asProjects);
+ }
+
+ getDefaultCodeName() {
+ for(const [sCodeName, asProject] of Object.entries(this)) {
+ if(asProject.default) return sCodeName;
+ }
+ }
+
+ getDefaultProject() {
+ const sCodeName = this.getDefaultCodeName();
+ return this[this.getDefaultCodeName()];
+ }
+}
diff --git a/src/scripts/spot.js b/src/scripts/spot.js
deleted file mode 100755
index 2de5a70..0000000
--- a/src/scripts/spot.js
+++ /dev/null
@@ -1,298 +0,0 @@
-import { copyArray, getElem, setElem } from './common.js';
-
-export default class Spot {
-
- constructor(asGlobals, oLang) {
- this.consts = asGlobals.consts;
- this.consts.hash_sep = '-';
- this.consts.title = 'Spotty';
- this.consts.default_page = 'project';
- //this.consts.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || this.consts.default_timezone;
- this.lang = oLang;
-
- this.pages = {};
-
- //Variables & constants from php
- this.vars('tmp', 'object');
- this.vars('page', 'string');
- Object.entries(asGlobals.vars).forEach(([sKey, oValue]) => {this.vars(sKey, oValue);});
-
- //page elem
- this.elem = {};
- }
-
- /* Initialization */
-
- /*
- init() {
- this.elem.container = $('#container');
- this.elem.main = $('#main');
-
- //On Key down
- $('html').on('keydown', (oEvent) => {this.onKeydown(oEvent);});
-
- //on window resize
- $(window).on('resize', () => {this.onResize();});
-
- //Hash management
- $(window)
- .on('hashchange', () => {this.onHashChange();})
- .trigger('hashchange');
- }
- */
-
- /* Variable Management */
-
- vars(oVarName, oValue) {
- var asVarName = (typeof oVarName == 'object')?oVarName:[oVarName];
-
- //Set, name & type / default value (init)
- if(typeof oValue !== 'undefined') setElem(this.vars, copyArray(asVarName), oValue);
-
- //Get, only name parameter
- return getElem(this.vars, asVarName);
- }
-
- tmp(sVarName, oValue) {
- var asVarName = (typeof sVarName == 'object')?sVarName:[sVarName];
- asVarName.unshift('tmp');
- return this.vars(asVarName, oValue);
- }
-
- /* Interface with server */
-
- /*
- get(sAction, fOnSuccess, oVars, fOnError, fonProgress) {
- oVars = oVars || {};
- fOnError = fOnError || function(sError) {console.log(sError);};
- fonProgress = fonProgress || function(sState){};
- fonProgress('start');
-
- oVars['a'] = sAction;
- oVars['t'] = this.consts.timezone;
- return $.ajax(
- {
- url: this.consts.process_page,
- data: oVars,
- dataType: 'json'
- })
- .done((oData) => {
- fonProgress('done');
- if(oData.desc.substr(0, this.consts.lang_prefix.length)==this.consts.lang_prefix) oData.desc = this.lang(oData.desc.substr(5));
-
- if(oData.result==this.consts.error) fOnError(oData.desc);
- else if(fOnSuccess) fOnSuccess(oData.data, oData.desc);
- })
- .fail((jqXHR, textStatus, errorThrown) => {
- fonProgress('fail');
- fOnError(textStatus+' '+errorThrown);
- });
- }
- */
-
- async get2(sAction, oVars, bLoading) {
- oVars = oVars || {};
- oVars['a'] = sAction;
- oVars['t'] = this.consts.timezone;
- bLoading = true;
-
- try {
- let oUrl = new URL(this.consts.process_page, this.consts.server);
- oUrl.search = new URLSearchParams(oVars).toString();
- const oRequest = await fetch(oUrl, {method: 'GET', /*body: JSON.stringify(oVars),*/ headers: {"Content-Type": "application/json"}});
- if(!oRequest.ok) {
- bLoading = false;
- throw new Error('Error HTTP '+oRequest.status+': '+oRequest.statusText);
- }
- else {
- let oResponse = await oRequest.json();
- bLoading = false;
- oResponse.desc = this.lang.parse(oResponse.desc);
-
- if(oResponse.result == this.consts.error) return Promise.reject(oResponse.desc);
- else return oResponse.data;
- }
- }
- catch(oError) {
- bLoading = false;
- throw oError;
- }
- }
-
- isMobile() {
- const mobileElem = document.getElementById('mobile');
- return !!mobileElem && getComputedStyle(mobileElem).display !== 'none';
- }
-
- /* Page Switch - Trigger & Event catching */
-
- /*
- onHashChange() {
- var asHash = this.getHash();
- if(asHash.hash !='' && asHash.page != '') this.switchPage(asHash); //page switching
- else if(this.vars('page')=='') this.setHash(this.consts.default_page); //first page
- }
-
- getHash() {
- var sHash = this.hash();
- var asHash = sHash.split(this.consts.hash_sep);
- var sPage = asHash.shift() || '';
- return {hash:sHash, page:sPage, items:asHash};
- }
-
- setHash(sPage, asItems, bReboot) {
- bReboot = bReboot || false;
- sPage = sPage || '';
- asItems = asItems || [];
- if(typeof asItems == 'string') asItems = [asItems];
-
- if(sPage != '') {
- var sItems = (asItems.length > 0)?this.consts.hash_sep+asItems.join(this.consts.hash_sep):'';
- this.hash(sPage+sItems, bReboot);
- }
- }
-
- hash(hash, bReboot) {
- bReboot = bReboot || false;
- if(!hash) return window.location.hash.slice(1);
- else window.location.hash = '#'+hash;
-
- if(bReboot) location.reload();
- }
-
- updateHash(sType, iId) {
- sType = sType || '';
- iId = iId || 0;
-
- var asHash = this.getHash();
- if(iId) this.setHash(asHash.page, [asHash.items[0], sType, iId]);
- }
-
- flushHash(asTypes) {
- asTypes = asTypes || [];
- var asHash = this.getHash();
- if(asHash.items.length > 1 && (asTypes.length == 0 || asTypes.indexOf(asHash.items[1]) != -1)) this.setHash(asHash.page, [asHash.items[0]]);
- }
- */
-
- /* Page Events */
-
- pageInit(asHash) {
- let sPage = this.vars('page');
- if(this.pages[sPage].pageInit) this.pages[sPage].pageInit(asHash);
- else console.log('no init for the page: '+asHash.page);
- }
-
- onSamePageMove(asNewHash, asOldHash) {
- let sPage = this.vars('page');
- return (this.pages[sPage] && this.pages[sPage].onSamePageMove)?this.pages[sPage].onSamePageMove(asNewHash, asOldHash):false;
- }
-
- onQuitPage() {
- let sPage = this.vars('page');
- return (this.pages[sPage] && this.pages[sPage].onQuitPage)?this.pages[sPage].onQuitPage():true;
- }
-
- onResize() {
- let sPage = this.vars('page');
- if(this.pages[sPage].onResize) this.pages[sPage].onResize();
- }
-
- onFeedback(sType, sMsg, asContext) {
- asContext = asContext || {};
- let sPage = this.vars('page');
- if(this.pages[sPage].onFeedback) this.pages[sPage].onFeedback(sType, sMsg, asContext);
- else console.log({type:sType, msg:sMsg, context:asContext});
- }
-
- onKeydown(oEvent) {
- let sPage = this.vars('page');
- if(this.pages[sPage].onKeydown) this.pages[sPage].onKeydown(oEvent);
- }
-
- /* Page Switch - DOM Replacement */
-
- /*
- getActionLink(sAction, oVars) {
- if(!oVars) oVars = {};
- let sVars = '';
-
- for(i in oVars) sVars += '&'+i+'='+oVars[i];
-
- return this.consts.process_page+'?a='+sAction+sVars;
- }
- */
-
- addPage(sPage, oPage) {
- this.pages[sPage] = oPage;
- }
-
- /*
- switchPage(asHash) {
- var sPageName = asHash.page;
- var bSamePage = (this.vars('page') == sPageName);
- var bFirstPage = (this.vars('page') == '');
-
- if(!this.consts.pages[sPageName]) { //Page does not exist
- if(bFirstPage) this.setHash(this.consts.default_page);
- else this.setHash(this.vars('page'), this.vars(['hash', 'items']));
- }
- else if(this.onQuitPage(bSamePage) && !bSamePage || this.onSamePageMove(asHash))
- {
- //Delete tmp variables
- this.vars('tmp', {});
-
- //Officially a new page
- this.vars('page', sPageName);
- this.vars('hash', asHash);
-
- //Update Page Title
- this.setPageTitle(sPageName+' '+(asHash.items[0] || ''));
-
- //Replacing DOM
- var $Dom = $(this.consts.pages[sPageName]);
- if(bFirstPage) this.splash($Dom, asHash, bFirstPage); //first page
- else this.elem.main.stop().fadeTo('fast', 0, () => {this.splash($Dom, asHash, bFirstPage);}); //Switching page
- }
- else if(bSamePage) this.vars('hash', asHash);
- }
-
- splash($Dom, asHash, bFirstPage)
- {
- //Switch main content
- this.elem.main.empty().html($Dom);
-
- //Page Bootstrap
- this.pageInit(asHash, bFirstPage);
-
- //Show main
- var $FadeInElem = bFirstPage?this.elem.container:this.elem.main;
- $FadeInElem.hide().fadeTo('slow', 1);
- }
- */
-
- setPageTitle(sTitle) {
- document.title = this.consts.title+' - '+sTitle;
- }
-
- getNaturalDuration(iHours) {
- var iTimeMinutes = 0, iTimeHours = 0, iTimeDays = Math.floor(iHours/8); //8 hours a day
- if(iTimeDays > 1) iTimeDays = Math.round(iTimeDays * 2) / 2; //Round down to the closest half day
- else {
- iTimeDays = 0;
- iTimeHours = Math.floor(iHours);
- iHours -= iTimeHours;
-
- iTimeMinutes = Math.floor(iHours * 4) * 15; //Round down to the closest 15 minutes
- }
- return '~ '
- +(iTimeDays>0?(iTimeDays+(iTimeDays%2==0?'':'½')+' '+this.lang.get(iTimeDays>1?'unit_days':'unit_day')):'') //Days
- +((iTimeHours>0 || iTimeDays==0)?iTimeHours+this.lang.get('unit_hour'):'') //Hours
- +((iTimeDays>0 || iTimeMinutes==0)?'':iTimeMinutes) //Minutes
-
- }
-
- checkClearance(sClearance) {
- return (this.vars(['user', 'clearance']) >= sClearance);
- }
-}
diff --git a/src/scripts/user.js b/src/scripts/user.js
new file mode 100644
index 0000000..2b22cb9
--- /dev/null
+++ b/src/scripts/user.js
@@ -0,0 +1,11 @@
+export default class User {
+
+ constructor(asUserInfo = {}, sDefaultTimeZone = '') {
+ Object.assign(this, asUserInfo);
+ this.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || this.timezone || sDefaultTimeZone;
+ }
+
+ hasClearance(sClearance) {
+ return this.clearance >= sClearance;
+ }
+}