This commit is contained in:
4
composer.lock
generated
4
composer.lock
generated
@@ -12,7 +12,7 @@
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://git.lutran.fr/franzz/objects",
|
||||
"reference": "e28c650f2254801caa6f9756ad30ce1244c4c0c2"
|
||||
"reference": "d13fdacddec581b5cf5179b625f414b2453b6bf3"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
},
|
||||
"description": "Objects",
|
||||
"time": "2026-05-22T22:11:13+00:00"
|
||||
"time": "2026-05-28T10:12:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpmailer/phpmailer",
|
||||
|
||||
@@ -167,7 +167,7 @@ class Media extends PhpObject {
|
||||
'-print_format json', //output format: json
|
||||
'-i' //input file
|
||||
));
|
||||
exec('ffprobe '.$sParams.' "'.$sMediaPath.'"', $asResult);
|
||||
exec('ffprobe '.$sParams.' '.escapeshellarg($sMediaPath), $asResult);
|
||||
$asExif = json_decode(implode('', $asResult), true);
|
||||
|
||||
//Taken On
|
||||
@@ -269,10 +269,10 @@ class Media extends PhpObject {
|
||||
$sTempPath = self::getMediaPath(uniqid('temp_').'.png');
|
||||
$asResult = array();
|
||||
$sParams = implode(' ', array(
|
||||
'-i "'.$sMediaPath.'"', //input file
|
||||
'-ss 00:00:01.000', //Image taken after x seconds
|
||||
'-vframes 1', //number of video frames to output
|
||||
'"'.$sTempPath.'"', //output file
|
||||
'-i '.escapeshellarg($sMediaPath), //input file
|
||||
'-ss 00:00:01.000', //Image taken after x seconds
|
||||
'-vframes 1', //number of video frames to output
|
||||
escapeshellarg($sTempPath), //output file
|
||||
));
|
||||
exec('ffmpeg '.$sParams, $asResult);
|
||||
|
||||
@@ -296,7 +296,8 @@ class Media extends PhpObject {
|
||||
$sMediaPath = self::getMediaPath($sMediaName);
|
||||
$sMediaMime = mime_content_type($sMediaPath);
|
||||
switch($sMediaMime) {
|
||||
case 'video/quicktime': $sType = 'video'; break;
|
||||
case 'video/quicktime':
|
||||
case 'video/mp4': $sType = 'video'; break;
|
||||
default: $sType = 'image'; break;
|
||||
}
|
||||
|
||||
|
||||
37
lib/Spot.php
37
lib/Spot.php
@@ -46,6 +46,19 @@ class Spot extends Main
|
||||
const MAIN_PAGE = 'index';
|
||||
|
||||
const DIST_FOLDER = '../dist/';
|
||||
const MUTATING_ACTIONS = array(
|
||||
'add_post',
|
||||
'subscribe',
|
||||
'unsubscribe',
|
||||
'update_project',
|
||||
'upload',
|
||||
'add_comment',
|
||||
'add_position',
|
||||
'admin_set',
|
||||
'admin_create',
|
||||
'admin_delete',
|
||||
'build_geojson'
|
||||
);
|
||||
|
||||
private Project $oProject;
|
||||
private Media $oMedia;
|
||||
@@ -186,7 +199,8 @@ class Spot extends Main
|
||||
'chunk_size' => self::FEED_CHUNK_SIZE,
|
||||
'hash_sep' => '-',
|
||||
'title' => self::PROJECT_NAME,
|
||||
'default_page' => 'project'
|
||||
'default_page' => 'project',
|
||||
'csrf_token' => $this->getCsrfToken()
|
||||
)
|
||||
),
|
||||
self::MAIN_PAGE,
|
||||
@@ -278,17 +292,6 @@ class Spot extends Main
|
||||
return $oEmail->send();
|
||||
}
|
||||
|
||||
public function genCronFile() {
|
||||
//$bSuccess = (file_put_contents('spot_cron.sh', '#!/bin/bash'."\n".'cd '.dirname($_SERVER['SCRIPT_FILENAME'])."\n".'php -f index.php a=update_feed')!==false);
|
||||
$sFileName = 'spot_cron.sh';
|
||||
$sContent =
|
||||
'#!/bin/bash'."\n".
|
||||
'wget -qO- '.$this->asContext['serv_name'].'index.php?a=update_project > /dev/null'."\n".
|
||||
'#Crontab job: 0 * * * * . '.dirname($_SERVER['SCRIPT_FILENAME']).'/'.$sFileName.' > /dev/null'."\n";
|
||||
$bSuccess = (file_put_contents($sFileName, $sContent)!==false);
|
||||
return self::getJsonResult($bSuccess, '');
|
||||
}
|
||||
|
||||
public function getMarkers($asMessageIds=array(), $asMediaIds=array(), $bInternal=false)
|
||||
{
|
||||
//Get messages
|
||||
@@ -579,10 +582,10 @@ class Spot extends Main
|
||||
return $bInternal?$asResult['feed']:self::getJsonResult(true, '', $asResult);
|
||||
}
|
||||
|
||||
public function getFeed($iRefId=0, $sDirection, $sSort) {
|
||||
$this->oDb->cleanSql($iRefId);
|
||||
$this->oDb->cleanSql($sDirection);
|
||||
$this->oDb->cleanSql($sSort);
|
||||
private function getFeed($iRefId, $sDirection, $sSort) {
|
||||
$sRefId = is_scalar($iRefId) && preg_match('/^\d+(?:\.\d+)?$/D', (string) $iRefId) ? (string) $iRefId : '0';
|
||||
$sDirection = ($sDirection === '>')?'>':'<';
|
||||
$sSort = ($sSort === 'ASC')?'ASC':'DESC';
|
||||
|
||||
$sProjectIdField = Db::getId(Project::PROJ_TABLE);
|
||||
$sMsgIdField = Db::getId(Feed::MSG_TABLE);
|
||||
@@ -605,7 +608,7 @@ class Spot extends Main
|
||||
"FROM ".self::POST_TABLE,
|
||||
$this->getFeedConstraints(self::POST_TABLE, 'site_time', 'sql'),
|
||||
") AS items",
|
||||
($iRefId > 0)?("WHERE ref ".$sDirection." ".$iRefId):"",
|
||||
($sRefId !== '0')?("WHERE ref ".$sDirection." ".$sRefId):"",
|
||||
"ORDER BY ref ".$sSort,
|
||||
"LIMIT ".self::FEED_CHUNK_SIZE
|
||||
));
|
||||
|
||||
@@ -46,12 +46,15 @@ class Uploader extends UploadHandler
|
||||
}
|
||||
|
||||
protected function handle_file_upload($uploaded_file, $name, $size, $type, $error, $index = null, $content_range = null) {
|
||||
$file = parent::handle_file_upload($uploaded_file, $name, $size, $type, $error, $index, $content_range);
|
||||
$sExt = strtolower(pathinfo((string) $name, PATHINFO_EXTENSION));
|
||||
$sStoredName = bin2hex(random_bytes(16)).($sExt !== ''?'.'.$sExt:'');
|
||||
$file = parent::handle_file_upload($uploaded_file, $sStoredName, $size, $type, $error, $index, $content_range);
|
||||
|
||||
if(empty($file->error)) {
|
||||
$asResult = $this->oMedia->addMedia($file->name);
|
||||
if(!$asResult['result']) $file->error = $this->get_error_message($asResult['desc'], $asResult['data']);
|
||||
else {
|
||||
$file->original_name = basename((string) $name);
|
||||
$file->id = $this->oMedia->getMediaId();
|
||||
$file->thumbnail = $asResult['data']['thumb_path'];
|
||||
}
|
||||
|
||||
@@ -9,33 +9,36 @@ ob_start();
|
||||
$oLoader = require __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
use Franzz\Objects\ToolBox;
|
||||
use Franzz\Objects\Main;
|
||||
use Franzz\Spot\Spot;
|
||||
use Franzz\Spot\User;
|
||||
|
||||
ToolBox::fixGlobalVars($argv ?? array());
|
||||
|
||||
//Available variables
|
||||
$sAction = $_REQUEST['a'] ?? '';
|
||||
$sTimezone = $_REQUEST['t'] ?? '';
|
||||
$sName = $_GET['name'] ?? '';
|
||||
$sContent = $_GET['content'] ?? '';
|
||||
$iProjectId = $_REQUEST['id_project'] ?? 0 ;
|
||||
$sField = $_REQUEST['field'] ?? '';
|
||||
$oValue = $_REQUEST['value'] ?? '';
|
||||
$iId = $_REQUEST['id'] ?? 0 ;
|
||||
$sType = $_REQUEST['type'] ?? '';
|
||||
$sEmail = $_REQUEST['email'] ?? '';
|
||||
$sLat = $_REQUEST['latitude'] ?? '';
|
||||
$sLng = $_REQUEST['longitude'] ?? '';
|
||||
$iTimestamp = $_REQUEST['timestamp'] ?? 0;
|
||||
$sAction = $_REQUEST['a'] ?? '';
|
||||
$sTimezone = $_REQUEST['t'] ?? '';
|
||||
$sName = $_REQUEST['name'] ?? '';
|
||||
$sContent = $_REQUEST['content'] ?? '';
|
||||
$iProjectId = Spot::validatePositiveInt($_REQUEST['id_project'] ?? 0);
|
||||
$sRefId = $_REQUEST['id'] ?? 0;
|
||||
$iEntityId = Spot::validatePositiveInt($_REQUEST['id'] ?? 0);
|
||||
$sField = $_REQUEST['field'] ?? '';
|
||||
$oValue = $_REQUEST['value'] ?? '';
|
||||
$sType = $_REQUEST['type'] ?? '';
|
||||
$sEmail = $_REQUEST['email'] ?? '';
|
||||
$sLat = $_REQUEST['latitude'] ?? '';
|
||||
$sLng = $_REQUEST['longitude'] ?? '';
|
||||
$iTimestamp = Spot::validatePositiveInt($_REQUEST['timestamp'] ?? 0);
|
||||
$sCsrfToken = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? ($_POST['csrf_token'] ?? '');
|
||||
|
||||
//Initiate class
|
||||
$oSpot = new Spot(__FILE__, $sTimezone);
|
||||
$oSpot->setProjectId($iProjectId);
|
||||
|
||||
$sResult = '';
|
||||
if($sAction!='')
|
||||
$bValidRequest = $oSpot->validateMutationRequest($sAction, $sCsrfToken);
|
||||
if(!$bValidRequest) $sResult = Spot::getJsonResult(false, Spot::UNAUTHORIZED);
|
||||
elseif($sAction == '') $sResult = $oSpot->getAppMainPage();
|
||||
else
|
||||
{
|
||||
switch($sAction)
|
||||
{
|
||||
@@ -49,10 +52,10 @@ if($sAction!='')
|
||||
$sResult = $oSpot->getProjectGeoJson();
|
||||
break;
|
||||
case 'next_feed':
|
||||
$sResult = $oSpot->getNextFeed($iId);
|
||||
$sResult = $oSpot->getNextFeed($sRefId);
|
||||
break;
|
||||
case 'new_feed':
|
||||
$sResult = $oSpot->getNewFeed($iId);
|
||||
$sResult = $oSpot->getNewFeed($sRefId);
|
||||
break;
|
||||
case 'add_post':
|
||||
$sResult = $oSpot->addPost($sName, $sContent);
|
||||
@@ -64,7 +67,7 @@ if($sAction!='')
|
||||
$sResult = $oSpot->unsubscribe();
|
||||
break;
|
||||
case 'unsubscribe_email':
|
||||
$sResult = $oSpot->unsubscribeFromEmail($iId);
|
||||
$sResult = $oSpot->unsubscribeFromEmail($iEntityId);
|
||||
break;
|
||||
case 'update_project':
|
||||
$sResult = $oSpot->updateProject();
|
||||
@@ -78,7 +81,7 @@ if($sAction!='')
|
||||
$sResult = $oSpot->upload();
|
||||
break;
|
||||
case 'add_comment':
|
||||
$sResult = $oSpot->addComment($iId, $sContent);
|
||||
$sResult = $oSpot->addComment($iEntityId, $sContent);
|
||||
break;
|
||||
case 'add_position':
|
||||
$sResult = $oSpot->addPosition($sLat, $sLng, $iTimestamp);
|
||||
@@ -87,16 +90,13 @@ if($sAction!='')
|
||||
$sResult = $oSpot->getAdminSettings();
|
||||
break;
|
||||
case 'admin_set':
|
||||
$sResult = $oSpot->setAdminSettings($sType, $iId, $sField, $oValue);
|
||||
$sResult = $oSpot->setAdminSettings($sType, $iEntityId, $sField, $oValue);
|
||||
break;
|
||||
case 'admin_create':
|
||||
$sResult = $oSpot->createAdminSettings($sType);
|
||||
break;
|
||||
case 'admin_delete':
|
||||
$sResult = $oSpot->deleteAdminSettings($sType, $iId);
|
||||
break;
|
||||
case 'generate_cron':
|
||||
$sResult = $oSpot->genCronFile();
|
||||
$sResult = $oSpot->deleteAdminSettings($sType, $iEntityId);
|
||||
break;
|
||||
case 'sql':
|
||||
$sResult = $oSpot->getDbBuildScript();
|
||||
@@ -105,13 +105,12 @@ if($sAction!='')
|
||||
$sResult = $oSpot->buildGeoJSON($sName);
|
||||
break;
|
||||
default:
|
||||
$sResult = Main::getJsonResult(false, Main::NOT_FOUND);
|
||||
$sResult = Spot::getJsonResult(false, Spot::NOT_FOUND);
|
||||
}
|
||||
}
|
||||
else $sResult = Main::getJsonResult(false, Main::NOT_FOUND);
|
||||
else $sResult = Spot::getJsonResult(false, Spot::NOT_FOUND);
|
||||
}
|
||||
}
|
||||
else $sResult = $oSpot->getAppMainPage();
|
||||
|
||||
$sDebug = ob_get_clean();
|
||||
if(Settings::DEBUG && $sDebug!='') $oSpot->addUncaughtError($sDebug);
|
||||
|
||||
@@ -22,6 +22,7 @@ const oApi = new Api({
|
||||
server: appConfig.consts.server,
|
||||
processPage: appConfig.consts.process_page,
|
||||
timezone: oUser.timezone,
|
||||
csrfToken: appConfig.consts.csrf_token,
|
||||
errorCode: appConfig.consts.error,
|
||||
lang: oLang
|
||||
});
|
||||
|
||||
@@ -50,7 +50,7 @@ export default {
|
||||
}
|
||||
},
|
||||
createElem(sType) {
|
||||
this.api.get('admin_create', {type: sType})
|
||||
this.api.post('admin_create', {type: sType})
|
||||
.then((aoNewElemTypes) => {
|
||||
for(const [sType, aoNewElems] of Object.entries(aoNewElemTypes)) {
|
||||
for(const [iKey, oNewElem] of Object.entries(aoNewElems)) {
|
||||
@@ -68,7 +68,7 @@ export default {
|
||||
id: oElem.id
|
||||
};
|
||||
|
||||
this.api.get('admin_delete', asInputs)
|
||||
this.api.post('admin_delete', asInputs)
|
||||
.then((asData) => {
|
||||
delete this.elems[asInputs.type][asInputs.id];
|
||||
this.addFeedback('success', this.l('admin.delete_success'), asInputs);
|
||||
@@ -90,7 +90,7 @@ export default {
|
||||
value: sNewVal
|
||||
};
|
||||
|
||||
this.api.get('admin_set', asInputs)
|
||||
this.api.post('admin_set', asInputs)
|
||||
.then((asData) => {
|
||||
this.elems[oElem.type][oElem.id][oEvent.target.name] = sNewVal;
|
||||
this.addFeedback('success', this.l('admin.save_success'), asInputs);
|
||||
@@ -106,7 +106,7 @@ export default {
|
||||
this.saveTimer = setTimeout(() => {this.updateElem(oElem, oEvent);}, 2000);
|
||||
},
|
||||
updateProject() {
|
||||
this.api.get('update_project')
|
||||
this.api.post('update_project')
|
||||
.then((asData, sMsg) => {this.addFeedback('success', sMsg, {'update':'project'});})
|
||||
.catch((sMsg) => {this.addFeedback('error', sMsg, {'update':'project'});});
|
||||
}
|
||||
|
||||
@@ -514,8 +514,8 @@ export default {
|
||||
return {
|
||||
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)
|
||||
left: this.mapPadding + ((!bIsMobile && this.panels.leftOpen && this.settings)?this.settings.getWidth():0),
|
||||
right: this.mapPadding + ((!bIsMobile && this.panels.rightOpen && this.feed)?this.feed.getWidth():0)
|
||||
};
|
||||
},
|
||||
updateMapPadding(iDuration=0) {
|
||||
|
||||
@@ -39,7 +39,7 @@ export default {
|
||||
const sAction = this.action;
|
||||
this.loading = true;
|
||||
|
||||
this.api.request(sAction, {'email': this.user.email, 'name': this.user.name})
|
||||
this.api.request(sAction, {'email': this.user.email, 'name': this.user.name}, 'POST')
|
||||
.then((asResponse) => {
|
||||
this.feedbacks.push({type: asResponse.result, msg: asResponse.desc});
|
||||
this.user.setInfo(asResponse.data);
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
send() {
|
||||
if(this.postMessage != '' && this.user.name != '') {
|
||||
this.sending = true;
|
||||
this.api.get(
|
||||
this.api.post(
|
||||
'add_post',
|
||||
{
|
||||
id_project: this.project.project.id,
|
||||
|
||||
@@ -51,6 +51,7 @@ export default {
|
||||
endpoint,
|
||||
fieldName: 'files[]',
|
||||
formData: true,
|
||||
headers: {'X-CSRF-Token': this.consts.csrf_token},
|
||||
allowedMetaFields: ['t', 'name', 'type'],
|
||||
getResponseData(xhr) {
|
||||
return JSON.parse(xhr.responseText || '{}');
|
||||
@@ -65,7 +66,7 @@ export default {
|
||||
const uploadedFiles = response?.body?.files || [];
|
||||
uploadedFiles.forEach((uploadedFile) => {
|
||||
const hasError = Object.prototype.hasOwnProperty.call(uploadedFile, 'error');
|
||||
this.logs.push(hasError ? uploadedFile.error : this.lang.get('upload.success', [uploadedFile.name]));
|
||||
this.logs.push(hasError ? uploadedFile.error : this.lang.get('upload.success', [uploadedFile.original_name || uploadedFile.name]));
|
||||
if(!hasError) this.files.push({...uploadedFile, content: ''});
|
||||
});
|
||||
});
|
||||
@@ -85,7 +86,7 @@ export default {
|
||||
event.target.value = '';
|
||||
},
|
||||
addComment(oFile) {
|
||||
this.api.get('add_comment', {
|
||||
this.api.post('add_comment', {
|
||||
id: oFile.id,
|
||||
content: oFile.content
|
||||
})
|
||||
@@ -98,7 +99,7 @@ export default {
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(position) => {
|
||||
this.logs.push('Sending position...');
|
||||
this.api.get('add_position', {
|
||||
this.api.post('add_position', {
|
||||
'latitude': position.coords.latitude,
|
||||
'longitude': position.coords.longitude,
|
||||
'timestamp': Math.round(position.timestamp / 1000)
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg width="406" height="469" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 406 469" style="enable-background:new 0 0 406 469;">
|
||||
<style type="text/css">.st0{fill:#F18A00;}</style>
|
||||
<g id="Layer_3"/>
|
||||
<g id="Layer_8"/>
|
||||
<g id="Layer_9"/>
|
||||
<g id="Layer_7"/>
|
||||
<g id="Layer_6"/>
|
||||
<g id="Layer_4"/>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="m85.80618,195.80013c-1,-0.8 -1.3,-2.3 -0.6,-3.4c11.1,-18.2 56.5,-85.8 117.3,-85.8c49.6,0 90.4,33.4 110.3,53.3c1.2,1.2 2.9,1.9 4.6,1.9c1.7,0 3.4,-0.7 4.6,-1.9l16.4,-16.3c1,-1 1.1,-2.5 0.2,-3.5c-5.1,-6.1 -15.3,-17.2 -29.8,-28.2c-31.7,-24.1 -67.5,-36.3 -106.3,-36.3c-79.4,0 -129.8,75.4 -142.3,96.5c-0.8,1.4 -2.6,1.7 -3.8,0.7l-55.4,-43.6c-1,-0.8 -1.3,-2.2 -0.7,-3.3c5.9,-10.8 23,-39.4 48.5,-63.7c42.8,-40.7 95.1,-62.2 151.4,-62.2c56,0 109.2,18.9 153.9,54.8c27.3,21.9 45.5,43.2 51.3,50.4c0.8,1 0.7,2.5 -0.2,3.5l-12.3,12.2c-1.1,1.1 -2.9,1 -3.8,-0.2c-7.3,-8.9 -26.2,-30.5 -49.7,-49.2c-41.1,-32.7 -88,-49.2 -139.2,-49.2c-50.9,0 -96.5,18.6 -135.4,55.4c-20.9,19.7 -30.4,34.1 -35.6,42.3c-0.7,1.1 -0.5,2.6 0.6,3.5l16.7,13.2c1.2,0.9 2.6,1.4 4.1,1.4c2.2,0 4.2,-1.1 5.4,-2.9c7.4,-10.7 15.9,-20.6 26.8,-31.1c34.3,-33.1 75.7,-50.6 119.9,-50.6c92,0 151.2,70.7 165.3,89.4c0.8,1.1 0.7,2.5 -0.3,3.4l-49.8,49.3c-1.1,1.1 -2.8,1 -3.8,-0.1c-15.9,-18.1 -63,-66.5 -111.4,-66.5c-23.6,0 -46.6,11.3 -68.4,33.5c-7.2,7.3 -13.9,15.6 -19.9,24.4c-0.8,1.1 -0.5,2.7 0.6,3.6l93.1,73.2c1.1,0.9 1.3,2.5 0.4,3.7l-10.5,13.3c-0.9,1.1 -2.5,1.3 -3.7,0.4l-108.5,-85.3z" />
|
||||
<path class="st0" d="m205.90618,468.90013c-56,0 -109.2,-18.9 -153.9,-54.8c-27.3,-21.9 -45.5,-43.2 -51.3,-50.4c-0.8,-1 -0.7,-2.5 0.2,-3.5l12.3,-12.2c1.1,-1.1 2.9,-1 3.8,0.2c7.3,8.9 26.2,30.5 49.7,49.2c41.1,32.7 88,49.2 139.2,49.2c50.9,0 96.5,-18.6 135.4,-55.4c20.9,-19.7 30.4,-34.1 35.6,-42.3c0.7,-1.1 0.5,-2.6 -0.6,-3.5l-16.7,-13.2c-1.2,-0.9 -2.6,-1.4 -4.1,-1.4c-2.2,0 -4.2,1.1 -5.4,2.9c-7.4,10.7 -15.9,20.6 -26.8,31.1c-34.3,33.1 -75.7,50.6 -119.8,50.6c-92,0 -151.2,-70.7 -165.3,-89.4c-0.8,-1.1 -0.7,-2.5 0.3,-3.4l49.8,-49.3c1.1,-1.1 2.8,-1 3.8,0.1c15.9,18.1 63,66.5 111.4,66.5c23.6,0 46.6,-11.3 68.4,-33.5c7.2,-7.3 13.9,-15.6 19.9,-24.4c0.8,-1.1 0.5,-2.7 -0.6,-3.6l-93.1,-73.2c-1.1,-0.9 -1.3,-2.5 -0.4,-3.7l10.5,-13.3c0.9,-1.1 2.5,-1.3 3.7,-0.4l108.3,85.2c1,0.8 1.3,2.3 0.6,3.4c-11.1,18.2 -56.5,85.8 -117.3,85.8c-49.6,0 -90.4,-33.4 -110.3,-53.3c-1.2,-1.2 -2.9,-1.9 -4.6,-1.9s-3.4,0.7 -4.6,1.9l-16.4,16.3c-1,1 -1.1,2.5 -0.2,3.5c5.1,6.1 15.3,17.2 29.8,28.2c31.7,24.1 67.5,36.3 106.3,36.3c79.4,0 129.8,-75.4 142.3,-96.5c0.8,-1.4 2.6,-1.7 3.8,-0.7l55.4,43.6c1,0.8 1.3,2.2 0.7,3.3c-5.9,10.8 -23,39.4 -48.5,63.7c-42.6,40.8 -95,62.3 -151.3,62.3z" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<svg width="406" height="469" enable-background="new 0 0 406 469" version="1.1" viewBox="0 0 406 469" xmlns="http://www.w3.org/2000/svg">
|
||||
<style type="text/css">.st0{fill:#F18A00;}</style>
|
||||
<path class="st0" d="m85.806 195.8c-1-0.8-1.3-2.3-0.6-3.4 11.1-18.2 56.5-85.8 117.3-85.8 49.6 0 90.4 33.4 110.3 53.3 1.2 1.2 2.9 1.9 4.6 1.9s3.4-0.7 4.6-1.9l16.4-16.3c1-1 1.1-2.5 0.2-3.5-5.1-6.1-15.3-17.2-29.8-28.2-31.7-24.1-67.5-36.3-106.3-36.3-79.4 0-129.8 75.4-142.3 96.5-0.8 1.4-2.6 1.7-3.8 0.7l-55.4-43.6c-1-0.8-1.3-2.2-0.7-3.3 5.9-10.8 23-39.4 48.5-63.7 42.8-40.7 95.1-62.2 151.4-62.2 56 0 109.2 18.9 153.9 54.8 27.3 21.9 45.5 43.2 51.3 50.4 0.8 1 0.7 2.5-0.2 3.5l-12.3 12.2c-1.1 1.1-2.9 1-3.8-0.2-7.3-8.9-26.2-30.5-49.7-49.2-41.1-32.7-88-49.2-139.2-49.2-50.9 0-96.5 18.6-135.4 55.4-20.9 19.7-30.4 34.1-35.6 42.3-0.7 1.1-0.5 2.6 0.6 3.5l16.7 13.2c1.2 0.9 2.6 1.4 4.1 1.4 2.2 0 4.2-1.1 5.4-2.9 7.4-10.7 15.9-20.6 26.8-31.1 34.3-33.1 75.7-50.6 119.9-50.6 92 0 151.2 70.7 165.3 89.4 0.8 1.1 0.7 2.5-0.3 3.4l-49.8 49.3c-1.1 1.1-2.8 1-3.8-0.1-15.9-18.1-63-66.5-111.4-66.5-23.6 0-46.6 11.3-68.4 33.5-7.2 7.3-13.9 15.6-19.9 24.4-0.8 1.1-0.5 2.7 0.6 3.6l93.1 73.2c1.1 0.9 1.3 2.5 0.4 3.7l-10.5 13.3c-0.9 1.1-2.5 1.3-3.7 0.4l-108.5-85.3z" enable-background="new 0 0 406 469"/>
|
||||
<path class="st0" d="m205.91 468.9c-56 0-109.2-18.9-153.9-54.8-27.3-21.9-45.5-43.2-51.3-50.4-0.8-1-0.7-2.5 0.2-3.5l12.3-12.2c1.1-1.1 2.9-1 3.8 0.2 7.3 8.9 26.2 30.5 49.7 49.2 41.1 32.7 88 49.2 139.2 49.2 50.9 0 96.5-18.6 135.4-55.4 20.9-19.7 30.4-34.1 35.6-42.3 0.7-1.1 0.5-2.6-0.6-3.5l-16.7-13.2c-1.2-0.9-2.6-1.4-4.1-1.4-2.2 0-4.2 1.1-5.4 2.9-7.4 10.7-15.9 20.6-26.8 31.1-34.3 33.1-75.7 50.6-119.8 50.6-92 0-151.2-70.7-165.3-89.4-0.8-1.1-0.7-2.5 0.3-3.4l49.8-49.3c1.1-1.1 2.8-1 3.8 0.1 15.9 18.1 63 66.5 111.4 66.5 23.6 0 46.6-11.3 68.4-33.5 7.2-7.3 13.9-15.6 19.9-24.4 0.8-1.1 0.5-2.7-0.6-3.6l-93.1-73.2c-1.1-0.9-1.3-2.5-0.4-3.7l10.5-13.3c0.9-1.1 2.5-1.3 3.7-0.4l108.3 85.2c1 0.8 1.3 2.3 0.6 3.4-11.1 18.2-56.5 85.8-117.3 85.8-49.6 0-90.4-33.4-110.3-53.3-1.2-1.2-2.9-1.9-4.6-1.9s-3.4 0.7-4.6 1.9l-16.4 16.3c-1 1-1.1 2.5-0.2 3.5 5.1 6.1 15.3 17.2 29.8 28.2 31.7 24.1 67.5 36.3 106.3 36.3 79.4 0 129.8-75.4 142.3-96.5 0.8-1.4 2.6-1.7 3.8-0.7l55.4 43.6c1 0.8 1.3 2.2 0.7 3.3-5.9 10.8-23 39.4-48.5 63.7-42.6 40.8-95 62.3-151.3 62.3z" enable-background="new 0 0 406 469"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -1,44 +1,60 @@
|
||||
export default class Api {
|
||||
|
||||
constructor({server, processPage, timezone, errorCode, lang}) {
|
||||
constructor({server, processPage, timezone, csrfToken, errorCode, lang}) {
|
||||
this.server = server;
|
||||
this.processPage = processPage;
|
||||
this.timezone = timezone;
|
||||
this.csrfToken = csrfToken;
|
||||
this.errorCode = errorCode;
|
||||
this.lang = lang;
|
||||
}
|
||||
|
||||
async get(action, params = {}) {
|
||||
const response = await this.request(action, params);
|
||||
async get(sAction, asParams = {}) {
|
||||
const response = await this.request(sAction, asParams, 'GET');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async request(action, params = {}) {
|
||||
const requestParams = {
|
||||
...params,
|
||||
a: action,
|
||||
async post(sAction, asParams = {}) {
|
||||
const response = await this.request(sAction, asParams, 'POST');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async request(sAction, asParams = {}, method = 'GET') {
|
||||
const oUrl = new URL(this.processPage, this.server);
|
||||
|
||||
const sUrlParams = new URLSearchParams({
|
||||
...asParams,
|
||||
a: sAction,
|
||||
t: this.timezone
|
||||
}).toString();
|
||||
|
||||
const asOptions = {
|
||||
method,
|
||||
headers: {'Accept': 'application/json'}
|
||||
};
|
||||
|
||||
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);
|
||||
if(method === 'GET') {
|
||||
oUrl.search = sUrlParams;
|
||||
}
|
||||
else {
|
||||
asOptions.headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
|
||||
asOptions.headers['X-CSRF-Token'] = this.csrfToken;
|
||||
asOptions.body = sUrlParams;
|
||||
}
|
||||
|
||||
const response = await request.json();
|
||||
response.desc = this.lang.parse(response.desc);
|
||||
const oRequest = await fetch(oUrl, asOptions);
|
||||
|
||||
if(response.result == this.errorCode) {
|
||||
throw response.desc;
|
||||
if(!oRequest.ok) {
|
||||
throw new Error('Error HTTP ' + oRequest.status + ': ' + oRequest.statusText);
|
||||
}
|
||||
|
||||
return response;
|
||||
const oResponse = await oRequest.json();
|
||||
oResponse.desc = this.lang.parse(oResponse.desc);
|
||||
|
||||
if(oResponse.result == this.errorCode) {
|
||||
throw oResponse.desc;
|
||||
}
|
||||
|
||||
return oResponse;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { LngLat } from 'maplibre-gl';
|
||||
const EARTH_RADIUS = 6371008.8;
|
||||
const DEGREES_TO_RADIANS = Math.PI / 180;
|
||||
|
||||
export default class Projects {
|
||||
|
||||
@@ -63,12 +64,10 @@ export default class Projects {
|
||||
let iDistance = 0, iElevDrop = 0, iElevGain = 0, iTime = 0;
|
||||
|
||||
for(let i = 1; i < aoCoords.length; i++) {
|
||||
const oCurrPoint = new LngLat(aoCoords[i][0], aoCoords[i][1]);
|
||||
const oPrevPoint = new LngLat(aoCoords[i - 1][0], aoCoords[i - 1][1]);
|
||||
const iCurrElev = Number(aoCoords[i][2]);
|
||||
const iPrevElev = Number(aoCoords[i - 1][2]);
|
||||
const iElevDelta = (Number.isFinite(iCurrElev) && Number.isFinite(iPrevElev))?(iCurrElev - iPrevElev):0;
|
||||
const iSegDistance = oCurrPoint.distanceTo(oPrevPoint);
|
||||
const iSegDistance = this.getDistance(aoCoords[i], aoCoords[i - 1]);
|
||||
if(iSegDistance <= 0) continue;
|
||||
|
||||
iDistance += iSegDistance;
|
||||
@@ -94,4 +93,14 @@ export default class Projects {
|
||||
time: iTime
|
||||
};
|
||||
}
|
||||
|
||||
getDistance(aoPointA, aoPointB) {
|
||||
const iLatA = aoPointA[1] * DEGREES_TO_RADIANS;
|
||||
const iLatB = aoPointB[1] * DEGREES_TO_RADIANS;
|
||||
const iAngle =
|
||||
Math.sin(iLatA) * Math.sin(iLatB)
|
||||
+ Math.cos(iLatA) * Math.cos(iLatB) * Math.cos((aoPointB[0] - aoPointA[0]) * DEGREES_TO_RADIANS);
|
||||
|
||||
return EARTH_RADIUS * Math.acos(Math.min(iAngle, 1));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user