* 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
@@ -0,0 +1,28 @@
import { generateId } from '../generate_id.js';
test('generate a valid 5 digit id', () => {
const id = generateId();
expect(id.length).toBe(5);
});
test('generate 100 with less than 110 attempts', () => {
const ids = new Set();
let attempts = 1;
while (ids.size < 100) {
ids.add(generateId());
attempts++;
}
expect(attempts).toBeLessThan(105);
});
describe('generate 1000 with less than 1020 attempts', () => {
const ids = new Set();
let attempts = 1;
while (ids.size < 1000) {
ids.add(generateId());
attempts++;
}
expect(attempts).toBeLessThan(1020);
});
+4
View File
@@ -0,0 +1,4 @@
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('1234567890abcdef', 5);
export const generateId = () => nanoid();
+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;
}
};