diff --git a/src/main/menu.js b/src/main/menu.js index 8b28696f8..338154b6c 100644 --- a/src/main/menu.js +++ b/src/main/menu.js @@ -1,5 +1,5 @@ import { openFile, saveFileAs, saveFile, importDataPackage } from './file.js' -import { showUrlDialog } from './url.js' +import { showUrlDialogForPackage, showUrlDialogForResourceSchema } from './url.js' import { createWindowTab, focusMainWindow } from './windows.js' import { importExcel } from './excel.js' import { showKeyboardHelp } from './help.js' @@ -59,8 +59,7 @@ class AppMenu { label: 'Import Column Properties', accelerator: 'Shift+CmdOrCtrl+I', click () { - webContents().send('importColumnProperties') - webContents().send('toggleLockColumnProperties') + showUrlDialogForResourceSchema() } }, { type: 'separator' @@ -343,8 +342,7 @@ class AppMenu { label: 'zip from URL...', enabled: true, click () { - // downloadDataPackageJson() - showUrlDialog() + showUrlDialogForPackage() } }, { label: 'zip from file...', @@ -356,8 +354,7 @@ class AppMenu { label: 'json from URL...', enabled: true, click () { - // downloadDataPackageJson() - showUrlDialog() + showUrlDialogForPackage() } }] } diff --git a/src/main/url.js b/src/main/url.js index 44836cf4b..6a17d3245 100644 --- a/src/main/url.js +++ b/src/main/url.js @@ -5,6 +5,7 @@ import { ipcMain as ipc, dialog } from 'electron' import { focusOrNewSecondaryWindow, focusMainWindow, closeWindowSafely } from './windows' import { disableOpenFileItems, enableOpenFileItems } from './menuUtils.js' import { Package } from 'datapackage' +import { Schema } from 'tableschema' import tmp from 'tmp' import _ from 'lodash' import { dataResourceToFormat } from '../renderer/file-formats.js' @@ -12,9 +13,16 @@ import { dataResourceToFormat } from '../renderer/file-formats.js' // auto cleanup tmp.setGracefulCleanup() +export function showUrlDialogForResourceSchema() { + showUrlDialogForCallback(handleJsonForResourceSchema) +} + // TODO: handle errors by rejecting promises and throwing back up stack -export function showUrlDialog() { - // let labels = ['zip from URL....', 'zip from file...', 'json from URL...'] +export function showUrlDialogForPackage() { + showUrlDialogForCallback(handleZipOrJsonForPackage) +} + +export function showUrlDialogForCallback(callback, errorMessage='There was a problem loading package or resource(s)') { disableOpenFileItems() let browserWindow = focusOrNewSecondaryWindow('urldialog', { width: 300, height: 150, modal: true, alwaysOnTop: true }) browserWindow.on('closed', () => { @@ -27,9 +35,9 @@ export function showUrlDialog() { ipc.once('urlSubmitted', (e, urlText) => { closeWindowSafely(browserWindow) try { - handleZipOrJson(urlText) + callback(urlText) } catch (error) { - console.error(`There was a problem loading package or resource(s)`, error) + console.error(errorMessage, error) const mainWindow = focusMainWindow() mainWindow.webContents.send('closeLoadingScreen') } @@ -37,13 +45,21 @@ export function showUrlDialog() { }) } -function handleZipOrJson(urlText) { +function handleZipOrJsonForPackage(urlText) { if (_.endsWith(urlText, '.json')) { loadPackageFromJsonUrl(urlText) } else if (_.endsWith(urlText, '.zip')) { importDataPackageZipFromUrl(urlText) } else { - showUrlPathNotSupportedMessage() + showUrlPathNotSupportedMessage(urlText, '".json" or ".zip"') + } +} + +function handleJsonForResourceSchema(urlText) { + if (_.endsWith(urlText, '.json')) { + loadResourceSchemaFromJsonUrl(urlText) + } else { + showUrlPathNotSupportedMessage(urlText, '".json"') } } @@ -95,48 +111,69 @@ function handleDownloadedZip(zipPath, mainWindow) { async function loadPackageFromJsonUrl(urlText) { const mainWindow = focusMainWindow() - mainWindow.webContents.send('closeAndshowLoadingScreen', 'Loading package URL..') - const dataPackageJson = await loadPackageJson(urlText, mainWindow) - if (!dataPackageJson) { + const dataPackageJson = await loadGenericFrictionlessFromJsonUrl(urlText, loadPackageJson, 'Data Package') + try { + await loadResources(dataPackageJson, mainWindow) + } catch (error) { + console.error('There was a problem loading package from json', error) + } +} + +async function loadResourceSchemaFromJsonUrl(urlText) { + try { + const resourceSchema = await loadGenericFrictionlessFromJsonUrl(urlText, loadTableResourceSchemaJson, 'Table Resource Schema') + const mainWindow = focusMainWindow() + mainWindow.webContents.send('closeLoadingScreen') + if (resourceSchema && resourceSchema.descriptor) { + const mainWindow = focusMainWindow() + mainWindow.webContents.send('addSchemaToTabAndLock', resourceSchema.descriptor) + } + } catch (error) { + console.error('There was a problem loading resource schema from json', error) + } +} + +// atm, 'types' are only : Data Package or Resource Schema +async function loadGenericFrictionlessFromJsonUrl(urlText, callback, frictionlessType) { + const mainWindow = focusMainWindow() + mainWindow.webContents.send('closeAndshowLoadingScreen', `Loading ${frictionlessType} URL..`) + const frictionlessTypeJson = await callback(urlText, mainWindow) + if (!frictionlessTypeJson) { dialog.showMessageBox(mainWindow, { type: 'warning', - title: `Unable to load Data Package`, + title: `Unable to load ${frictionlessType}`, message: - `The data package, ${urlText}, could not be loaded. - If the data package is a URL, please check that the URL exists.` + `The ${frictionlessType}, ${urlText}, could not be loaded. + If the ${frictionlessType} is a URL, please check that the URL exists.` }) mainWindow.webContents.send('closeLoadingScreen') return } - if (!dataPackageJson.valid) { - showInvalidMessage(urlText, mainWindow) + if (!frictionlessTypeJson.valid) { + showInvalidMessage(urlText, mainWindow, frictionlessType) mainWindow.webContents.send('closeLoadingScreen') return } - try { - await loadResources(dataPackageJson, mainWindow) - } catch (error) { - console.error('There was a problem loading package from json', error) - } + return frictionlessTypeJson } -function showInvalidMessage(urlText, mainWindow) { +function showInvalidMessage(urlText, mainWindow, frictionlessType) { dialog.showMessageBox(mainWindow, { type: 'warning', - title: `Invalid Data Package`, + title: `Invalid ${frictionlessType}`, message: - `The data package, at ${urlText}, is not valid. Please refer to + `The ${frictionlessType}, at ${urlText}, is not valid. Please refer to https://frictionlessdata.io/specs/ for more information.` }) } -function showUrlPathNotSupportedMessage(urlText, mainWindow) { - dialog.showMessageBox(mainWindow, { +function showUrlPathNotSupportedMessage(urlText, supportedFileExtensions) { + dialog.showMessageBox(focusMainWindow(), { type: 'warning', title: `Unsupported URL Path extension`, message: - `Data Curator, does not support downloading ${urlText}, as the path does not end in ".zip" or ".json"` + `Data Curator, does not support downloading ${urlText}, as the path does not end in ${supportedFileExtensions}` }) } @@ -150,7 +187,8 @@ export async function loadPackageJson(json) { } } -async function loadResources(dataPackageJson, mainWindow) { +async function loadResources(dataPackageJson) { + const mainWindow = focusMainWindow() let packageProperties = _.assign({}, dataPackageJson.descriptor) _.unset(packageProperties, 'resources') mainWindow.webContents.send('resetPackagePropertiesToObject', packageProperties) @@ -166,6 +204,15 @@ async function loadResources(dataPackageJson, mainWindow) { } } +async function loadTableResourceSchemaJson(json) { + try { + const resourceSchema = await Schema.load(json) + return resourceSchema + } catch (error) { + console.error(`There was a problem loading the table resource schema: ${json}`, error) + } +} + export async function loadResourceDataFromPackageUrl(url, resourceName) { const dataPackage = await loadPackageJson(url) const rowOfObjects = [] diff --git a/src/renderer/components/Home.vue b/src/renderer/components/Home.vue index e10062b6f..254461baf 100644 --- a/src/renderer/components/Home.vue +++ b/src/renderer/components/Home.vue @@ -498,6 +498,9 @@ export default { ipc.on('addTabWithFormattedDataAndDescriptor', function(e, data, format, descriptor) { self.addTab(data, format, descriptor) }) + ipc.on('addSchemaToTabAndLock', function(e, schema) { + self.safeInitHotColumnPropertiesFromSchema(schema) + }) ipc.on('addTabWithFormattedDataFile', function(e, data, format, filename) { self.addTabWithFilename(data, format, filename) }) @@ -912,6 +915,33 @@ export default { tableHotIdProperties[hotId] = tableProperties this.resetTablePropertiesToObject(tableHotIdProperties) }, + + safeInitHotColumnPropertiesFromSchema: function(schema) { + try { + const schemaFieldsCount = _.get(schema, 'fields', 0).length + const columnCount = getColumnCount() + if (this.currentHotId && schemaFieldsCount === columnCount) { + this.initHotColumnPropertiesFromSchema(this.currentHotId, schema) + // hot rendering problem when tabs opened quickly - https://github.com/ODIQueensland/data-curator/issues/803- workaround as selecting table re-renders + getCurrentColumnIndexOrMin() + updateHotDimensions$.next() + LockProperties.toggleLockColumnProperties() + this.addImportDataPropertiesError('Import Column properties success', `${schemaFieldsCount} schema fields were imported.`) + } else { + const errorMessage = `Unable to import ${schemaFieldsCount} schema fields to a ${columnCount}-column table` + this.addImportDataPropertiesError('Validation Error', errorMessage) + } + } catch (error) { + console.error('ERROR: Import Column Properties: ', error) + this.addImportDataPropertiesError('Import Column properties error', 'The column properties could not be imported.') + } + }, + addImportDataPropertiesError: function(title, message) { + this.messagesTitle = title + this.messages = message + // as there's only 1 message, simpler to send as feedback (without rows/cols for error) + this.messagesType = 'feedback' + }, initHotColumnPropertiesFromSchema: function(hotId, schema) { // TODO : move this to similar logic in importDataPackage to tidy up if (!_.isEmpty(schema)) { diff --git a/src/renderer/index.js b/src/renderer/index.js index 638b764bb..6f5a8ee98 100644 --- a/src/renderer/index.js +++ b/src/renderer/index.js @@ -132,10 +132,6 @@ ipc.on('toggleCaseSensitiveHeader', function() { ipc.send('hasCaseSensitiveHeader', toggledCase) }) -ipc.on('importColumnProperties', function() { - console.log('arrived') -}) - export function closeSecondaryWindow(windowName) { ipc.sendSync('closeSecondaryWindow', windowName) }