Compare commits
13 Commits
master
...
3611f2206f
| Author | SHA1 | Date | |
|---|---|---|---|
| 3611f2206f | |||
| 828d32b0ef | |||
| f5d193e42b | |||
| c45a19e6bf | |||
| f86dadfc7d | |||
| 9d676c339b | |||
| 2f3a3f9561 | |||
| 55e40f76a1 | |||
| 4e9fb52318 | |||
| 850d2e7235 | |||
| 97645b3476 | |||
| ab914a391f | |||
| 842e02f4bb |
21
.gitignore
vendored
@@ -1,14 +1,7 @@
|
|||||||
/settings.php
|
/vendor/
|
||||||
/style/.sass-cache/
|
/config/settings.php
|
||||||
/files/**/*.jpg
|
/files/
|
||||||
/files/**/*.JPG
|
/geo/
|
||||||
/files/**/*.jpeg
|
/node_modules/
|
||||||
/files/**/*.JPEG
|
/log.html
|
||||||
/files/**/*.png
|
/dist/
|
||||||
/files/**/*.PNG
|
|
||||||
/files/**/*.mov
|
|
||||||
/files/**/*.MOV
|
|
||||||
/geo/*.geojson
|
|
||||||
/spot_cron.sh
|
|
||||||
/vendor/*
|
|
||||||
/log.html
|
|
||||||
|
|||||||
12
build/paths.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
SRC: path.resolve(__dirname, '..', 'src'),
|
||||||
|
DIST: path.resolve(__dirname, '..', 'dist'),
|
||||||
|
ASSETS: path.resolve(__dirname, '..', 'assets'),
|
||||||
|
IMAGES: path.resolve(__dirname, '..', 'src/images'),
|
||||||
|
STYLES: path.resolve(__dirname, '..', 'src/styles'),
|
||||||
|
LIB: path.resolve(__dirname, '..', 'lib'),
|
||||||
|
MASKS: path.resolve(__dirname, '..', 'src/masks'),
|
||||||
|
ROOT: path.resolve(__dirname, '..')
|
||||||
|
}
|
||||||
108
build/webpack.common.js
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const { SRC, DIST, ASSETS, LIB } = require('./paths');
|
||||||
|
var webpack = require("webpack");
|
||||||
|
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
|
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||||
|
const SymlinkWebpackPlugin = require('symlink-webpack-plugin');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: {
|
||||||
|
app: path.resolve(SRC, 'scripts', 'app.js')
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: DIST,
|
||||||
|
filename: '[name].js',
|
||||||
|
publicPath: './' //meaning dist/
|
||||||
|
},
|
||||||
|
devtool: "inline-source-map",
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
use: {
|
||||||
|
loader: 'babel-loader',
|
||||||
|
options: {
|
||||||
|
presets: ['@babel/preset-env'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.html$/i,
|
||||||
|
loader: "html-loader",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(woff|woff2|eot|ttf|otf)$/i,
|
||||||
|
type: 'asset/resource'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.s[ac]ss$/i,
|
||||||
|
use: [
|
||||||
|
'style-loader',
|
||||||
|
'css-loader',
|
||||||
|
'resolve-url-loader',
|
||||||
|
{
|
||||||
|
loader: 'sass-loader',
|
||||||
|
options: {
|
||||||
|
implementation: require.resolve('sass'),
|
||||||
|
sourceMap: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/i,
|
||||||
|
use: ["style-loader", "css-loader"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(png|svg|jpg|jpeg|gif)$/i,
|
||||||
|
use: {
|
||||||
|
loader: "url-loader",
|
||||||
|
options: {
|
||||||
|
limit: 1 * 1024,
|
||||||
|
//name: "images/[name].[hash:7].[ext]"
|
||||||
|
name: "images/[name].[ext]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*type: 'asset',
|
||||||
|
parser: {
|
||||||
|
dataUrlCondition: {
|
||||||
|
maxSize: 8*1024
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new webpack.ProvidePlugin({
|
||||||
|
$: require.resolve('jquery'),
|
||||||
|
jQuery: require.resolve('jquery'),
|
||||||
|
//L: require.resolve('leaflet')
|
||||||
|
}),
|
||||||
|
new CopyWebpackPlugin({
|
||||||
|
patterns: [{
|
||||||
|
from: 'geo/',
|
||||||
|
to: path.resolve(DIST, 'geo')
|
||||||
|
}, {
|
||||||
|
from: path.resolve(LIB, 'index.php'),
|
||||||
|
to: 'index.php'
|
||||||
|
}, {
|
||||||
|
from: path.resolve(SRC, 'images/icons'),
|
||||||
|
to: 'images/icons'
|
||||||
|
}]
|
||||||
|
}),
|
||||||
|
new SymlinkWebpackPlugin({ origin: '../files/', symlink: 'files' }),
|
||||||
|
new CleanWebpackPlugin()
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
extensions: ['', '.js'],
|
||||||
|
alias: {
|
||||||
|
"@scripts": path.resolve(SRC, "scripts"),
|
||||||
|
'load-image': 'blueimp-load-image/js/load-image.js',
|
||||||
|
'load-image-meta': 'blueimp-load-image/js/load-image-meta.js',
|
||||||
|
'load-image-exif': 'blueimp-load-image/js/load-image-exif.js',
|
||||||
|
'canvas-to-blob': 'blueimp-canvas-to-blob/js/canvas-to-blob.js',
|
||||||
|
'jquery-ui/ui/widget': 'blueimp-file-upload/js/vendor/jquery.ui.widget.js'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
6
build/webpack.dev.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const { merge } = require('webpack-merge')
|
||||||
|
|
||||||
|
module.exports = merge(require('./webpack.common.js'), {
|
||||||
|
mode: 'development',
|
||||||
|
watch: true
|
||||||
|
})
|
||||||
5
build/webpack.prod.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
const { merge } = require('webpack-merge')
|
||||||
|
|
||||||
|
module.exports = merge(require('./webpack.common.js'), {
|
||||||
|
mode: 'production'
|
||||||
|
})
|
||||||
4
cli/cron.sh
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
wget -qO- https://spot.lutran.fr/index.php?a=update_project > /dev/null
|
||||||
|
|
||||||
|
#Crontab job: 0 * * * * . /var/www/spot/spot_cron.sh > /dev/null
|
||||||
@@ -9,15 +9,15 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"franzz/objects": "dev-composer",
|
"franzz/objects": "dev-vue",
|
||||||
"phpmailer/phpmailer": "^6.5"
|
"phpmailer/phpmailer": "^6.5"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Franzz\\Spot\\": "inc/"
|
"Franzz\\Spot\\": "lib/"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"settings.php"
|
"config/settings.php"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
22
composer.lock
generated
@@ -4,15 +4,15 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "164c903fea5bdcfb36cf6ea31ec0c307",
|
"content-hash": "12bb836a394b645df50c14652a2ae5bf",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "franzz/objects",
|
"name": "franzz/objects",
|
||||||
"version": "dev-composer",
|
"version": "dev-vue",
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "path",
|
"type": "path",
|
||||||
"url": "../objects",
|
"url": "../objects",
|
||||||
"reference": "e1cf78b992a6f52742d6834f7508c0ef373ac860"
|
"reference": "a9f8601384a0078cf8531576768e2c5bad4bbf95"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -27,16 +27,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpmailer/phpmailer",
|
"name": "phpmailer/phpmailer",
|
||||||
"version": "v6.8.0",
|
"version": "v6.8.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/PHPMailer/PHPMailer.git",
|
"url": "https://github.com/PHPMailer/PHPMailer.git",
|
||||||
"reference": "df16b615e371d81fb79e506277faea67a1be18f1"
|
"reference": "e88da8d679acc3824ff231fdc553565b802ac016"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/df16b615e371d81fb79e506277faea67a1be18f1",
|
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/e88da8d679acc3824ff231fdc553565b802ac016",
|
||||||
"reference": "df16b615e371d81fb79e506277faea67a1be18f1",
|
"reference": "e88da8d679acc3824ff231fdc553565b802ac016",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -46,13 +46,13 @@
|
|||||||
"php": ">=5.5.0"
|
"php": ">=5.5.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.2",
|
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
|
||||||
"doctrine/annotations": "^1.2.6 || ^1.13.3",
|
"doctrine/annotations": "^1.2.6 || ^1.13.3",
|
||||||
"php-parallel-lint/php-console-highlighter": "^1.0.0",
|
"php-parallel-lint/php-console-highlighter": "^1.0.0",
|
||||||
"php-parallel-lint/php-parallel-lint": "^1.3.2",
|
"php-parallel-lint/php-parallel-lint": "^1.3.2",
|
||||||
"phpcompatibility/php-compatibility": "^9.3.5",
|
"phpcompatibility/php-compatibility": "^9.3.5",
|
||||||
"roave/security-advisories": "dev-latest",
|
"roave/security-advisories": "dev-latest",
|
||||||
"squizlabs/php_codesniffer": "^3.7.1",
|
"squizlabs/php_codesniffer": "^3.7.2",
|
||||||
"yoast/phpunit-polyfills": "^1.0.4"
|
"yoast/phpunit-polyfills": "^1.0.4"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
|
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/PHPMailer/PHPMailer/issues",
|
"issues": "https://github.com/PHPMailer/PHPMailer/issues",
|
||||||
"source": "https://github.com/PHPMailer/PHPMailer/tree/v6.8.0"
|
"source": "https://github.com/PHPMailer/PHPMailer/tree/v6.8.1"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-03-06T14:43:22+00:00"
|
"time": "2023-08-29T08:26:30+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packages-dev": [],
|
"packages-dev": [],
|
||||||
|
|||||||
69304
geo/snt.gpx
@@ -2,6 +2,7 @@ locale = en_NZ
|
|||||||
page_og_desc = Keep contact with François when he is off hiking
|
page_og_desc = Keep contact with François when he is off hiking
|
||||||
error_commit_db = Issue committing to DB
|
error_commit_db = Issue committing to DB
|
||||||
unknown_field = Field "$0" is unknown
|
unknown_field = Field "$0" is unknown
|
||||||
|
impossible_value = Value "$0" is not possible for field "$1"
|
||||||
|
|
||||||
nav_back = Back
|
nav_back = Back
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ locale = es_ES
|
|||||||
page_og_desc = Mantente en contacto con François durante sus aventuras a la montaña
|
page_og_desc = Mantente en contacto con François durante sus aventuras a la montaña
|
||||||
error_commit_db = Error SQL
|
error_commit_db = Error SQL
|
||||||
unknown_field = Campo "$0" desconocido
|
unknown_field = Campo "$0" desconocido
|
||||||
|
impossible_value = Valor "$0" no es posible para campo "$1"
|
||||||
|
|
||||||
nav_back = Atrás
|
nav_back = Atrás
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ locale = fr_CH
|
|||||||
page_og_desc = Gardez le contact avec François lorsqu'il part sur les chemins
|
page_og_desc = Gardez le contact avec François lorsqu'il part sur les chemins
|
||||||
error_commit_db = Error lors de la requête SQL
|
error_commit_db = Error lors de la requête SQL
|
||||||
unknown_field = Champ "$0" inconnu
|
unknown_field = Champ "$0" inconnu
|
||||||
|
impossible_value = La valeur "$0" n'est pas possible pour le champ "$1"
|
||||||
|
|
||||||
nav_back = Retour
|
nav_back = Retour
|
||||||
|
|
||||||
|
|||||||
@@ -36,18 +36,18 @@ class Converter extends PhpObject {
|
|||||||
|
|
||||||
public static function isGeoJsonValid($sCodeName) {
|
public static function isGeoJsonValid($sCodeName) {
|
||||||
$bResult = false;
|
$bResult = false;
|
||||||
$sGpxFilePath = Geo::getFilePath($sCodeName, Gpx::EXT);
|
$sGpxFilePath = Gpx::getFilePath($sCodeName);
|
||||||
$sGeoJsonFilePath = Geo::getFilePath($sCodeName, GeoJson::EXT);
|
$sGeoJsonFilePath = GeoJson::getFilePath($sCodeName);
|
||||||
|
|
||||||
//No need to generate if gpx is missing
|
//No need to generate if gpx is missing
|
||||||
if(!file_exists($sGpxFilePath) || file_exists($sGeoJsonFilePath) && filemtime($sGeoJsonFilePath) > filemtime(Geo::getFilePath($sCodeName, Gpx::EXT))) $bResult = true;
|
if(!file_exists($sGpxFilePath) || file_exists($sGeoJsonFilePath) && filemtime($sGeoJsonFilePath) > filemtime(Gpx::getFilePath($sCodeName))) $bResult = true;
|
||||||
return $bResult;
|
return $bResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Geo extends PhpObject {
|
class Geo extends PhpObject {
|
||||||
|
|
||||||
const GEO_FOLDER = 'geo/';
|
const GEO_FOLDER = '../geo/';
|
||||||
const OPT_SIMPLE = 'simplification';
|
const OPT_SIMPLE = 'simplification';
|
||||||
|
|
||||||
protected $asTracks;
|
protected $asTracks;
|
||||||
@@ -55,12 +55,16 @@ class Geo extends PhpObject {
|
|||||||
|
|
||||||
public function __construct($sCodeName) {
|
public function __construct($sCodeName) {
|
||||||
parent::__construct(get_class($this), Settings::DEBUG, PhpObject::MODE_HTML);
|
parent::__construct(get_class($this), Settings::DEBUG, PhpObject::MODE_HTML);
|
||||||
$this->sFilePath = self::getFilePath($sCodeName, static::EXT);
|
$this->sFilePath = self::getFilePath($sCodeName);
|
||||||
$this->asTracks = array();
|
$this->asTracks = array();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getFilePath($sCodeName, $sExt) {
|
public static function getFilePath($sCodeName) {
|
||||||
return self::GEO_FOLDER.$sCodeName.$sExt;
|
return self::GEO_FOLDER.$sCodeName.static::EXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getDistFilePath($sCodeName) {
|
||||||
|
return 'geo/'.$sCodeName.static::EXT;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLog() {
|
public function getLog() {
|
||||||
@@ -168,31 +168,6 @@ class Feed extends PhpObject {
|
|||||||
return $bNewMsg;
|
return $bNewMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addManualPosition($sLat, $sLng, $iTimestamp) {
|
|
||||||
$sTimeZone = date_default_timezone_get();
|
|
||||||
$oDateTime = new \DateTime('@'.$iTimestamp);
|
|
||||||
$oDateTime->setTimezone(new \DateTimeZone($sTimeZone));
|
|
||||||
$asWeather = $this->getWeather(array($sLat, $sLng), $iTimestamp);
|
|
||||||
|
|
||||||
$asMsg = [
|
|
||||||
'ref_msg_id' => $iTimestamp.'/man',
|
|
||||||
'id_feed' => $this->getFeedId(),
|
|
||||||
'type' => 'OK',
|
|
||||||
'latitude' => $sLat,
|
|
||||||
'longitude' => $sLng,
|
|
||||||
'iso_time' => $oDateTime->format("Y-m-d\TH:i:sO"), //Incorrect ISO 8601 format, but compliant with Spot data
|
|
||||||
'site_time' => $oDateTime->format(Db::TIMESTAMP_FORMAT),
|
|
||||||
'timezone' => $sTimeZone,
|
|
||||||
'unix_time' => $iTimestamp,
|
|
||||||
'content' => '',
|
|
||||||
'battery_state' => '',
|
|
||||||
'posted_on' => date(Db::TIMESTAMP_FORMAT),
|
|
||||||
];
|
|
||||||
|
|
||||||
$iMessageId = $this->oDb->insertRow(self::MSG_TABLE, array_merge($asMsg, $asWeather));
|
|
||||||
return $iMessageId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function updateFeed() {
|
private function updateFeed() {
|
||||||
$bNewMsg = false;
|
$bNewMsg = false;
|
||||||
$asData = $this->retrieveFeed();
|
$asData = $this->retrieveFeed();
|
||||||
@@ -144,8 +144,8 @@ class Project extends PhpObject {
|
|||||||
|
|
||||||
if($sCodeName != '' && !Converter::isGeoJsonValid($sCodeName)) Converter::convertToGeoJson($sCodeName);
|
if($sCodeName != '' && !Converter::isGeoJsonValid($sCodeName)) Converter::convertToGeoJson($sCodeName);
|
||||||
|
|
||||||
$asProject['geofilepath'] = Spot::addTimestampToFilePath(Geo::getFilePath($sCodeName, GeoJson::EXT));
|
$asProject['geofilepath'] = Spot::addTimestampToFilePath(GeoJson::getDistFilePath($sCodeName));
|
||||||
$asProject['gpxfilepath'] = Spot::addTimestampToFilePath(Geo::getFilePath($sCodeName, Gpx::EXT));
|
$asProject['gpxfilepath'] = Spot::addTimestampToFilePath(Gpx::getDistFilePath($sCodeName));
|
||||||
$asProject['codename'] = $sCodeName;
|
$asProject['codename'] = $sCodeName;
|
||||||
}
|
}
|
||||||
return $bSpecificProj?$asProject:$asProjects;
|
return $bSpecificProj?$asProject:$asProjects;
|
||||||
@@ -28,6 +28,8 @@ use \Settings;
|
|||||||
* - Posts (table `posts`):
|
* - Posts (table `posts`):
|
||||||
* - site_time: timestamp in Site Time
|
* - site_time: timestamp in Site Time
|
||||||
* - timezone: Local Timezone
|
* - timezone: Local Timezone
|
||||||
|
* - Users (table `users`):
|
||||||
|
* - timezone: Site Timezone (stored user's timezone for emails)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class Spot extends Main
|
class Spot extends Main
|
||||||
@@ -40,6 +42,8 @@ class Spot extends Main
|
|||||||
|
|
||||||
const DEFAULT_LANG = 'en';
|
const DEFAULT_LANG = 'en';
|
||||||
|
|
||||||
|
const MAIN_PAGE = 'index';
|
||||||
|
|
||||||
private Project $oProject;
|
private Project $oProject;
|
||||||
private Media $oMedia;
|
private Media $oMedia;
|
||||||
private User $oUser;
|
private User $oUser;
|
||||||
@@ -158,8 +162,8 @@ class Spot extends Main
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAppMainPage()
|
public function getAppMainPage() {
|
||||||
{
|
|
||||||
//Cache Page List
|
//Cache Page List
|
||||||
$asPages = array_diff($this->asMasks, array('email_update', 'email_conf'));
|
$asPages = array_diff($this->asMasks, array('email_update', 'email_conf'));
|
||||||
if(!$this->oUser->checkUserClearance(User::CLEARANCE_ADMIN)) {
|
if(!$this->oUser->checkUserClearance(User::CLEARANCE_ADMIN)) {
|
||||||
@@ -175,23 +179,16 @@ class Spot extends Main
|
|||||||
'user' => $this->oUser->getUserInfo()
|
'user' => $this->oUser->getUserInfo()
|
||||||
),
|
),
|
||||||
'consts' => array(
|
'consts' => array(
|
||||||
'server' => $this->asContext['serv_name'],
|
|
||||||
'modes' => Project::MODES,
|
'modes' => Project::MODES,
|
||||||
'clearances' => User::CLEARANCES,
|
'clearances' => User::CLEARANCES,
|
||||||
'default_timezone' => Settings::TIMEZONE
|
'default_timezone' => Settings::TIMEZONE
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
'index',
|
self::MAIN_PAGE,
|
||||||
array(
|
array(
|
||||||
'language' => $this->oLang->getLanguage(),
|
'language' => $this->oLang->getLanguage(),
|
||||||
'host_url' => $this->asContext['serv_name'],
|
'filepath_css' => self::addTimestampToFilePath('spot.css'),
|
||||||
'filepath_css' => self::addTimestampToFilePath('style/spot.css'),
|
'filepath_js' => self::addTimestampToFilePath('../dist/app.js'),
|
||||||
'filepath_js_d3' => self::addTimestampToFilePath('script/d3.min.js'),
|
|
||||||
'filepath_js_leaflet' => self::addTimestampToFilePath('script/leaflet.min.js'),
|
|
||||||
'filepath_js_jquery' => self::addTimestampToFilePath('script/jquery.min.js'),
|
|
||||||
'filepath_js_jquery_mods' => self::addTimestampToFilePath('script/jquery.mods.js'),
|
|
||||||
'filepath_js_spot' => self::addTimestampToFilePath('script/spot.js'),
|
|
||||||
'filepath_js_lightbox' => self::addTimestampToFilePath('script/lightbox.js')
|
|
||||||
),
|
),
|
||||||
$asPages
|
$asPages
|
||||||
);
|
);
|
||||||
@@ -630,13 +627,6 @@ class Spot extends Main
|
|||||||
return self::getJsonResult($asResult['result'], $asResult['desc'], $asResult['data']);
|
return self::getJsonResult($asResult['result'], $asResult['desc'], $asResult['data']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addPosition($sLat, $sLng, $iTimestamp) {
|
|
||||||
$oFeed = new Feed($this->oDb, $this->oProject->getFeedIds()[0]);
|
|
||||||
$bResult = ($oFeed->addManualPosition($sLat, $sLng, $iTimestamp) > 0);
|
|
||||||
|
|
||||||
return self::getJsonResult($bResult, $bResult?'':$this->oDb->getLastError());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAdminSettings($sType='') {
|
public function getAdminSettings($sType='') {
|
||||||
$oFeed = new Feed($this->oDb);
|
$oFeed = new Feed($this->oDb);
|
||||||
$asData = array(
|
$asData = array(
|
||||||
@@ -659,6 +649,8 @@ class Spot extends Main
|
|||||||
$sDesc = '';
|
$sDesc = '';
|
||||||
$asResult = array();
|
$asResult = array();
|
||||||
|
|
||||||
|
if($this->oDb->isId($sField) && $sValue <= 0) return self::getJsonResult(false, $this->oLang->getTranslation('impossible_value', [$sValue, $sField]));
|
||||||
|
|
||||||
switch($sType) {
|
switch($sType) {
|
||||||
case 'project':
|
case 'project':
|
||||||
$oProject = new Project($this->oDb, $iId);
|
$oProject = new Project($this->oDb, $iId);
|
||||||
@@ -751,10 +743,6 @@ class Spot extends Main
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function buildGeoJSON($sCodeName) {
|
|
||||||
return Converter::convertToGeoJson($sCodeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function decToDms($dValue, $sType) {
|
public static function decToDms($dValue, $sType) {
|
||||||
if($sType=='lat') $sDirection = ($dValue >= 0)?'N':'S'; //Latitude
|
if($sType=='lat') $sDirection = ($dValue >= 0)?'N':'S'; //Latitude
|
||||||
else $sDirection = ($dValue >= 0)?'E':'W'; //Longitude
|
else $sDirection = ($dValue >= 0)?'E':'W'; //Longitude
|
||||||
@@ -25,7 +25,12 @@ class Uploader extends UploadHandler
|
|||||||
$this->oMedia = &$oMedia;
|
$this->oMedia = &$oMedia;
|
||||||
$this->oLang = &$oLang;
|
$this->oLang = &$oLang;
|
||||||
$this->sBody = '';
|
$this->sBody = '';
|
||||||
parent::__construct(array('image_versions'=>array(), 'accept_file_types'=>'/\.(gif|jpe?g|png|mov|mp4)$/i'));
|
|
||||||
|
parent::__construct(array(
|
||||||
|
'upload_dir' => Media::MEDIA_FOLDER,
|
||||||
|
'image_versions' => array(),
|
||||||
|
'accept_file_types' => '/\.(gif|jpe?g|png|mov|mp4)$/i'
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function validate($uploaded_file, $file, $error, $index, $content_range) {
|
protected function validate($uploaded_file, $file, $error, $index, $content_range) {
|
||||||
@@ -5,7 +5,8 @@
|
|||||||
//Start buffering
|
//Start buffering
|
||||||
ob_start();
|
ob_start();
|
||||||
|
|
||||||
$oLoader = require __DIR__.'/vendor/autoload.php';
|
//Run from /dist/
|
||||||
|
$oLoader = require __DIR__.'/../vendor/autoload.php';
|
||||||
|
|
||||||
use Franzz\Objects\ToolBox;
|
use Franzz\Objects\ToolBox;
|
||||||
use Franzz\Objects\Main;
|
use Franzz\Objects\Main;
|
||||||
@@ -25,9 +26,6 @@ $oValue = $_REQUEST['value'] ?? '';
|
|||||||
$iId = $_REQUEST['id'] ?? 0 ;
|
$iId = $_REQUEST['id'] ?? 0 ;
|
||||||
$sType = $_REQUEST['type'] ?? '';
|
$sType = $_REQUEST['type'] ?? '';
|
||||||
$sEmail = $_REQUEST['email'] ?? '';
|
$sEmail = $_REQUEST['email'] ?? '';
|
||||||
$sLat = $_REQUEST['latitude'] ?? '';
|
|
||||||
$sLng = $_REQUEST['longitude'] ?? '';
|
|
||||||
$iTimestamp = $_REQUEST['timestamp'] ?? 0;
|
|
||||||
|
|
||||||
//Initiate class
|
//Initiate class
|
||||||
$oSpot = new Spot(__FILE__, $sTimezone);
|
$oSpot = new Spot(__FILE__, $sTimezone);
|
||||||
@@ -73,9 +71,6 @@ if($sAction!='')
|
|||||||
case 'add_comment':
|
case 'add_comment':
|
||||||
$sResult = $oSpot->addComment($iId, $sContent);
|
$sResult = $oSpot->addComment($iId, $sContent);
|
||||||
break;
|
break;
|
||||||
case 'add_position':
|
|
||||||
$sResult = $oSpot->addPosition($sLat, $sLng, $iTimestamp);
|
|
||||||
break;
|
|
||||||
case 'admin_new':
|
case 'admin_new':
|
||||||
$sResult = $oSpot->createProject();
|
$sResult = $oSpot->createProject();
|
||||||
break;
|
break;
|
||||||
@@ -94,9 +89,6 @@ if($sAction!='')
|
|||||||
case 'sql':
|
case 'sql':
|
||||||
$sResult = $oSpot->getDbBuildScript();
|
$sResult = $oSpot->getDbBuildScript();
|
||||||
break;
|
break;
|
||||||
case 'build_geojson':
|
|
||||||
$sResult = $oSpot->buildGeoJSON($sName);
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
$sResult = Main::getJsonResult(false, Main::NOT_FOUND);
|
$sResult = Main::getJsonResult(false, Main::NOT_FOUND);
|
||||||
}
|
}
|
||||||
212
masks/admin.html
@@ -1,212 +0,0 @@
|
|||||||
<div id="admin">
|
|
||||||
<a name="back" class="button" href="[#]host_url[#]"><i class="fa fa-back push"></i>[#]lang:nav_back[#]</a>
|
|
||||||
<h1>[#]lang:projects[#]</h1>
|
|
||||||
<div id="project_section">
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>[#]lang:id_project[#]</th>
|
|
||||||
<th>[#]lang:project[#]</th>
|
|
||||||
<th>[#]lang:mode[#]</th>
|
|
||||||
<th>[#]lang:code_name[#]</th>
|
|
||||||
<th>[#]lang:start[#]</th>
|
|
||||||
<th>[#]lang:end[#]</th>
|
|
||||||
<th>[#]lang:delete[#]</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody></tbody>
|
|
||||||
</table>
|
|
||||||
<div id="new"></div>
|
|
||||||
</div>
|
|
||||||
<h1>[#]lang:feeds[#]</h1>
|
|
||||||
<div id="feed_section">
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>[#]lang:id_feed[#]</th>
|
|
||||||
<th>[#]lang:ref_feed_id[#]</th>
|
|
||||||
<th>[#]lang:id_spot[#]</th>
|
|
||||||
<th>[#]lang:id_project[#]</th>
|
|
||||||
<th>[#]lang:name[#]</th>
|
|
||||||
<th>[#]lang:status[#]</th>
|
|
||||||
<th>[#]lang:last_update[#]</th>
|
|
||||||
<th>[#]lang:delete[#]</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody></tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<h1>Spots</h1>
|
|
||||||
<div id="spot_section">
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>[#]lang:id_spot[#]</th>
|
|
||||||
<th>[#]lang:ref_spot_id[#]</th>
|
|
||||||
<th>[#]lang:name[#]</th>
|
|
||||||
<th>[#]lang:model[#]</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody></tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<h1>[#]lang:active_users[#]</h1>
|
|
||||||
<div id="user_section">
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>[#]lang:id_user[#]</th>
|
|
||||||
<th>[#]lang:user_name[#]</th>
|
|
||||||
<th>[#]lang:language[#]</th>
|
|
||||||
<th>[#]lang:time_zone[#]</th>
|
|
||||||
<th>[#]lang:clearance[#]</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody></tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<h1>[#]lang:toolbox[#]</h1>
|
|
||||||
<div id="toolbox"></div>
|
|
||||||
<div id="feedback" class="feedback"></div>
|
|
||||||
</div>
|
|
||||||
<script type="text/javascript">
|
|
||||||
oSpot.pageInit = function(asHash) {
|
|
||||||
self.get('admin_get', setProjects);
|
|
||||||
$('#new').addButton('new', self.lang('new_project'), 'new', createProject);
|
|
||||||
$('#toolbox').addButton('refresh', self.lang('update_project'), 'refresh', updateProject);
|
|
||||||
};
|
|
||||||
|
|
||||||
oSpot.onFeedback = function(sType, sMsg, asContext) {
|
|
||||||
delete asContext.a;
|
|
||||||
delete asContext.t;
|
|
||||||
sMsg += ' (';
|
|
||||||
$.each(asContext, function(sKey, sElem) {
|
|
||||||
sMsg += sKey+'='+sElem+' / ' ;
|
|
||||||
});
|
|
||||||
sMsg = sMsg.slice(0, -3)+')';
|
|
||||||
$('#feedback').append($('<p>', {'class': sType}).text(sMsg));
|
|
||||||
};
|
|
||||||
|
|
||||||
function setProjects(asElemTypes) {
|
|
||||||
var aoEvents = [{on:'change', callback:commit}, {on:'keyup', callback:waitAndCommit}];
|
|
||||||
var aoChangeEvent = [aoEvents[0]];
|
|
||||||
|
|
||||||
$.each(asElemTypes, function(sElemType, aoElems) {
|
|
||||||
$.each(aoElems, function(iKey, oElem) {
|
|
||||||
var sElemId = sElemType+'_'+oElem.id;
|
|
||||||
var bNew = ($('#'+sElemId).length == 0);
|
|
||||||
|
|
||||||
var $Elem = (bNew?$('<tr>', {'id': sElemId}):$('#'+sElemId))
|
|
||||||
.data('type', sElemType)
|
|
||||||
.data('id', oElem.id);
|
|
||||||
|
|
||||||
if(oElem.del) $Elem.remove();
|
|
||||||
else if(!bNew) {
|
|
||||||
$Elem.find('input').each(function(iKey, oInput){
|
|
||||||
var $Input = $(oInput);
|
|
||||||
if($Input.attr('name') in oElem && $Input.attr('type')!='date') $Input.val(oElem[$Input.attr('name')]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$Elem.append($('<td>').text(oElem.id || ''));
|
|
||||||
switch(sElemType) {
|
|
||||||
case 'project':
|
|
||||||
$Elem
|
|
||||||
.append($('<td>').addInput('text', 'name', oElem.name, aoEvents))
|
|
||||||
.append($('<td>', {'class': 'mode'}).text(oElem.mode))
|
|
||||||
.append($('<td>').addInput('text', 'codename', oElem.codename, aoEvents))
|
|
||||||
.append($('<td>').addInput('date', 'active_from', oElem.active_from, aoChangeEvent))
|
|
||||||
.append($('<td>').addInput('date', 'active_to', oElem.active_to, aoChangeEvent))
|
|
||||||
.append($('<td>').addButton('close fa-lg', '', 'del_proj', del));
|
|
||||||
break;
|
|
||||||
case 'feed':
|
|
||||||
$Elem
|
|
||||||
.append($('<td>').addInput('text', 'ref_feed_id', oElem.ref_feed_id, aoEvents))
|
|
||||||
.append($('<td>').addInput('number', 'id_spot', oElem.id_spot, aoEvents))
|
|
||||||
.append($('<td>').addInput('number', 'id_project', oElem.id_project, aoEvents))
|
|
||||||
.append($('<td>').text(oElem.name))
|
|
||||||
.append($('<td>').text(oElem.status))
|
|
||||||
.append($('<td>').text(oElem.last_update))
|
|
||||||
.append($('<td>').addButton('close fa-lg', '', 'del_feed', del));
|
|
||||||
break;
|
|
||||||
case 'spot':
|
|
||||||
$Elem
|
|
||||||
.append($('<td>').text(oElem.ref_spot_id))
|
|
||||||
.append($('<td>').text(oElem.name))
|
|
||||||
.append($('<td>').text(oElem.model))
|
|
||||||
break;
|
|
||||||
case 'user':
|
|
||||||
$Elem
|
|
||||||
.append($('<td>').text(oElem.name))
|
|
||||||
.append($('<td>').text(oElem.language))
|
|
||||||
.append($('<td>').text(oElem.timezone))
|
|
||||||
.append($('<td>').addInput('number', 'clearance', oElem.clearance, aoEvents))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$Elem.appendTo($('#'+sElemType+'_section').find('table tbody'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createProject() {
|
|
||||||
self.get('admin_new', setProjects);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateProject() {
|
|
||||||
self.get(
|
|
||||||
'update_project',
|
|
||||||
function(asData, sMsg){oSpot.onFeedback('success', sMsg, {'update':'project'});},
|
|
||||||
{},
|
|
||||||
function(sMsg){oSpot.onFeedback('error', sMsg, {'update':'project'});}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function commit(event, $This) {
|
|
||||||
$This = $This || $(this);
|
|
||||||
if(typeof self.tmp('wait') != 'undefined') clearTimeout(self.tmp('wait'));
|
|
||||||
|
|
||||||
var sOldVal = $This.data('old_value');
|
|
||||||
var sNewVal = $This.val();
|
|
||||||
if(sOldVal!=sNewVal) {
|
|
||||||
$This.data('old_value', sNewVal);
|
|
||||||
|
|
||||||
var $Record = $This.closest('tr');
|
|
||||||
var asInputs = {type: $Record.data('type'), id: $Record.data('id'), field: $This.attr('name'), value: sNewVal};
|
|
||||||
self.get(
|
|
||||||
'admin_set',
|
|
||||||
function(asData){
|
|
||||||
oSpot.onFeedback('success', self.lang('admin_save_success'), asInputs);
|
|
||||||
setProjects(asData);
|
|
||||||
},
|
|
||||||
asInputs,
|
|
||||||
function(sError){
|
|
||||||
$This.data('old_value', sOldVal);
|
|
||||||
oSpot.onFeedback('error', sError, asInputs);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function waitAndCommit(event) {
|
|
||||||
if(typeof self.tmp('wait') != 'undefined') clearTimeout(self.tmp('wait'));
|
|
||||||
self.tmp('wait', setTimeout(()=>{commit(event,$(this));}, 2000));
|
|
||||||
}
|
|
||||||
|
|
||||||
function del() {
|
|
||||||
var $Record = $(this).closest('tr');
|
|
||||||
var asInputs = {type: $Record.data('type'), id: $Record.data('id')};
|
|
||||||
self.get(
|
|
||||||
'admin_del',
|
|
||||||
function(asData){
|
|
||||||
oSpot.onFeedback('success', self.lang('admin_save_success'), asInputs);
|
|
||||||
setProjects(asData);
|
|
||||||
},
|
|
||||||
asInputs,
|
|
||||||
function(sError){
|
|
||||||
oSpot.onFeedback('error', sError, asInputs);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
1205
masks/project.html
@@ -1,94 +0,0 @@
|
|||||||
<div id="upload">
|
|
||||||
<a name="back" class="button" href="[#]host_url[#]"><i class="fa fa-back push"></i>[#]lang:nav_back[#]</a>
|
|
||||||
<h1>[#]lang:upload_title[#]</h1>
|
|
||||||
<input id="fileupload" type="file" name="files[]" multiple>
|
|
||||||
<div id="progress">
|
|
||||||
<div class="bar" style="width: 0%;"></div>
|
|
||||||
</div>
|
|
||||||
<div id="comments"></div>
|
|
||||||
<div id="location">
|
|
||||||
<button id="add_loc"><i class="fa fa-message push"></i>New Position</button>
|
|
||||||
</div>
|
|
||||||
<div id="status"></div>
|
|
||||||
</div>
|
|
||||||
<script type="text/javascript">
|
|
||||||
oSpot.pageInit = function(asHash) {
|
|
||||||
var asProject = self.vars(['projects', self.vars('default_project_codename')]);
|
|
||||||
self.tmp('status-box', $('#status'));
|
|
||||||
if(asProject.editable) {
|
|
||||||
$('#fileupload')
|
|
||||||
.attr('data-url', self.getActionLink('upload'))
|
|
||||||
.fileupload({
|
|
||||||
dataType: 'json',
|
|
||||||
formData: {t: self.consts.timezone},
|
|
||||||
acceptFileTypes: /(\.|\/)(gif|jpe?g|png|mov)$/i,
|
|
||||||
done: function (e, asData) {
|
|
||||||
$.each(asData.result.files, function(iKey, oFile) {
|
|
||||||
var bError = ('error' in oFile);
|
|
||||||
|
|
||||||
//Feedback
|
|
||||||
addStatus(bError?oFile.error:(self.lang('upload_success', [oFile.name])));
|
|
||||||
|
|
||||||
//Comments
|
|
||||||
if(!bError) addCommentBox(oFile.id, oFile.thumbnail);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
progressall: function (e, data) {
|
|
||||||
var progress = parseInt(data.loaded / data.total * 100, 10);
|
|
||||||
$('#progress .bar').css('width', progress+'%');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#add_loc').click(() => {
|
|
||||||
if(navigator.geolocation) {
|
|
||||||
addStatus('Determining position...');
|
|
||||||
navigator.geolocation.getCurrentPosition(
|
|
||||||
(position) => {
|
|
||||||
addStatus('Sending position...');
|
|
||||||
oSpot.get(
|
|
||||||
'add_position',
|
|
||||||
function(asData){addStatus('Position sent');},
|
|
||||||
{'latitude':position.coords.latitude, 'longitude':position.coords.longitude, 'timestamp':Math.round(position.timestamp / 1000)},
|
|
||||||
function(sMsgId){addStatus(self.lang(sMsgId));},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
addStatus(error.message);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else addStatus('This browser does not support geolocation');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else addStatus(self.lang('upload_mode_archived', [asProject.name]), true);
|
|
||||||
};
|
|
||||||
|
|
||||||
function addCommentBox(iMediaId, sThumbnailPath) {
|
|
||||||
$('#comments').append($('<div>', {'class':'comment'})
|
|
||||||
.append($('<img>', {'class':'thumb', 'src':sThumbnailPath}))
|
|
||||||
.append($('<form>')
|
|
||||||
.append($('<input>', {'class':'content', 'name':'content', 'type':'text'}))
|
|
||||||
.append($('<input>', {'class':'id', 'name':'id', 'type':'hidden', 'value':iMediaId}))
|
|
||||||
.append($('<button>', {'class':'save', 'type':'button'})
|
|
||||||
.click(function(){
|
|
||||||
var $Form = $(this).parent();
|
|
||||||
oSpot.get(
|
|
||||||
'add_comment',
|
|
||||||
function(asData){addStatus(self.lang('media_comment_update', asData.filename));},
|
|
||||||
{id:$Form.find('.id').val(), content:$Form.find('.content').val()},
|
|
||||||
function(sMsgId){addStatus(self.lang(sMsgId));},
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.text(self.lang('save'))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function addStatus(sMsg, bClear) {
|
|
||||||
bClear = bClear || false;
|
|
||||||
if(bClear) self.tmp('status-box').empty();
|
|
||||||
|
|
||||||
self.tmp('status-box').append($('<p>').text(sMsg));
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
5487
package-lock.json
generated
Normal file
45
package.json
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.23.2",
|
||||||
|
"@babel/preset-env": "^7.23.2",
|
||||||
|
"babel-loader": "^9.1.3",
|
||||||
|
"resolve-url-loader": "^5.0.0",
|
||||||
|
"symlink-webpack-plugin": "^1.1.0",
|
||||||
|
"webpack": "^5.89.0",
|
||||||
|
"webpack-cli": "^5.1.4"
|
||||||
|
},
|
||||||
|
"name": "spot",
|
||||||
|
"description": "FindMeSpot & GPX integration",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "webpack --config build/webpack.dev.js",
|
||||||
|
"prod": "webpack --config build/webpack.prod.js"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "Franzz",
|
||||||
|
"dependencies": {
|
||||||
|
"autosize": "^6.0.1",
|
||||||
|
"blueimp-file-upload": "^10.32.0",
|
||||||
|
"clean-webpack-plugin": "^4.0.0",
|
||||||
|
"copy-webpack-plugin": "^11.0.0",
|
||||||
|
"css-loader": "^6.8.1",
|
||||||
|
"d3": "^7.8.5",
|
||||||
|
"file-loader": "^6.2.0",
|
||||||
|
"html-loader": "^4.2.0",
|
||||||
|
"jquery": "^3.7.1",
|
||||||
|
"jquery-mousewheel": "^3.1.13",
|
||||||
|
"jquery.waitforimages": "^2.4.0",
|
||||||
|
"leaflet": "^1.9.4",
|
||||||
|
"leaflet-geometryutil": "^0.10.2",
|
||||||
|
"leaflet.heightgraph": "^1.4.0",
|
||||||
|
"lightbox2": "^2.11.4",
|
||||||
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
|
"sass": "^1.69.4",
|
||||||
|
"sass-loader": "^13.3.2",
|
||||||
|
"simplebar": "^6.2.5",
|
||||||
|
"style-loader": "^3.3.3",
|
||||||
|
"url-loader": "^4.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
15
readme.md
@@ -1,6 +1,8 @@
|
|||||||
# Spot Project
|
# Spot Project
|
||||||
[Spot](https://www.findmespot.com) & GPX integration
|
[Spot](https://www.findmespot.com) & GPX integration
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
* npm 18+
|
||||||
|
* composer
|
||||||
* php-mbstring
|
* php-mbstring
|
||||||
* php-imagick
|
* php-imagick
|
||||||
* php-gd
|
* php-gd
|
||||||
@@ -17,12 +19,13 @@
|
|||||||
* max_file_uploads = 50
|
* max_file_uploads = 50
|
||||||
## Getting started
|
## Getting started
|
||||||
1. Clone Git onto web server
|
1. Clone Git onto web server
|
||||||
2. Install dependencies & update php.ini parameters
|
2. composer install
|
||||||
3. Copy timezone data: mariadb_tzinfo_to_sql /usr/share/zoneinfo | mariadb -u root mysql
|
3. npm run dev
|
||||||
4. Copy settings-sample.php to settings.php and populate
|
4. Update php.ini parameters
|
||||||
5. Go to #admin and create a new project, feed & maps
|
5. Copy timezone data: mariadb_tzinfo_to_sql /usr/share/zoneinfo | mariadb -u root mysql
|
||||||
6. Add a GPX file named <project_codename>.gpx to /geo/
|
6. Copy settings-sample.php to settings.php and populate
|
||||||
7. Run composer install
|
7. Go to #admin and create a new project, feed & maps
|
||||||
|
8. Add a GPX file named <project_codename>.gpx to /geo/
|
||||||
## To Do List
|
## To Do List
|
||||||
* ECMA import/export
|
* ECMA import/export
|
||||||
* Add mail frequency slider
|
* Add mail frequency slider
|
||||||
|
|||||||
2
script/d3.min.js
vendored
2
script/jquery.min.js
vendored
23
script/leaflet.min.js
vendored
468
script/spot.js
@@ -1,468 +0,0 @@
|
|||||||
function Spot(asGlobals)
|
|
||||||
{
|
|
||||||
self = this;
|
|
||||||
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;
|
|
||||||
|
|
||||||
/* Initialization */
|
|
||||||
|
|
||||||
this.init = function()
|
|
||||||
{
|
|
||||||
//Variables & constants from php
|
|
||||||
self.vars('tmp', 'object');
|
|
||||||
self.vars('page', 'string');
|
|
||||||
self.updateVars(asGlobals.vars);
|
|
||||||
|
|
||||||
//page elem
|
|
||||||
self.elem = {};
|
|
||||||
self.elem.container = $('#container');
|
|
||||||
self.elem.main = $('#main');
|
|
||||||
|
|
||||||
self.resetTmpFunctions();
|
|
||||||
|
|
||||||
//On Key down
|
|
||||||
$('html').on('keydown', function(oEvent){self.onKeydown(oEvent);});
|
|
||||||
|
|
||||||
//on window resize
|
|
||||||
$(window).on('resize', function(){self.onResize();});
|
|
||||||
|
|
||||||
//Setup menu
|
|
||||||
//self.initMenu();
|
|
||||||
|
|
||||||
//Hash management
|
|
||||||
$(window)
|
|
||||||
.bind('hashchange', self.onHashChange)
|
|
||||||
.trigger('hashchange');
|
|
||||||
};
|
|
||||||
|
|
||||||
this.updateVars = function(asVars)
|
|
||||||
{
|
|
||||||
$.each(asVars, function(sKey, oValue){self.vars(sKey, oValue)});
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Variable Management */
|
|
||||||
|
|
||||||
this.vars = function(oVarName, oValue)
|
|
||||||
{
|
|
||||||
var asVarName = (typeof oVarName == 'object')?oVarName:[oVarName];
|
|
||||||
|
|
||||||
//Set, name & type / default value (init)
|
|
||||||
if(typeof oValue !== 'undefined') setElem(self.vars, copyArray(asVarName), oValue);
|
|
||||||
|
|
||||||
//Get, only name parameter
|
|
||||||
return getElem(self.vars, asVarName);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.tmp = function(sVarName, oValue)
|
|
||||||
{
|
|
||||||
var asVarName = (typeof sVarName == 'object')?sVarName:[sVarName];
|
|
||||||
asVarName.unshift('tmp');
|
|
||||||
return self.vars(asVarName, oValue);
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Interface with server */
|
|
||||||
|
|
||||||
this.get = function(sAction, fOnSuccess, oVars, fOnError, fonProgress)
|
|
||||||
{
|
|
||||||
if(!oVars) oVars = {};
|
|
||||||
fOnError = fOnError || function(sError) {console.log(sError);};
|
|
||||||
fonProgress = fonProgress || function(sState){};
|
|
||||||
fonProgress('start');
|
|
||||||
|
|
||||||
oVars['a'] = sAction;
|
|
||||||
oVars['t'] = self.consts.timezone;
|
|
||||||
return $.ajax(
|
|
||||||
{
|
|
||||||
url: self.consts.process_page,
|
|
||||||
data: oVars,
|
|
||||||
dataType: 'json'
|
|
||||||
})
|
|
||||||
.done(function(oData)
|
|
||||||
{
|
|
||||||
fonProgress('done');
|
|
||||||
if(oData.desc.substr(0, self.consts.lang_prefix.length)==self.consts.lang_prefix) oData.desc = self.lang(oData.desc.substr(5));
|
|
||||||
|
|
||||||
if(oData.result==self.consts.error) fOnError(oData.desc);
|
|
||||||
else fOnSuccess(oData.data, oData.desc);
|
|
||||||
})
|
|
||||||
.fail(function(jqXHR, textStatus, errorThrown)
|
|
||||||
{
|
|
||||||
fonProgress('fail');
|
|
||||||
fOnError(textStatus+' '+errorThrown);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.lang = function(sKey, asParams) {
|
|
||||||
var sParamType = $.type(asParams);
|
|
||||||
if(sParamType == 'undefined') asParams = [];
|
|
||||||
else if($.type(asParams) != 'array') asParams = [asParams];
|
|
||||||
var sLang = '';
|
|
||||||
|
|
||||||
if(sKey in self.consts.lang) {
|
|
||||||
sLang = self.consts.lang[sKey];
|
|
||||||
for(i in asParams) sLang = sLang.replace('$'+i, asParams[i]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log('missing translation: '+sKey);
|
|
||||||
sLang = sKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sLang;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Page Switch - Trigger & Event catching */
|
|
||||||
|
|
||||||
this.onHashChange = function()
|
|
||||||
{
|
|
||||||
var asHash = self.getHash();
|
|
||||||
if(asHash.hash !='' && asHash.page != '') self.switchPage(asHash); //page switching
|
|
||||||
else if(self.vars('page')=='') self.setHash(self.consts.default_page); //first page
|
|
||||||
};
|
|
||||||
|
|
||||||
this.getHash = function()
|
|
||||||
{
|
|
||||||
var sHash = self.hash();
|
|
||||||
var asHash = sHash.split(self.consts.hash_sep);
|
|
||||||
var sPage = asHash.shift() || '';
|
|
||||||
return {hash:sHash, page:sPage, items:asHash};
|
|
||||||
};
|
|
||||||
|
|
||||||
this.setHash = function(sPage, asItems, bReboot)
|
|
||||||
{
|
|
||||||
bReboot = bReboot || false;
|
|
||||||
sPage = sPage || '';
|
|
||||||
asItems = asItems || [];
|
|
||||||
if(typeof asItems == 'string') asItems = [asItems];
|
|
||||||
if(sPage != '')
|
|
||||||
{
|
|
||||||
var sItems = (asItems.length > 0)?self.consts.hash_sep+asItems.join(self.consts.hash_sep):'';
|
|
||||||
self.hash(sPage+sItems, bReboot);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.hash = function(hash, bReboot)
|
|
||||||
{
|
|
||||||
bReboot = bReboot || false;
|
|
||||||
if(!hash) return window.location.hash.slice(1);
|
|
||||||
else window.location.hash = '#'+hash;
|
|
||||||
|
|
||||||
if(bReboot) location.reload();
|
|
||||||
};
|
|
||||||
|
|
||||||
this.updateHash = function(sType, iId) {
|
|
||||||
sType = sType || '';
|
|
||||||
iId = iId || 0;
|
|
||||||
|
|
||||||
var asHash = self.getHash();
|
|
||||||
if(iId) self.setHash(asHash.page, [asHash.items[0], sType, iId]);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.flushHash = function(asTypes) {
|
|
||||||
asTypes = asTypes || [];
|
|
||||||
var asHash = self.getHash();
|
|
||||||
if(asHash.items.length > 1 && (asTypes.length == 0 || asTypes.indexOf(asHash.items[1]) != -1)) self.setHash(asHash.page, [asHash.items[0]]);
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Page Switch - DOM Replacement */
|
|
||||||
|
|
||||||
this.getActionLink = function(sAction, oVars)
|
|
||||||
{
|
|
||||||
if(!oVars) oVars = {};
|
|
||||||
sVars = '';
|
|
||||||
for(i in oVars)
|
|
||||||
{
|
|
||||||
sVars += '&'+i+'='+oVars[i];
|
|
||||||
}
|
|
||||||
return self.consts.process_page+'?a='+sAction+sVars;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.resetTmpFunctions = function()
|
|
||||||
{
|
|
||||||
self.pageInit = function(asHash){console.log('no init for the page: '+asHash.page)};
|
|
||||||
self.onSamePageMove = function(asHash){return false};
|
|
||||||
self.onQuitPage = function(){return true};
|
|
||||||
self.onResize = function(){};
|
|
||||||
self.onFeedback = function(sType, sMsg){};
|
|
||||||
self.onKeydown = function(oEvent){};
|
|
||||||
};
|
|
||||||
|
|
||||||
this.switchPage = function(asHash)
|
|
||||||
{
|
|
||||||
var sPageName = asHash.page;
|
|
||||||
var bSamePage = (self.vars('page') == sPageName);
|
|
||||||
var bFirstPage = (self.vars('page') == '');
|
|
||||||
|
|
||||||
if(!self.consts.pages[sPageName]) { //Page does not exist
|
|
||||||
if(bFirstPage) self.setHash(self.consts.default_page);
|
|
||||||
else self.setHash(self.vars('page'), self.vars(['hash', 'items']));
|
|
||||||
}
|
|
||||||
else if(self.onQuitPage(bSamePage) && !bSamePage || self.onSamePageMove(asHash))
|
|
||||||
{
|
|
||||||
//Delete tmp variables
|
|
||||||
self.vars('tmp', {});
|
|
||||||
|
|
||||||
//disable tmp functions
|
|
||||||
self.resetTmpFunctions();
|
|
||||||
|
|
||||||
//Officially a new page
|
|
||||||
self.vars('page', sPageName);
|
|
||||||
self.vars('hash', asHash);
|
|
||||||
|
|
||||||
//Update Page Title
|
|
||||||
this.setPageTitle(sPageName+' '+(asHash.items[0] || ''));
|
|
||||||
|
|
||||||
//Replacing DOM
|
|
||||||
var $Dom = $(self.consts.pages[sPageName]);
|
|
||||||
if(bFirstPage)
|
|
||||||
{
|
|
||||||
self.splash($Dom, asHash, bFirstPage); //first page
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
self.elem.main.stop().fadeTo('fast', 0, function(){self.splash($Dom, asHash, bFirstPage);}); //Switching page
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(bSamePage) self.vars('hash', asHash);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.setPageTitle = function(sTitle) {
|
|
||||||
document.title = self.consts.title+' - '+sTitle;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.splash = function($Dom, asHash, bFirstPage)
|
|
||||||
{
|
|
||||||
//Switch main content
|
|
||||||
self.elem.main.empty().html($Dom);
|
|
||||||
|
|
||||||
//Page Bootstrap
|
|
||||||
self.pageInit(asHash, bFirstPage);
|
|
||||||
|
|
||||||
//Show main
|
|
||||||
var $FadeInElem = bFirstPage?self.elem.container:self.elem.main;
|
|
||||||
$FadeInElem.hide().fadeTo('slow', 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.getNaturalDuration = function(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?'':'½')+' '+self.lang(iTimeDays>1?'unit_days':'unit_day')):'') //Days
|
|
||||||
+((iTimeHours>0 || iTimeDays==0)?iTimeHours+self.lang('unit_hour'):'') //Hours
|
|
||||||
+((iTimeDays>0 || iTimeMinutes==0)?'':iTimeMinutes) //Minutes
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
this.checkClearance = function(sClearance) {
|
|
||||||
return (self.vars(['user', 'clearance']) >= sClearance);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Common Functions */
|
|
||||||
|
|
||||||
function copyArray(asArray)
|
|
||||||
{
|
|
||||||
return asArray.slice(0); //trick to copy array
|
|
||||||
}
|
|
||||||
|
|
||||||
function getElem(aoAnchor, asPath)
|
|
||||||
{
|
|
||||||
return (typeof asPath == 'object' && asPath.length > 1)?getElem(aoAnchor[asPath.shift()], asPath):aoAnchor[(typeof asPath == 'object')?asPath.shift():asPath];
|
|
||||||
}
|
|
||||||
|
|
||||||
function setElem(aoAnchor, asPath, oValue)
|
|
||||||
{
|
|
||||||
var asTypes = {boolean:false, string:'', integer:0, int:0, array:[], object:{}};
|
|
||||||
if(typeof asPath == 'object' && asPath.length > 1)
|
|
||||||
{
|
|
||||||
var nextlevel = asPath.shift();
|
|
||||||
if(!(nextlevel in aoAnchor)) aoAnchor[nextlevel] = {}; //Creating a new level
|
|
||||||
if(typeof aoAnchor[nextlevel] !== 'object') debug('Error - setElem() : Already existing path at level "'+nextlevel+'". Cancelling setElem() action');
|
|
||||||
return setElem(aoAnchor[nextlevel], asPath, oValue);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var sKey = (typeof asPath == 'object')?asPath.shift():asPath;
|
|
||||||
return aoAnchor[sKey] = (!(sKey in aoAnchor) && (oValue in asTypes))?asTypes[oValue]:oValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$.prototype.addInput = function(sType, sName, sValue, aoEvents)
|
|
||||||
{
|
|
||||||
aoEvents = aoEvents || [];
|
|
||||||
var $Input = $('<input>', {type: sType, name: sName, value: sValue}).data('old_value', sValue);
|
|
||||||
$.each(aoEvents, function(iIndex, aoEvent) {
|
|
||||||
$Input.on(aoEvent.on, aoEvent.callback);
|
|
||||||
});
|
|
||||||
return $(this).append($Input);
|
|
||||||
};
|
|
||||||
|
|
||||||
$.prototype.addButton = function(sIcon, sText, sName, fOnClick, sClass)
|
|
||||||
{
|
|
||||||
sText = sText || '';
|
|
||||||
sClass = sClass || '';
|
|
||||||
var $Btn = $('<button>', {name: sName, 'class':sClass})
|
|
||||||
.addIcon('fa-'+sIcon, (sText != ''))
|
|
||||||
.append(sText)
|
|
||||||
.click(fOnClick);
|
|
||||||
|
|
||||||
return $(this).append($Btn);
|
|
||||||
};
|
|
||||||
|
|
||||||
$.prototype.addIcon = function(sIcon, bMargin, sStyle)
|
|
||||||
{
|
|
||||||
bMargin = bMargin || false;
|
|
||||||
sStyle = sStyle || '';
|
|
||||||
return $(this).append($('<i>', {'class':'fa'+sStyle+' '+sIcon+(bMargin?' push':'')}));
|
|
||||||
};
|
|
||||||
|
|
||||||
$.prototype.defaultVal = function(sDefaultValue)
|
|
||||||
{
|
|
||||||
$(this)
|
|
||||||
.data('default_value', sDefaultValue)
|
|
||||||
.val(sDefaultValue)
|
|
||||||
.addClass('defaultText')
|
|
||||||
.focus(function()
|
|
||||||
{
|
|
||||||
var $This = $(this);
|
|
||||||
if($This.val() == $This.data('default_value')) $This.val('').removeClass('defaultText');
|
|
||||||
})
|
|
||||||
.blur(function()
|
|
||||||
{
|
|
||||||
var $This = $(this);
|
|
||||||
if($This.val() == '') $This.val($This.data('default_value')).addClass('defaultText');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$.prototype.checkForm = function(sSelector)
|
|
||||||
{
|
|
||||||
sSelector = sSelector || 'input[type="text"], textarea';
|
|
||||||
var $This = $(this);
|
|
||||||
var bOk = true;
|
|
||||||
$This.find(sSelector).each(function()
|
|
||||||
{
|
|
||||||
$This = $(this);
|
|
||||||
bOk = bOk && $This.val()!='' && $This.val()!=$This.data('default_value');
|
|
||||||
});
|
|
||||||
return bOk;
|
|
||||||
};
|
|
||||||
|
|
||||||
$.prototype.cascadingDown = function(sDuration)
|
|
||||||
{
|
|
||||||
return $(this).slideDown(sDuration, function(){$(this).next().cascadingDown(sDuration);});
|
|
||||||
};
|
|
||||||
|
|
||||||
$.prototype.hoverSwap = function(sDefault, sHover)
|
|
||||||
{
|
|
||||||
return $(this)
|
|
||||||
.data('default', sDefault)
|
|
||||||
.data('hover', sHover)
|
|
||||||
.hover(function(){
|
|
||||||
var $This = $(this),
|
|
||||||
sHover = $This.data('hover');
|
|
||||||
sDefault = $This.data('default');
|
|
||||||
|
|
||||||
if(sDefault!='' && sHover != '') {
|
|
||||||
$This.fadeOut('fast', function() {
|
|
||||||
var $This = $(this);
|
|
||||||
$This.text((sDefault==$This.text())?sHover:sDefault).fadeIn('fast');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.text(sDefault);
|
|
||||||
};
|
|
||||||
|
|
||||||
$.prototype.onSwipe = function(fOnStart, fOnMove, fOnEnd){
|
|
||||||
return $(this)
|
|
||||||
.on('dragstart', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
})
|
|
||||||
.on('mousedown touchstart', (e) => {
|
|
||||||
var $This = $(this);
|
|
||||||
var oPos = getDragPosition(e);
|
|
||||||
$This.data('x-start', oPos.x);
|
|
||||||
$This.data('y-start', oPos.y);
|
|
||||||
$This.data('x-move', oPos.x);
|
|
||||||
$This.data('y-move', oPos.y);
|
|
||||||
$This.data('moving', true).addClass('moving');
|
|
||||||
fOnStart({
|
|
||||||
xStart: $This.data('x-start'),
|
|
||||||
yStart: $This.data('y-start')
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.on('touchmove mousemove', (e) => {
|
|
||||||
var $This = $(this);
|
|
||||||
if($This.data('moving')) {
|
|
||||||
var oPos = getDragPosition(e);
|
|
||||||
$This.data('x-move', oPos.x);
|
|
||||||
$This.data('y-move', oPos.y);
|
|
||||||
fOnMove({
|
|
||||||
xStart: $This.data('x-start'),
|
|
||||||
yStart: $This.data('y-start'),
|
|
||||||
xMove: $This.data('x-move'),
|
|
||||||
yMove: $This.data('y-move')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on('mouseup mouseleave touchend', (e) => {
|
|
||||||
var $This = $(this);
|
|
||||||
if($This.data('moving')) {
|
|
||||||
$This.data('moving', false).removeClass('moving');
|
|
||||||
fOnEnd({
|
|
||||||
xStart: $This.data('x-start'),
|
|
||||||
yStart: $This.data('y-start'),
|
|
||||||
xEnd: $This.data('x-move'),
|
|
||||||
yEnd: $This.data('y-move')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function getDragPosition(oEvent) {
|
|
||||||
let bMouse = oEvent.type.includes('mouse');
|
|
||||||
return {
|
|
||||||
x: bMouse?oEvent.pageX:oEvent.touches[0].clientX,
|
|
||||||
y: bMouse?oEvent.pageY:oEvent.touches[0].clientY
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyTextToClipboard(text) {
|
|
||||||
if(!navigator.clipboard) {
|
|
||||||
var textArea = document.createElement('textarea');
|
|
||||||
textArea.value = text;
|
|
||||||
|
|
||||||
// Avoid scrolling to bottom
|
|
||||||
textArea.style.top = '0';
|
|
||||||
textArea.style.left = '0';
|
|
||||||
textArea.style.position = 'fixed';
|
|
||||||
|
|
||||||
document.body.appendChild(textArea);
|
|
||||||
textArea.focus();
|
|
||||||
textArea.select();
|
|
||||||
|
|
||||||
try {
|
|
||||||
var successful = document.execCommand('copy');
|
|
||||||
if(!successful) console.error('Fallback: Oops, unable to copy', text);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Fallback: Oops, unable to copy', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
document.body.removeChild(textArea);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
navigator.clipboard.writeText(text).then(
|
|
||||||
function() {},
|
|
||||||
function(err) {
|
|
||||||
console.error('Async: Could not copy text: ', err);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 821 B After Width: | Height: | Size: 821 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 261 KiB After Width: | Height: | Size: 261 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
71
src/masks/admin.html
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<div id="admin">
|
||||||
|
<a name="back" class="button" href="[#]server[#]"><i class="fa fa-back push"></i>[#]lang:nav_back[#]</a>
|
||||||
|
<h1>[#]lang:projects[#]</h1>
|
||||||
|
<div id="project_section">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>[#]lang:id_project[#]</th>
|
||||||
|
<th>[#]lang:project[#]</th>
|
||||||
|
<th>[#]lang:mode[#]</th>
|
||||||
|
<th>[#]lang:code_name[#]</th>
|
||||||
|
<th>[#]lang:start[#]</th>
|
||||||
|
<th>[#]lang:end[#]</th>
|
||||||
|
<th>[#]lang:delete[#]</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
<div id="new"></div>
|
||||||
|
</div>
|
||||||
|
<h1>[#]lang:feeds[#]</h1>
|
||||||
|
<div id="feed_section">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>[#]lang:id_feed[#]</th>
|
||||||
|
<th>[#]lang:ref_feed_id[#]</th>
|
||||||
|
<th>[#]lang:id_spot[#]</th>
|
||||||
|
<th>[#]lang:id_project[#]</th>
|
||||||
|
<th>[#]lang:name[#]</th>
|
||||||
|
<th>[#]lang:status[#]</th>
|
||||||
|
<th>[#]lang:last_update[#]</th>
|
||||||
|
<th>[#]lang:delete[#]</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<h1>Spots</h1>
|
||||||
|
<div id="spot_section">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>[#]lang:id_spot[#]</th>
|
||||||
|
<th>[#]lang:ref_spot_id[#]</th>
|
||||||
|
<th>[#]lang:name[#]</th>
|
||||||
|
<th>[#]lang:model[#]</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<h1>[#]lang:active_users[#]</h1>
|
||||||
|
<div id="user_section">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>[#]lang:id_user[#]</th>
|
||||||
|
<th>[#]lang:user_name[#]</th>
|
||||||
|
<th>[#]lang:language[#]</th>
|
||||||
|
<th>[#]lang:time_zone[#]</th>
|
||||||
|
<th>[#]lang:clearance[#]</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<h1>[#]lang:toolbox[#]</h1>
|
||||||
|
<div id="toolbox"></div>
|
||||||
|
<div id="feedback" class="feedback"></div>
|
||||||
|
</div>
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
<meta property="og:title" content="Spotty" />
|
<meta property="og:title" content="Spotty" />
|
||||||
<meta property="og:description" content="[#]lang:page_og_desc[#]" />
|
<meta property="og:description" content="[#]lang:page_og_desc[#]" />
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:url" content="[#]host_url[#]" />
|
<meta property="og:url" content="[#]server[#]" />
|
||||||
<meta property="og:image" content="images/ogp.png" />
|
<meta property="og:image" content="images/ogp.png" />
|
||||||
<meta property="og:locale" content="[#]lang:locale[#]" />
|
<meta property="og:locale" content="[#]lang:locale[#]" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="images/icons/apple-touch-icon.png?v=GvmqYyKwbb">
|
<link rel="apple-touch-icon" sizes="180x180" href="images/icons/apple-touch-icon.png?v=GvmqYyKwbb">
|
||||||
@@ -19,18 +19,9 @@
|
|||||||
<meta name="msapplication-TileColor" content="#00a300">
|
<meta name="msapplication-TileColor" content="#00a300">
|
||||||
<meta name="msapplication-config" content="images/icons/browserconfig.xml?v=GvmqYyKwbb">
|
<meta name="msapplication-config" content="images/icons/browserconfig.xml?v=GvmqYyKwbb">
|
||||||
<meta name="theme-color" content="#ffffff">
|
<meta name="theme-color" content="#ffffff">
|
||||||
<link type="text/css" href="[#]filepath_css[#]" rel="stylesheet" media="all" />
|
<script type="text/javascript">window.params = [#]GLOBAL_VARS[#];</script>
|
||||||
<script type="text/javascript" src="[#]filepath_js_d3[#]"></script>
|
<script type="text/javascript" src="[#]filepath_js[#]"></script>
|
||||||
<script type="text/javascript" src="[#]filepath_js_leaflet[#]"></script>
|
<title>Spotty</title>
|
||||||
<script type="text/javascript" src="[#]filepath_js_jquery[#]"></script>
|
|
||||||
<script type="text/javascript" src="[#]filepath_js_jquery_mods[#]"></script>
|
|
||||||
<script type="text/javascript" src="[#]filepath_js_spot[#]"></script>
|
|
||||||
<script type="text/javascript" src="[#]filepath_js_lightbox[#]"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
var oSpot = new Spot([#]GLOBAL_VARS[#]);
|
|
||||||
$(document).ready(oSpot.init);
|
|
||||||
</script>
|
|
||||||
<title></title>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="container">
|
<div id="container">
|
||||||
57
src/masks/project.html
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<div id="projects">
|
||||||
|
<div id="background"></div>
|
||||||
|
<div id="submap">
|
||||||
|
<div class="loader fa fa-fw fa-map flicker" id="map_loading"></div>
|
||||||
|
</div>
|
||||||
|
<div id="map"></div>
|
||||||
|
<div id="settings">
|
||||||
|
<div id="settings-panel">
|
||||||
|
<div class="settings-header">
|
||||||
|
<div class="logo"><img width="289" height="72" src="images/logo_black.png" alt="Spotty" /></div>
|
||||||
|
<div id="last_update"><p><span><img src="images/spot-logo-only.svg" alt="" /></span><abbr></abbr></p></div>
|
||||||
|
</div>
|
||||||
|
<div class="settings-sections">
|
||||||
|
<div id="settings-sections-scrollbox">
|
||||||
|
<div class="settings-section">
|
||||||
|
<h1><i class="fa fa-fw push fa-project"></i>[#]lang:hikes[#]</h1>
|
||||||
|
<div id="settings-projects"></div>
|
||||||
|
</div>
|
||||||
|
<div class="settings-section">
|
||||||
|
<h1><i class="fa fa-fw push fa-map"></i>[#]lang:maps[#]</h1>
|
||||||
|
<div id="layers"></div>
|
||||||
|
</div>
|
||||||
|
<div class="settings-section newsletter">
|
||||||
|
<h1><i class="fa fa-fw push fa-newsletter"></i>[#]lang:newsletter[#]</h1>
|
||||||
|
<input type="email" name="email" id="email" placeholder="[#]lang:nl_email_placeholder[#]" /><button id="nl_btn"><span><i class="fa"></i></span></button>
|
||||||
|
<div id="settings-feedback" class="feedback"></div>
|
||||||
|
<div id="nl_desc"></div>
|
||||||
|
</div>
|
||||||
|
<div class="settings-section admin" id="admin_link">
|
||||||
|
<h1><i class="fa fa-fw push fa-admin"></i>[#]lang:admin[#]</h1>
|
||||||
|
<a class="button" id="admin_config" name="admin_config" href="#admin"><i class="fa fa-config push"></i>[#]lang:admin_config[#]</a>
|
||||||
|
<a class="button" id="admin_upload" name="admin_upload" href="#upload"><i class="fa fa-upload push"></i>[#]lang:admin_upload[#]</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="settings-footer"><a href="https://git.lutran.fr/franzz/spot" title="[#]lang:credits_git[#]" target="_blank" rel="noopener"><i class="fa fa-credits push"></i>[#]lang:credits_project[#]</a> [#]lang:credits_license[#]</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="feed">
|
||||||
|
<div id="feed-panel">
|
||||||
|
<div id="poster"></div>
|
||||||
|
<div id="posts_list"></div>
|
||||||
|
<div id="loading"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="elems">
|
||||||
|
<div id="settings-button" class="spot-control"><i class="fa fa-menu"></i></div>
|
||||||
|
<div id="feed-button" class="spot-control"><i class="fa"></i></div>
|
||||||
|
<div id="legend" class="leaflet-control-layers leaflet-control leaflet-control-layers-expanded">
|
||||||
|
<div class="track"><span class="line main"></span><span class="desc">[#]lang:track_main[#]</span></div>
|
||||||
|
<div class="track"><span class="line off-track"></span><span class="desc">[#]lang:track_off-track[#]</span></div>
|
||||||
|
<div class="track"><span class="line hitchhiking"></span><span class="desc">[#]lang:track_hitchhiking[#]</span></div>
|
||||||
|
</div>
|
||||||
|
<div id="title" class="leaflet-control-layers leaflet-control leaflet-control-layers-expanded leaflet-control-inline"><span id="project_name" class=""></span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="mobile" class="mobile"></div>
|
||||||
10
src/masks/upload.html
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<div id="upload">
|
||||||
|
<a name="back" class="button" href="[#]server[#]"><i class="fa fa-back push"></i>[#]lang:nav_back[#]</a>
|
||||||
|
<h1>[#]lang:upload_title[#]</h1>
|
||||||
|
<input id="fileupload" type="file" name="files[]" multiple>
|
||||||
|
<div id="progress">
|
||||||
|
<div class="bar" style="width: 0%;"></div>
|
||||||
|
</div>
|
||||||
|
<div id="comments"></div>
|
||||||
|
<div id="status"></div>
|
||||||
|
</div>
|
||||||
36
src/scripts/app.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
//jQuery
|
||||||
|
import './jquery.helpers.js';
|
||||||
|
|
||||||
|
//Common
|
||||||
|
import { copyArray, getElem, setElem, getDragPosition, copyTextToClipboard } from './common.js';
|
||||||
|
window.copyArray = copyArray;
|
||||||
|
window.getElem = getElem;
|
||||||
|
window.setElem = setElem;
|
||||||
|
window.getDragPosition = getDragPosition;
|
||||||
|
window.copyTextToClipboard = copyTextToClipboard;
|
||||||
|
|
||||||
|
import Css from './../styles/spot.scss';
|
||||||
|
import LogoText from '../images/logo_black.png';
|
||||||
|
import Logo from '../images/spot-logo-only.svg';
|
||||||
|
console.log(Logo);
|
||||||
|
|
||||||
|
//Masks
|
||||||
|
import Spot from './spot.js';
|
||||||
|
import Project from './page.project.js';
|
||||||
|
import Upload from './page.upload.js';
|
||||||
|
import Admin from './page.admin.js';
|
||||||
|
|
||||||
|
//const Upload = () => import('@scripts/page.upload.js');
|
||||||
|
|
||||||
|
let oSpot = new Spot(params);
|
||||||
|
|
||||||
|
let oProject = new Project(oSpot);
|
||||||
|
oSpot.addPage('project', oProject);
|
||||||
|
|
||||||
|
let oUpload = new Upload(oSpot);
|
||||||
|
oSpot.addPage('upload', oUpload);
|
||||||
|
|
||||||
|
let oAdmin = new Admin(oSpot);
|
||||||
|
oSpot.addPage('admin', oAdmin);
|
||||||
|
|
||||||
|
$(() => {oSpot.init();});
|
||||||
68
src/scripts/common.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
/* Common Functions */
|
||||||
|
|
||||||
|
export function copyArray(asArray)
|
||||||
|
{
|
||||||
|
return asArray.slice(0); //trick to copy array
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getElem(aoAnchor, asPath)
|
||||||
|
{
|
||||||
|
return (typeof asPath == 'object' && asPath.length > 1)?getElem(aoAnchor[asPath.shift()], asPath):aoAnchor[(typeof asPath == 'object')?asPath.shift():asPath];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setElem(aoAnchor, asPath, oValue)
|
||||||
|
{
|
||||||
|
var asTypes = {boolean:false, string:'', integer:0, int:0, array:[], object:{}};
|
||||||
|
if(typeof asPath == 'object' && asPath.length > 1)
|
||||||
|
{
|
||||||
|
var nextlevel = asPath.shift();
|
||||||
|
if(!(nextlevel in aoAnchor)) aoAnchor[nextlevel] = {}; //Creating a new level
|
||||||
|
if(typeof aoAnchor[nextlevel] !== 'object') debug('Error - setElem() : Already existing path at level "'+nextlevel+'". Cancelling setElem() action');
|
||||||
|
return setElem(aoAnchor[nextlevel], asPath, oValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var sKey = (typeof asPath == 'object')?asPath.shift():asPath;
|
||||||
|
return aoAnchor[sKey] = (!(sKey in aoAnchor) && (oValue in asTypes))?asTypes[oValue]:oValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDragPosition(oEvent) {
|
||||||
|
let bMouse = oEvent.type.includes('mouse');
|
||||||
|
return {
|
||||||
|
x: bMouse?oEvent.pageX:oEvent.touches[0].clientX,
|
||||||
|
y: bMouse?oEvent.pageY:oEvent.touches[0].clientY
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function copyTextToClipboard(text) {
|
||||||
|
if(!navigator.clipboard) {
|
||||||
|
var textArea = document.createElement('textarea');
|
||||||
|
textArea.value = text;
|
||||||
|
|
||||||
|
// Avoid scrolling to bottom
|
||||||
|
textArea.style.top = '0';
|
||||||
|
textArea.style.left = '0';
|
||||||
|
textArea.style.position = 'fixed';
|
||||||
|
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.focus();
|
||||||
|
textArea.select();
|
||||||
|
|
||||||
|
try {
|
||||||
|
var successful = document.execCommand('copy');
|
||||||
|
if(!successful) console.error('Fallback: Oops, unable to copy', text);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Fallback: Oops, unable to copy', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigator.clipboard.writeText(text).then(
|
||||||
|
function() {},
|
||||||
|
function(err) {
|
||||||
|
console.error('Async: Could not copy text: ', err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
129
src/scripts/jquery.helpers.js
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
$.prototype.addInput = function(sType, sName, sValue, aoEvents) {
|
||||||
|
aoEvents = aoEvents || [];
|
||||||
|
let $Input = $('<input>', {type: sType, name: sName, value: sValue}).data('old_value', sValue);
|
||||||
|
$.each(aoEvents, (iIndex, aoEvent) => {
|
||||||
|
$Input.on(aoEvent.on, aoEvent.callback);
|
||||||
|
});
|
||||||
|
return $(this).append($Input);
|
||||||
|
};
|
||||||
|
|
||||||
|
$.prototype.addButton = function(sIcon, sText, sName, fOnClick, sClass)
|
||||||
|
{
|
||||||
|
sText = sText || '';
|
||||||
|
sClass = sClass || '';
|
||||||
|
var $Btn = $('<button>', {name: sName, 'class':sClass})
|
||||||
|
.addIcon('fa-'+sIcon, (sText != ''))
|
||||||
|
.append(sText)
|
||||||
|
.click(fOnClick);
|
||||||
|
|
||||||
|
return $(this).append($Btn);
|
||||||
|
};
|
||||||
|
|
||||||
|
$.prototype.addIcon = function(sIcon, bMargin, sStyle)
|
||||||
|
{
|
||||||
|
bMargin = bMargin || false;
|
||||||
|
sStyle = sStyle || '';
|
||||||
|
return $(this).append($('<i>', {'class':'fa'+sStyle+' '+sIcon+(bMargin?' push':'')}));
|
||||||
|
};
|
||||||
|
|
||||||
|
$.prototype.defaultVal = function(sDefaultValue)
|
||||||
|
{
|
||||||
|
$(this)
|
||||||
|
.data('default_value', sDefaultValue)
|
||||||
|
.val(sDefaultValue)
|
||||||
|
.addClass('defaultText')
|
||||||
|
.focus(function()
|
||||||
|
{
|
||||||
|
var $This = $(this);
|
||||||
|
if($This.val() == $This.data('default_value')) $This.val('').removeClass('defaultText');
|
||||||
|
})
|
||||||
|
.blur(function()
|
||||||
|
{
|
||||||
|
var $This = $(this);
|
||||||
|
if($This.val() == '') $This.val($This.data('default_value')).addClass('defaultText');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$.prototype.checkForm = function(sSelector)
|
||||||
|
{
|
||||||
|
sSelector = sSelector || 'input[type="text"], textarea';
|
||||||
|
var $This = $(this);
|
||||||
|
var bOk = true;
|
||||||
|
$This.find(sSelector).each(function()
|
||||||
|
{
|
||||||
|
$This = $(this);
|
||||||
|
bOk = bOk && $This.val()!='' && $This.val()!=$This.data('default_value');
|
||||||
|
});
|
||||||
|
return bOk;
|
||||||
|
};
|
||||||
|
|
||||||
|
$.prototype.cascadingDown = function(sDuration)
|
||||||
|
{
|
||||||
|
return $(this).slideDown(sDuration, function(){$(this).next().cascadingDown(sDuration);});
|
||||||
|
};
|
||||||
|
|
||||||
|
$.prototype.hoverSwap = function(sDefault, sHover)
|
||||||
|
{
|
||||||
|
return $(this)
|
||||||
|
.data('default', sDefault)
|
||||||
|
.data('hover', sHover)
|
||||||
|
.hover(function(){
|
||||||
|
var $This = $(this),
|
||||||
|
sHover = $This.data('hover');
|
||||||
|
sDefault = $This.data('default');
|
||||||
|
|
||||||
|
if(sDefault!='' && sHover != '') {
|
||||||
|
$This.fadeOut('fast', function() {
|
||||||
|
var $This = $(this);
|
||||||
|
$This.text((sDefault==$This.text())?sHover:sDefault).fadeIn('fast');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.text(sDefault);
|
||||||
|
};
|
||||||
|
|
||||||
|
$.prototype.onSwipe = function(fOnStart, fOnMove, fOnEnd){
|
||||||
|
return $(this)
|
||||||
|
.on('dragstart', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
})
|
||||||
|
.on('mousedown touchstart', (e) => {
|
||||||
|
var $This = $(this);
|
||||||
|
var oPos = getDragPosition(e);
|
||||||
|
$This.data('x-start', oPos.x);
|
||||||
|
$This.data('y-start', oPos.y);
|
||||||
|
$This.data('x-move', oPos.x);
|
||||||
|
$This.data('y-move', oPos.y);
|
||||||
|
$This.data('moving', true).addClass('moving');
|
||||||
|
fOnStart({
|
||||||
|
xStart: $This.data('x-start'),
|
||||||
|
yStart: $This.data('y-start')
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.on('touchmove mousemove', (e) => {
|
||||||
|
var $This = $(this);
|
||||||
|
if($This.data('moving')) {
|
||||||
|
var oPos = getDragPosition(e);
|
||||||
|
$This.data('x-move', oPos.x);
|
||||||
|
$This.data('y-move', oPos.y);
|
||||||
|
fOnMove({
|
||||||
|
xStart: $This.data('x-start'),
|
||||||
|
yStart: $This.data('y-start'),
|
||||||
|
xMove: $This.data('x-move'),
|
||||||
|
yMove: $This.data('y-move')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on('mouseup mouseleave touchend', (e) => {
|
||||||
|
var $This = $(this);
|
||||||
|
if($This.data('moving')) {
|
||||||
|
$This.data('moving', false).removeClass('moving');
|
||||||
|
fOnEnd({
|
||||||
|
xStart: $This.data('x-start'),
|
||||||
|
yStart: $This.data('y-start'),
|
||||||
|
xEnd: $This.data('x-move'),
|
||||||
|
yEnd: $This.data('y-move')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
14
src/scripts/leaflet.helpers.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/* Additional Leaflet functions */
|
||||||
|
|
||||||
|
L.Map.include({
|
||||||
|
setOffsetView: function (iOffsetRatioX, oCenter, iZoomLevel) {
|
||||||
|
var oCenter = (typeof oCenter == 'object')?$.extend({}, oCenter):this.getCenter();
|
||||||
|
iZoomLevel = iZoomLevel || this.getZoom();
|
||||||
|
|
||||||
|
var oBounds = this.getBounds();
|
||||||
|
var iOffsetX = (oBounds.getEast() - oBounds.getWest()) * iOffsetRatioX / ( 2 * Math.pow(2, iZoomLevel - this.getZoom()));
|
||||||
|
oCenter.lng = oCenter.lng - iOffsetX;
|
||||||
|
|
||||||
|
this.setView(oCenter, iZoomLevel);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -62,6 +62,7 @@
|
|||||||
sanitizeTitle: false
|
sanitizeTitle: false
|
||||||
, hasVideo: true
|
, hasVideo: true
|
||||||
, onMediaChange: (oMedia) => {}
|
, onMediaChange: (oMedia) => {}
|
||||||
|
, onClosing: () => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
Lightbox.prototype.option = function(options) {
|
Lightbox.prototype.option = function(options) {
|
||||||
@@ -477,6 +478,7 @@
|
|||||||
var $hasVideoNav = this.$container.hasClass('lb-video-nav');
|
var $hasVideoNav = this.$container.hasClass('lb-video-nav');
|
||||||
switch(self.album[imageNumber].type) {
|
switch(self.album[imageNumber].type) {
|
||||||
case 'video':
|
case 'video':
|
||||||
|
self.$image.removeAttr('src');
|
||||||
this.$video.on('loadedmetadata', function(){
|
this.$video.on('loadedmetadata', function(){
|
||||||
self.album[imageNumber].width = this.videoWidth;
|
self.album[imageNumber].width = this.videoWidth;
|
||||||
self.album[imageNumber].height = this.videoHeight;
|
self.album[imageNumber].height = this.videoHeight;
|
||||||
@@ -489,7 +491,7 @@
|
|||||||
if(!$hasVideoNav) this.$container.addClass('lb-video-nav');
|
if(!$hasVideoNav) this.$container.addClass('lb-video-nav');
|
||||||
break;
|
break;
|
||||||
case 'image':
|
case 'image':
|
||||||
this.$video.attr('src', '');
|
this.$video.trigger('pause').removeAttr('src');
|
||||||
if($hasVideoNav) this.$container.removeClass('lb-video-nav');
|
if($hasVideoNav) this.$container.removeClass('lb-video-nav');
|
||||||
|
|
||||||
// When image to show is preloaded, we send the width and height to sizeContainer()
|
// When image to show is preloaded, we send the width and height to sizeContainer()
|
||||||
@@ -718,11 +720,10 @@
|
|||||||
if(this.options.hasVideo) {
|
if(this.options.hasVideo) {
|
||||||
var $lbContainer = this.$lightbox.find('.lb-container');
|
var $lbContainer = this.$lightbox.find('.lb-container');
|
||||||
var $hasVideoNav = $lbContainer.hasClass('lb-video-nav');
|
var $hasVideoNav = $lbContainer.hasClass('lb-video-nav');
|
||||||
this.$video.attr('src', '');
|
this.$video.trigger('pause').removeAttr('src');
|
||||||
|
|
||||||
if($hasVideoNav) $lbContainer.removeClass('lb-video-nav');
|
if($hasVideoNav) $lbContainer.removeClass('lb-video-nav');
|
||||||
}
|
}
|
||||||
oSpot.flushHash();
|
|
||||||
|
|
||||||
$(window).off('resize', this.sizeOverlay);
|
$(window).off('resize', this.sizeOverlay);
|
||||||
this.$nav.off('mousewheel');
|
this.$nav.off('mousewheel');
|
||||||
@@ -732,6 +733,8 @@
|
|||||||
if (this.options.disableScrolling) {
|
if (this.options.disableScrolling) {
|
||||||
$('body').removeClass('lb-disable-scrolling');
|
$('body').removeClass('lb-disable-scrolling');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.options.onClosing();
|
||||||
};
|
};
|
||||||
|
|
||||||
return new Lightbox();
|
return new Lightbox();
|
||||||
165
src/scripts/page.admin.js
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
export default class Admin {
|
||||||
|
|
||||||
|
constructor(oSpot) {
|
||||||
|
this.spot = oSpot;
|
||||||
|
}
|
||||||
|
|
||||||
|
pageInit(asHash) {
|
||||||
|
this.spot.get('admin_get', (asElemTypes) => {this.setProjects(asElemTypes);});
|
||||||
|
$('#new').addButton('new', this.spot.lang('new_project'), 'new', () => {this.createProject();});
|
||||||
|
$('#toolbox').addButton('refresh', this.spot.lang('update_project'), 'refresh', () => {this.updateProject();});
|
||||||
|
}
|
||||||
|
|
||||||
|
onFeedback(sType, sMsg, asContext) {
|
||||||
|
delete asContext.a;
|
||||||
|
delete asContext.t;
|
||||||
|
sMsg += ' (';
|
||||||
|
$.each(asContext, function(sKey, sElem) {
|
||||||
|
sMsg += sKey+'='+sElem+' / ' ;
|
||||||
|
});
|
||||||
|
sMsg = sMsg.slice(0, -3)+')';
|
||||||
|
$('#feedback').append($('<p>', {'class': sType}).text(sMsg));
|
||||||
|
}
|
||||||
|
|
||||||
|
setProjects(asElemTypes) {
|
||||||
|
let aoEvents = [
|
||||||
|
{
|
||||||
|
on:'change',
|
||||||
|
callback: (oEvent) => {this.commit(oEvent);}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
on:'keyup',
|
||||||
|
callback: (oEvent) => {this.waitAndCommit(oEvent);}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
let aoChangeEvent = [aoEvents[0]];
|
||||||
|
|
||||||
|
$.each(asElemTypes, (sElemType, aoElems) => {
|
||||||
|
$.each(aoElems, (iKey, oElem) => {
|
||||||
|
var sElemId = sElemType+'_'+oElem.id;
|
||||||
|
var bNew = ($('#'+sElemId).length == 0);
|
||||||
|
|
||||||
|
var $Elem = (bNew?$('<tr>', {'id': sElemId}):$('#'+sElemId))
|
||||||
|
.data('type', sElemType)
|
||||||
|
.data('id', oElem.id);
|
||||||
|
|
||||||
|
if(oElem.del) $Elem.remove();
|
||||||
|
else if(!bNew) {
|
||||||
|
$Elem.find('input').each((iKey, oInput) => {
|
||||||
|
var $Input = $(oInput);
|
||||||
|
if($Input.attr('name') in oElem && $Input.attr('type')!='date') $Input.val(oElem[$Input.attr('name')]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$Elem.append($('<td>').text(oElem.id || ''));
|
||||||
|
switch(sElemType) {
|
||||||
|
case 'project':
|
||||||
|
$Elem
|
||||||
|
.append($('<td>').addInput('text', 'name', oElem.name, aoEvents))
|
||||||
|
.append($('<td>', {'class': 'mode'}).text(oElem.mode))
|
||||||
|
.append($('<td>').addInput('text', 'codename', oElem.codename, aoEvents))
|
||||||
|
.append($('<td>').addInput('date', 'active_from', oElem.active_from, aoChangeEvent))
|
||||||
|
.append($('<td>').addInput('date', 'active_to', oElem.active_to, aoChangeEvent))
|
||||||
|
.append($('<td>').addButton('close fa-lg', '', 'del_proj', (oEvent)=>{this.del(oEvent);}));
|
||||||
|
break;
|
||||||
|
case 'feed':
|
||||||
|
$Elem
|
||||||
|
.append($('<td>').addInput('text', 'ref_feed_id', oElem.ref_feed_id, aoEvents))
|
||||||
|
.append($('<td>').addInput('number', 'id_spot', oElem.id_spot, aoEvents))
|
||||||
|
.append($('<td>').addInput('number', 'id_project', oElem.id_project, aoEvents))
|
||||||
|
.append($('<td>').text(oElem.name))
|
||||||
|
.append($('<td>').text(oElem.status))
|
||||||
|
.append($('<td>').text(oElem.last_update))
|
||||||
|
.append($('<td>').addButton('close fa-lg', '', 'del_feed', (oEvent)=>{this.del(oEvent);}));
|
||||||
|
break;
|
||||||
|
case 'spot':
|
||||||
|
$Elem
|
||||||
|
.append($('<td>').text(oElem.ref_spot_id))
|
||||||
|
.append($('<td>').text(oElem.name))
|
||||||
|
.append($('<td>').text(oElem.model))
|
||||||
|
break;
|
||||||
|
case 'user':
|
||||||
|
$Elem
|
||||||
|
.append($('<td>').text(oElem.name))
|
||||||
|
.append($('<td>').text(oElem.language))
|
||||||
|
.append($('<td>').text(oElem.timezone))
|
||||||
|
.append($('<td>').addInput('number', 'clearance', oElem.clearance, aoEvents))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$Elem.appendTo($('#'+sElemType+'_section').find('table tbody'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createProject() {
|
||||||
|
this.spot.get('admin_new', this.setProjects);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProject() {
|
||||||
|
this.spot.get(
|
||||||
|
'update_project',
|
||||||
|
(asData, sMsg) => {this.spot.onFeedback('success', sMsg, {'update':'project'});},
|
||||||
|
{},
|
||||||
|
(sMsg) => {this.spot.onFeedback('error', sMsg, {'update':'project'});}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
commit(oEvent) {
|
||||||
|
let $Elem = $(oEvent.currentTarget);
|
||||||
|
if(typeof this.spot.tmp('wait') != 'undefined') clearTimeout(this.spot.tmp('wait'));
|
||||||
|
|
||||||
|
var sOldVal = $Elem.data('old_value');
|
||||||
|
var sNewVal = $Elem.val();
|
||||||
|
if(sOldVal != sNewVal) {
|
||||||
|
$Elem.data('old_value', sNewVal);
|
||||||
|
|
||||||
|
var $Record = $Elem.closest('tr');
|
||||||
|
var asInputs = {
|
||||||
|
type: $Record.data('type'),
|
||||||
|
id: $Record.data('id'),
|
||||||
|
field: $Elem.attr('name'),
|
||||||
|
value: sNewVal
|
||||||
|
};
|
||||||
|
|
||||||
|
this.spot.get(
|
||||||
|
'admin_set',
|
||||||
|
(asData) => {
|
||||||
|
this.spot.onFeedback('success', this.spot.lang('admin_save_success'), asInputs);
|
||||||
|
this.setProjects(asData);
|
||||||
|
},
|
||||||
|
asInputs,
|
||||||
|
(sError) => {
|
||||||
|
$Elem.data('old_value', sOldVal);
|
||||||
|
this.spot.onFeedback('error', sError, asInputs);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
waitAndCommit(oEvent) {
|
||||||
|
if(typeof this.spot.tmp('wait') != 'undefined') clearTimeout(this.spot.tmp('wait'));
|
||||||
|
this.spot.tmp('wait', setTimeout(() => {this.commit(oEvent);}, 2000));
|
||||||
|
}
|
||||||
|
|
||||||
|
del(oEvent) {
|
||||||
|
var $Record = $(oEvent.currentTarget).closest('tr');
|
||||||
|
var asInputs = {
|
||||||
|
type: $Record.data('type'),
|
||||||
|
id: $Record.data('id')
|
||||||
|
};
|
||||||
|
|
||||||
|
this.spot.get(
|
||||||
|
'admin_del',
|
||||||
|
(asData) => {
|
||||||
|
this.spot.onFeedback('success', this.spot.lang('admin_save_success'), asInputs);
|
||||||
|
this.setProjects(asData);
|
||||||
|
},
|
||||||
|
asInputs,
|
||||||
|
(sError) => {
|
||||||
|
this.spot.onFeedback('error', sError, asInputs);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
1176
src/scripts/page.project.js
Normal file
77
src/scripts/page.upload.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import "blueimp-file-upload/js/vendor/jquery.ui.widget.js";
|
||||||
|
import "blueimp-file-upload/js/jquery.iframe-transport.js";
|
||||||
|
import "blueimp-file-upload/js/jquery.fileupload.js";
|
||||||
|
//import "blueimp-file-upload/js/jquery.fileupload-image.js";
|
||||||
|
|
||||||
|
export default class Upload {
|
||||||
|
|
||||||
|
constructor(oSpot) {
|
||||||
|
this.spot = oSpot;
|
||||||
|
}
|
||||||
|
|
||||||
|
pageInit(asHash) {
|
||||||
|
let asProject = this.spot.vars(['projects', this.spot.vars('default_project_codename')]);
|
||||||
|
this.spot.tmp('status-box', $('#status'));
|
||||||
|
if(asProject.editable) {
|
||||||
|
$('#fileupload')
|
||||||
|
.attr('data-url', this.spot.getActionLink('upload'))
|
||||||
|
.fileupload({
|
||||||
|
dataType: 'json',
|
||||||
|
formData: {t: this.spot.consts.timezone},
|
||||||
|
acceptFileTypes: /(\.|\/)(gif|jpe?g|png|mov)$/i,
|
||||||
|
done: (e, asData) => {
|
||||||
|
$.each(asData.result.files, (iKey, oFile) => {
|
||||||
|
let bError = ('error' in oFile);
|
||||||
|
|
||||||
|
//Feedback
|
||||||
|
this.addStatus(bError?oFile.error:(this.spot.lang('upload_success', [oFile.name])));
|
||||||
|
|
||||||
|
//Comments
|
||||||
|
if(!bError) this.addCommentBox(oFile.id, oFile.thumbnail);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
progressall: (e, data) => {
|
||||||
|
let progress = parseInt(data.loaded / data.total * 100, 10);
|
||||||
|
$('#progress .bar').css('width', progress+'%');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else this.addStatus(this.spot.lang('upload_mode_archived', [asProject.name]), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
addCommentBox(iMediaId, sThumbnailPath) {
|
||||||
|
$('#comments').append($('<div>', {'class':'comment'})
|
||||||
|
.append($('<img>', {'class':'thumb', 'src':sThumbnailPath}))
|
||||||
|
.append($('<form>')
|
||||||
|
.append($('<input>', {'class':'content', 'name':'content', 'type':'text'}))
|
||||||
|
.append($('<input>', {'class':'id', 'name':'id', 'type':'hidden', 'value':iMediaId}))
|
||||||
|
.append($('<button>', {'class':'save', 'type':'button'})
|
||||||
|
.on('click', (oEvent) => {
|
||||||
|
var $Form = $(oEvent.currentTarget).parent();
|
||||||
|
this.spot.get(
|
||||||
|
'add_comment',
|
||||||
|
(asData) => {
|
||||||
|
this.addStatus(this.spot.lang('media_comment_update', asData.filename));
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: $Form.find('.id').val(),
|
||||||
|
content: $Form.find('.content').val()
|
||||||
|
},
|
||||||
|
(sMsgId) => {
|
||||||
|
this.addStatus(this.spot.lang(sMsgId));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.text(this.spot.lang('save'))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
addStatus(sMsg, bClear) {
|
||||||
|
bClear = bClear || false;
|
||||||
|
if(bClear) this.spot.tmp('status-box').empty();
|
||||||
|
|
||||||
|
this.spot.tmp('status-box').append($('<p>').text(sMsg));
|
||||||
|
}
|
||||||
|
}
|
||||||
267
src/scripts/spot.js
Executable file
@@ -0,0 +1,267 @@
|
|||||||
|
export default class Spot {
|
||||||
|
|
||||||
|
constructor(asGlobals) {
|
||||||
|
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.pages = {};
|
||||||
|
|
||||||
|
//Variables & constants from php
|
||||||
|
this.vars('tmp', 'object');
|
||||||
|
this.vars('page', 'string');
|
||||||
|
$.each(asGlobals.vars, (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 fOnSuccess(oData.data, oData.desc);
|
||||||
|
})
|
||||||
|
.fail((jqXHR, textStatus, errorThrown) => {
|
||||||
|
fonProgress('fail');
|
||||||
|
fOnError(textStatus+' '+errorThrown);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
lang(sKey, asParams) {
|
||||||
|
asParams = asParams || [];
|
||||||
|
if(typeof asParams == 'string') asParams = [asParams];
|
||||||
|
|
||||||
|
var sLang = '';
|
||||||
|
if(sKey in this.consts.lang) {
|
||||||
|
sLang = this.consts.lang[sKey];
|
||||||
|
for(let i in asParams) sLang = sLang.replace('$'+i, asParams[i]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log('missing translation: '+sKey);
|
||||||
|
sLang = sKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sLang;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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(asHash) {
|
||||||
|
let sPage = this.vars('page');
|
||||||
|
return (this.pages[sPage] && this.pages[sPage].onSamePageMove)?this.pages[sPage].onSamePageMove(asHash):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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(iTimeDays>1?'unit_days':'unit_day')):'') //Days
|
||||||
|
+((iTimeHours>0 || iTimeDays==0)?iTimeHours+this.lang('unit_hour'):'') //Hours
|
||||||
|
+((iTimeDays>0 || iTimeMinutes==0)?'':iTimeMinutes) //Minutes
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
checkClearance(sClearance) {
|
||||||
|
return (this.vars(['user', 'clearance']) >= sClearance);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
$fa-font-path: "fa/fonts";
|
$fa-font-path: 'fonts';
|
||||||
$fa-css-prefix: fa;
|
$fa-css-prefix: fa;
|
||||||
|
|
||||||
@import 'fa/solid';
|
@import 'fa/solid';
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Google Fonts - Ubuntu v15 - https://fonts.googleapis.com/css?family=Ubuntu:400,700&subset=latin-ext&display=swap */
|
/* Google Fonts - Ubuntu v20 - https://fonts.googleapis.com/css?family=Ubuntu:400,700&subset=latin-ext&display=swap */
|
||||||
|
|
||||||
/* cyrillic-ext */
|
/* cyrillic-ext */
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
src: url(https://fonts.gstatic.com/s/ubuntu/v15/4iCs6KVjbNBYlgoKcg72j00.woff2) format('woff2');
|
src: url(https://fonts.gstatic.com/s/ubuntu/v20/4iCs6KVjbNBYlgoKcg72j00.woff2) format('woff2');
|
||||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||||
}
|
}
|
||||||
/* cyrillic */
|
/* cyrillic */
|
||||||
@@ -15,8 +15,8 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
src: url(https://fonts.gstatic.com/s/ubuntu/v15/4iCs6KVjbNBYlgoKew72j00.woff2) format('woff2');
|
src: url(https://fonts.gstatic.com/s/ubuntu/v20/4iCs6KVjbNBYlgoKew72j00.woff2) format('woff2');
|
||||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
}
|
}
|
||||||
/* greek-ext */
|
/* greek-ext */
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
src: url(https://fonts.gstatic.com/s/ubuntu/v15/4iCs6KVjbNBYlgoKcw72j00.woff2) format('woff2');
|
src: url(https://fonts.gstatic.com/s/ubuntu/v20/4iCs6KVjbNBYlgoKcw72j00.woff2) format('woff2');
|
||||||
unicode-range: U+1F00-1FFF;
|
unicode-range: U+1F00-1FFF;
|
||||||
}
|
}
|
||||||
/* greek */
|
/* greek */
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
src: url(https://fonts.gstatic.com/s/ubuntu/v15/4iCs6KVjbNBYlgoKfA72j00.woff2) format('woff2');
|
src: url(https://fonts.gstatic.com/s/ubuntu/v20/4iCs6KVjbNBYlgoKfA72j00.woff2) format('woff2');
|
||||||
unicode-range: U+0370-03FF;
|
unicode-range: U+0370-03FF;
|
||||||
}
|
}
|
||||||
/* latin-ext */
|
/* latin-ext */
|
||||||
@@ -42,8 +42,8 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
src: url(https://fonts.gstatic.com/s/ubuntu/v15/4iCs6KVjbNBYlgoKcQ72j00.woff2) format('woff2');
|
src: url(https://fonts.gstatic.com/s/ubuntu/v20/4iCs6KVjbNBYlgoKcQ72j00.woff2) format('woff2');
|
||||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
}
|
}
|
||||||
/* latin */
|
/* latin */
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
src: url(fonts/4iCs6KVjbNBYlgoKfw72.woff2) format('woff2');
|
src: url(fonts/4iCs6KVjbNBYlgoKfw72.woff2) format('woff2');
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
}
|
}
|
||||||
/* cyrillic-ext */
|
/* cyrillic-ext */
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
src: url(https://fonts.gstatic.com/s/ubuntu/v15/4iCv6KVjbNBYlgoCxCvjvWyNL4U.woff2) format('woff2');
|
src: url(https://fonts.gstatic.com/s/ubuntu/v20/4iCv6KVjbNBYlgoCxCvjvWyNL4U.woff2) format('woff2');
|
||||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||||
}
|
}
|
||||||
/* cyrillic */
|
/* cyrillic */
|
||||||
@@ -69,8 +69,8 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
src: url(https://fonts.gstatic.com/s/ubuntu/v15/4iCv6KVjbNBYlgoCxCvjtGyNL4U.woff2) format('woff2');
|
src: url(https://fonts.gstatic.com/s/ubuntu/v20/4iCv6KVjbNBYlgoCxCvjtGyNL4U.woff2) format('woff2');
|
||||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
}
|
}
|
||||||
/* greek-ext */
|
/* greek-ext */
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
src: url(https://fonts.gstatic.com/s/ubuntu/v15/4iCv6KVjbNBYlgoCxCvjvGyNL4U.woff2) format('woff2');
|
src: url(https://fonts.gstatic.com/s/ubuntu/v20/4iCv6KVjbNBYlgoCxCvjvGyNL4U.woff2) format('woff2');
|
||||||
unicode-range: U+1F00-1FFF;
|
unicode-range: U+1F00-1FFF;
|
||||||
}
|
}
|
||||||
/* greek */
|
/* greek */
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
src: url(https://fonts.gstatic.com/s/ubuntu/v15/4iCv6KVjbNBYlgoCxCvjs2yNL4U.woff2) format('woff2');
|
src: url(https://fonts.gstatic.com/s/ubuntu/v20/4iCv6KVjbNBYlgoCxCvjs2yNL4U.woff2) format('woff2');
|
||||||
unicode-range: U+0370-03FF;
|
unicode-range: U+0370-03FF;
|
||||||
}
|
}
|
||||||
/* latin-ext */
|
/* latin-ext */
|
||||||
@@ -96,8 +96,8 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
src: url(https://fonts.gstatic.com/s/ubuntu/v15/4iCv6KVjbNBYlgoCxCvjvmyNL4U.woff2) format('woff2');
|
src: url(https://fonts.gstatic.com/s/ubuntu/v20/4iCv6KVjbNBYlgoCxCvjvmyNL4U.woff2) format('woff2');
|
||||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
}
|
}
|
||||||
/* latin */
|
/* latin */
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -106,5 +106,5 @@
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
src: url(fonts/4iCv6KVjbNBYlgoCxCvjsGyN.woff2) format('woff2');
|
src: url(fonts/4iCv6KVjbNBYlgoCxCvjsGyN.woff2) format('woff2');
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
}
|
}
|
||||||
@@ -9,8 +9,8 @@ $stroke-width-mouse-focus : 1;
|
|||||||
$stroke-width-height-focus: 2;
|
$stroke-width-height-focus: 2;
|
||||||
$stroke-width-axis : 2;
|
$stroke-width-axis : 2;
|
||||||
|
|
||||||
@import 'leaflet/leaflet';
|
@import '../../node_modules/leaflet/dist/leaflet.css';
|
||||||
@import 'leaflet/leaflet_heightgraph';
|
@import '../../node_modules/leaflet.heightgraph/src/L.Control.Heightgraph.css';
|
||||||
|
|
||||||
/* Leaflet fixes */
|
/* Leaflet fixes */
|
||||||
.leaflet-container {
|
.leaflet-container {
|
||||||
@@ -108,3 +108,7 @@ $stroke-width-axis : 2;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.leaflet-default-icon-path {
|
||||||
|
background-image: none;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@import 'lightbox/lightbox';
|
@import '../../node_modules/lightbox2/src/css/lightbox.css';
|
||||||
|
|
||||||
@mixin lightbox-icon($icon) {
|
@mixin lightbox-icon($icon) {
|
||||||
background: none;
|
background: none;
|
||||||