Bye bye spot.js
This commit is contained in:
@@ -120,6 +120,7 @@ class Project extends PhpObject {
|
|||||||
|
|
||||||
public function getProjects($iProjectId=0) {
|
public function getProjects($iProjectId=0) {
|
||||||
$bSpecificProj = ($iProjectId > 0);
|
$bSpecificProj = ($iProjectId > 0);
|
||||||
|
$sDefaultProjectCodeName = $this->getProjectCodeName();
|
||||||
$asInfo = array(
|
$asInfo = array(
|
||||||
'select'=> array(
|
'select'=> array(
|
||||||
Db::getId(self::PROJ_TABLE)." AS id",
|
Db::getId(self::PROJ_TABLE)." AS id",
|
||||||
@@ -145,6 +146,7 @@ class Project extends PhpObject {
|
|||||||
//$asProject['geofilepath'] = Spot::addTimestampToFilePath(GeoJson::getDistFilePath($sCodeName));
|
//$asProject['geofilepath'] = Spot::addTimestampToFilePath(GeoJson::getDistFilePath($sCodeName));
|
||||||
$asProject['gpxfilepath'] = Spot::addTimestampToFilePath(Gpx::getDistFilePath($sCodeName));
|
$asProject['gpxfilepath'] = Spot::addTimestampToFilePath(Gpx::getDistFilePath($sCodeName));
|
||||||
$asProject['codename'] = $sCodeName;
|
$asProject['codename'] = $sCodeName;
|
||||||
|
$asProject['default'] = ($sCodeName == $sDefaultProjectCodeName);
|
||||||
}
|
}
|
||||||
return $bSpecificProj?$asProject:$asProjects;
|
return $bSpecificProj?$asProject:$asProjects;
|
||||||
}
|
}
|
||||||
|
|||||||
12
lib/Spot.php
12
lib/Spot.php
@@ -173,16 +173,16 @@ class Spot extends Main
|
|||||||
|
|
||||||
return parent::getMainPage(
|
return parent::getMainPage(
|
||||||
array(
|
array(
|
||||||
'vars' => array(
|
'projects' => $this->oProject->getProjects(),
|
||||||
'default_project_codename' => $this->oProject->getProjectCodeName(),
|
'user' => $this->oUser->getUserInfo(),
|
||||||
'projects' => $this->oProject->getProjects(),
|
|
||||||
'user' => $this->oUser->getUserInfo()
|
|
||||||
),
|
|
||||||
'consts' => array(
|
'consts' => array(
|
||||||
'modes' => Project::MODES,
|
'modes' => Project::MODES,
|
||||||
'clearances' => User::CLEARANCES,
|
'clearances' => User::CLEARANCES,
|
||||||
'default_timezone' => Settings::TIMEZONE,
|
'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,
|
self::MAIN_PAGE,
|
||||||
|
|||||||
46
src/Spot.vue
46
src/Spot.vue
@@ -13,59 +13,59 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
hash: {page: '', items: []},
|
hash: {page: '', items: []},
|
||||||
consts: this.spot.consts,
|
consts: this.appConfig.consts,
|
||||||
user: this.spot.vars('user'),
|
mobile: false
|
||||||
routes: aoRoutes
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
provide() {
|
provide() {
|
||||||
return {
|
return {
|
||||||
hash: this.hash,
|
hash: this.hash,
|
||||||
projects: this.spot.vars('projects'),
|
|
||||||
consts: this.consts,
|
consts: this.consts,
|
||||||
user: this.user
|
isMobile: () => this.isMobile()
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
route() {
|
route() {
|
||||||
return this.routes[this.hash.page];
|
return aoRoutes[this.hash.page];
|
||||||
},
|
},
|
||||||
hashSnapshot() {
|
hashSnapshot() {
|
||||||
return JSON.stringify(this.hash);
|
return JSON.stringify(this.hash);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
inject: ['spot'],
|
inject: ['appConfig'],
|
||||||
created() {
|
created() {
|
||||||
//User
|
|
||||||
this.user.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || this.consts.default_timezone;
|
|
||||||
|
|
||||||
//Set initial page
|
//Set initial page
|
||||||
let asInitHash = this.getBrowserHash();
|
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);
|
this.setVarHash(asInitHash);
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
//Catch browser hash change
|
//Catch browser hash change
|
||||||
window.addEventListener('hashchange', this.onBrowserHashChange);
|
window.addEventListener('hashchange', this.onBrowserHashChange);
|
||||||
|
window.addEventListener('resize', this.updateMobile);
|
||||||
|
this.updateMobile();
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
hashSnapshot(jNewHash, jOldHash) {
|
hashSnapshot(jNewHash, jOldHash) {
|
||||||
const asNewHash = JSON.parse(jNewHash);
|
const asNewHash = JSON.parse(jNewHash);
|
||||||
const asOldHash = JSON.parse(jOldHash);
|
|
||||||
this.spot.vars('page', this.hash.page); //FIXME remove
|
|
||||||
|
|
||||||
//Sync variable -> #hash
|
//Sync variable -> #hash
|
||||||
if(asNewHash != this.getBrowserHash()) {
|
if(asNewHash != this.getBrowserHash()) {
|
||||||
this.setBrowserHash(asNewHash.page, asNewHash.items);
|
this.setBrowserHash(asNewHash.page, asNewHash.items);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Same Page change
|
this.setPageTitle(asNewHash.page);
|
||||||
if(asNewHash != asOldHash && asNewHash.page == asOldHash.page) {
|
|
||||||
this.spot.onSamePageMove(asNewHash, asOldHash);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
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) {
|
setVarHash(asHash) {
|
||||||
this.hash.page = asHash.page || '';
|
this.hash.page = asHash.page || '';
|
||||||
this.hash.items = Array.isArray(asHash.items) ? [...asHash.items.filter(n => n)] : [];
|
this.hash.items = Array.isArray(asHash.items) ? [...asHash.items.filter(n => n)] : [];
|
||||||
@@ -73,22 +73,22 @@ export default {
|
|||||||
onBrowserHashChange() { //Sync #hash -> variable
|
onBrowserHashChange() { //Sync #hash -> variable
|
||||||
let asHash = this.getBrowserHash();
|
let asHash = this.getBrowserHash();
|
||||||
if(asHash != this.hash) this.setVarHash(asHash);
|
if(asHash != this.hash) this.setVarHash(asHash);
|
||||||
this.spot.vars('page', this.hash.page); //FIXME remove
|
|
||||||
},
|
},
|
||||||
getBrowserHash() {
|
getBrowserHash() {
|
||||||
let sHash = window.location.hash.slice(1);
|
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() || '';
|
let sPage = asHash.shift() || '';
|
||||||
return {page: sPage, items: asHash};
|
return {page: sPage, items: asHash};
|
||||||
},
|
},
|
||||||
setBrowserHash(sPage = '', asItems = []) {
|
setBrowserHash(sPage = '', asItems = []) {
|
||||||
if(typeof asItems == 'string' && asItems != '') asItems = [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;
|
window.location.hash = '#' + sPage + sItems;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
window.removeEventListener('hashchange', this.onBrowserHashChange)
|
window.removeEventListener('hashchange', this.onBrowserHashChange);
|
||||||
|
window.removeEventListener('resize', this.updateMobile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -96,5 +96,5 @@ export default {
|
|||||||
<div id="main">
|
<div id="main">
|
||||||
<component :is="route" />
|
<component :is="route" />
|
||||||
</div>
|
</div>
|
||||||
<div id="mobile"></div>
|
<div id="mobile" ref="mobile"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -9,38 +9,37 @@ export default {
|
|||||||
SpotButton,
|
SpotButton,
|
||||||
AdminInput
|
AdminInput
|
||||||
},
|
},
|
||||||
inject: ['spot', 'lang'],
|
inject: ['api', 'lang'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
elems: {},
|
elems: {},
|
||||||
feedbacks: []
|
feedbacks: [],
|
||||||
};
|
saveTimer: null
|
||||||
},
|
};
|
||||||
mounted() {
|
},
|
||||||
this.setEvents();
|
beforeUnmount() {
|
||||||
this.setProjects();
|
if(this.saveTimer) clearTimeout(this.saveTimer);
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.setProjects();
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
l(id) {
|
l(id) {
|
||||||
return this.lang.get(id);
|
return this.lang.get(id);
|
||||||
},
|
},
|
||||||
setEvents() {
|
addFeedback(sType, sMsg, asContext = {}) {
|
||||||
this.spot.addPage('admin', {
|
delete asContext.a;
|
||||||
onFeedback: (sType, sMsg, asContext) => {
|
delete asContext.t;
|
||||||
delete asContext.a;
|
sMsg += ' (';
|
||||||
delete asContext.t;
|
for(const [sKey, sElem] of Object.entries(asContext)) {
|
||||||
sMsg += ' (';
|
sMsg += sKey+'='+sElem+' / ' ;
|
||||||
for(const [sKey, sElem] of Object.entries(asContext)) {
|
}
|
||||||
sMsg += sKey+'='+sElem+' / ' ;
|
sMsg = sMsg.slice(0, -3)+')';
|
||||||
}
|
|
||||||
sMsg = sMsg.slice(0, -3)+')';
|
|
||||||
|
|
||||||
this.feedbacks.push({type:sType, msg:sMsg});
|
this.feedbacks.push({type:sType, msg:sMsg});
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
async setProjects() {
|
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)) {
|
for(const [sType, aoElems] of Object.entries(aoElemTypes)) {
|
||||||
this.elems[sType] = {};
|
this.elems[sType] = {};
|
||||||
@@ -51,17 +50,17 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
createElem(sType) {
|
createElem(sType) {
|
||||||
this.spot.get2('admin_create', {type: sType})
|
this.api.get('admin_create', {type: sType})
|
||||||
.then((aoNewElemTypes) => {
|
.then((aoNewElemTypes) => {
|
||||||
for(const [sType, aoNewElems] of Object.entries(aoNewElemTypes)) {
|
for(const [sType, aoNewElems] of Object.entries(aoNewElemTypes)) {
|
||||||
for(const [iKey, oNewElem] of Object.entries(aoNewElems)) {
|
for(const [iKey, oNewElem] of Object.entries(aoNewElems)) {
|
||||||
oNewElem.type = sType;
|
oNewElem.type = sType;
|
||||||
this.elems[sType][oNewElem.id] = oNewElem;
|
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) {
|
deleteElem(oElem) {
|
||||||
const asInputs = {
|
const asInputs = {
|
||||||
@@ -69,23 +68,20 @@ export default {
|
|||||||
id: oElem.id
|
id: oElem.id
|
||||||
};
|
};
|
||||||
|
|
||||||
this.spot.get(
|
this.api.get('admin_delete', asInputs)
|
||||||
'admin_delete',
|
.then((asData) => {
|
||||||
(asData) => {
|
delete this.elems[asInputs.type][asInputs.id];
|
||||||
delete this.elems[asInputs.type][asInputs.id];
|
this.addFeedback('success', this.lang.get('admin_delete_success'), asInputs);
|
||||||
this.spot.onFeedback('success', this.lang.get('admin_delete_success'), asInputs);
|
})
|
||||||
},
|
.catch((sError) => {
|
||||||
asInputs,
|
this.addFeedback('error', sError, asInputs);
|
||||||
(sError) => {
|
});
|
||||||
this.spot.onFeedback('error', sError, asInputs);
|
},
|
||||||
}
|
updateElem(oElem, oEvent) {
|
||||||
);
|
if(this.saveTimer) clearTimeout(this.saveTimer);
|
||||||
},
|
|
||||||
updateElem(oElem, oEvent) {
|
let sOldVal = this.elems[oElem.type][oElem.id][oEvent.target.name];
|
||||||
if(typeof this.spot.tmp('wait') != 'undefined') clearTimeout(this.spot.tmp('wait'));
|
let sNewVal = oEvent.target.value;
|
||||||
|
|
||||||
let sOldVal = this.elems[oElem.type][oElem.id][oEvent.target.name];
|
|
||||||
let sNewVal = oEvent.target.value;
|
|
||||||
if(sOldVal != sNewVal) {
|
if(sOldVal != sNewVal) {
|
||||||
let asInputs = {
|
let asInputs = {
|
||||||
type: oElem.type,
|
type: oElem.type,
|
||||||
@@ -94,25 +90,25 @@ export default {
|
|||||||
value: sNewVal
|
value: sNewVal
|
||||||
};
|
};
|
||||||
|
|
||||||
this.spot.get2('admin_set', asInputs)
|
this.api.get('admin_set', asInputs)
|
||||||
.then((asData) => {
|
.then((asData) => {
|
||||||
this.elems[oElem.type][oElem.id][oEvent.target.name] = sNewVal;
|
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) => {
|
.catch((sError) => {
|
||||||
oEvent.target.value = sOldVal;
|
oEvent.target.value = sOldVal;
|
||||||
this.spot.onFeedback('error', sError, asInputs);
|
this.addFeedback('error', sError, asInputs);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
queue(oElem, oEvent) {
|
queue(oElem, oEvent) {
|
||||||
if(typeof this.spot.tmp('wait') != 'undefined') clearTimeout(this.spot.tmp('wait'));
|
if(this.saveTimer) clearTimeout(this.saveTimer);
|
||||||
this.spot.tmp('wait', setTimeout(() => {this.updateElem(oElem, oEvent);}, 2000));
|
this.saveTimer = setTimeout(() => {this.updateElem(oElem, oEvent);}, 2000);
|
||||||
},
|
},
|
||||||
updateProject() {
|
updateProject() {
|
||||||
this.spot.get2('update_project')
|
this.api.get('update_project')
|
||||||
.then((asData, sMsg) => {this.spot.onFeedback('success', sMsg, {'update':'project'});})
|
.then((asData, sMsg) => {this.addFeedback('success', sMsg, {'update':'project'});})
|
||||||
.catch((sMsg) => {this.spot.onFeedback('error', sMsg, {'update':'project'});});
|
.catch((sMsg) => {this.addFeedback('error', sMsg, {'update':'project'});});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,4 +228,4 @@ export default {
|
|||||||
<p v-for="feedback in feedbacks" :class="feedback.type">{{ feedback.msg }}</p>
|
<p v-for="feedback in feedbacks" :class="feedback.type">{{ feedback.msg }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
server: this.spot.consts.server,
|
server: this.consts.server,
|
||||||
feed: {loading:false, updatable:true, outOfData:false, refIdFirst:0, refIdLast:0, firstChunk:true},
|
feed: {loading:false, updatable:true, outOfData:false, refIdFirst:0, refIdLast:0, firstChunk:true},
|
||||||
refreshRate: 60,
|
refreshRate: 60,
|
||||||
lastUpdate: { unix_time: 0, relative_time: '', formatted_time: ''},
|
lastUpdate: { unix_time: 0, relative_time: '', formatted_time: ''},
|
||||||
@@ -63,9 +63,6 @@ export default {
|
|||||||
},
|
},
|
||||||
nlAction() {
|
nlAction() {
|
||||||
return this.subscribed?'unsubscribe':'subscribe';
|
return this.subscribed?'unsubscribe':'subscribe';
|
||||||
},
|
|
||||||
mobile() {
|
|
||||||
return this.spot.isMobile();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -75,6 +72,12 @@ export default {
|
|||||||
if(sNewBaseMap && this.map.getLayer(sNewBaseMap)) this.map.setLayoutProperty(sNewBaseMap, 'visibility', 'visible');
|
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() {
|
provide() {
|
||||||
return {
|
return {
|
||||||
@@ -87,42 +90,28 @@ export default {
|
|||||||
project: this
|
project: this
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
inject: ['spot', 'lang', 'hash', 'projects', 'user'],
|
inject: ['api', 'lang', 'hash', 'projects', 'user', 'consts', 'isMobile'],
|
||||||
beforeMount() {
|
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() {
|
mounted() {
|
||||||
this.spot.addPage('project', {
|
window.addEventListener('resize', this.handleResize);
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
|
window.removeEventListener('resize', this.handleResize);
|
||||||
this.quit();
|
this.quit();
|
||||||
},
|
},
|
||||||
methods: {
|
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() {
|
async init() {
|
||||||
let bFirstLoad = (typeof this.currProject.codename == 'undefined');
|
let bFirstLoad = (typeof this.currProject.codename == 'undefined');
|
||||||
this.initProject();
|
this.initProject();
|
||||||
@@ -142,9 +131,25 @@ export default {
|
|||||||
this.setFeedUpdateTimer(-1);
|
this.setFeedUpdateTimer(-1);
|
||||||
this.map.remove();
|
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() {
|
initProject() {
|
||||||
this.currProject = this.projects[this.hash.items[0]];
|
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.feed = {loading:false, updatable:true, outOfData:false, refIdFirst:0, refIdLast:0, firstChunk:true};
|
||||||
this.posts = [];
|
this.posts = [];
|
||||||
//this.baseMap = null;
|
//this.baseMap = null;
|
||||||
@@ -185,8 +190,8 @@ export default {
|
|||||||
},
|
},
|
||||||
async initMap() {
|
async initMap() {
|
||||||
//Start async calls
|
//Start async calls
|
||||||
const oMarkersPromise = this.spot.get2('markers', {id_project: this.currProject.id});
|
const oMarkersPromise = this.api.get('markers', {id_project: this.currProject.id});
|
||||||
const oTrackPromise = this.spot.get2('geojson', {id_project: this.currProject.id});
|
const oTrackPromise = this.api.get('geojson', {id_project: this.currProject.id});
|
||||||
|
|
||||||
//Build Map
|
//Build Map
|
||||||
if(this.map) this.map.remove();
|
if(this.map) this.map.remove();
|
||||||
@@ -324,11 +329,11 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
async positionMap(oTrack) {
|
async positionMap(oTrack) {
|
||||||
let bOpenFeedPanel = !this.mobile;
|
let bOpenFeedPanel = !this.isMobile();
|
||||||
let oBounds = new LngLatBounds();
|
let oBounds = new LngLatBounds();
|
||||||
|
|
||||||
if( //Blog Mode: Fit to last message
|
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.markers.messages.length > 0 &&
|
||||||
this.hash.items[2] != 'message'
|
this.hash.items[2] != 'message'
|
||||||
) {
|
) {
|
||||||
@@ -399,7 +404,11 @@ export default {
|
|||||||
medias: [oMedia],
|
medias: [oMedia],
|
||||||
project: this.currProject
|
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) {
|
openMarkerPopup(oFeature) {
|
||||||
this.closeMarkerPopup();
|
this.closeMarkerPopup();
|
||||||
@@ -434,7 +443,11 @@ export default {
|
|||||||
medias: JSON.parse(oFeature.properties.medias || '[]'),
|
medias: JSON.parse(oFeature.properties.medias || '[]'),
|
||||||
project: this.currProject
|
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() {
|
closeMarkerPopup() {
|
||||||
if(this.popup.content) {
|
if(this.popup.content) {
|
||||||
@@ -450,11 +463,11 @@ export default {
|
|||||||
if(!this.feed.outOfData && !this.feed.loading) {
|
if(!this.feed.outOfData && !this.feed.loading) {
|
||||||
//Get next chunk
|
//Get next chunk
|
||||||
this.feed.loading = true;
|
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;
|
let iPostCount = Object.keys(aoData.feed).length;
|
||||||
|
|
||||||
//Update pointers
|
//Update pointers
|
||||||
this.feed.outOfData = (iPostCount < this.spot.consts.chunk_size);
|
this.feed.outOfData = (iPostCount < this.consts.chunk_size);
|
||||||
if(iPostCount > 0) {
|
if(iPostCount > 0) {
|
||||||
this.feed.refIdLast = aoData.ref_id_last;
|
this.feed.refIdLast = aoData.ref_id_last;
|
||||||
if(this.feed.firstChunk) this.feed.refIdFirst = aoData.ref_id_first;
|
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);
|
if(iSeconds >= 0) this.feedTimer = setTimeout(this.checkNewFeed, iSeconds * 1000);
|
||||||
},
|
},
|
||||||
async checkNewFeed() {
|
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) {
|
if(Object.keys(aoData.feed).length > 0) {
|
||||||
//Update pointer
|
//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,}))$/;
|
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')});
|
if(!regexEmail.test(this.user.email)) this.nlFeedbacks.push({type:'error', 'msg':this.lang.get('nl_invalid_email')});
|
||||||
else {
|
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) => {
|
.then((asUser, sDesc) => {
|
||||||
this.nlFeedbacks.push('success', sDesc);
|
this.nlFeedbacks.push('success', sDesc);
|
||||||
this.user = asUser;
|
Object.assign(this.user, asUser);
|
||||||
})
|
})
|
||||||
.catch((sDesc) => {this.nlFeedbacks.push('error', sDesc);});
|
.catch((sDesc) => {this.nlFeedbacks.push('error', sDesc);});
|
||||||
}
|
}
|
||||||
@@ -545,8 +558,8 @@ export default {
|
|||||||
let bOldValue = this.feedPanelOpen;
|
let bOldValue = this.feedPanelOpen;
|
||||||
this.feedPanelOpen = (typeof bShow === 'object' || typeof bShow === 'undefined')?(!this.feedPanelOpen):bShow;
|
this.feedPanelOpen = (typeof bShow === 'object' || typeof bShow === 'undefined')?(!this.feedPanelOpen):bShow;
|
||||||
|
|
||||||
if(bOldValue != this.feedPanelOpen && !this.mobile) {
|
if(bOldValue != this.feedPanelOpen && !this.isMobile()) {
|
||||||
this.spot.onResize();
|
this.handleResize();
|
||||||
|
|
||||||
sMapAction = sMapAction || 'panTo';
|
sMapAction = sMapAction || 'panTo';
|
||||||
switch(sMapAction) {
|
switch(sMapAction) {
|
||||||
@@ -579,8 +592,8 @@ export default {
|
|||||||
let bOldValue = this.settingsPanelOpen;
|
let bOldValue = this.settingsPanelOpen;
|
||||||
this.settingsPanelOpen = (typeof bShow === 'object' || typeof bShow === 'undefined')?(!this.settingsPanelOpen):bShow;
|
this.settingsPanelOpen = (typeof bShow === 'object' || typeof bShow === 'undefined')?(!this.settingsPanelOpen):bShow;
|
||||||
|
|
||||||
if(bOldValue != this.settingsPanelOpen && !this.mobile) {
|
if(bOldValue != this.settingsPanelOpen && !this.isMobile()) {
|
||||||
this.spot.onResize();
|
this.handleResize();
|
||||||
|
|
||||||
sMapAction = sMapAction || 'panTo';
|
sMapAction = sMapAction || 'panTo';
|
||||||
switch(sMapAction) {
|
switch(sMapAction) {
|
||||||
@@ -640,7 +653,7 @@ export default {
|
|||||||
<div id="settings-panel" class="map-panel">
|
<div id="settings-panel" class="map-panel">
|
||||||
<div class="settings-header">
|
<div class="settings-header">
|
||||||
<div class="logo"><img width="289" height="72" src="images/logo_black.png" alt="Spotty" /></div>
|
<div class="logo"><img width="289" height="72" src="images/logo_black.png" alt="Spotty" /></div>
|
||||||
<div id="last_update" v-if="this.currProject.mode == this.spot.consts.modes.blog && lastUpdate.unix_time > 0">
|
<div id="last_update" v-if="this.currProject.mode == this.consts.modes.blog && lastUpdate.unix_time > 0">
|
||||||
<p><span><img src="images/spot-logo-only.svg" alt="" /></span><abbr :title="lastUpdate.formatted_time">{{ lang.get('last_update')+' '+lastUpdate.relative_time }}</abbr></p>
|
<p><span><img src="images/spot-logo-only.svg" alt="" /></span><abbr :title="lastUpdate.formatted_time">{{ lang.get('last_update')+' '+lastUpdate.relative_time }}</abbr></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -680,7 +693,7 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
{{ lang.get(subscribed?'nl_subscribed_desc':'nl_unsubscribed_desc') }}
|
{{ lang.get(subscribed?'nl_subscribed_desc':'nl_unsubscribed_desc') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-section admin" v-if="spot.checkClearance(spot.consts.clearances.admin)">
|
<div class="settings-section admin" v-if="user.hasClearance(consts.clearances.admin)">
|
||||||
<h1><SpotIcon :icon="'admin fa-fw'" :text="lang.get('admin')" /></h1>
|
<h1><SpotIcon :icon="'admin fa-fw'" :text="lang.get('admin')" /></h1>
|
||||||
<a class="button" href="#admin"><SpotIcon :icon="'config'" :text="lang.get('admin_config')" /></a>
|
<a class="button" href="#admin"><SpotIcon :icon="'config'" :text="lang.get('admin_config')" /></a>
|
||||||
<a class="button" href="#upload"><SpotIcon :icon="'upload'" :text="lang.get('admin_upload')" /></a>
|
<a class="button" href="#upload"><SpotIcon :icon="'upload'" :text="lang.get('admin_upload')" /></a>
|
||||||
@@ -692,16 +705,16 @@ export default {
|
|||||||
<SpotIcon :icon="'credits'" :text="lang.get('credits_project')" />
|
<SpotIcon :icon="'credits'" :text="lang.get('credits_project')" />
|
||||||
</a> {{ lang.get('credits_license') }}</div>
|
</a> {{ lang.get('credits_license') }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div :class="'map-control map-control-icon settings-control map-control-'+(mobile?'bottom':'top')" @click="toggleSettingsPanel">
|
<div :class="'map-control map-control-icon settings-control map-control-'+(isMobile()?'bottom':'top')" @click="toggleSettingsPanel">
|
||||||
<SpotIcon :icon="settingsPanelOpen?'prev':'menu'" />
|
<SpotIcon :icon="settingsPanelOpen?'prev':'menu'" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!mobile" id="legend" class="map-control settings-control map-control-bottom">
|
<div v-if="!isMobile()" id="legend" class="map-control settings-control map-control-bottom">
|
||||||
<div v-for="(color, hikeType) in hikes.colors" class="track">
|
<div v-for="(color, hikeType) in hikes.colors" class="track">
|
||||||
<span class="line" :style="'background-color:'+color+'; height:'+hikes.width+'px;'"></span>
|
<span class="line" :style="'background-color:'+color+'; height:'+hikes.width+'px;'"></span>
|
||||||
<span class="desc">{{ lang.get('track_'+hikeType) }}</span>
|
<span class="desc">{{ lang.get('track_'+hikeType) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="title" :class="'map-control settings-control map-control-'+(mobile?'bottom':'top')">
|
<div id="title" :class="'map-control settings-control map-control-'+(isMobile()?'bottom':'top')">
|
||||||
<span>{{ currProject.name }}</span>
|
<span>{{ currProject.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -718,7 +731,7 @@ export default {
|
|||||||
<ProjectPost :options="{type: 'loading', headerless: true}" />
|
<ProjectPost :options="{type: 'loading', headerless: true}" />
|
||||||
</div>
|
</div>
|
||||||
</Simplebar>
|
</Simplebar>
|
||||||
<div :class="'map-control map-control-icon feed-control map-control-'+(mobile?'bottom':'top')" @click="toggleFeedPanel">
|
<div :class="'map-control map-control-icon feed-control map-control-'+(isMobile()?'bottom':'top')" @click="toggleFeedPanel">
|
||||||
<SpotIcon :icon="feedPanelOpen?'next':'post'" />
|
<SpotIcon :icon="feedPanelOpen?'next':'post'" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export default {
|
|||||||
medias: Array,
|
medias: Array,
|
||||||
project: Object
|
project: Object
|
||||||
},
|
},
|
||||||
inject: ['spot', 'lang'],
|
inject: ['lang', 'consts'],
|
||||||
computed: {
|
computed: {
|
||||||
timeIcon() {
|
timeIcon() {
|
||||||
return (this.type == 'media')?'image-shot':'time';
|
return (this.type == 'media')?'image-shot':'time';
|
||||||
@@ -45,7 +45,7 @@ export default {
|
|||||||
</p>
|
</p>
|
||||||
<p class="time">
|
<p class="time">
|
||||||
<projectRelTime :icon="timeIcon" :localTime="options.formatted_time_local" :siteTime="options.formatted_time" :offset="options.day_offset" />
|
<projectRelTime :icon="timeIcon" :localTime="options.formatted_time_local" :siteTime="options.formatted_time" :offset="options.day_offset" />
|
||||||
<span v-if="project.mode==spot.consts.modes.blog"> ({{ options.relative_time }})</span>
|
<span v-if="project.mode==consts.modes.blog"> ({{ options.relative_time }})</span>
|
||||||
</p>
|
</p>
|
||||||
<p class="weather" v-if="options.weather_icon && options.weather_icon!='unknown'" :title="options.weather_cond==''?'':lang.get(options.weather_cond)">
|
<p class="weather" v-if="options.weather_icon && options.weather_icon!='unknown'" :title="options.weather_cond==''?'':lang.get(options.weather_cond)">
|
||||||
<spotIcon :icon="options.weather_icon" :classes="'fa-fw fa-lg'" :text="options.weather_temp+'°C'" />
|
<spotIcon :icon="options.weather_icon" :classes="'fa-fw fa-lg'" :text="options.weather_temp+'°C'" />
|
||||||
|
|||||||
@@ -52,10 +52,10 @@
|
|||||||
return this.options.displayed_id?(this.lang.get('counter', this.options.displayed_id)):'';
|
return this.options.displayed_id?(this.lang.get('counter', this.options.displayed_id)):'';
|
||||||
},
|
},
|
||||||
anchorLink() {
|
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() {
|
modeHisto() {
|
||||||
return (this.project.currProject.mode == this.spot.consts.modes.histo);
|
return (this.project.currProject.mode == this.consts.modes.histo);
|
||||||
},
|
},
|
||||||
relTime() {
|
relTime() {
|
||||||
return this.modeHisto?(this.options.formatted_time || '').substr(0, 10):this.options.relative_time;
|
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: {
|
methods: {
|
||||||
copyAnchor() {
|
copyAnchor() {
|
||||||
copyTextToClipboard(this.spot.consts.server+this.anchorLink);
|
copyTextToClipboard(this.consts.server+this.anchorLink);
|
||||||
this.anchorTitle = this.lang.get('link_copied');
|
this.anchorTitle = this.lang.get('link_copied');
|
||||||
this.anchorIcon = 'copied';
|
this.anchorIcon = 'copied';
|
||||||
setTimeout(()=>{ //TODO animation
|
setTimeout(()=>{ //TODO animation
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
},
|
},
|
||||||
panMapToMessage() {
|
panMapToMessage() {
|
||||||
this.popupRequested = true;
|
this.popupRequested = true;
|
||||||
if(this.spot.isMobile()) this.project.toggleFeedPanel(false, 'panToInstant');
|
if(this.isMobile()) this.project.toggleFeedPanel(false, 'panToInstant');
|
||||||
this.map.panToBetweenPanels(
|
this.map.panToBetweenPanels(
|
||||||
this.lngLat,
|
this.lngLat,
|
||||||
this.focusZoomLevel,
|
this.focusZoomLevel,
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
send() {
|
send() {
|
||||||
if(this.postMessage != '' && this.user.name != '') {
|
if(this.postMessage != '' && this.user.name != '') {
|
||||||
this.sending = true;
|
this.sending = true;
|
||||||
this.spot.get2(
|
this.api.get(
|
||||||
'add_post',
|
'add_post',
|
||||||
{
|
{
|
||||||
id_project: this.project.currProject.id,
|
id_project: this.project.currProject.id,
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ import SpotButton from './spotButton.vue';
|
|||||||
export default {
|
export default {
|
||||||
name: 'upload',
|
name: 'upload',
|
||||||
components: { SpotButton, SpotIcon },
|
components: { SpotButton, SpotIcon },
|
||||||
inject: ['spot', 'lang', 'projects', 'consts', 'user'],
|
inject: ['api', 'lang', 'projects', 'consts', 'user'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
project: this.projects[this.spot.vars('default_project_codename')],
|
project: this.projects.getDefaultProject(),
|
||||||
files: [],
|
files: [],
|
||||||
logs: [],
|
logs: [],
|
||||||
progress: 0,
|
progress: 0,
|
||||||
@@ -21,8 +21,6 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.spot.addPage('upload', {});
|
|
||||||
|
|
||||||
if(!this.project.editable) {
|
if(!this.project.editable) {
|
||||||
this.logs = [this.lang.get('upload_mode_archived', [this.project.name])];
|
this.logs = [this.lang.get('upload_mode_archived', [this.project.name])];
|
||||||
return;
|
return;
|
||||||
@@ -87,7 +85,10 @@ export default {
|
|||||||
event.target.value = '';
|
event.target.value = '';
|
||||||
},
|
},
|
||||||
addComment(oFile) {
|
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));})
|
.then((asData) => {this.logs.push(this.lang.get('media_comment_update', asData.filename));})
|
||||||
.catch((sMsgId) => {this.logs.push(this.lang.get(sMsgId));});
|
.catch((sMsgId) => {this.logs.push(this.lang.get(sMsgId));});
|
||||||
},
|
},
|
||||||
@@ -97,7 +98,11 @@ export default {
|
|||||||
navigator.geolocation.getCurrentPosition(
|
navigator.geolocation.getCurrentPosition(
|
||||||
(position) => {
|
(position) => {
|
||||||
this.logs.push('Sending 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'));})
|
.then((asData) => {this.logs.push(this.lang.get('success'));})
|
||||||
.catch((sMsgId) => {this.logs.push(this.lang.get(sMsgId));});
|
.catch((sMsgId) => {this.logs.push(this.lang.get(sMsgId));});
|
||||||
},
|
},
|
||||||
@@ -122,20 +127,20 @@ export default {
|
|||||||
<div class="section progress" v-if="progress > 0">
|
<div class="section progress" v-if="progress > 0">
|
||||||
<div class="bar" :style="{width:progress+'%'}"></div>
|
<div class="bar" :style="{width:progress+'%'}"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="section comment" v-for="file in files">
|
<div class="section comment" v-for="file in files">
|
||||||
<img class="thumb" :src="file.thumbnail" />
|
<img class="thumb" :src="file.thumbnail" />
|
||||||
<div class="form">
|
<div class="form">
|
||||||
<input class="content" name="content" type="text" v-model="file.content" />
|
<input class="content" name="content" type="text" v-model="file.content" />
|
||||||
<input class="id" name="id" type="hidden" :value="file.id" />
|
<input class="id" name="id" type="hidden" :value="file.id" />
|
||||||
<SpotButton :classes="'save'" :icon="'save'" :text="lang.get('save')" @click="addComment(file)" />
|
<SpotButton :classes="'save'" :icon="'save'" :text="lang.get('save')" @click="addComment(file)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h1>{{ lang.get('upload_pos_title') }}</h1>
|
<h1>{{ lang.get('upload_pos_title') }}</h1>
|
||||||
<div class="section location">
|
<div class="section location">
|
||||||
<SpotButton :icon="'message'" :text="lang.get('new_position')" @click="addPosition()" />
|
<SpotButton :icon="'message'" :text="lang.get('new_position')" @click="addPosition()" />
|
||||||
</div>
|
</div>
|
||||||
<div class="section logs" v-if="logs.length > 0">
|
<div class="section logs" v-if="logs.length > 0">
|
||||||
<p class="log" v-for="log in logs">{{ log }}.</p>
|
<p class="log" v-for="log in logs">{{ log }}.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
39
src/scripts/api.js
Normal file
39
src/scripts/api.js
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
//Librairies
|
//Librairies
|
||||||
|
import Api from './api.js';
|
||||||
import Lang from './lang.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 { createApp } from 'vue';
|
||||||
import SpotVue from '../Spot.vue';
|
|
||||||
|
//Main template
|
||||||
|
import Spot from '../Spot.vue';
|
||||||
|
|
||||||
//Style
|
//Style
|
||||||
import Css from './../styles/spot.scss';
|
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);
|
const appConfig = JSON.parse(document.getElementById('app-config').textContent);
|
||||||
|
|
||||||
//Instances
|
//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 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
|
//Mount app
|
||||||
const oSpotVue = createApp(SpotVue);
|
const oSpot = createApp(Spot);
|
||||||
oSpotVue.provide('spot', oSpot);
|
oSpot.provide('appConfig', appConfig);
|
||||||
oSpotVue.provide('lang', oLang);
|
oSpot.provide('api', oApi);
|
||||||
oSpotVue.mount('#container');
|
oSpot.provide('lang', oLang);
|
||||||
|
oSpot.provide('projects', oProjects);
|
||||||
|
oSpot.provide('user', oUser);
|
||||||
|
oSpot.mount('#container');
|
||||||
|
|||||||
17
src/scripts/projects.js
Normal file
17
src/scripts/projects.js
Normal file
@@ -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()];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
11
src/scripts/user.js
Normal file
11
src/scripts/user.js
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user