diff --git a/src/Globals.ts b/src/Globals.ts index 167cb43f..7196633a 100644 --- a/src/Globals.ts +++ b/src/Globals.ts @@ -15,6 +15,12 @@ export enum Status { WARNING, } +export type YamlConfigs = { + dashboards: { [filename: string]: string } + topsheets: { [filename: string]: string } + vizes: { [filename: string]: string } +} + export const UI_FONT = "'Titillium Web', 'Roboto', 'Open Sans', Avenir, Arial, Helvetica, sans-serif" diff --git a/src/components/TopSheet/TopSheet.vue b/src/components/TopSheet/TopSheet.vue index f9299835..a3083997 100644 --- a/src/components/TopSheet/TopSheet.vue +++ b/src/components/TopSheet/TopSheet.vue @@ -15,7 +15,7 @@ diff --git a/src/js/HTTPFileSystem.ts b/src/js/HTTPFileSystem.ts index 534adf78..f643ff56 100644 --- a/src/js/HTTPFileSystem.ts +++ b/src/js/HTTPFileSystem.ts @@ -1,6 +1,14 @@ -import { DirectoryEntry, FileSystemConfig } from '@/Globals' +import micromatch from 'micromatch' +import { DirectoryEntry, FileSystemConfig, YamlConfigs } from '@/Globals' import globalStore from '@/store' +const YAML_FOLDER = 'simwrapper' + +// Cache directory listings for each slug & directory +const CACHE: { [slug: string]: { [dir: string]: DirectoryEntry } } = {} + +// --------------------------------------------------------------------------- + class SVNFileSystem { private baseUrl: string private urlId: string @@ -12,6 +20,12 @@ class SVNFileSystem { this.baseUrl = project.baseURL if (!project.baseURL.endsWith('/')) this.baseUrl += '/' + + if (!CACHE[this.urlId]) CACHE[this.urlId] = {} + } + + public clearCache() { + CACHE[this.urlId] = {} } public cleanURL(scaryPath: string) { @@ -38,7 +52,7 @@ class SVNFileSystem { } const myRequest = new Request(path, { headers }) - const response = await fetch(myRequest).then(response => { + const response = await fetch(myRequest).then((response) => { // Check HTTP Response code: 200 is OK, everything else is a problem if (response.status != 200) { console.log('Status:', response.status) @@ -82,10 +96,78 @@ class SVNFileSystem { // don't download any files! if (!stillScaryPath.endsWith('/')) stillScaryPath += '/' + // Use cached version if we have it + const cachedEntry = CACHE[this.urlId][stillScaryPath] + if (cachedEntry) return cachedEntry + + // Generate and cache the listing const response = await this._getFileResponse(stillScaryPath).then() const htmlListing = await response.text() + const dirEntry = this.buildListFromHtml(htmlListing) + CACHE[this.urlId][stillScaryPath] = dirEntry + + return dirEntry + } + + async findAllYamlConfigs(folder: string): Promise { + const yamls: YamlConfigs = { dashboards: {}, topsheets: {}, vizes: {} } + + const configFolders = [] + + // first find all simwrapper folders + let currentPath = '/' + const { dirs } = await this.getDirectory(currentPath) + if (dirs.indexOf(YAML_FOLDER) > -1) + configFolders.push(`${currentPath}/${YAML_FOLDER}`.replaceAll('//', '/')) + + const pathChunks = folder.split('/') + for (const chunk of pathChunks) { + currentPath = `${currentPath}${chunk}/` + const { dirs } = await this.getDirectory(currentPath) + if (dirs.indexOf(YAML_FOLDER) > -1) + configFolders.push(`${currentPath}/${YAML_FOLDER}`.replaceAll('//', '/')) + } + + // also add current working folder as final option, which supercedes all others + configFolders.push(folder) + + // console.log('configFolders', configFolders) + + // find all dashboards, topsheets, and viz-* yamls in each configuration folder. + // Overwrite keys as we go; identically-named configs from parent folders get superceded as we go. + const dashboard = 'dashboard*.y?(a)ml' + const topsheet = 'topsheet*.y?(a)ml' + const viz = 'viz*.y?(a)ml' + + for (const configFolder of configFolders) { + const { files } = await this.getDirectory(configFolder) + + micromatch + .match(files, dashboard) + .map((yaml) => (yamls.dashboards[yaml] = `${configFolder}/${yaml}`.replaceAll('//', '/'))) + + micromatch + .match(files, topsheet) + .map((yaml) => (yamls.topsheets[yaml] = `${configFolder}/${yaml}`.replaceAll('//', '/'))) + + micromatch + .match(files, viz) + .map((yaml) => (yamls.vizes[yaml] = `${configFolder}/${yaml}`.replaceAll('//', '/'))) + } - return this.buildListFromHtml(htmlListing) + // Sort them all by filename + yamls.dashboards = Object.fromEntries( + Object.entries(yamls.dashboards).sort((a, b) => (a[0] > b[0] ? 1 : -1)) + ) + yamls.topsheets = Object.fromEntries( + Object.entries(yamls.topsheets).sort((a, b) => (a[0] > b[0] ? 1 : -1)) + ) + yamls.vizes = Object.fromEntries( + Object.entries(yamls.vizes).sort((a, b) => (a[0] > b[0] ? 1 : -1)) + ) + + console.log(yamls) + return yamls } private buildListFromHtml(data: string): DirectoryEntry { diff --git a/src/js/RunFinder.ts b/src/js/RunFinder.ts index 0590bd88..478adc92 100644 --- a/src/js/RunFinder.ts +++ b/src/js/RunFinder.ts @@ -5,7 +5,7 @@ import { FileSystemConfig } from '@/Globals' let foundFolders = { number: 0, folders: {} as any } -const findRuns = function() { +const findRuns = function () { // check date and time const storedUpdate = localStorage.getItem('RunFinder.lastUpdate') const lastUpdate = storedUpdate ? parseInt(storedUpdate) : 0 @@ -38,37 +38,34 @@ const populate = () => { foundFolders = { number: 0, folders: {} } store.commit('updateRunFolders', foundFolders) - store.state.svnProjects.forEach(root => { + store.state.svnProjects.forEach((root) => { if (!root.hidden && root.slug !== 'gallery') drillIntoRootProject(root) }) } -const drillIntoRootProject = function(root: FileSystemConfig) { +const drillIntoRootProject = function (root: FileSystemConfig) { console.log('Drilling into:', root.name) + const fileSystem = new HTTPFileSystem(root) + fileSystem.clearCache() foundFolders.folders[root.name] = [] fetchFolders(root, fileSystem, '') } -const fetchFolders = async function( +const fetchFolders = async function ( root: FileSystemConfig, fileSystem: HTTPFileSystem, folder: string ) { try { // skip some big folders we know we don't care about - if (root.skipList && root.skipList.filter(f => folder.endsWith(f)).length) return + if (root.skipList && root.skipList.filter((f) => folder.endsWith(f)).length) return // skip .dot and __MACOSX folders if (folder.endsWith('__MACOSX')) return - if ( - folder - .split('/') - .pop() - ?.startsWith('.') - ) { + if (folder.split('/').pop()?.startsWith('.')) { return } diff --git a/src/plugins/agent-animation/AgentAnimation.vue b/src/plugins/agent-animation/AgentAnimation.vue index 36f548af..a7c7fb7a 100644 --- a/src/plugins/agent-animation/AgentAnimation.vue +++ b/src/plugins/agent-animation/AgentAnimation.vue @@ -212,9 +212,13 @@ class MyComponent extends Vue { private async getVizDetails() { // first get config try { - const text = await this.myState.fileApi.getFileText( - this.myState.subfolder + '/' + this.myState.yamlConfig - ) + // might be a project config: + const filename = + this.myState.yamlConfig.indexOf('/') > -1 + ? this.myState.yamlConfig + : this.myState.subfolder + '/' + this.myState.yamlConfig + + const text = await this.myState.fileApi.getFileText(filename) this.vizDetails = yaml.parse(text) } catch (e) { console.log('failed') @@ -367,7 +371,7 @@ globalStore.commit('registerPlugin', { kebabName: 'agent-animation', prettyName: 'Agent Animation', description: 'birds', - filePatterns: ['viz-agent-anim*.y?(a)ml'], + filePatterns: ['**/viz-agent-anim*.y?(a)ml'], component: MyComponent, } as VisualizationPlugin) diff --git a/src/plugins/aggregate-od/AggregateOd.vue b/src/plugins/aggregate-od/AggregateOd.vue index d0f2fe56..e70fce25 100644 --- a/src/plugins/aggregate-od/AggregateOd.vue +++ b/src/plugins/aggregate-od/AggregateOd.vue @@ -334,9 +334,13 @@ class MyComponent extends Vue { if (!this.myState.fileApi) return // first get config try { - const text = await this.myState.fileApi.getFileText( - this.myState.subfolder + '/' + this.myState.yamlConfig - ) + // might be a project config: + const filename = + this.myState.yamlConfig.indexOf('/') > -1 + ? this.myState.yamlConfig + : this.myState.subfolder + '/' + this.myState.yamlConfig + + const text = await this.myState.fileApi.getFileText(filename) this.vizDetails = yaml.parse(text) } catch (err) { const e = err as any @@ -1242,7 +1246,7 @@ globalStore.commit('registerPlugin', { kebabName: 'aggregate-od', prettyName: 'Origin/Destination Patterns', description: 'Depicts aggregate O/D flows between areas.', - filePatterns: ['viz-od*.y?(a)ml'], + filePatterns: ['**/viz-od*.y?(a)ml'], component: MyComponent, } as VisualizationPlugin) diff --git a/src/plugins/carrier-viewer/Plugin.vue b/src/plugins/carrier-viewer/Plugin.vue index d6e95ea1..1b94539e 100644 --- a/src/plugins/carrier-viewer/Plugin.vue +++ b/src/plugins/carrier-viewer/Plugin.vue @@ -515,9 +515,13 @@ class CarrierPlugin extends Vue { if (!this.myState.fileApi) return // first get config try { - const text = await this.myState.fileApi.getFileText( - this.myState.subfolder + '/' + this.myState.yamlConfig - ) + // might be a project config: + const filename = + this.myState.yamlConfig.indexOf('/') > -1 + ? this.myState.yamlConfig + : this.myState.subfolder + '/' + this.myState.yamlConfig + + const text = await this.myState.fileApi.getFileText(filename) this.vizDetails = YAML.parse(text) if (!this.vizDetails.center) this.vizDetails.center = [13.4, 52.5] } catch (e) { @@ -771,7 +775,7 @@ globalStore.commit('registerPlugin', { kebabName: 'carrier-viewer', prettyName: 'Carrier Viewer', description: 'For freight etc!', - filePatterns: ['viz-carrier*.y?(a)ml'], + filePatterns: ['**/viz-carrier*.y?(a)ml'], component: CarrierPlugin, } as VisualizationPlugin) diff --git a/src/plugins/link-vols/LinkVolumes.vue b/src/plugins/link-vols/LinkVolumes.vue index a73ca9f3..b5a029f1 100644 --- a/src/plugins/link-vols/LinkVolumes.vue +++ b/src/plugins/link-vols/LinkVolumes.vue @@ -327,11 +327,16 @@ class MyComponent extends Vue { private async getVizDetails() { try { - const text = await this.myState.fileApi.getFileText( - this.myState.subfolder + '/' + this.myState.yamlConfig - ) + // might be a project config: + const filename = + this.myState.yamlConfig.indexOf('/') > -1 + ? this.myState.yamlConfig + : this.myState.subfolder + '/' + this.myState.yamlConfig + + const text = await this.myState.fileApi.getFileText(filename) this.vizDetails = yaml.parse(text) - } catch (e) { + } catch (err) { + const e = err as any // maybe it failed because password? if (this.myState.fileSystem && this.myState.fileSystem.needPassword && e.status === 401) { globalStore.commit('requestLogin', this.myState.fileSystem.slug) @@ -966,7 +971,7 @@ globalStore.commit('registerPlugin', { kebabName: 'link-volumes', prettyName: 'Volumes', description: 'Aggregate volumes on network links', - filePatterns: ['viz-link*.y?(a)ml'], + filePatterns: ['**/viz-link*.y?(a)ml'], component: MyComponent, } as VisualizationPlugin) diff --git a/src/plugins/links-gl/LinkVolumes.vue b/src/plugins/links-gl/LinkVolumes.vue index 1c3a491e..5d19f8d3 100644 --- a/src/plugins/links-gl/LinkVolumes.vue +++ b/src/plugins/links-gl/LinkVolumes.vue @@ -240,9 +240,13 @@ class MyPlugin extends Vue { // first get config try { - const text = await this.myState.fileApi.getFileText( - this.myState.subfolder + '/' + this.myState.yamlConfig - ) + // might be a project config: + const filename = + this.myState.yamlConfig.indexOf('/') > -1 + ? this.myState.yamlConfig + : this.myState.subfolder + '/' + this.myState.yamlConfig + + const text = await this.myState.fileApi.getFileText(filename) this.vizDetails = YAML.parse(text) } catch (err) { console.error('failed') @@ -593,7 +597,7 @@ globalStore.commit('registerPlugin', { kebabName: 'links-gl', prettyName: 'Links', description: 'Network link attributes', - filePatterns: ['viz-gl-link*.y?(a)ml'], + filePatterns: ['**/viz-link*.y?(a)ml'], component: MyPlugin, } as VisualizationPlugin) diff --git a/src/plugins/sankey/SankeyDiagram.vue b/src/plugins/sankey/SankeyDiagram.vue index 966e21ca..31ebc29c 100644 --- a/src/plugins/sankey/SankeyDiagram.vue +++ b/src/plugins/sankey/SankeyDiagram.vue @@ -114,7 +114,11 @@ class MyComponent extends Vue { try { this.loadingText = 'Loading files...' - const text = await this.fileApi.getFileText(this.subfolder + '/' + this.yamlConfig) + // might be a project config: + const filename = + this.yamlConfig.indexOf('/') > -1 ? this.yamlConfig : this.subfolder + '/' + this.yamlConfig + + const text = await this.fileApi.getFileText(filename) this.vizDetails = yaml.parse(text) this.$emit('title', this.vizDetails.title) @@ -221,7 +225,7 @@ globalStore.commit('registerPlugin', { kebabName: 'sankey-diagram', prettyName: 'Flow Diagram', description: 'Depicts flows between choices', - filePatterns: ['sankey*.y?(a)ml'], + filePatterns: ['**/sankey*.y?(a)ml', '**/viz-sankey*.y?(a)ml'], component: MyComponent, } as VisualizationPlugin) diff --git a/src/plugins/shape-file/ShapeFile.vue b/src/plugins/shape-file/ShapeFile.vue index 306b71b5..b642336d 100644 --- a/src/plugins/shape-file/ShapeFile.vue +++ b/src/plugins/shape-file/ShapeFile.vue @@ -289,11 +289,6 @@ class MyPlugin extends Vue { this.$store.commit('setFullScreen', false) } - // private handleClickColumnSelector() { - // console.log('click!') - // this.isButtonActiveColumn = !this.isButtonActiveColumn - // } - private async loadShapefile() { if (!this.myState.fileApi) return diff --git a/src/plugins/transit-demand/TransitDemand.vue b/src/plugins/transit-demand/TransitDemand.vue index de82b2ec..cc5f1b07 100644 --- a/src/plugins/transit-demand/TransitDemand.vue +++ b/src/plugins/transit-demand/TransitDemand.vue @@ -385,9 +385,13 @@ class MyComponent extends Vue { private async loadYamlConfig() { // first get config try { - const text = await this.myState.fileApi.getFileText( - this.myState.subfolder + '/' + this.myState.yamlConfig - ) + // might be a project config: + const filename = + this.myState.yamlConfig.indexOf('/') > -1 + ? this.myState.yamlConfig + : this.myState.subfolder + '/' + this.myState.yamlConfig + + const text = await this.myState.fileApi.getFileText(filename) this.vizDetails = yaml.parse(text) } catch (e) { // maybe it failed because password? diff --git a/src/plugins/vega-lite/VegaLite.vue b/src/plugins/vega-lite/VegaLite.vue index 00345e0a..132ff005 100644 --- a/src/plugins/vega-lite/VegaLite.vue +++ b/src/plugins/vega-lite/VegaLite.vue @@ -154,9 +154,13 @@ class VegaComponent extends Vue { try { this.loadingText = 'Loading chart...' - json = await this.myState.fileApi.getFileJson( - this.myState.subfolder + '/' + this.myState.yamlConfig - ) + // might be a project config: + const filename = + this.myState.yamlConfig.indexOf('/') > -1 + ? this.myState.yamlConfig + : this.myState.subfolder + '/' + this.myState.yamlConfig + + json = await this.myState.fileApi.getFileJson(filename) this.description = json.description ? json.description : '' this.title = json.title @@ -226,7 +230,7 @@ globalStore.commit('registerPlugin', { kebabName: 'vega-lite', prettyName: 'Chart', description: 'Interactive chart visualization', - filePatterns: ['*.vega.json'], + filePatterns: ['**/*.vega.json'], component: VegaComponent, } as VisualizationPlugin) diff --git a/src/plugins/vehicle-animation/VehicleAnimation.vue b/src/plugins/vehicle-animation/VehicleAnimation.vue index cff3e786..da34d1a8 100644 --- a/src/plugins/vehicle-animation/VehicleAnimation.vue +++ b/src/plugins/vehicle-animation/VehicleAnimation.vue @@ -276,9 +276,13 @@ class VehicleAnimation extends Vue { // first get config try { - const text = await this.myState.fileApi.getFileText( - this.myState.subfolder + '/' + this.myState.yamlConfig - ) + // might be a project config: + const filename = + this.myState.yamlConfig.indexOf('/') > -1 + ? this.myState.yamlConfig + : this.myState.subfolder + '/' + this.myState.yamlConfig + + const text = await this.myState.fileApi.getFileText(filename) this.vizDetails = YAML.parse(text) if (!this.vizDetails.center) this.vizDetails.center = [14, 52.1] } catch (err) { @@ -709,7 +713,7 @@ globalStore.commit('registerPlugin', { kebabName: 'vehicle-animation', prettyName: 'Trip Viewer', description: 'Deck.gl based trip viewer', - filePatterns: ['viz-vehicles*.y?(a)ml'], + filePatterns: ['**/viz-vehicles*.y?(a)ml'], component: VehicleAnimation, } as VisualizationPlugin) diff --git a/src/plugins/xy-hexagons/XyHexagons.vue b/src/plugins/xy-hexagons/XyHexagons.vue index 687eb3a8..51c6490b 100644 --- a/src/plugins/xy-hexagons/XyHexagons.vue +++ b/src/plugins/xy-hexagons/XyHexagons.vue @@ -375,39 +375,49 @@ class XyHexagons extends Vue { } private async getVizDetails() { - if (!this.myState.fileApi) return - const hasYaml = new RegExp('.*(yml|yaml)$').test(this.myState.yamlConfig) - if (!hasYaml) { - let projection = 'EPSG:31468' // 'EPSG:25832', // 'EPSG:31468', // TODO: fix - if (!this.myState.thumbnail) { - projection = prompt('Enter projection: e.g. "EPSG:31468"') || 'EPSG:31468' - if (!!parseInt(projection, 10)) projection = 'EPSG:' + projection - } - // output_trips: - this.vizDetails = { - title: 'Output Trips', - description: this.myState.yamlConfig, - file: this.myState.yamlConfig, - projection, - aggregations: { - 'Trip Summary': [ - { title: 'Origins', x: 'start_x', y: 'start_y' }, - { title: 'Destinations', x: 'end_x', y: 'end_y' }, - ], - }, - } - this.$emit('title', this.vizDetails.title) - // this.solveProjection() - return + if (hasYaml) { + await this.loadYamlConfig() + } else { + this.loadOutputTripsConfig() } + } - // first get config + private loadOutputTripsConfig() { + let projection = 'EPSG:31468' // 'EPSG:25832', // 'EPSG:31468', // TODO: fix + if (!this.myState.thumbnail) { + projection = prompt('Enter projection: e.g. "EPSG:31468"') || 'EPSG:31468' + if (!!parseInt(projection, 10)) projection = 'EPSG:' + projection + } + // output_trips: + this.vizDetails = { + title: 'Output Trips', + description: this.myState.yamlConfig, + file: this.myState.yamlConfig, + projection, + aggregations: { + 'Trip Summary': [ + { title: 'Origins', x: 'start_x', y: 'start_y' }, + { title: 'Destinations', x: 'end_x', y: 'end_y' }, + ], + }, + } + this.$emit('title', this.vizDetails.title) + // this.solveProjection() + return + } + + private async loadYamlConfig() { + if (!this.myState.fileApi) return try { - const text = await this.myState.fileApi.getFileText( - this.myState.subfolder + '/' + this.myState.yamlConfig - ) + // might be a project config: + const filename = + this.myState.yamlConfig.indexOf('/') > -1 + ? this.myState.yamlConfig + : this.myState.subfolder + '/' + this.myState.yamlConfig + + const text = await this.myState.fileApi.getFileText(filename) this.vizDetails = YAML.parse(text) } catch (err) { const e = err as any @@ -619,7 +629,6 @@ class XyHexagons extends Vue { try { let filename = `${this.myState.subfolder}/${this.vizDetails.file}` - await this.parseCSVFile(filename) } catch (e) { console.error(e) @@ -637,7 +646,7 @@ globalStore.commit('registerPlugin', { kebabName: 'xy-hexagons', prettyName: 'XY Aggregator', description: 'Collects XY data into geographic hexagons', - filePatterns: ['viz-xy*.y?(a)ml', '*output_trips.csv?(.gz)'], + filePatterns: ['**/viz-xy*.y?(a)ml', '*output_trips.csv?(.gz)'], component: XyHexagons, } as VisualizationPlugin) diff --git a/src/styles.scss b/src/styles.scss index b677fbeb..a00cf1c7 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -39,7 +39,7 @@ $dashboardWidth: 70rem; --bgTabBanner: linear-gradient(150deg, #345be6, #29d299); --text: #363636; --textBold: #1d3355; - --textFancy: #1d3355; + --textFancy: #564c9d; --textPale: #777777; --textVeryPale: #bbbbcc; --link: #196096; diff --git a/src/views/DashBoard.vue b/src/views/DashBoard.vue index c6b00f34..c4233de8 100644 --- a/src/views/DashBoard.vue +++ b/src/views/DashBoard.vue @@ -41,8 +41,10 @@ :datamanager="datamanager" :style="{opacity: opacity[card.id]}" :cardId="card.id" + :allConfigFiles="allConfigFiles" @isLoaded="handleCardIsLoaded(card)" @dimension-resizer="setDimensionResizer" + @titles="setCardTitles(card, $event)" ) @@ -52,7 +54,7 @@ import { Vue, Component, Watch, Prop } from 'vue-property-decorator' import YAML from 'yaml' import HTTPFileSystem from '@/js/HTTPFileSystem' -import { FileSystemConfig } from '@/Globals' +import { FileSystemConfig, YamlConfigs } from '@/Globals' import TopSheet from '@/components/TopSheet/TopSheet.vue' import charts, { plotlyCharts } from '@/charts/allCharts' import DashboardDataManager from '@/js/DashboardDataManager' @@ -78,6 +80,7 @@ export default class VueComponent extends Vue { @Prop({ required: false }) private config!: any @Prop({ required: false }) private zoomed!: boolean @Prop({ required: true }) private datamanager!: DashboardDataManager + @Prop({ required: true }) private allConfigFiles!: YamlConfigs private fileSystemConfig!: FileSystemConfig private fileApi!: HTTPFileSystem @@ -119,6 +122,16 @@ export default class VueComponent extends Vue { window.removeEventListener('resize', this.resizeAllCards) } + /** + * This only gets triggered when a topsheet has some titles. + * Remove the dashboard titles and use the ones from the topsheet. + */ + private setCardTitles(card: any, event: any) { + console.log(card, event) + card.title = event + card.description = '' + } + private resizeAllCards() { for (const row of this.rows) { for (const card of row) { @@ -347,6 +360,7 @@ export default class VueComponent extends Vue { .dash-card { transition: opacity 0.5s; + overflow-x: hidden; } @media only screen and (max-width: 50em) { diff --git a/src/views/FolderBrowser.vue b/src/views/FolderBrowser.vue index f1dbf1ed..aabb7cd5 100644 --- a/src/views/FolderBrowser.vue +++ b/src/views/FolderBrowser.vue @@ -116,7 +116,7 @@ import globalStore from '@/store' import plugins from '@/plugins/pluginRegistry' import TabbedDashboardView from '@/views/TabbedDashboardView.vue' import HTTPFileSystem from '@/js/HTTPFileSystem' -import { BreadCrumb, VisualizationPlugin, FileSystemConfig } from '@/Globals' +import { BreadCrumb, FileSystemConfig, YamlConfigs } from '@/Globals' import TopsheetsFinder from '@/components/TopsheetsFinder/TopsheetsFinder.vue' const allComponents = Object.assign({ TopsheetsFinder }, plugins) @@ -131,6 +131,9 @@ export default class VueComponent extends Vue { @Prop({ required: true }) private root!: string + @Prop({ required: true }) + private allConfigFiles!: YamlConfigs + private globalState = globalStore.state private mdRenderer = new markdown() @@ -189,7 +192,6 @@ export default class VueComponent extends Vue { // save them! globalStore.commit('setBreadCrumbs', crumbs) - return crumbs } @@ -228,15 +230,10 @@ export default class VueComponent extends Vue { @Watch('xsubfolder') private updateRoute() { - // console.log({ xsubfolder: this.xsubfolder, xproject: this.root }) - - // why would this be here, don't remember - // if (!this.$route.name) return - const svnProject = this.getFileSystem(this.root) this.myState.svnProject = svnProject - this.myState.subfolder = this.xsubfolder || '' // this.$route.params.pathMatch ? this.$route.params.pathMatch : '' + this.myState.subfolder = this.xsubfolder || '' if (!this.myState.svnProject) return this.myState.svnRoot = new HTTPFileSystem(this.myState.svnProject) @@ -292,7 +289,6 @@ export default class VueComponent extends Vue { for (const viz of this.globalState.visualizationTypes.values()) { // filter based on file matching const matches = micromatch(this.myState.files, viz.filePatterns) - for (const file of matches) { // add thumbnail for each matching file this.myState.vizes.push({ component: viz.kebabName, config: file, title: '◆' }) @@ -366,7 +362,6 @@ export default class VueComponent extends Vue { this.myState.isLoading = true this.myState.errorStatus = '' this.myState.files = [] - // this.myState.folders = [] try { const folderContents = await this.myState.svnRoot.getDirectory(this.myState.subfolder) @@ -375,9 +370,19 @@ export default class VueComponent extends Vue { const folders = folderContents.dirs.filter((f) => !f.startsWith('.')).sort() const files = folderContents.files.filter((f) => !f.startsWith('.')).sort() + // Also show any project-level viz thumbnails from other folders + // (but, ensure that files in this folder supercede any project viz files + // with the same name) + const mergedFilesAndVizes = Object.assign({}, this.allConfigFiles.vizes) + for (const file of files) { + mergedFilesAndVizes[file] = file + } + + const allVizes = Object.values(mergedFilesAndVizes) + this.myState.errorStatus = '' this.myState.folders = folders - this.myState.files = files + this.myState.files = allVizes } catch (err) { // Bad things happened! Tell user const e = err as any diff --git a/src/views/TabbedDashboardView.vue b/src/views/TabbedDashboardView.vue index 6a552e39..f13e1896 100644 --- a/src/views/TabbedDashboardView.vue +++ b/src/views/TabbedDashboardView.vue @@ -22,12 +22,14 @@ :config="dashboards[activeTab]" :datamanager="dashboardDataManager" :zoomed="isZoomed" + :allConfigFiles="allConfigFiles" @zoom="handleZoom" ) folder-browser(v-if="activeTab && activeTab === 'FILE__BROWSER'" :root="root" :xsubfolder="xsubfolder" + :allConfigFiles="allConfigFiles" @navigate="onNavigate" ) @@ -35,10 +37,9 @@