Convert project to webpack

This commit is contained in:
2023-11-11 17:23:33 +01:00
parent 9d676c339b
commit f86dadfc7d
21 changed files with 7765 additions and 1749 deletions

19
.gitignore vendored
View File

@@ -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
/files/**/*.png
/files/**/*.PNG
/files/**/*.mov
/files/**/*.MOV
/geo/*.geojson
/spot_cron.sh
/vendor/*
/log.html /log.html
/dist/

12
build/paths.js Normal file
View 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, '..')
}

109
build/webpack.common.js Normal file
View File

@@ -0,0 +1,109 @@
const path = require('path');
const { SRC, DIST, ASSETS, LIB } = require('./paths');
var webpack = require("webpack");
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-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 HtmlWebpackPlugin({
template: path.resolve(LIB, 'index.php'),
chunks: ['index'],
filename: 'index.php',
}),
new CopyWebpackPlugin({
patterns: [{
from: 'geo/',
to: path.resolve(DIST, 'geo')
}]
}),
new SymlinkWebpackPlugin({ origin: '../files/', symlink: 'files' }),
new CleanWebpackPlugin()
],
resolve: {
extensions: ['', '.js'],
alias: {
'load-image': 'blueimp-load-image/js/load-image.js',
//'load-image-orientation': 'blueimp-load-image/js/load-image-orientation.js',
//'load-image-scale': 'blueimp-load-image/js/load-image-scale.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
View 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
View File

@@ -0,0 +1,5 @@
const { merge } = require('webpack-merge')
module.exports = merge(require('./webpack.common.js'), {
mode: 'production'
})

View File

@@ -1,5 +1,5 @@
<div id="admin"> <div id="admin">
<a name="back" class="button" href="[#]host_url[#]"><i class="fa fa-back push"></i>[#]lang:nav_back[#]</a> <a name="back" class="button" href="[#]server[#]"><i class="fa fa-back push"></i>[#]lang:nav_back[#]</a>
<h1>[#]lang:projects[#]</h1> <h1>[#]lang:projects[#]</h1>
<div id="project_section"> <div id="project_section">
<table> <table>
@@ -69,144 +69,3 @@
<div id="toolbox"></div> <div id="toolbox"></div>
<div id="feedback" class="feedback"></div> <div id="feedback" class="feedback"></div>
</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>

View File

@@ -8,29 +8,19 @@
<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="[#]host_url[#]" />
<meta property="og:image" content="images/ogp.png" /> <meta property="og:image" content="../src/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="../src/images/icons/apple-touch-icon.png?v=GvmqYyKwbb">
<link rel="icon" type="image/png" sizes="32x32" href="images/icons/favicon-32x32.png?v=GvmqYyKwbb"> <link rel="icon" type="image/png" sizes="32x32" href="../src/images/icons/favicon-32x32.png?v=GvmqYyKwbb">
<link rel="icon" type="image/png" sizes="16x16" href="images/icons/favicon-16x16.png?v=GvmqYyKwbb"> <link rel="icon" type="image/png" sizes="16x16" href="../src/images/icons/favicon-16x16.png?v=GvmqYyKwbb">
<link rel="manifest" href="images/icons/site.webmanifest?v=GvmqYyKwbb"> <link rel="manifest" href="../src/images/icons/site.webmanifest?v=GvmqYyKwbb">
<link rel="mask-icon" href="images/icons/safari-pinned-tab.svg?v=GvmqYyKwbb" color="#44d15a"> <link rel="mask-icon" href="../src/images/icons/safari-pinned-tab.svg?v=GvmqYyKwbb" color="#44d15a">
<link rel="shortcut icon" href="images/icons/favicon.ico?v=GvmqYyKwbb"> <link rel="shortcut icon" href="../src/images/icons/favicon.ico?v=GvmqYyKwbb">
<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="../src/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" src="[#]filepath_js[#]"></script>
<script type="text/javascript" src="[#]filepath_js_d3[#]"></script> <title>Spotty</title>
<script type="text/javascript" src="[#]filepath_js_leaflet[#]"></script>
<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">

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
<div id="upload"> <div id="upload">
<a name="back" class="button" href="[#]host_url[#]"><i class="fa fa-back push"></i>[#]lang:nav_back[#]</a> <a name="back" class="button" href="[#]server[#]"><i class="fa fa-back push"></i>[#]lang:nav_back[#]</a>
<h1>[#]lang:upload_title[#]</h1> <h1>[#]lang:upload_title[#]</h1>
<input id="fileupload" type="file" name="files[]" multiple> <input id="fileupload" type="file" name="files[]" multiple>
<div id="progress"> <div id="progress">
@@ -8,63 +8,3 @@
<div id="comments"></div> <div id="comments"></div>
<div id="status"></div> <div id="status"></div>
</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+'%');
}
});
}
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>

5731
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

46
package.json Normal file
View File

@@ -0,0 +1,46 @@
{
"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",
"html-webpack-plugin": "^5.5.3",
"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"
}
}

View File

@@ -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,11 +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. 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

46
src/scripts/app.js Normal file
View File

@@ -0,0 +1,46 @@
//jQuery
import './jquery.helpers.js';
window.lightbox = require('./lightbox.js');
//Simplebar
import SimpleBar from 'simplebar';
window.SimpleBar = SimpleBar;
import 'simplebar/dist/simplebar.css';
import ResizeObserver from 'resize-observer-polyfill';
window.ResizeObserver = ResizeObserver;
//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;
//Masks
import Spot from './spot.js';
import Project from './page.project.js';
import Upload from './page.upload.js';
import Admin from './page.admin.js';
require('./../styles/spot.scss');
import './../../masks/index.html';
import LogoText from '../images/logo_black.png';
import Logo from '../images/spot-logo-only.svg';
console.log(Logo);
$.get('index.php?a=params').done((e)=>{
let oSpot = new Spot(e.data);
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
View 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);
}
);
}

View 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')
});
}
});
};

View 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);
}
});

View File

@@ -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
View 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);
}
);
}
}

1168
src/scripts/page.project.js Normal file

File diff suppressed because it is too large Load Diff

View 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));
}
}

View File

@@ -1,109 +1,96 @@
function Spot(asGlobals) export default class Spot {
{
self = this; constructor(asGlobals) {
this.consts = asGlobals.consts; this.consts = asGlobals.consts;
this.consts.hash_sep = '-'; this.consts.hash_sep = '-';
this.consts.title = 'Spotty'; this.consts.title = 'Spotty';
this.consts.default_page = 'project'; this.consts.default_page = 'project';
this.consts.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || this.consts.default_timezone; this.consts.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || this.consts.default_timezone;
/* Initialization */ this.pages = {};
this.init = function()
{
//Variables & constants from php //Variables & constants from php
self.vars('tmp', 'object'); this.vars('tmp', 'object');
self.vars('page', 'string'); this.vars('page', 'string');
self.updateVars(asGlobals.vars); $.each(asGlobals.vars, (sKey, oValue) => {this.vars(sKey, oValue)});
//page elem //page elem
self.elem = {}; this.elem = {};
self.elem.container = $('#container'); }
self.elem.main = $('#main');
self.resetTmpFunctions(); /* Initialization */
init() {
this.elem.container = $('#container');
this.elem.main = $('#main');
//On Key down //On Key down
$('html').on('keydown', function(oEvent){self.onKeydown(oEvent);}); $('html').on('keydown', (oEvent) => {this.onKeydown(oEvent);});
//on window resize //on window resize
$(window).on('resize', function(){self.onResize();}); $(window).on('resize', () => {this.onResize();});
//Setup menu
//self.initMenu();
//Hash management //Hash management
$(window) $(window)
.bind('hashchange', self.onHashChange) .on('hashchange', () => {this.onHashChange();})
.trigger('hashchange'); .trigger('hashchange');
}; }
this.updateVars = function(asVars)
{
$.each(asVars, function(sKey, oValue){self.vars(sKey, oValue)});
};
/* Variable Management */ /* Variable Management */
this.vars = function(oVarName, oValue) vars(oVarName, oValue) {
{
var asVarName = (typeof oVarName == 'object')?oVarName:[oVarName]; var asVarName = (typeof oVarName == 'object')?oVarName:[oVarName];
//Set, name & type / default value (init) //Set, name & type / default value (init)
if(typeof oValue !== 'undefined') setElem(self.vars, copyArray(asVarName), oValue); if(typeof oValue !== 'undefined') setElem(this.vars, copyArray(asVarName), oValue);
//Get, only name parameter //Get, only name parameter
return getElem(self.vars, asVarName); return getElem(this.vars, asVarName);
}; }
this.tmp = function(sVarName, oValue) tmp(sVarName, oValue) {
{
var asVarName = (typeof sVarName == 'object')?sVarName:[sVarName]; var asVarName = (typeof sVarName == 'object')?sVarName:[sVarName];
asVarName.unshift('tmp'); asVarName.unshift('tmp');
return self.vars(asVarName, oValue); return this.vars(asVarName, oValue);
}; }
/* Interface with server */ /* Interface with server */
this.get = function(sAction, fOnSuccess, oVars, fOnError, fonProgress) get(sAction, fOnSuccess, oVars, fOnError, fonProgress) {
{ oVars = oVars || {};
if(!oVars) oVars = {};
fOnError = fOnError || function(sError) {console.log(sError);}; fOnError = fOnError || function(sError) {console.log(sError);};
fonProgress = fonProgress || function(sState){}; fonProgress = fonProgress || function(sState){};
fonProgress('start'); fonProgress('start');
oVars['a'] = sAction; oVars['a'] = sAction;
oVars['t'] = self.consts.timezone; oVars['t'] = this.consts.timezone;
return $.ajax( return $.ajax(
{ {
url: self.consts.process_page, url: this.consts.process_page,
data: oVars, data: oVars,
dataType: 'json' dataType: 'json'
}) })
.done(function(oData) .done((oData) => {
{
fonProgress('done'); 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.desc.substr(0, this.consts.lang_prefix.length)==this.consts.lang_prefix) oData.desc = this.lang(oData.desc.substr(5));
if(oData.result==self.consts.error) fOnError(oData.desc); if(oData.result==this.consts.error) fOnError(oData.desc);
else fOnSuccess(oData.data, oData.desc); else fOnSuccess(oData.data, oData.desc);
}) })
.fail(function(jqXHR, textStatus, errorThrown) .fail((jqXHR, textStatus, errorThrown) => {
{
fonProgress('fail'); fonProgress('fail');
fOnError(textStatus+' '+errorThrown); fOnError(textStatus+' '+errorThrown);
}); });
}; }
lang(sKey, asParams) {
asParams = asParams || [];
if(typeof asParams == 'string') asParams = [asParams];
this.lang = function(sKey, asParams) {
var sParamType = $.type(asParams);
if(sParamType == 'undefined') asParams = [];
else if($.type(asParams) != 'array') asParams = [asParams];
var sLang = ''; var sLang = '';
if(sKey in this.consts.lang) {
if(sKey in self.consts.lang) { sLang = this.consts.lang[sKey];
sLang = self.consts.lang[sKey]; for(let i in asParams) sLang = sLang.replace('$'+i, asParams[i]);
for(i in asParams) sLang = sLang.replace('$'+i, asParams[i]);
} }
else { else {
console.log('missing translation: '+sKey); console.log('missing translation: '+sKey);
@@ -111,141 +98,153 @@ function Spot(asGlobals)
} }
return sLang; return sLang;
}; }
/* Page Switch - Trigger & Event catching */ /* Page Switch - Trigger & Event catching */
this.onHashChange = function() onHashChange() {
{ var asHash = this.getHash();
var asHash = self.getHash(); if(asHash.hash !='' && asHash.page != '') this.switchPage(asHash); //page switching
if(asHash.hash !='' && asHash.page != '') self.switchPage(asHash); //page switching else if(this.vars('page')=='') this.setHash(this.consts.default_page); //first page
else if(self.vars('page')=='') self.setHash(self.consts.default_page); //first page }
};
this.getHash = function() getHash() {
{ var sHash = this.hash();
var sHash = self.hash(); var asHash = sHash.split(this.consts.hash_sep);
var asHash = sHash.split(self.consts.hash_sep);
var sPage = asHash.shift() || ''; var sPage = asHash.shift() || '';
return {hash:sHash, page:sPage, items:asHash}; return {hash:sHash, page:sPage, items:asHash};
}; }
this.setHash = function(sPage, asItems, bReboot) setHash(sPage, asItems, bReboot) {
{
bReboot = bReboot || false; bReboot = bReboot || false;
sPage = sPage || ''; sPage = sPage || '';
asItems = asItems || []; asItems = asItems || [];
if(typeof asItems == 'string') 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) 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; bReboot = bReboot || false;
if(!hash) return window.location.hash.slice(1); if(!hash) return window.location.hash.slice(1);
else window.location.hash = '#'+hash; else window.location.hash = '#'+hash;
if(bReboot) location.reload(); if(bReboot) location.reload();
}; }
this.updateHash = function(sType, iId) { updateHash(sType, iId) {
sType = sType || ''; sType = sType || '';
iId = iId || 0; iId = iId || 0;
var asHash = self.getHash(); var asHash = this.getHash();
if(iId) self.setHash(asHash.page, [asHash.items[0], sType, iId]); if(iId) this.setHash(asHash.page, [asHash.items[0], sType, iId]);
}; }
this.flushHash = function(asTypes) { flushHash(asTypes) {
asTypes = asTypes || []; asTypes = asTypes || [];
var asHash = self.getHash(); var asHash = this.getHash();
if(asHash.items.length > 1 && (asTypes.length == 0 || asTypes.indexOf(asHash.items[1]) != -1)) self.setHash(asHash.page, [asHash.items[0]]); 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 */ /* Page Switch - DOM Replacement */
this.getActionLink = function(sAction, oVars) getActionLink(sAction, oVars) {
{
if(!oVars) oVars = {}; if(!oVars) oVars = {};
sVars = ''; let sVars = '';
for(i in oVars)
{ for(i in oVars) sVars += '&'+i+'='+oVars[i];
sVars += '&'+i+'='+oVars[i];
return this.consts.process_page+'?a='+sAction+sVars;
} }
return self.consts.process_page+'?a='+sAction+sVars;
};
this.resetTmpFunctions = function() addPage(sPage, oPage) {
{ this.pages[sPage] = oPage;
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) switchPage(asHash) {
{
var sPageName = asHash.page; var sPageName = asHash.page;
var bSamePage = (self.vars('page') == sPageName); var bSamePage = (this.vars('page') == sPageName);
var bFirstPage = (self.vars('page') == ''); var bFirstPage = (this.vars('page') == '');
if(!self.consts.pages[sPageName]) { //Page does not exist if(!this.consts.pages[sPageName]) { //Page does not exist
if(bFirstPage) self.setHash(self.consts.default_page); if(bFirstPage) this.setHash(this.consts.default_page);
else self.setHash(self.vars('page'), self.vars(['hash', 'items'])); else this.setHash(this.vars('page'), this.vars(['hash', 'items']));
} }
else if(self.onQuitPage(bSamePage) && !bSamePage || self.onSamePageMove(asHash)) else if(this.onQuitPage(bSamePage) && !bSamePage || this.onSamePageMove(asHash))
{ {
//Delete tmp variables //Delete tmp variables
self.vars('tmp', {}); this.vars('tmp', {});
//disable tmp functions
self.resetTmpFunctions();
//Officially a new page //Officially a new page
self.vars('page', sPageName); this.vars('page', sPageName);
self.vars('hash', asHash); this.vars('hash', asHash);
//Update Page Title //Update Page Title
this.setPageTitle(sPageName+' '+(asHash.items[0] || '')); this.setPageTitle(sPageName+' '+(asHash.items[0] || ''));
//Replacing DOM //Replacing DOM
var $Dom = $(self.consts.pages[sPageName]); var $Dom = $(this.consts.pages[sPageName]);
if(bFirstPage) if(bFirstPage) this.splash($Dom, asHash, bFirstPage); //first page
{ else this.elem.main.stop().fadeTo('fast', 0, () => {this.splash($Dom, asHash, bFirstPage);}); //Switching page
self.splash($Dom, asHash, bFirstPage); //first page
} }
else else if(bSamePage) this.vars('hash', asHash);
{
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) { splash($Dom, asHash, bFirstPage)
document.title = self.consts.title+' - '+sTitle;
};
this.splash = function($Dom, asHash, bFirstPage)
{ {
//Switch main content //Switch main content
self.elem.main.empty().html($Dom); this.elem.main.empty().html($Dom);
//Page Bootstrap //Page Bootstrap
self.pageInit(asHash, bFirstPage); this.pageInit(asHash, bFirstPage);
//Show main //Show main
var $FadeInElem = bFirstPage?self.elem.container:self.elem.main; var $FadeInElem = bFirstPage?this.elem.container:this.elem.main;
$FadeInElem.hide().fadeTo('slow', 1); $FadeInElem.hide().fadeTo('slow', 1);
}; }
this.getNaturalDuration = function(iHours) { setPageTitle(sTitle) {
document.title = this.consts.title+' - '+sTitle;
}
getNaturalDuration(iHours) {
var iTimeMinutes = 0, iTimeHours = 0, iTimeDays = Math.floor(iHours/8); //8 hours a day 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 if(iTimeDays > 1) iTimeDays = Math.round(iTimeDays * 2) / 2; //Round down to the closest half day
else { else {
@@ -256,213 +255,13 @@ function Spot(asGlobals)
iTimeMinutes = Math.floor(iHours * 4) * 15; //Round down to the closest 15 minutes iTimeMinutes = Math.floor(iHours * 4) * 15; //Round down to the closest 15 minutes
} }
return '~ ' return '~ '
+(iTimeDays>0?(iTimeDays+(iTimeDays%2==0?'':'½')+' '+self.lang(iTimeDays>1?'unit_days':'unit_day')):'') //Days +(iTimeDays>0?(iTimeDays+(iTimeDays%2==0?'':'½')+' '+this.lang(iTimeDays>1?'unit_days':'unit_day')):'') //Days
+((iTimeHours>0 || iTimeDays==0)?iTimeHours+self.lang('unit_hour'):'') //Hours +((iTimeHours>0 || iTimeDays==0)?iTimeHours+this.lang('unit_hour'):'') //Hours
+((iTimeDays>0 || iTimeMinutes==0)?'':iTimeMinutes) //Minutes +((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
{ checkClearance(sClearance) {
var sKey = (typeof asPath == 'object')?asPath.shift():asPath; return (this.vars(['user', 'clearance']) >= sClearance);
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);
}
);
}