From 997c724ac93cfac506a23d282d8f46fefbadac18 Mon Sep 17 00:00:00 2001 From: Brandon Davis Date: Thu, 15 Oct 2020 09:59:37 -0400 Subject: [PATCH 1/4] add electron --- client/.eslintignore | 2 + client/.gitignore | 1 + client/README.md | 29 + client/package.json | 19 + client/platform/desktop/App.vue | 29 + client/platform/desktop/api/main.ts | 125 ++ client/platform/desktop/api/mediaserver.ts | 77 + client/platform/desktop/background.ts | 118 ++ client/platform/desktop/components/Recent.vue | 85 ++ .../desktop/components/ViewerLoader.vue | 49 + client/platform/desktop/main.ts | 27 + client/platform/desktop/plugins/vuetify.js | 32 + client/platform/desktop/router.ts | 27 + client/platform/desktop/store/dataset.ts | 64 + client/platform/web-girder/store/Dataset.ts | 7 +- client/rollup.config.js | 77 +- client/src/index.ts | 3 +- client/tsconfig.json | 6 +- client/tsconfig.rollup.json | 2 + client/viame-web-common/apispec.ts | 8 + .../components/NavigationTitle.vue | 4 +- client/viame-web-common/components/Viewer.vue | 56 +- client/vue.config.js | 15 +- client/yarn.lock | 1299 ++++++++++++++++- 24 files changed, 2036 insertions(+), 125 deletions(-) create mode 100644 client/.eslintignore create mode 100644 client/platform/desktop/App.vue create mode 100644 client/platform/desktop/api/main.ts create mode 100644 client/platform/desktop/api/mediaserver.ts create mode 100644 client/platform/desktop/background.ts create mode 100644 client/platform/desktop/components/Recent.vue create mode 100644 client/platform/desktop/components/ViewerLoader.vue create mode 100644 client/platform/desktop/main.ts create mode 100644 client/platform/desktop/plugins/vuetify.js create mode 100644 client/platform/desktop/router.ts create mode 100644 client/platform/desktop/store/dataset.ts diff --git a/client/.eslintignore b/client/.eslintignore new file mode 100644 index 000000000..346ddc29b --- /dev/null +++ b/client/.eslintignore @@ -0,0 +1,2 @@ +lib/**/* +dist/**/* diff --git a/client/.gitignore b/client/.gitignore index f709a154b..3168d3886 100644 --- a/client/.gitignore +++ b/client/.gitignore @@ -1,6 +1,7 @@ .DS_Store node_modules /dist +/dist_electron /lib # local env files diff --git a/client/README.md b/client/README.md index 03141d7ce..dc55c47c0 100644 --- a/client/README.md +++ b/client/README.md @@ -88,3 +88,32 @@ This style guarantees matching types are passed through provide and inject witho ## Tests Note that `tsconfig.spec.json` is an exact copy of `tsconfig.json` but the `target` and `module` are changed such that babel is not required for jest to execute tests. + +## Typescript vue-media-annotator library + +Parts of the annotator in `src/` can be included from an external annotator library. Requires `@vue/composition-api`. + +``` bash +npm install vue-media-annotator +``` + +Now include the parts you want. + +``` js +import { + providers, + use, + Track, + components, +} from 'vue-media-annotator/lib'; + +const { + VideoAnnotator, LayerManager, Controls, TimelineWrapper, Timeline, LineChart, +} = components; +``` + +> **Note** that you must abandon `vuetify-loader` in order to use this lib. It relies on vuetify's components to be registered with the global context, which doesn't happen with an a-la-carte installation. + +> **Note** you can clone this repo, use `yarn link`, and `yarn link vue-media-annotator` in your own project to modify the source library as you go. You'll have to `yarn build:lib` after changes, and you must `mv node_modeles/ node_modules.old/` in order to prevent your consumer app from using this project's `node_modules` libs instead of yours. This could cause problems like multiple instances of vue or composition api. + +The above problems are known and we are working to solve them. diff --git a/client/package.json b/client/package.json index c5213c91c..bdab44e40 100644 --- a/client/package.json +++ b/client/package.json @@ -1,9 +1,15 @@ { "name": "vue-media-annotator", +<<<<<<< HEAD "version": "1.0.3", +======= + "version": "1.0.1", +>>>>>>> 42cd42e... add electron "scripts": { "serve": "vue-cli-service serve platform/web-girder/main.ts", + "serve:desktop": "vue-cli-service electron:serve", "build:web": "vue-cli-service build platform/web-girder/main.ts", + "build:desktop": "vue-cli-service electron:build", "build:lib": "rollup -c", "lint": "vue-cli-service lint src/ viame-web-common/ platform/", "lint:templates": "vtc --workspace . --srcDir src/", @@ -24,6 +30,7 @@ "@sentry/browser": "^5.24.2", "@sentry/integrations": "^5.24.2", "@types/mousetrap": "^1.6.3", + "@types/source-map": "0.5.2", "@vue/composition-api": "^1.0.0-beta.14", "axios": "^0.19.2", "core-js": "^3.6.4", @@ -40,10 +47,15 @@ "devDependencies": { "@types/axios": "^0.14.0", "@types/d3": "^5.7.2", + "@types/electron-devtools-installer": "^2.2.0", "@types/geojson": "^7946.0.7", "@types/jest": "^25.2.3", "@types/lodash": "^4.14.151", + "@types/mime-types": "^2.1.0", "@types/node": "^14.0.5", + "@types/pump": "^1.1.0", + "@types/range-parser": "^1.2.3", + "@types/request": "^2.48.5", "@typescript-eslint/eslint-plugin": "^2.33.0", "@typescript-eslint/parser": "^2.33.0", "@vue/cli-plugin-babel": "~4.3.1", @@ -57,6 +69,8 @@ "babel-eslint": "^10.1.0", "babel-jest": "^26.0.1", "babel-register": "^6.26.0", + "electron": "^10.1.3", + "electron-devtools-installer": "^3.1.1", "eslint": "^6.7.2", "eslint-import-resolver-typescript": "^2.2.0", "eslint-plugin-import": "^2.20.2", @@ -64,6 +78,10 @@ "git-describe": "^4.0.4", "jest": "^26.0.1", "jest-transform-stub": "^2.0.0", + "mime-types": "^2.1.27", + "pump": "^3.0.0", + "range-parser": "^1.2.1", + "request": "^2.88.2", "rollup": "^2.29.0", "rollup-plugin-cleaner": "^1.0.0", "rollup-plugin-scss": "^2.6.1", @@ -73,6 +91,7 @@ "sass-loader": "^8.0.2", "ts-jest": "^26.0.0", "typescript": "~3.8.3", + "vue-cli-plugin-electron-builder": "^2.0.0-rc.4", "vue-cli-plugin-vuetify": "^2.0.5", "vue-jest": "^3.0.5", "vue-template-compiler": "^2.6.12", diff --git a/client/platform/desktop/App.vue b/client/platform/desktop/App.vue new file mode 100644 index 000000000..e9b6483b9 --- /dev/null +++ b/client/platform/desktop/App.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/client/platform/desktop/api/main.ts b/client/platform/desktop/api/main.ts new file mode 100644 index 000000000..926ea1213 --- /dev/null +++ b/client/platform/desktop/api/main.ts @@ -0,0 +1,125 @@ +import { TrackData } from 'vue-media-annotator/track'; +import fs from 'fs'; +// eslint-disable-next-line import/no-extraneous-dependencies +import mime from 'mime-types'; +import path from 'path'; +import { + Attribute, DatasetMeta, DatasetMetaMutable, FrameImage, Pipelines, SaveDetectionsArgs, +} from 'viame-web-common/apispec'; +// eslint-disable-next-line +import { ipcRenderer, remote } from 'electron'; +import { AddressInfo } from 'net'; + +const websafeVideoTypes = [ + 'video/mp4', + 'video/webm', +]; + +const websafeImageTypes = [ + 'image/apng', + 'image/bmp', + 'image/gif', + 'image/jpeg', + 'image/png', + 'image/svg+xml', + 'image/webp', +]; + +export interface DesktopDataset { + root: string; + videoPath?: string; + meta: DatasetMeta; +} + +const mediaServerInfo: AddressInfo = ipcRenderer.sendSync('info'); + +async function openFromDisk() { + const results = await remote.dialog.showOpenDialog({ + properties: ['openFile', 'openDirectory'], + }); + return results; +} + +async function getAttributes() { + return Promise.resolve([] as Attribute[]); +} +async function getPipelineList() { + return Promise.resolve({} as Pipelines); +} +// eslint-disable-next-line +async function runPipeline(itemId: string, pipeline: string) { + return Promise.resolve(); +} +// eslint-disable-next-line +async function loadDetections(datasetId: string) { + return Promise.resolve({} as { [key: string]: TrackData }); +} +// eslint-disable-next-line +async function saveDetections(datasetId: string, args: SaveDetectionsArgs) { + return Promise.resolve(); +} + +async function loadMetadata(datasetId: string): Promise { + let datasetType = undefined as 'video' | 'image-sequence' | undefined; + let videoUrl = ''; + let videoPath = ''; + const imageData = [] as FrameImage[]; + + function processFile(abspath: string) { + const basename = path.basename(abspath); + const abspathuri = `http://localhost:${mediaServerInfo.port}${abspath}`; + const mimetype = mime.lookup(abspath); + if (mimetype && websafeVideoTypes.includes(mimetype)) { + datasetType = 'video'; + videoPath = abspath; + videoUrl = abspathuri; + } else if (mimetype && websafeImageTypes.includes(mimetype)) { + datasetType = 'image-sequence'; + imageData.push({ + url: abspathuri, + filename: basename, + }); + } + } + + const info = fs.statSync(datasetId); + + if (info.isDirectory()) { + const contents = fs.readdirSync(datasetId); + for (let i = 0; i < contents.length; i += 1) { + processFile(path.join(datasetId, contents[i])); + } + } else { + processFile(datasetId); + } + + if (datasetType === undefined) { + throw new Error(`Cannot open dataset ${datasetId}: No images or video found`); + } + + return Promise.resolve({ + root: datasetId, + videoPath, + meta: { + type: datasetType, + fps: 10, + imageData: datasetType === 'image-sequence' ? imageData : [], + videoUrl: datasetType === 'video' ? videoUrl : undefined, + }, + }); +} +// eslint-disable-next-line +async function saveMetadata(datasetId: string, metadata: DatasetMetaMutable) { + return Promise.resolve(); +} + +export { + getAttributes, + getPipelineList, + runPipeline, + loadDetections, + openFromDisk, + saveDetections, + loadMetadata, + saveMetadata, +}; diff --git a/client/platform/desktop/api/mediaserver.ts b/client/platform/desktop/api/mediaserver.ts new file mode 100644 index 000000000..2921c4b18 --- /dev/null +++ b/client/platform/desktop/api/mediaserver.ts @@ -0,0 +1,77 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import mime from 'mime-types'; +// eslint-disable-next-line import/no-extraneous-dependencies +import pump from 'pump'; +// eslint-disable-next-line import/no-extraneous-dependencies +import rangeParser from 'range-parser'; +import http from 'http'; +import fs from 'fs'; + +function fail(res: http.ServerResponse, code: number, message = '') { + // eslint-disable-next-line no-param-reassign + res.statusCode = code; + res.write(message); + res.end(); +} + +/* If error are uncaught, they get thrown to a GTK Alert :( */ +const withErrorHandler = (handler: http.RequestListener): http.RequestListener => (req, res) => { + try { + return handler(req, res); + } catch (err) { + console.error('BAD'); + return fail(res, 500, 'Server error'); + } +}; + +const server = http.createServer(withErrorHandler((req, res) => { + let path = req.url; + if (path === undefined) { + return fail(res, 404, `Invalid path: ${path}`); + } + + path = decodeURI(path); + let filestat; + try { + filestat = fs.statSync(path); + if (!filestat.isFile()) { + return fail(res, 404, `Invalid file for path: ${path}`); + } + } catch (err) { + return fail(res, 404); + } + + const type = mime.lookup(path); + if (type === false) { + return fail(res, 404, `Mime lookup failed for path: ${path}`); + } + + const ranges = req.headers.range && rangeParser(filestat.size, req.headers.range); + if (ranges === -1 || ranges === -2) { + return fail(res, 400, `Range parse failed: ${req.headers.range}`); + } + + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Accept-Ranges', 'bytes'); + res.setHeader('Content-Type', type); + + if (ranges === undefined || ranges === '') { + res.setHeader('Content-Length', filestat.size); + if (req.method === 'HEAD') { + return res.end(); + } + pump(fs.createReadStream(path), res); + return res.end(); + } + + const range = ranges[0]; + // eslint-disable-next-line no-param-reassign + res.statusCode = 206; + res.setHeader('Content-Length', range.end - range.start + 1); + res.setHeader('Content-Range', `bytes ${range.start}-${range.end}/${filestat.size}`); + if (req.method === 'HEAD') return res.end(); + pump(fs.createReadStream(path, range), res); + return res.end(); +})); + +export default server; diff --git a/client/platform/desktop/background.ts b/client/platform/desktop/background.ts new file mode 100644 index 000000000..41adab140 --- /dev/null +++ b/client/platform/desktop/background.ts @@ -0,0 +1,118 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { + app, protocol, BrowserWindow, ipcMain, +} from 'electron'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'; +// eslint-disable-next-line import/no-extraneous-dependencies +import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'; + +import server from './api/mediaserver'; + +app.commandLine.appendSwitch('no-sandbox'); +// To support a broader number of systems. +app.commandLine.appendSwitch('ignore-gpu-blacklist'); + +const isDevelopment = process.env.NODE_ENV !== 'production'; + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +let win: BrowserWindow | null; + +// Scheme must be registered before the app is ready +protocol.registerSchemesAsPrivileged([ + { scheme: 'app', privileges: { secure: true, standard: true } }, +]); + +function createWindow() { + // Create the browser window. + win = new BrowserWindow({ + width: 800, + height: 600, + webPreferences: { + // Use pluginOptions.nodeIntegration, leave this alone + // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html + // #node-integration for more info + nodeIntegration: (process.env + .ELECTRON_NODE_INTEGRATION as unknown) as boolean, + plugins: true, + }, + }); + + if (process.env.IS_ELECTRON) { + // Load the url of the dev server if in development mode + win.loadURL(process.env.WEBPACK_DEV_SERVER_URL as string); + // win.loadURL(`file:/${__dirname}/index.html`); + + if (!process.env.IS_TEST) win.webContents.openDevTools(); + } else { + createProtocol('app'); + // Load the index.html when not in development + win.loadURL(`file://${__dirname}/index.html`); + } + + win.on('closed', () => { + win = null; + }); + server.listen(0, () => { + let address = server.address(); + let port = 0; + if (typeof address === 'object' && address !== null) { + port = address.port || 0; + address = address.address || ''; + } + console.error(`Server listening on ${address}:${port}`); + }); +} + +ipcMain.on('info', (event) => { + // eslint-disable-next-line no-param-reassign + event.returnValue = server.address(); +}); + +// Quit when all windows are closed. +app.on('window-all-closed', () => { + // On macOS it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform !== 'darwin') { + app.quit(); + } +}); + +app.on('activate', () => { + // On macOS it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (win === null) { + createWindow(); + } +}); + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.on('ready', async () => { + if (isDevelopment && !process.env.IS_TEST) { + // Install Vue Devtools + try { + await installExtension(VUEJS_DEVTOOLS); + } catch (e) { + console.error('Vue Devtools failed to install:', e.toString()); + } + } + createWindow(); +}); + +// Exit cleanly on request from parent process in development mode. +if (isDevelopment) { + if (process.platform === 'win32') { + process.on('message', (data) => { + if (data === 'graceful-exit') { + app.quit(); + } + }); + } else { + process.on('SIGTERM', () => { + app.quit(); + }); + } +} diff --git a/client/platform/desktop/components/Recent.vue b/client/platform/desktop/components/Recent.vue new file mode 100644 index 000000000..fa91cdaff --- /dev/null +++ b/client/platform/desktop/components/Recent.vue @@ -0,0 +1,85 @@ + + + diff --git a/client/platform/desktop/components/ViewerLoader.vue b/client/platform/desktop/components/ViewerLoader.vue new file mode 100644 index 000000000..7c862ca9b --- /dev/null +++ b/client/platform/desktop/components/ViewerLoader.vue @@ -0,0 +1,49 @@ + + + diff --git a/client/platform/desktop/main.ts b/client/platform/desktop/main.ts new file mode 100644 index 000000000..4d4a94b2e --- /dev/null +++ b/client/platform/desktop/main.ts @@ -0,0 +1,27 @@ +import Vue from 'vue'; +import VueCompositionApi from '@vue/composition-api'; + +import snackbarService from 'viame-web-common/vue-utilities/snackbar-service'; +import promptService from 'viame-web-common/vue-utilities/prompt-service'; +import vMousetrap from 'viame-web-common/vue-utilities/v-mousetrap'; + +import vuetify from './plugins/vuetify'; +import router from './router'; +import App from './App.vue'; + +Vue.config.productionTip = false; +Vue.use(VueCompositionApi); +Vue.use(snackbarService(vuetify)); +Vue.use(promptService(vuetify)); +Vue.use(vMousetrap); + + +new Vue({ + vuetify, + router, + provide: { vuetify }, + render: (h) => h(App), +}) + .$mount('#app') + .$snackbarAttach() + .$promptAttach(); diff --git a/client/platform/desktop/plugins/vuetify.js b/client/platform/desktop/plugins/vuetify.js new file mode 100644 index 000000000..96ca687ad --- /dev/null +++ b/client/platform/desktop/plugins/vuetify.js @@ -0,0 +1,32 @@ +import Vue from 'vue'; +import Vuetify from 'vuetify/lib'; +import colors from 'vuetify/lib/util/colors'; +import girderVuetifyConfig from '@girder/components/src/utils/vuetifyConfig'; +import { merge } from 'lodash'; + +import '@mdi/font/css/materialdesignicons.css'; + +Vue.use(Vuetify); + +const appVuetifyConfig = merge(girderVuetifyConfig, { + theme: { + dark: true, + options: { + customProperties: true, + }, + themes: { + light: { + accent: colors.blue.lighten1, + secondary: colors.grey.darken1, + primary: colors.blue.darken2, + neutral: colors.grey.lighten5, + }, + dark: { + accent: colors.blue.lighten1, + accentBackground: '#2c7596', + }, + }, + }, +}); + +export default new Vuetify(appVuetifyConfig); diff --git a/client/platform/desktop/router.ts b/client/platform/desktop/router.ts new file mode 100644 index 000000000..3395036a3 --- /dev/null +++ b/client/platform/desktop/router.ts @@ -0,0 +1,27 @@ +import Vue from 'vue'; +import Router from 'vue-router'; + +import Recent from './components/Recent.vue'; +import ViewerLoader from './components/ViewerLoader.vue'; + +Vue.use(Router); + +export default new Router({ + routes: [ + { + path: '/recent', + name: 'recent', + component: Recent, + }, + { + path: '/viewer/:path', + name: 'viewer', + component: ViewerLoader, + props: true, + }, + { + path: '*', + redirect: '/recent', + }, + ], +}); diff --git a/client/platform/desktop/store/dataset.ts b/client/platform/desktop/store/dataset.ts new file mode 100644 index 000000000..79e1d4da9 --- /dev/null +++ b/client/platform/desktop/store/dataset.ts @@ -0,0 +1,64 @@ +import Vue from 'vue'; +import { uniq } from 'lodash'; +import Install, { ref, computed } from '@vue/composition-api'; +import { Api } from 'viame-web-common/apispec'; +import * as api from '../api/main'; + +// TODO remove this: this won't be necessary in Vue 3 +Vue.use(Install); + +const dsmap = ref({} as Record); + +function getDataset(id: string) { + return computed(() => dsmap.value[id]); +} + +function getRecents(): string[] { + const arrStr = window.localStorage.getItem('recent'); + let returnVal = [] as string[]; + try { + if (arrStr) { + const maybeArr = JSON.parse(arrStr); + if (maybeArr.length) { + returnVal = maybeArr as string[]; + } + } + } catch (err) { + return returnVal; + } + return returnVal; +} + +function setRecents(id: string) { + let recents = getRecents(); + recents.push(id); + recents = uniq(recents); + window.localStorage.setItem('recent', JSON.stringify(recents)); +} + +function setDataset(id: string, ds: api.DesktopDataset) { + Vue.set(dsmap.value, id, ds); + setRecents(id); +} + +/** + * Returns wrapped API to update store + */ +function observe(): Api { + async function loadMetadata(datasetId: string) { + const ds = await api.loadMetadata(datasetId); + setDataset(datasetId, ds); + return ds.meta; + } + return { + ...api, + loadMetadata, + }; +} + +export { + getDataset, + setDataset, + getRecents, + observe, +}; diff --git a/client/platform/web-girder/store/Dataset.ts b/client/platform/web-girder/store/Dataset.ts index 0f8b0f2a1..0d286b307 100644 --- a/client/platform/web-girder/store/Dataset.ts +++ b/client/platform/web-girder/store/Dataset.ts @@ -2,7 +2,7 @@ import { GirderModel } from '@girder/components/src'; import { Module } from 'vuex'; import { ImageSequenceType, VideoType } from 'viame-web-common/constants'; -import { DatasetMeta } from 'viame-web-common/apispec'; +import { DatasetMeta, FrameImage } from 'viame-web-common/apispec'; import { getFolder, getItemDownloadUri } from '../api/girder.service'; import { getValidWebImages } from '../api/viame.service'; @@ -12,11 +12,6 @@ export interface VIAMEDataset extends GirderModel { meta: DatasetMeta; } -interface FrameImage { - url: string; - filename: string; -} - const defaultFrameRate = 30; interface DatasetState { diff --git a/client/rollup.config.js b/client/rollup.config.js index 76488ae0e..847f3d31a 100644 --- a/client/rollup.config.js +++ b/client/rollup.config.js @@ -17,56 +17,29 @@ const external = [ 'vue', ]; -export default [ - { - external, - input: './src/index.ts', - output: { - dir: './lib/', - format: 'esm', - name: 'vue-media-annotator', - }, - plugins: [ - // clear out dist before build - cleaner({ targets: ['lib'] }), - typescript({ - clean, - tsconfig, - useTsconfigDeclarationDir: true, - }), - ], +export default { + external, + input: './src/index.ts', + output: { + dir: './lib/', + format: 'esm', + name: 'vue-media-annotator', }, - { - external, - input: './src/components/index.js', - plugins: [ - typescript({ - // don't use build cache - clean, - tsconfig, - tsconfigOverride: { - compilerOptions: { - declaration: false, - }, - include: [ - 'src/**/*.ts', - 'src/**/*.tsx', - /** - * TODO: DANGER: this line should not be needed - * nothing from viame-web-common/ ends up in the built artifact. - * nothing from viame-web-common/ is imported from src/components/index.js - * ...but the build fails without it. - */ - 'viame-web-common/**/*.ts', - ], - }, - }), - vue(), - scss(), - ], - output: { - file: './lib/components.js', - format: 'esm', - }, - }, -]; + plugins: [ + // clear out dist before build + cleaner({ targets: ['lib'] }), + typescript({ + clean, + tsconfig, + useTsconfigDeclarationDir: true, + tsconfigOverride: { + include: [ + 'src/**/*.ts', + 'src/**/*.tsx', + ], + }, + }), + vue(), + scss(), + ], +}; diff --git a/client/src/index.ts b/client/src/index.ts index 71180eac7..25084a9c8 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -5,13 +5,14 @@ import * as utils from './utils'; import * as use from './use'; import * as layers from './layers'; +import * as components from './components'; export { listUtils, providers, Track, utils, - + components, use, layers, }; diff --git a/client/tsconfig.json b/client/tsconfig.json index 09f5ea46a..9056e76eb 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -10,7 +10,7 @@ "outDir": "./dist", "esModuleInterop": true, "allowSyntheticDefaultImports": true, - "sourceMap": true, + "sourceMap": false, "baseUrl": ".", "types": [ "vuetify", @@ -40,8 +40,8 @@ "include": [ "viame-web-common/**/*.ts", "viame-web-common/**/*.vue", - "platform/*/**.vue", - "platform/*/**.ts", + "platform/**/*.vue", + "platform/**/*.ts", "src/**/*.ts", "src/**/*.vue", "tests/**/*.ts", diff --git a/client/tsconfig.rollup.json b/client/tsconfig.rollup.json index ee7e6afef..1ff638101 100644 --- a/client/tsconfig.rollup.json +++ b/client/tsconfig.rollup.json @@ -1,6 +1,8 @@ { "extends": "./tsconfig.json", "compilerOptions": { + "target": "es2015", + "module": "es2015", "outDir": "./lib/", "declaration": true, "declarationDir": "./lib/types", diff --git a/client/viame-web-common/apispec.ts b/client/viame-web-common/apispec.ts index 462b07b93..860332c06 100644 --- a/client/viame-web-common/apispec.ts +++ b/client/viame-web-common/apispec.ts @@ -37,6 +37,11 @@ interface SaveDetectionsArgs { upsert: Map; } +interface FrameImage { + url: string; + filename: string; +} + interface DatasetMetaMutable { customTypeStyling?: Record; confidenceFilters?: Record; @@ -45,6 +50,8 @@ interface DatasetMetaMutable { interface DatasetMeta extends DatasetMetaMutable { type: Readonly<'video' | 'image-sequence'>; fps: Readonly; + imageData: FrameImage[]; + videoUrl: string | undefined; } interface Api { @@ -81,6 +88,7 @@ export { Attribute, DatasetMeta, DatasetMetaMutable, + FrameImage, Pipe, Pipelines, SaveDetectionsArgs, diff --git a/client/viame-web-common/components/NavigationTitle.vue b/client/viame-web-common/components/NavigationTitle.vue index 5c2c0aa45..7c4b1893e 100644 --- a/client/viame-web-common/components/NavigationTitle.vue +++ b/client/viame-web-common/components/NavigationTitle.vue @@ -16,7 +16,9 @@ export default { bottom > {{ version }} diff --git a/client/viame-web-common/components/Viewer.vue b/client/viame-web-common/components/Viewer.vue index 595a636c3..15d7f13f5 100644 --- a/client/viame-web-common/components/Viewer.vue +++ b/client/viame-web-common/components/Viewer.vue @@ -1,6 +1,6 @@