* chore: upgrade dependencies
* feat/mac: draft pipeline
* feat/mac: use public folder for transient files
* feat/mac: set app fullscreen
* feat/mac: cmd + , toggles menu
* feat/mac: update readme
* refact: easier login process
* refact prevent style issues with safari
* refact reduce css download
* style: cleanup paginator design
* style: studio clock is responsive
* style: fix spacing style issues with safari
This commit is contained in:
Carlos Valente
2022-04-17 20:30:39 +02:00
committed by GitHub
parent 20d3ddbc18
commit db0eee3b9c
140 changed files with 10160 additions and 2139 deletions
-70
View File
@@ -1,70 +0,0 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `yarn start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.\
You will also see any lint errors in the console.
### `yarn test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `yarn build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `yarn eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
### Analyzing the Bundle Size
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
### Making a Progressive Web App
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
### `yarn build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
+6 -13
View File
@@ -54,13 +54,10 @@ let tray = null;
// Ensure there isn't another instance of the app running already
const lock = app.requestSingleInstanceLock();
if (!lock) {
dialog.showErrorBox(
'Multiple instances',
'An instance if the App is already running.'
);
dialog.showErrorBox('Multiple instances', 'An instance if the App is already running.');
app.quit();
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
app.on('second-instance', () => {
// Someone tried to run a second instance, we should focus our window.
if (win) {
if (win.isMinimized()) win.restore();
@@ -134,9 +131,7 @@ app.whenReady().then(() => {
setTimeout(() => {
// Load page served by node
const reactApp =
env === 'prod'
? 'http://localhost:4001/editor'
: 'http://localhost:3000/editor';
env === 'prod' ? 'http://localhost:4001/editor' : 'http://localhost:3000/editor';
win.loadURL(reactApp).then(() => {
win.webContents.setBackgroundThrottling(false);
@@ -190,13 +185,12 @@ app.whenReady().then(() => {
tray.setContextMenu(trayContextMenu);
// on tray click event, show main window
tray.on('click', function (e) {
tray.on('click', function () {
if (!win.isVisible()) {
win.show();
}
win.focus();
});
});
// unregister shortcuts before quitting
@@ -216,7 +210,7 @@ ipcMain.on('test-message', (event, arg) => {
});
// Terminate
ipcMain.on('shutdown', (event, arg) => {
ipcMain.on('shutdown', () => {
console.log('Got IPC shutdown');
// terminate node service
@@ -238,8 +232,7 @@ ipcMain.on('set-window', (event, arg) => {
if (arg === 'to-max') {
// window full
win.setContentSize(1920, 1000);
win.setPosition(0, 0);
win.maximize();
} else if (arg === 'to-tray') {
// window to tray
win.hide();
+2 -16
View File
@@ -1,6 +1,6 @@
{
"name": "ontime",
"version": "0.8.2",
"version": "0.9.0",
"author": "Carlos Valente",
"description": "Time keeping for live events",
"repository": "https://github.com/cpvalente/ontime",
@@ -14,7 +14,7 @@
"devDependencies": {
"cypress": "^9.4.1",
"electron": "13.6.7",
"electron-builder": "^22.14.5",
"electron-builder": "^23.0.3",
"eslint": "^8.5.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-cypress": "^2.12.1",
@@ -27,10 +27,8 @@
},
"scripts": {
"nodestart": "NODE_ENV=development node src/app.js",
"make": "mkdir -p src/data",
"setdb": "cp data/db.json src/data/db.json",
"clean": "rm -rf ../client/build/ && rm -rf ../client/node_modules && rm -rf src/node_modules && rm -rf ./node_modules && rm -rf ./dist",
"cheat": "rm src/node_modules/ontime-utils && cp -R utils src/node_modules/ontime-utils",
"prep": "yarn clean && yarn setdb",
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
"start": "NODE_ENV=development electron .",
@@ -111,18 +109,6 @@
"!**/{test,tests,__test__,__tests__}",
"!**/{mock,mocks,__mock__,__mocks__}"
]
},
{
"from": "utils",
"to": "extraResources/utils",
"filter": [
"**/*",
"!cypress/",
"!**/{yarn.lock,yarn-error.log}",
"!**/{test,tests,__test__,__tests__}",
"!**/{mock,mocks,__mock__,__mocks__}",
"!*{.spec.js,*.test.js}"
]
}
]
},
+6 -6
View File
@@ -5,8 +5,8 @@ import { OSCIntegration } from './integrations/Osc.js';
import { HTTPIntegration } from './integrations/Http.js';
import { cleanURL } from '../utils/url.js';
import getRandomName from '../utils/getRandomName.js';
import { stringFromMillis } from 'ontime-utils/time.js';
import { generateId } from 'ontime-utils/generate_id.js';
import { generateId } from '../utils/generate_id.js';
import { stringFromMillis } from '../utils/time.js';
/*
* Class EventTimer adds functions specific to APP
@@ -764,7 +764,7 @@ export class EventTimer extends Timer {
const numEvents = events.length;
// is this the first event
let first = this.numEvents === 0;
const first = this.numEvents === 0;
// set general
this._eventlist = events;
@@ -822,7 +822,7 @@ export class EventTimer extends Timer {
if (e.id === this.selectedEventId) {
// handle reload selected
// Reload data if running
let type = this.selectedEventId === id && this._startedAt != null ? 'reload' : 'load';
const type = this.selectedEventId === id && this._startedAt != null ? 'reload' : 'load';
this.loadEvent(this.selectedEventIndex, type);
} else if (e.id === this.nextEventId) {
// roll needs to recalculate
@@ -1178,7 +1178,7 @@ export class EventTimer extends Timer {
rollLoad() {
const now = this._getCurrentTime();
let prevLoaded = this.selectedEventId;
const prevLoaded = this.selectedEventId;
// maybe roll has already been loaded
if (this.secondaryTimer === null) {
@@ -1428,7 +1428,7 @@ export class EventTimer extends Timer {
* @param {string} message
* @param {any} [payload]
*/
async sendOsc(message, payload = undefined) {
async sendOsc(message, payload) {
// Todo: add disabled osc check
const reply = await this.osc.send(message, payload);
if (!reply.success) {
+4 -11
View File
@@ -1,11 +1,11 @@
import { stringFromMillis } from '../utils/time.js';
/*
* Timer implements simple countdown timer functions
* User needs to use setup function to be able to use
*
*/
import { stringFromMillis } from 'ontime-utils/time.js';
export class Timer {
constructor() {
this.clock = null;
@@ -62,11 +62,7 @@ export class Timer {
if (this._startedAt != null) {
// update current timer
this.current =
this._startedAt +
this.duration +
this._pausedTotal +
this._pausedInterval -
now;
this._startedAt + this.duration + this._pausedTotal + this._pausedInterval - now;
}
// enable flag
@@ -114,10 +110,7 @@ export class Timer {
if (this._finishedAt) return this._finishedAt;
return Math.max(
this._startedAt +
this.duration +
this._pausedInterval +
this._pausedTotal,
this._startedAt + this.duration + this._pausedInterval + this._pausedTotal,
this._startedAt
);
}
+6 -17
View File
@@ -10,8 +10,7 @@ export const DAY_TO_MS = 86400000;
* @param {number} end - When does the event end
* @returns {number} normalised time
*/
export const normaliseEndTime = (start, end) =>
end < start ? end + DAY_TO_MS : end;
export const normaliseEndTime = (start, end) => (end < start ? end + DAY_TO_MS : end);
/**
* @description Sorts an array of objects by given property
@@ -34,7 +33,7 @@ export const sortArrayByProperty = (arr, property) => {
*/
export const replacePlaceholder = (str, values) => {
for (let [k, v] of Object.entries(values)) {
for (const [k, v] of Object.entries(values)) {
str = str.replace(k, v);
}
return str;
@@ -71,10 +70,7 @@ export const getSelectionByRoll = (arr, now) => {
// exit early if we are past the events
const lastEvent = orderedEvents[orderedEvents.length - 1];
const lastNormalEnd = normaliseEndTime(
lastEvent.timeStart,
lastEvent.timeEnd
);
const lastNormalEnd = normaliseEndTime(lastEvent.timeStart, lastEvent.timeEnd);
if (now > lastNormalEnd) {
return {
nowIndex,
@@ -163,14 +159,8 @@ export const getSelectionByRoll = (arr, now) => {
* @returns {object} object with selection variables
*/
export const updateRoll = (currentTimers) => {
const {
selectedEventId,
current,
_finishAt,
clock,
secondaryTimer,
_secondaryTarget,
} = currentTimers;
const { selectedEventId, current, _finishAt, clock, secondaryTimer, _secondaryTarget } =
currentTimers;
// timers
let updatedTimer = current;
@@ -200,8 +190,7 @@ export const updateRoll = (currentTimers) => {
// a) we just finished an event (finished was set to true)
// b) we need to look for events
// this could be caused by a secondary timer or event finished
const secondaryRunning =
updatedSecondaryTimer <= 0 && updatedSecondaryTimer != null;
const secondaryRunning = updatedSecondaryTimer <= 0 && updatedSecondaryTimer != null;
if (isFinished || secondaryRunning) {
// look for events
+3 -9
View File
@@ -68,10 +68,7 @@ export const initiateOSC = (config) => {
try {
const t = parseInt(args);
if (isNaN(t)) {
global.timer.error(
'RX',
`OSC IN: delay time not recognised ${args}`
);
global.timer.error('RX', `OSC IN: delay time not recognised ${args}`);
return;
}
global.timer.increment(t * 1000 * 60);
@@ -82,7 +79,7 @@ export const initiateOSC = (config) => {
case 'goto':
try {
const eventIndex = parseInt(args);
if (isNaN(eventIndex) || eventIndex <= 0 || eventIndex == null) {
if (isNaN(eventIndex) || eventIndex <= 0) {
global.timer.error(
'RX',
`OSC IN: event index not recognised or out of range ${eventIndex}`
@@ -96,10 +93,7 @@ export const initiateOSC = (config) => {
case 'gotoid':
console.log('calling gotoid with', args);
if (args == null) {
global.timer.error(
'RX',
`OSC IN: event id not recognised or out of range ${args}}`
);
global.timer.error('RX', `OSC IN: event id not recognised or out of range ${args}}`);
return;
}
try {
+8 -8
View File
@@ -2,20 +2,20 @@
import { data, db } from '../app.js';
// utils
import { generateId } from 'ontime-utils/generate_id.js';
import {
block as blockDef,
delay as delayDef,
event as eventDef,
} from '../models/eventsDefinition.js';
import { generateId } from '../utils/generate_id.js';
const MAX_EVENTS = 99;
async function _insertAt(entry, index) {
// get events
let events = data.events;
let count = events.length;
let order = entry.order;
const events = data.events;
const count = events.length;
const order = entry.order;
// Remove order field from object
delete entry.order;
@@ -146,7 +146,7 @@ export const eventsPut = async (req, res) => {
return;
}
let eventId = req.body.id;
const eventId = req.body.id;
if (!eventId) {
res.status(400).send(`Object malformed: id missing`);
return;
@@ -191,8 +191,8 @@ export const eventsReorder = async (req, res) => {
const { index, from, to } = req.body;
// get events
let events = data.events;
let idx = events.findIndex((e) => e.id === index, from);
const events = data.events;
const idx = events.findIndex((e) => e.id === index, from);
// Check if item is at given index
if (idx !== from) {
@@ -233,7 +233,7 @@ export const eventsApplyDelay = async (req, res) => {
try {
// get events
let events = data.events;
const events = data.events;
// AUX
let delayIndex = null;
+1 -1
View File
@@ -6,7 +6,7 @@ import { fileURLToPath } from 'url';
import { data, db } from '../app.js';
import { networkInterfaces } from 'os';
import { fileHandler } from '../utils/parser.js';
import { generateId } from 'ontime-utils/generate_id.js';
import { generateId } from '../utils/generate_id.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
View File
-1
View File
@@ -11,7 +11,6 @@
"nanoid": "^3.2.0",
"node-osc": "6.1.12",
"node-xlsx": "^0.21.0",
"ontime-utils": "link: ../server/utils/",
"passport": "^0.5.2",
"passport-local": "~1.0.0",
"socket.io": "^4.4.1",
+69 -69
View File
@@ -2,8 +2,6 @@ import fs from 'fs';
import xlsx from 'node-xlsx';
import { event as eventDef } from '../models/eventsDefinition.js';
import { dbModelv1 } from '../models/dataModel.js';
import { generateId } from 'ontime-utils/generate_id.js';
import { excelDateStringToMillis } from 'ontime-utils/time.js';
import { deleteFile, makeString, validateDuration } from './parserUtils.js';
import {
parseAliases_v1,
@@ -14,86 +12,25 @@ import {
parseSettings_v1,
parseUserFields_v1,
} from './parserUtils_v1.js';
import { excelDateStringToMillis } from './time.js';
import { generateId } from './generate_id.js';
export const EXCEL_MIME = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
export const JSON_MIME = 'application/json';
export const MAX_EVENTS = 99;
/**
* @description Middleware function that checks file type and calls relevant parser
* @param {string} file - reference to file
* @return {object} - parse result message
*/
export const fileHandler = async (file) => {
let res = {};
// check which file type are we dealing with
if (file.endsWith('.xlsx')) {
try {
const excelData = xlsx
.parse(file, { cellDates: true })
.find(
({ name }) => name.toLowerCase() === 'ontime' || name.toLowerCase() === 'event schedule'
);
// we only look at worksheets called ontime or event schedule
if (excelData?.data) {
const dataFromExcel = await parseExcel_v1(excelData.data);
res.data = await parseJson_v1(dataFromExcel);
res.message = 'success';
} else {
console.log('Error: No sheets found named ontime or event schedule');
res = {
error: true,
message: `No sheets found named ontime or event schedule`,
};
}
} catch (error) {
res = { error: true, message: `Error parsing file: ${error}` };
}
}
if (file.endsWith('.json')) {
// if json check version
const rawdata = fs.readFileSync(file);
let uploadedJson = null;
try {
uploadedJson = JSON.parse(rawdata);
} catch (error) {
return { error: true, message: 'Error parsing JSON file' };
}
if (uploadedJson.settings.version === 1) {
try {
res.data = await parseJson_v1(uploadedJson);
res.message = 'success';
} catch (error) {
res = { error: true, message: `Error parsing file: ${error}` };
}
} else {
res = { error: true, message: 'Error parsing file, version unknown' };
}
}
// delete file
await deleteFile(file);
return res;
};
/**
* @description Excel array parser
* @param {array} excelData - array with excel sheet
* @returns {object} - parsed object
*/
export const parseExcel_v1 = async (excelData) => {
let eventData = {
const eventData = {
title: '',
url: '',
};
let customUserFields = {};
let events = [];
const customUserFields = {};
const events = [];
let timeStartIndex = null;
let timeEndIndex = null;
let titleIndex = null;
@@ -291,7 +228,7 @@ export const parseJson_v1 = async (jsonData, enforce = false) => {
}
// object containing the parsed data
let returnData = {};
const returnData = {};
// parse Events
returnData.events = parseEvents_v1(jsonData);
@@ -366,3 +303,66 @@ export const validateEvent_v1 = (eventArgs) => {
return event;
};
/**
* @description Middleware function that checks file type and calls relevant parser
* @param {string} file - reference to file
* @return {object} - parse result message
*/
export const fileHandler = async (file) => {
let res = {};
// check which file type are we dealing with
if (file.endsWith('.xlsx')) {
try {
const excelData = xlsx
.parse(file, { cellDates: true })
.find(
({ name }) => name.toLowerCase() === 'ontime' || name.toLowerCase() === 'event schedule'
);
// we only look at worksheets called ontime or event schedule
if (excelData?.data) {
const dataFromExcel = await parseExcel_v1(excelData.data);
res.data = await parseJson_v1(dataFromExcel);
res.message = 'success';
} else {
console.log('Error: No sheets found named ontime or event schedule');
res = {
error: true,
message: `No sheets found named ontime or event schedule`,
};
}
} catch (error) {
res = { error: true, message: `Error parsing file: ${error}` };
}
}
if (file.endsWith('.json')) {
// if json check version
const rawdata = fs.readFileSync(file);
let uploadedJson = null;
try {
uploadedJson = JSON.parse(rawdata);
} catch (error) {
return { error: true, message: 'Error parsing JSON file' };
}
if (uploadedJson.settings.version === 1) {
try {
res.data = await parseJson_v1(uploadedJson);
res.message = 'success';
} catch (error) {
res = { error: true, message: `Error parsing file: ${error}` };
}
} else {
res = { error: true, message: 'Error parsing file, version unknown' };
}
}
// delete file
await deleteFile(file);
return res;
};
+1 -1
View File
@@ -9,7 +9,7 @@ import fs from 'fs';
export const makeString = (val, fallback = '') => {
if (typeof val === 'string') return val;
else if (val == null || val.constructor === Object) return fallback;
else return val.toString();
return val.toString();
};
/**
+10 -10
View File
@@ -1,7 +1,7 @@
import { block as blockDef, delay as delayDef } from '../models/eventsDefinition.js';
import { generateId } from 'ontime-utils/generate_id.js';
import { dbModelv1 } from '../models/dataModel.js';
import { MAX_EVENTS, validateEvent_v1 } from './parser.js';
import { generateId } from './generate_id.js';
/**
* Parse events array of an entry
@@ -12,8 +12,8 @@ export const parseEvents_v1 = (data) => {
let newEvents = [];
if ('events' in data) {
console.log('Found events definition, importing...');
let events = [];
let ids = [];
const events = [];
const ids = [];
for (const e of data.events) {
// cap number of events
if (events.length >= MAX_EVENTS) {
@@ -28,7 +28,7 @@ export const parseEvents_v1 = (data) => {
}
if (e.type === 'event') {
let event = validateEvent_v1(e);
const event = validateEvent_v1(e);
if (event != null) {
events.push(event);
ids.push(event.id);
@@ -94,7 +94,7 @@ export const parseSettings_v1 = (data, enforce) => {
if (s.app == null || s.version == null) {
console.log('ERROR: unknown app version, skipping');
} else {
let settings = {
const settings = {
lock: s.lock || null,
pinCode: s.pinCode || null,
};
@@ -123,12 +123,12 @@ export const parseOsc_v1 = (data, enforce) => {
if ('osc' in data) {
console.log('Found OSC definition, importing...');
const s = data.osc;
let osc = {};
const osc = {};
if (s.port) osc.port = s.port;
if (s.portOut) osc.portOut = s.portOut;
if (s.targetIP) osc.targetIP = s.targetIP;
if (s.enabled !== undefined) osc.enabled = s.enabled;
if (typeof s.enabled !== 'undefined') osc.enabled = s.enabled;
// write to db
newOsc = {
@@ -149,7 +149,7 @@ export const parseOsc_v1 = (data, enforce) => {
* @returns {object} - event object data
*/
export const parseHttp_v1 = (data, enforce) => {
let newHttp = {};
const newHttp = {};
if ('http' in data) {
console.log('Found HTTP definition, importing...');
const h = data.osc;
@@ -207,13 +207,13 @@ export const parseAliases_v1 = (data) => {
* @returns {object} - event object data
*/
export const parseUserFields_v1 = (data) => {
let newUserFields = dbModelv1.userFields;
const newUserFields = dbModelv1.userFields;
if ('userFields' in data) {
console.log('Found User Fields definition, importing...');
// we will only be importing the fields we know, so look for that
let fieldsFound = 0;
for (let n in newUserFields) {
for (const n in newUserFields) {
if (n in data.userFields) {
fieldsFound++;
newUserFields[n] = data.userFields[n];
+2 -7
View File
@@ -27,12 +27,7 @@ export const nowInMillis = () => {
* @returns {string} String representing time 00:12:02
*/
export const stringFromMillis = (
ms,
showSeconds = true,
delim = ':',
ifNull = '...'
) => {
export const stringFromMillis = (ms, showSeconds = true, delim = ':', ifNull = '...') => {
if (ms == null || isNaN(ms)) return ifNull;
const isNegative = ms < 0 ? '-' : '';
const millis = Math.abs(ms);
@@ -57,7 +52,7 @@ export const stringFromMillis = (
export const excelDateStringToMillis = (excelDate) => {
const date = new Date(excelDate);
if (date instanceof Date && !isNaN(date)) {
const h = date.getUTCHours();
const h = date.getHours();
const m = date.getMinutes();
const s = date.getSeconds();
+38 -12
View File
@@ -1,26 +1,52 @@
import multer from 'multer';
import { statSync, mkdirSync } from 'fs';
import { existsSync, mkdirSync } from 'fs';
import * as path from 'path';
import { EXCEL_MIME, JSON_MIME } from './parser.js';
/**
* @description Returns public path depending on os
* @return {string|*}
*/
function getAppDataPath() {
switch (process.platform) {
case 'darwin': {
return path.join(process.env.HOME, 'Library', 'Application Support', 'Ontime');
}
case 'win32': {
return path.join(process.env.APPDATA, 'Ontime');
}
case 'linux': {
return path.join(process.env.HOME, '.Ontime');
}
default: {
return '';
}
}
}
// Define multer storage object
const storage = multer.diskStorage({
destination: function (req, file, cb) {
let newDestination = 'uploads/';
let stat = null;
try {
stat = statSync(newDestination);
} catch (err) {
mkdirSync(newDestination);
// get platform path
const appDataPath = getAppDataPath();
if (appDataPath === '') {
throw new Error('Could not resolve public folder for platform');
}
if (stat && !stat.isDirectory()) {
throw new Error(
`Directory cannot be created because an inode of a different type exists at ${newDestination}`
);
// append uploads folder
const newDestination = path.join(appDataPath, 'uploads');
// Create directory if not exist
if (!existsSync(newDestination)) {
try {
mkdirSync(newDestination);
} catch (err) {
throw new Error('Could not create directory');
}
}
cb(null, newDestination);
},
filename: function (req, file, cb) {
cb(null, Date.now() + '--' + file.originalname);
cb(null, `${Date.now()}--${file.originalname}`);
},
});
+4 -6
View File
@@ -3,8 +3,7 @@
* @param {string} url - URL to be checked
* @returns {string} Sanitized url
*/
export const cleanURL = (url) => {
export const cleanURL = (url) => {
// trim whitespaces
let r = url.trim();
@@ -12,10 +11,9 @@ export const cleanURL = (url) => {
r = r.split(' ').join('%20');
// contain only allowed characters
r = r.replace(/([^\x00-\x7F]|[@\s<>\[\]{}|\\^])+/g, '')
r = r.replace(/([@\s<>[\]{}|\\^])+/g, '');
// starts with http://
if (!r.startsWith('http://')) r = `http://${r}`
if (!r.startsWith('http://')) r = `http://${r}`;
return r;
}
};
-4
View File
@@ -1103,10 +1103,6 @@ once@^1.3.0:
dependencies:
wrappy "1"
"ontime-utils@link: ../server/utils":
version "0.0.0"
uid ""
optionator@^0.9.1:
version "0.9.1"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
-76
View File
@@ -1,76 +0,0 @@
import { excelDateStringToMillis, stringFromMillis } from '../time.js';
describe('test string to millis function', () => {
it('test with null values', () => {
const t = { val: null, result: '...' };
expect(stringFromMillis(t.val)).toBe(t.result);
});
it('test with valid millis', () => {
const t = { val: 3600000, result: '01:00:00' };
expect(stringFromMillis(t.val)).toBe(t.result);
});
it('test with negative millis', () => {
const t = { val: -3600000, result: '-01:00:00' };
expect(stringFromMillis(t.val)).toBe(t.result);
});
it('test with -1', () => {
const t = { val: -1, result: '-00:00:00' };
expect(stringFromMillis(t.val)).toBe(t.result);
});
it('test with 0', () => {
const t = { val: 0, result: '00:00:00' };
expect(stringFromMillis(t.val)).toBe(t.result);
});
it('test with -0', () => {
const t = { val: -0, result: '00:00:00' };
expect(stringFromMillis(t.val)).toBe(t.result);
});
it('test with 999', () => {
const t = { val: 999, result: '00:00:00' };
expect(stringFromMillis(t.val)).toBe(t.result);
});
it('test with 1000', () => {
const t = { val: 1000, result: '00:00:01' };
expect(stringFromMillis(t.val)).toBe(t.result);
});
it('test with 86400000 (24 hours)', () => {
const t = { val: 86400000, result: '00:00:00' };
expect(stringFromMillis(t.val)).toBe(t.result);
});
it('test with 86401000 (24 hours and 1 second)', () => {
const t = { val: 86401000, result: '00:00:01' };
expect(stringFromMillis(t.val)).toBe(t.result);
});
it('test with -86401000 (-24 hours and 1 second)', () => {
const t = { val: -86401000, result: '-00:00:01' };
expect(stringFromMillis(t.val)).toBe(t.result);
});
});
describe('test stringFromMillis handles partial secs', () => {
it('test with 1795829', () => {
const t = { val: 1795829, result: '00:29:55' };
expect(stringFromMillis(t.val)).toBe(t.result);
});
it('test with 1797482', () => {
const t = { val: 1797482, result: '00:29:57' };
expect(stringFromMillis(t.val)).toBe(t.result);
});
});
describe('test excel date parser', () => {
it('handles an invalid date string', () => {
const s = 'hello';
expect(excelDateStringToMillis(s)).toBe(0);
});
});
-11
View File
@@ -1,11 +0,0 @@
{
"name": "ontime-utils",
"type": "module",
"private": true,
"version": "1.0.0",
"description": "ontime app utility functions",
"dependencies": {
"nanoid": "^3.2.0"
},
"devDependencies": {}
}
-67
View File
@@ -1,67 +0,0 @@
const mts = 1000; // millis to seconds
const mtm = 1000 * 60; // millis to minutes
const mth = 1000 * 60 * 60; // millis to hours
/**
* Returns current time in milliseconds
* @returns {number}
*/
export const nowInMillis = () => {
const now = new Date();
// extract milliseconds since midnight
let elapsed = now.getHours() * 3600000;
elapsed += now.getMinutes() * 60000;
elapsed += now.getSeconds() * 1000;
elapsed += now.getMilliseconds();
return elapsed;
};
/**
* @description Converts milliseconds to string representing time
* @param {number} ms - time in milliseconds
* @param {boolean} showSeconds - weather to show the seconds
* @param {string} delim - character between HH MM SS
* @param {string} ifNull - what to return if value is null
* @returns {string} String representing time 00:12:02
*/
export const stringFromMillis = (
ms,
showSeconds = true,
delim = ':',
ifNull = '...'
) => {
if (ms == null || isNaN(ms)) return ifNull;
const isNegative = ms < 0 ? '-' : '';
const millis = Math.abs(ms);
const showWith0 = (value) => (value < 10 ? `0${value}` : value);
const hours = showWith0(Math.floor(((millis / mth) % 60) % 24));
const minutes = showWith0(Math.floor((millis / mtm) % 60));
const seconds = showWith0(Math.floor((millis / mts) % 60));
return showSeconds
? `${isNegative}${
parseInt(hours) ? `${hours}${delim}` : `00${delim}`
}${minutes}${delim}${seconds}`
: `${isNegative}${parseInt(hours) ? `${hours}` : '00'}${delim}${minutes}`;
};
/**
* @description Converts an excel date to milliseconds
* @argument {string} excelDate - excel string date
* @returns {number} - time in milliseconds
*/
export const excelDateStringToMillis = (excelDate) => {
const date = new Date(excelDate);
if (date instanceof Date && !isNaN(date)) {
const h = date.getHours();
const m = date.getMinutes();
const s = date.getSeconds();
return h * mth + m * mtm + s * mts;
}
return 0;
};
-8
View File
@@ -1,8 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
nanoid@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c"
integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==
+72 -54
View File
@@ -538,16 +538,18 @@
global-agent "^3.0.0"
global-tunnel-ng "^2.7.1"
"@electron/universal@1.0.5":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@electron/universal/-/universal-1.0.5.tgz#b812340e4ef21da2b3ee77b2b4d35c9b86defe37"
integrity sha512-zX9O6+jr2NMyAdSkwEUlyltiI4/EBLu2Ls/VD3pUQdi3cAYeYfdQnT2AJJ38HE4QxLccbU13LSpccw1IWlkyag==
"@electron/universal@1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@electron/universal/-/universal-1.2.0.tgz#518cac72bccd79c00bf41345119e6fdbabdb871d"
integrity sha512-eu20BwNsrMPKoe2bZ3/l9c78LclDvxg3PlVXrQf3L50NaUuW5M59gbPytI+V4z7/QMrohUHetQaU0ou+p1UG9Q==
dependencies:
"@malept/cross-spawn-promise" "^1.1.0"
asar "^3.0.3"
asar "^3.1.0"
debug "^4.3.1"
dir-compare "^2.4.0"
fs-extra "^9.0.1"
minimatch "^3.0.4"
plist "^3.0.4"
"@eslint/eslintrc@^1.0.5":
version "1.0.5"
@@ -853,6 +855,11 @@
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
"@tootallnate/once@2":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==
"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14":
version "7.1.17"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.17.tgz#f50ac9d20d64153b510578d84f9643f9a3afbe64"
@@ -1137,29 +1144,29 @@ anymatch@^3.0.3, anymatch@~3.1.2:
normalize-path "^3.0.0"
picomatch "^2.0.4"
app-builder-bin@3.7.1:
version "3.7.1"
resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-3.7.1.tgz#cb0825c5e12efc85b196ac3ed9c89f076c61040e"
integrity sha512-ql93vEUq6WsstGXD+SBLSIQw6SNnhbDEM0swzgugytMxLp3rT24Ag/jcC80ZHxiPRTdew1niuR7P3/FCrDqIjw==
app-builder-bin@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-4.0.0.tgz#1df8e654bd1395e4a319d82545c98667d7eed2f0"
integrity sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA==
app-builder-lib@22.14.5:
version "22.14.5"
resolved "https://registry.yarnpkg.com/app-builder-lib/-/app-builder-lib-22.14.5.tgz#a61a50b132b858e98fdc70b6b88994ae99b4f96d"
integrity sha512-k3VwKP4kpsnUaXoUkm1s4zaSHPHIMFnN4kPMU9yXaKmE1LfHHqBaEah5bXeTAX5V/BC41wFdg8CF5vOjvgy8Rg==
app-builder-lib@23.0.3:
version "23.0.3"
resolved "https://registry.yarnpkg.com/app-builder-lib/-/app-builder-lib-23.0.3.tgz#44c90237abdc4ad9b34a24658bee022828ad6205"
integrity sha512-1qrtXYHXJfXhzJnMtVGjIva3067F1qYQubl2oBjI61gCBoCHvhghdYJ57XxXTQQ0VxnUhg1/Iaez87uXp8mD8w==
dependencies:
"7zip-bin" "~5.1.1"
"@develar/schema-utils" "~2.6.5"
"@electron/universal" "1.0.5"
"@electron/universal" "1.2.0"
"@malept/flatpak-bundler" "^0.4.0"
async-exit-hook "^2.0.1"
bluebird-lst "^1.0.9"
builder-util "22.14.5"
builder-util-runtime "8.9.1"
builder-util "23.0.2"
builder-util-runtime "9.0.0"
chromium-pickle-js "^0.2.0"
debug "^4.3.2"
ejs "^3.1.6"
electron-osx-sign "^0.5.0"
electron-publish "22.14.5"
electron-osx-sign "^0.6.0"
electron-publish "23.0.2"
form-data "^4.0.0"
fs-extra "^10.0.0"
hosted-git-info "^4.0.2"
@@ -1195,7 +1202,7 @@ asap@^2.0.0:
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
asar@^3.0.3:
asar@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/asar/-/asar-3.1.0.tgz#70b0509449fe3daccc63beb4d3c7d2e24d3c6473"
integrity sha512-vyxPxP5arcAqN4F/ebHd/HhwnAiZtwhglvdmc7BR2f0ywbVNTOpSeyhLDbGXtE/y58hv1oC75TaNIXutnsOZsQ==
@@ -1464,29 +1471,31 @@ buffer@^5.1.0, buffer@^5.6.0:
base64-js "^1.3.1"
ieee754 "^1.1.13"
builder-util-runtime@8.9.1:
version "8.9.1"
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-8.9.1.tgz#25f066b3fbc20b3e6236a9b956b1ebb0e33ff66a"
integrity sha512-c8a8J3wK6BIVLW7ls+7TRK9igspTbzWmUqxFbgK0m40Ggm6efUbxtWVCGIjc+dtchyr5qAMAUL6iEGRdS/6vwg==
builder-util-runtime@9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.0.0.tgz#3a40ba7382712ccdb24471567f91d7c167e00830"
integrity sha512-SkpEtSmTkREDHRJnxKEv43aAYp8sYWY8fxYBhGLBLOBIRXeaIp6Kv3lBgSD7uR8jQtC7CA659sqJrpSV6zNvSA==
dependencies:
debug "^4.3.2"
sax "^1.2.4"
builder-util@22.14.5:
version "22.14.5"
resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-22.14.5.tgz#42a18608d2a566c0846e91266464776c8bfb0cc9"
integrity sha512-zqIHDFJwmA7jV7SC9aI+33MWwT2mWoijH+Ol9IntNAwuuRXoS+7XeJwnhLBXOhcDBzXT4kDzHnRk4JKeaygEYA==
builder-util@23.0.2:
version "23.0.2"
resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-23.0.2.tgz#da84a971076397e3a671726f4bb96f0c2214fea7"
integrity sha512-HaNHL3axNW/Ms8O1mDx3I07G+ZnZ/TKSWWvorOAPau128cdt9S+lNx5ocbx8deSaHHX4WFXSZVHh3mxlaKJNgg==
dependencies:
"7zip-bin" "~5.1.1"
"@types/debug" "^4.1.6"
"@types/fs-extra" "^9.0.11"
app-builder-bin "3.7.1"
app-builder-bin "4.0.0"
bluebird-lst "^1.0.9"
builder-util-runtime "8.9.1"
builder-util-runtime "9.0.0"
chalk "^4.1.1"
cross-spawn "^7.0.3"
debug "^4.3.2"
fs-extra "^10.0.0"
http-proxy-agent "^5.0.0"
https-proxy-agent "^5.0.0"
is-ci "^3.0.0"
js-yaml "^4.1.0"
source-map-support "^0.5.19"
@@ -2013,14 +2022,14 @@ dir-compare@^2.4.0:
commander "2.9.0"
minimatch "3.0.4"
dmg-builder@22.14.5:
version "22.14.5"
resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-22.14.5.tgz#137c0b55e639badcc0b119eb060e6fa4ed61d948"
integrity sha512-1GvFGQE332bvPamcMwZDqWqfWfJTyyDLOsHMcGi0zs+Jh7JOn6/zuBkHJIWHdsj2QJbhzLVyd2/ZqttOKv7I8w==
dmg-builder@23.0.3:
version "23.0.3"
resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-23.0.3.tgz#ea94bc76fcd94612641580f3c6ae42c3f07f3fee"
integrity sha512-mBYrHHnSM5PC656TDE+xTGmXIuWHAGmmRfyM+dV0kP+AxtwPof4pAXNQ8COd0/exZQ4dqf72FiPS3B9G9aB5IA==
dependencies:
app-builder-lib "22.14.5"
builder-util "22.14.5"
builder-util-runtime "8.9.1"
app-builder-lib "23.0.3"
builder-util "23.0.2"
builder-util-runtime "9.0.0"
fs-extra "^10.0.0"
iconv-lite "^0.6.2"
js-yaml "^4.1.0"
@@ -2097,17 +2106,17 @@ ejs@^3.1.6:
dependencies:
jake "^10.6.1"
electron-builder@^22.14.5:
version "22.14.5"
resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-22.14.5.tgz#3a25547bd4fe3728d4704da80956a794c5c31496"
integrity sha512-N73hSbXFz6Mz5Z6h6C5ly6CB+dUN6k1LuCDJjI8VF47bMXv/QE0HE+Kkb0GPKqTqM7Hsk/yIYX+kHCfSkR5FGg==
electron-builder@^23.0.3:
version "23.0.3"
resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-23.0.3.tgz#16264a0d8e3d40da1467bcc8ef7917538b54a3bc"
integrity sha512-0lnTsljAgcOMuIiOjPcoFf+WxOOe/O04hZPgIvvUBXIbz3kolbNu0Xdch1f5WuQ40NdeZI7oqs8Eo395PcuGHQ==
dependencies:
"@types/yargs" "^17.0.1"
app-builder-lib "22.14.5"
builder-util "22.14.5"
builder-util-runtime "8.9.1"
app-builder-lib "23.0.3"
builder-util "23.0.2"
builder-util-runtime "9.0.0"
chalk "^4.1.1"
dmg-builder "22.14.5"
dmg-builder "23.0.3"
fs-extra "^10.0.0"
is-ci "^3.0.0"
lazy-val "^1.0.5"
@@ -2115,10 +2124,10 @@ electron-builder@^22.14.5:
update-notifier "^5.1.0"
yargs "^17.0.1"
electron-osx-sign@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/electron-osx-sign/-/electron-osx-sign-0.5.0.tgz#fc258c5e896859904bbe3d01da06902c04b51c3a"
integrity sha512-icoRLHzFz/qxzDh/N4Pi2z4yVHurlsCAYQvsCSG7fCedJ4UJXBS6PoQyGH71IfcqKupcKeK7HX/NkyfG+v6vlQ==
electron-osx-sign@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/electron-osx-sign/-/electron-osx-sign-0.6.0.tgz#9b69c191d471d9458ef5b1e4fdd52baa059f1bb8"
integrity sha512-+hiIEb2Xxk6eDKJ2FFlpofCnemCbjbT5jz+BKGpVBrRNT3kWTGs4DfNX6IzGwgi33hUcXF+kFs9JW+r6Wc1LRg==
dependencies:
bluebird "^3.5.0"
compare-version "^0.1.2"
@@ -2127,14 +2136,14 @@ electron-osx-sign@^0.5.0:
minimist "^1.2.0"
plist "^3.0.1"
electron-publish@22.14.5:
version "22.14.5"
resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-22.14.5.tgz#34bcdce671f0e651330db20040d6919c77c94bd6"
integrity sha512-h+NANRdaA0PqGF15GKvorseWPzh1PXa/zx4I37//PIokW8eKIov8ky23foUSb55ZFWUHGpxQJux7y2NCfBtQeg==
electron-publish@23.0.2:
version "23.0.2"
resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-23.0.2.tgz#aa11419ae57b847df4beb63b95e2b2a43161957c"
integrity sha512-8gMYgWqv96lc83FCm85wd+tEyxNTJQK7WKyPkNkO8GxModZqt1GO8S+/vAnFGxilS/7vsrVRXFfqiCDUCSuxEg==
dependencies:
"@types/fs-extra" "^9.0.11"
builder-util "22.14.5"
builder-util-runtime "8.9.1"
builder-util "23.0.2"
builder-util-runtime "9.0.0"
chalk "^4.1.1"
fs-extra "^10.0.0"
lazy-val "^1.0.5"
@@ -2873,6 +2882,15 @@ http-proxy-agent@^4.0.1:
agent-base "6"
debug "4"
http-proxy-agent@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43"
integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==
dependencies:
"@tootallnate/once" "2"
agent-base "6"
debug "4"
http-signature@~1.3.6:
version "1.3.6"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9"