mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-11 18:33:53 +00:00
217 lines
6.4 KiB
TypeScript
217 lines
6.4 KiB
TypeScript
import {
|
|
OntimeEntry,
|
|
RGBColour,
|
|
isOntimeDelay,
|
|
OntimeEntryCommonKeys,
|
|
isOntimeGroup,
|
|
isOntimeMilestone,
|
|
} from 'ontime-types';
|
|
import { cssOrHexToColour, isLightColour, millisToString, mixColours } from 'ontime-utils';
|
|
|
|
import type { sheets_v4 } from '@googleapis/sheets';
|
|
import { is } from '../../utils/is.js';
|
|
|
|
export type ClientSecret = {
|
|
installed: {
|
|
client_id: string;
|
|
auth_uri: string;
|
|
token_uri: string;
|
|
auth_provider_x509_cert_url: string;
|
|
client_secret: string;
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Guard validates a given client secrets file
|
|
* @param clientSecret
|
|
* @throws
|
|
*/
|
|
export function isClientSecret(clientSecret: object): clientSecret is ClientSecret {
|
|
if (!('installed' in clientSecret)) {
|
|
return false;
|
|
}
|
|
|
|
const { installed } = clientSecret;
|
|
if (!is.object(installed)) {
|
|
return false;
|
|
}
|
|
|
|
// we expect client secret file to contain the following keys
|
|
return is.objectWithKeys(installed, [
|
|
'client_id',
|
|
'auth_uri',
|
|
'token_uri',
|
|
'token_uri',
|
|
'auth_provider_x509_cert_url',
|
|
'client_secret',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {number} row - The row number of the cell reference. Row 1 is row number 0.
|
|
* @param {number} column - The column number of the cell reference. A is column number 0.
|
|
* @returns {string} - Returns a cell reference as a string using A1 Notation
|
|
* @author https://www.labnol.org/convert-column-a1-notation-210601
|
|
* @example
|
|
*
|
|
* getA1Notation(2, 4) returns "E3"
|
|
* getA1Notation(99, 26) returns "AA100"
|
|
*
|
|
*/
|
|
export function getA1Notation(row: number, column: number): string {
|
|
if (row < 0 || column < 0) {
|
|
throw new Error('Index can not be less than 0');
|
|
}
|
|
const a1Notation = [`${row + 1}`];
|
|
const totalAlphabets = 'Z'.charCodeAt(0) - 'A'.charCodeAt(0) + 1;
|
|
let block = column;
|
|
|
|
while (block >= 0) {
|
|
a1Notation.unshift(String.fromCharCode((block % totalAlphabets) + 'A'.charCodeAt(0)));
|
|
block = Math.floor(block / totalAlphabets) - 1;
|
|
}
|
|
|
|
return a1Notation.join('');
|
|
}
|
|
|
|
/**
|
|
* @description - creates updateCells request from ontime event
|
|
* @param {OntimeEntry} entry
|
|
* @param {number} index - index of the event
|
|
* @param {number} worksheetId
|
|
* @param {object} metadata - object with all the cell positions of the title of each attribute
|
|
* @returns {sheets_v4.Schema} - list of update requests
|
|
*/
|
|
export function cellRequestFromEvent(
|
|
entry: OntimeEntry,
|
|
index: number,
|
|
worksheetId: number,
|
|
metadata: object,
|
|
): sheets_v4.Schema$Request {
|
|
const rowData = Object.entries(metadata) // check what headings are available in the sheet
|
|
.filter(([_, value]) => value !== undefined) // drop anything that is undefined
|
|
.sort(([_a, a], [_b, b]) => a['col'] - b['col']) as [
|
|
OntimeEntryCommonKeys | 'blank',
|
|
{ col: number; row: number },
|
|
][]; // sort the array by the column index
|
|
|
|
// inset blank data is there is spacing between relevant ontime columns
|
|
for (const [index, e] of rowData.entries()) {
|
|
if (index === 0) continue;
|
|
const prevCol = rowData[index - 1][1].col;
|
|
const thisCol = e[1].col;
|
|
const diff = thisCol - prevCol;
|
|
if (diff > 1) {
|
|
const fillArr = new Array<(typeof rowData)[0]>(1).fill(['blank', { row: e[1].row, col: prevCol + 1 }]);
|
|
rowData.splice(index, 0, ...fillArr);
|
|
}
|
|
}
|
|
|
|
const colours = 'colour' in entry ? getAccessibleColour(entry.colour) : undefined;
|
|
const cellColor: sheets_v4.Schema$CellData = !colours
|
|
? {}
|
|
: {
|
|
userEnteredFormat: {
|
|
backgroundColor: toSheetColourLevel(colours.background),
|
|
textFormat: {
|
|
foregroundColor: toSheetColourLevel(colours.text),
|
|
},
|
|
borders: {
|
|
bottom: {
|
|
style: 'SOLID',
|
|
color: toSheetColourLevel(colours.border),
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
const returnRows: sheets_v4.Schema$CellData[] = rowData.map(([key, _]) => {
|
|
return { ...getCellData(key, entry), ...cellColor };
|
|
});
|
|
|
|
const headerLocation = rowData[0][1];
|
|
|
|
return {
|
|
updateCells: {
|
|
start: {
|
|
sheetId: worksheetId,
|
|
rowIndex: index + headerLocation.row + 1,
|
|
columnIndex: headerLocation.col,
|
|
},
|
|
fields: 'userEnteredValue,userEnteredFormat',
|
|
rows: [
|
|
{
|
|
values: returnRows,
|
|
},
|
|
],
|
|
},
|
|
};
|
|
}
|
|
|
|
function getCellData(key: OntimeEntryCommonKeys | 'blank', entry: OntimeEntry) {
|
|
if (isOntimeDelay(entry) || key === 'blank') {
|
|
return {};
|
|
}
|
|
|
|
// we need to remap the event type to timer type in the case of groups and milestones
|
|
if (key === 'timerType') {
|
|
if (isOntimeGroup(entry))
|
|
return { userEnteredValue: { stringValue: entry.id.startsWith('group-end') ? 'group-end' : 'group' } };
|
|
if (isOntimeMilestone(entry)) return { userEnteredValue: { stringValue: 'milestone' } };
|
|
return { userEnteredValue: { stringValue: entry.timerType } };
|
|
}
|
|
|
|
// all other data is not relevant for the group end entry
|
|
if (entry.id.startsWith('group-end')) {
|
|
return {};
|
|
}
|
|
|
|
// we need to flatten the milestones
|
|
if (key.startsWith('custom')) {
|
|
const customKey = key.split(':')[1];
|
|
return { userEnteredValue: { stringValue: entry.custom[customKey] } };
|
|
}
|
|
|
|
// typescript cannot guarantee that the key exists for every entry
|
|
// so we check for the key existence and assert the type
|
|
if (!(key in entry)) return {};
|
|
const value = entry[key as keyof OntimeEntry];
|
|
|
|
if (typeof value === 'number') {
|
|
return { userEnteredValue: { stringValue: millisToString(value) } };
|
|
}
|
|
if (typeof value === 'string') {
|
|
return { userEnteredValue: { stringValue: value } };
|
|
}
|
|
if (typeof value === 'boolean') {
|
|
return { userEnteredValue: { boolValue: value } };
|
|
}
|
|
}
|
|
|
|
type googleSheetCellColour = {
|
|
background: RGBColour;
|
|
text: RGBColour;
|
|
border: RGBColour;
|
|
};
|
|
|
|
function getAccessibleColour(bgColour?: string): googleSheetCellColour | undefined {
|
|
if (!bgColour) return undefined;
|
|
|
|
const background = cssOrHexToColour(bgColour);
|
|
if (!background) return undefined;
|
|
|
|
const text = isLightColour(background) ? BLACK : WHITE;
|
|
const border = mixColours(background, text, 0.2);
|
|
|
|
return { background, text, border };
|
|
}
|
|
|
|
const BLACK: RGBColour = { red: 0, green: 0, blue: 0, alpha: 1 };
|
|
const WHITE: RGBColour = { red: 255, green: 255, blue: 255, alpha: 0.98 };
|
|
|
|
// sheets use color values from 0 to 1
|
|
function toSheetColourLevel(colour: RGBColour): RGBColour {
|
|
return { red: colour.red / 255, green: colour.green / 255, blue: colour.blue / 255, alpha: colour.alpha };
|
|
}
|