diff --git a/apps/client/src/common/api/ontimeApi.ts b/apps/client/src/common/api/ontimeApi.ts
index 072e541bd..c40d37b0a 100644
--- a/apps/client/src/common/api/ontimeApi.ts
+++ b/apps/client/src/common/api/ontimeApi.ts
@@ -265,3 +265,14 @@ export const postPreviewSheet = async (sheetId: string, worksheet: string, optio
});
return response.data.data;
};
+
+export const postPushSheet = async (sheetId: string, worksheet: string, options?: ExcelImportMap) => {
+ const response = await axios.post(`${ontimeURL}/sheet-push`, {
+ sheetid: sheetId,
+ worksheet: worksheet,
+ options: options,
+ });
+ return response.data.data;
+};
+
+
diff --git a/apps/client/src/features/modals/sheets-modal/SheetsModal.tsx b/apps/client/src/features/modals/sheets-modal/SheetsModal.tsx
index caadb1ee6..b7516a604 100644
--- a/apps/client/src/features/modals/sheets-modal/SheetsModal.tsx
+++ b/apps/client/src/features/modals/sheets-modal/SheetsModal.tsx
@@ -17,6 +17,7 @@ import {
postPreviewSheet,
uploadSheetClientFile,
patchData,
+ postPushSheet,
} from '../../../common/api/ontimeApi';
import { OntimeRundown, ProjectData, UserFields } from 'ontime-types';
@@ -88,6 +89,12 @@ export default function SheetsModal(props: SheetsModalProps) {
});
};
+ const handlePushData = () => {
+ postPushSheet(sheetid.current?.value ?? '', worksheet.current?.value ?? '').then((data) => {
+ console.log(data);
+ });
+ };
+
const handleFinalise = async () => {
// this step is currently only used for excel files, after preview
if (rundown && userFields && project) {
@@ -181,6 +188,9 @@ export default function SheetsModal(props: SheetsModalProps) {
+
>
)}
diff --git a/apps/server/src/controllers/ontimeController.ts b/apps/server/src/controllers/ontimeController.ts
index 4007cffbd..b2f1e0232 100644
--- a/apps/server/src/controllers/ontimeController.ts
+++ b/apps/server/src/controllers/ontimeController.ts
@@ -405,6 +405,19 @@ export async function previewSheet(req, res) {
}
}
+/**
+ * downloads and parses an sheet
+ * @returns parsed result
+ */
+export async function pushSheet(req, res) {
+ try {
+ const data = await Sheet.push(req.body.sheetid, req.body.worksheet);
+ res.status(200).send('ok');
+ } catch (error) {
+ res.status(500).send({ message: error.toString() });
+ }
+}
+
/**
* uploads Client secrets file
* @returns parsed result
diff --git a/apps/server/src/routes/ontimeRouter.ts b/apps/server/src/routes/ontimeRouter.ts
index a16a11f9d..fe2caf131 100644
--- a/apps/server/src/routes/ontimeRouter.ts
+++ b/apps/server/src/routes/ontimeRouter.ts
@@ -23,6 +23,7 @@ import {
sheetAuthState,
sheetClientFile,
previewSheet,
+ pushSheet,
} from '../controllers/ontimeController.js';
import {
@@ -103,4 +104,7 @@ router.get('/sheet-authstatus', sheetAuthState);
router.get('/sheet-authurl', sheetAuthUrl);
// create route between controller and '/ontime/preview-sheet' endpoint
-router.post('/sheet-preview', validateSheetPreview, previewSheet);
\ No newline at end of file
+router.post('/sheet-preview', validateSheetPreview, previewSheet);
+
+// create route between controller and '/ontime/preview-sheet' endpoint
+router.post('/sheet-push', pushSheet);
\ No newline at end of file
diff --git a/apps/server/src/utils/sheetsAuth.ts b/apps/server/src/utils/sheetsAuth.ts
index 0ce8c6d3f..1de920a08 100644
--- a/apps/server/src/utils/sheetsAuth.ts
+++ b/apps/server/src/utils/sheetsAuth.ts
@@ -6,10 +6,11 @@ import { URL } from 'url';
import { logger } from '../classes/Logger.js';
import { getAppDataPath } from '../setup.js';
import { DatabaseModel, LogOrigin, OntimeRundownEntry, isOntimeEvent } from 'ontime-types';
-import { ExcelImportOptions, isExcelImportMap, defaultExcelImportMap } from 'ontime-utils';
+import { ExcelImportOptions, isExcelImportMap, defaultExcelImportMap, millisToString } from 'ontime-utils';
import { parseExcel } from './parser.js';
import { parseProject, parseRundown, parseUserFields } from './parserFunctions.js';
import { ensureDirectory } from './fileManagement.js';
+import { DataProvider } from '../classes/data-provider/DataProvider.js';
type ResponseOK = {
data: Partial;
@@ -47,6 +48,71 @@ class sheet {
if (!isExcelImportMap(options)) {
throw new Error('Got incorrect options to excel import', JSON.parse(options));
}
+ const rq = await sheets({ version: 'v4', auth: sheet.client }).spreadsheets.values.get({
+ spreadsheetId: sheetId,
+ valueRenderOption: 'FORMATTED_VALUE',
+ majorDimension: 'ROWS',
+ range: worksheet + '!A:Z', //FIXME: this is an abitrary range
+ });
+ if (rq.status === 200) {
+ const { projectMetadata, rundownMetadata } = parseExcel(rq.data.values, options);
+ const rundown = DataProvider.getRundown();
+ const titleRow = Object.values(rundownMetadata)[0]['row'];
+
+ const updateRundown = Array();
+
+ // we can't delete the last unflozzen row so we create an empty one
+ updateRundown.push({
+ insertDimension: {
+ inheritFromBefore: false,
+ range: { dimension: 'ROWS', startIndex: titleRow + 1, endIndex: titleRow + 2, sheetId: 0 },
+ },
+ });
+ //and delete the rest
+ updateRundown.push({
+ deleteDimension: { range: { dimension: 'ROWS', startIndex: titleRow + 2, sheetId: 0 } },
+ });
+ // insert the lenght of the rundown
+ updateRundown.push({
+ insertDimension: {
+ inheritFromBefore: false,
+ range: {
+ dimension: 'ROWS',
+ startIndex: titleRow + 1,
+ endIndex: titleRow + rundown.length,
+ sheetId: 0,
+ },
+ },
+ });
+
+ //TODO: this is a mess
+ const titleCol = Object.values(rundownMetadata)
+ // .filter(([key, value]) => value !== undefined)
+ .reduce((accumulator: number, val) => Math.min(accumulator, val['col']), Number.MAX_VALUE);
+ if (titleCol == Number.MAX_VALUE) {
+ throw new Error(`could not finde starting title column`);
+ }
+ //update the corespunding row with event data
+ rundown.forEach((entry, index) =>
+ updateRundown.push(this.cellRequenstFromEvent(entry, index, rundownMetadata, titleCol as number)),
+ );
+ const writeResponds = await sheets({ version: 'v4', auth: sheet.client }).spreadsheets.batchUpdate({
+ spreadsheetId: sheetId,
+ requestBody: {
+ includeSpreadsheetInResponse: false,
+ responseRanges: [worksheet + '!A:Z'], //FIXME:
+ requests: updateRundown,
+ },
+ });
+
+ if (writeResponds.status == 200) {
+ logger.info(LogOrigin.Server, `Sheet write: ${writeResponds.statusText}`);
+ } else {
+ throw new Error(`Sheet write faild: ${writeResponds.statusText}`);
+ }
+ } else {
+ throw new Error(`Sheet read faild: ${rq.statusText}`);
+ }
}
/**
@@ -149,51 +215,68 @@ class sheet {
}
/**
- * @param index - the index of the event in Sheet dimentions
+ * @param index - the index of the event in rundown dimentions
*/
- private cellRequenstFromEvent(event: OntimeRundownEntry, index: number): sheets_v4.Schema$Request['updateCells'] {
- const titelCol = 1
- // index += titelRow;
+ private cellRequenstFromEvent(
+ event: OntimeRundownEntry,
+ index: number,
+ metadata,
+ titleCol: number,
+ ): sheets_v4.Schema$Request {
let r: sheets_v4.Schema$CellData[] = [];
- Object.entries(this.metadata['rundown'])
+ const tmp = Object.entries(metadata)
.filter(([key, value]) => value !== undefined)
- .sort(([aKey, a], [bKey, b]) => a['col'] - b['col'])
- .forEach(([key, value]) => {
- if (isOntimeEvent(event)) {
- if (key === 'colour') {
- r.push({
- userEnteredValue: { stringValue: event.colour },
- userEnteredFormat: { backgroundColor: hexToRgb(event?.colour ?? null) },
- });
- } else if (typeof event[key] === 'number') {
- r.push({
- userEnteredValue: { stringValue: millisToString(event[key], true) },
- });
- } else if (typeof event[key] === 'string') {
- r.push({
- userEnteredValue: { stringValue: event[key] },
- });
- } else if (typeof event[key] === 'boolean') {
- r.push({
- userEnteredValue: { stringValue: event[key] ? 'x' : '' },
- });
- } else {
- r.push({});
- }
+ .sort(([aKey, a], [bKey, b]) => a['col'] - b['col']);
+
+ tmp.forEach(([key, value], index, arr) => {
+ if (index != 0) {
+ if (arr[index - 1][1]['col'] + 1 < value['col']) {
+ console.log('missing', key, value['col'], arr[index - 1][1]['col']);
+ arr.splice(index, 0, ['blank', { col: arr[index - 1][1]['col'] + 1 }]);
}
- });
+ }
+ });
+ console.log(tmp);
+
+ tmp.forEach(([key, value]) => {
+ if (isOntimeEvent(event)) {
+ if (key === 'blank') {
+ r.push({});
+ } else if (key === 'colour') {
+ r.push({
+ userEnteredValue: { stringValue: event.colour },
+ });
+ } else if (typeof event[key] === 'number') {
+ r.push({
+ userEnteredValue: { stringValue: millisToString(event[key], true) },
+ });
+ } else if (typeof event[key] === 'string') {
+ r.push({
+ userEnteredValue: { stringValue: event[key] },
+ });
+ } else if (typeof event[key] === 'boolean') {
+ r.push({
+ userEnteredValue: { stringValue: event[key] ? 'x' : '' },
+ });
+ } else {
+ r.push({});
+ }
+ }
+ });
return {
- start: {
- sheetId: 0, //FIXME: get from smalest medata col
- rowIndex: index,
- columnIndex: titelCol,
- },
- fields: 'userEnteredValue,userEnteredFormat.backgroundColor',
- rows: [
- {
- values: r,
+ updateCells: {
+ start: {
+ sheetId: 0, //FIXME:
+ rowIndex: index + Object.values(metadata)[0]['row'] + 1,
+ columnIndex: titleCol,
},
- ],
+ fields: 'userEnteredValue',
+ rows: [
+ {
+ values: r,
+ },
+ ],
+ },
};
}