Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 83f1e57b6e | |||
| b883379bca | |||
| f65b7de227 | |||
| 539c9160b0 | |||
| 1014c96543 | |||
| 585883d94d |
@@ -0,0 +1,13 @@
|
||||
let
|
||||
headers = [#"Content-Type" = "application/json"],
|
||||
postData = Json.FromValue([key = 235.7, value = 41.53]),
|
||||
response = Web.Contents(
|
||||
"http://localhost:8091/send-data",
|
||||
[
|
||||
Headers = headers,
|
||||
Content = postData
|
||||
]
|
||||
),
|
||||
jsonResponse = Json.Document(response)
|
||||
in
|
||||
jsonResponse
|
||||
@@ -52,6 +52,18 @@ shared Speckle.GetRawJSON = Value.ReplaceType(
|
||||
type function (url as Uri.Type) as table
|
||||
);
|
||||
|
||||
[DataSource.Kind = "Speckle"]
|
||||
shared Speckle.GetRawJSON2 = Value.ReplaceType(
|
||||
Speckle.LoadFunction("GetRawJSON2.pqm"),
|
||||
type function (url as Uri.Type) as table
|
||||
);
|
||||
|
||||
[DataSource.Kind = "Speckle"]
|
||||
shared Speckle.LocalSend = Value.ReplaceType(
|
||||
Speckle.LoadFunction("LocalSend.pqm"),
|
||||
type function (url as Uri.Type) as table
|
||||
);
|
||||
|
||||
[DataSource.Kind = "Speckle"]
|
||||
shared Speckle.GetStructuredData = Value.ReplaceType(
|
||||
Speckle.LoadFunction("GetStructuredData.pqm"),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// use this file to write queries to test your data connector
|
||||
let
|
||||
result = Speckle.GetByUrl(
|
||||
|
||||
result = Speckle.GetRawJSON(
|
||||
"https://app.speckle.systems/projects/e9141d302e/models/482749356d"
|
||||
)
|
||||
in
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
Message.Parameters = {fileName, e[Reason], e[Message]},
|
||||
Detail = [File = fileName, Error = e]
|
||||
],
|
||||
|
||||
|
||||
// get parsed URL components and model info
|
||||
parsedUrl = Parser(url),
|
||||
server = parsedUrl[baseUrl],
|
||||
@@ -35,6 +35,7 @@
|
||||
Headers = [
|
||||
#"Authorization" = if apiKey = null then "" else Text.Format("Bearer #{0}", {apiKey})
|
||||
],
|
||||
Query = [timestamp = Text.From(DateTime.LocalNow())],
|
||||
ManualStatusHandling = {400, 401, 403}
|
||||
]
|
||||
),
|
||||
@@ -77,6 +78,16 @@
|
||||
|
||||
// convert to table
|
||||
Chunks = Table.FromRecords(List.Transform(ChunkIndices, CreateChunk)),
|
||||
|
||||
Response = Web.Contents(
|
||||
"http://localhost:8091/send-data",
|
||||
[
|
||||
Headers = [#"Content-Type" = "application/json"],
|
||||
Query = [timestamp = Text.From(DateTime.LocalNow())],
|
||||
Content = Json.FromValue([key = 235.7, value = 41.53])
|
||||
]
|
||||
),
|
||||
JsonResponse = Json.Document(Response),
|
||||
|
||||
// set correct data types
|
||||
FinalTable = Table.TransformColumnTypes(
|
||||
@@ -84,6 +95,6 @@
|
||||
{
|
||||
{"viewer_data", type text}
|
||||
}
|
||||
)
|
||||
)
|
||||
in
|
||||
FinalTable
|
||||
@@ -0,0 +1,43 @@
|
||||
// function for getting JSON data with prefixed chunks
|
||||
(url as text) as table =>
|
||||
let
|
||||
// import the Parser and GetModel functions
|
||||
Parser = Extension.LoadFunction("Parser.pqm"),
|
||||
GetModel = Extension.LoadFunction("GetModel.pqm"),
|
||||
Extension.LoadFunction = (fileName as text) =>
|
||||
let
|
||||
binary = Extension.Contents(fileName),
|
||||
asText = Text.FromBinary(binary)
|
||||
in
|
||||
try
|
||||
Expression.Evaluate(asText, #shared)
|
||||
catch (e) =>
|
||||
error
|
||||
[
|
||||
Reason = "Extension.LoadFunction Failure",
|
||||
Message.Format = "Loading '#{0}' failed - '#{1}': '#{2}'",
|
||||
Message.Parameters = {fileName, e[Reason], e[Message]},
|
||||
Detail = [File = fileName, Error = e]
|
||||
],
|
||||
|
||||
// get parsed URL components and model info
|
||||
parsedUrl = Parser(url),
|
||||
server = parsedUrl[baseUrl],
|
||||
modelInfo = GetModel(url),
|
||||
|
||||
// get API key if available
|
||||
apiKey = try Extension.CurrentCredential()[Key] otherwise null,
|
||||
|
||||
// make the API request to objects endpoint
|
||||
Source = Web.Contents(
|
||||
Text.Combine({server, "objects", parsedUrl[projectId], modelInfo[rootObjectId]}, "/"),
|
||||
[
|
||||
Headers = [
|
||||
#"Authorization" = if apiKey = null then "" else Text.Format("Bearer #{0}", {apiKey})
|
||||
],
|
||||
ManualStatusHandling = {400, 401, 403}
|
||||
]
|
||||
),
|
||||
Result = Json.FromValue(Json.Document(Source))
|
||||
in
|
||||
Result
|
||||
@@ -0,0 +1,14 @@
|
||||
(url as text) as table =>
|
||||
let
|
||||
headers = [#"Content-Type" = "application/json"],
|
||||
postData = Json.FromValue([key = 235.7, value = 41.53]),
|
||||
response = Web.Contents(
|
||||
"http://localhost:8091/send-data",
|
||||
[
|
||||
Headers = headers,
|
||||
Content = postData
|
||||
]
|
||||
),
|
||||
jsonResponse = Json.Document(response)
|
||||
in
|
||||
jsonResponse
|
||||
@@ -1,10 +1,5 @@
|
||||
{
|
||||
"dataRoles": [
|
||||
{
|
||||
"displayName": "Viewer Data",
|
||||
"kind": "Grouping",
|
||||
"name": "viewerData"
|
||||
},
|
||||
{
|
||||
"displayName": "Object IDs",
|
||||
"kind": "Grouping",
|
||||
@@ -31,11 +26,6 @@
|
||||
}
|
||||
},
|
||||
"select": [
|
||||
{
|
||||
"bind": {
|
||||
"to": "viewerData"
|
||||
}
|
||||
},
|
||||
{
|
||||
"bind": {
|
||||
"to": "objectColorBy"
|
||||
@@ -60,7 +50,6 @@
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"viewerData": { "max": 1 },
|
||||
"objectIds": { "max": 1 }
|
||||
}
|
||||
]
|
||||
@@ -208,7 +197,7 @@
|
||||
{
|
||||
"essential": true,
|
||||
"name": "WebAccess",
|
||||
"parameters": ["https://analytics.speckle.systems", "*"]
|
||||
"parameters": ["https://analytics.speckle.systems", "http://localhost:8091", "*"]
|
||||
},
|
||||
{
|
||||
"essential": false,
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
<template>
|
||||
<ViewerView v-if="status === 'valid'" />
|
||||
<ViewerView v-if="visualStore.isViewerReadyToLoad" />
|
||||
<HomeView v-else />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import HomeView from './views/HomeView.vue'
|
||||
import ViewerView from './views/ViewerView.vue'
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { onMounted } from 'vue'
|
||||
import { useVisualStore } from './store/visualStore'
|
||||
|
||||
const visualStore = useVisualStore()
|
||||
let status = computed(() => {
|
||||
return visualStore.dataInputStatus
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
console.log('App mounted')
|
||||
|
||||
@@ -39,6 +39,7 @@ export interface IViewerEvents {
|
||||
unIsolateObjects: () => void
|
||||
zoomExtends: () => void
|
||||
loadObjects: (dataInput: SpeckleDataInput) => void
|
||||
loadObjectsFromJSON: (objects: object[]) => void
|
||||
}
|
||||
|
||||
export class ViewerHandler {
|
||||
@@ -58,6 +59,7 @@ export class ViewerHandler {
|
||||
this.emitter.on('zoomExtends', this.zoomExtends)
|
||||
this.emitter.on('zoomObjects', this.zoomObjects)
|
||||
this.emitter.on('loadObjects', this.loadObjects)
|
||||
this.emitter.on('loadObjectsFromJSON', this.loadObjectsFromJSON)
|
||||
}
|
||||
|
||||
async init(parent: HTMLElement) {
|
||||
@@ -143,6 +145,13 @@ export class ViewerHandler {
|
||||
}
|
||||
}
|
||||
|
||||
public loadObjectsFromJSON = async (objects: object[]) => {
|
||||
await this.viewer.unloadAll()
|
||||
const stringifiedObject = JSON.stringify(objects)
|
||||
const loader = new SpeckleOfflineLoader(this.viewer.getWorldTree(), stringifiedObject)
|
||||
await this.viewer.loadObject(loader, true)
|
||||
}
|
||||
|
||||
public loadObjects = async (dataInput: SpeckleDataInput) => {
|
||||
const stringifiedObject = JSON.stringify(dataInput.objects)
|
||||
await this.viewer.unloadAll()
|
||||
|
||||
@@ -6,7 +6,6 @@ import { ref, shallowRef } from 'vue'
|
||||
export type InputState = 'valid' | 'incomplete' | 'invalid'
|
||||
|
||||
export type FieldInputState = {
|
||||
viewerData: boolean
|
||||
objectIds: boolean
|
||||
colorBy: boolean
|
||||
tooltipData: boolean
|
||||
@@ -16,10 +15,10 @@ export const useVisualStore = defineStore('visualStore', () => {
|
||||
const host = shallowRef<powerbi.extensibility.visual.IVisualHost>()
|
||||
const objectsFromStore = ref<object[]>(undefined)
|
||||
const isViewerInitialized = ref<boolean>(false)
|
||||
const isViewerReadyToInitialize = ref<boolean>(false)
|
||||
const isViewerReadyToLoad = ref<boolean>(false)
|
||||
const isViewerObjectsLoaded = ref<boolean>(false)
|
||||
const viewerReloadNeeded = ref<boolean>(false)
|
||||
const fieldInputState = ref<FieldInputState>({
|
||||
viewerData: false,
|
||||
objectIds: false,
|
||||
colorBy: false,
|
||||
tooltipData: false
|
||||
@@ -74,11 +73,28 @@ export const useVisualStore = defineStore('visualStore', () => {
|
||||
id: string
|
||||
}
|
||||
|
||||
const loadObjectsFromFile = async (objects: object[]) => {
|
||||
await viewerEmit.value('loadObjectsFromJSON', objects)
|
||||
host.value.persistProperties({
|
||||
merge: [
|
||||
{
|
||||
objectName: 'storedData',
|
||||
properties: {
|
||||
fullData: JSON.stringify(objects)
|
||||
},
|
||||
selector: null
|
||||
}
|
||||
]
|
||||
})
|
||||
isViewerObjectsLoaded.value = true
|
||||
}
|
||||
|
||||
const loadObjectsFromStore = async () => {
|
||||
lastLoadedRootObjectId.value = (dataInput.value.objects[0] as SpeckleObject).id
|
||||
console.log(`📦 Loading viewer from cached data with ${lastLoadedRootObjectId.value} id.`)
|
||||
viewerReloadNeeded.value = false
|
||||
await viewerEmit.value('loadObjects', dataInput.value)
|
||||
isViewerObjectsLoaded.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,85 +107,46 @@ export const useVisualStore = defineStore('visualStore', () => {
|
||||
await loadObjectsFromStore()
|
||||
return
|
||||
}
|
||||
// here we have to check upcoming data is require viewer to force update! like a new model or some explicit force..
|
||||
if (viewerReloadNeeded.value || !lastLoadedRootObjectId.value) {
|
||||
lastLoadedRootObjectId.value = (dataInput.value.objects[0] as SpeckleObject).id
|
||||
console.log(
|
||||
`🔄 Forcing viewer re-render for new root object with ${lastLoadedRootObjectId.value} id.`
|
||||
)
|
||||
viewerReloadNeeded.value = false
|
||||
await viewerEmit.value('loadObjects', dataInput.value)
|
||||
|
||||
if (dataInput.value.selectedIds.length > 0) {
|
||||
viewerEmit.value('isolateObjects', dataInput.value.selectedIds)
|
||||
} else {
|
||||
if (dataInput.value.selectedIds.length > 0) {
|
||||
viewerEmit.value('isolateObjects', dataInput.value.selectedIds)
|
||||
} else {
|
||||
viewerEmit.value('isolateObjects', dataInput.value.objectIds)
|
||||
}
|
||||
viewerEmit.value('colorObjectsByGroup', dataInput.value.colorByIds)
|
||||
viewerEmit.value('isolateObjects', dataInput.value.objectIds)
|
||||
}
|
||||
viewerEmit.value('colorObjectsByGroup', dataInput.value.colorByIds)
|
||||
}
|
||||
|
||||
const setFieldInputState = (newFieldInputState: FieldInputState) => {
|
||||
if (!newFieldInputState.viewerData || !newFieldInputState.objectIds) {
|
||||
setInputStatus('incomplete')
|
||||
} else {
|
||||
setInputStatus('valid')
|
||||
}
|
||||
|
||||
// Check for the changes on fields that viewer care, if user changes important fields, we have to ask for viewer reload
|
||||
if (
|
||||
fieldInputState.value.viewerData &&
|
||||
fieldInputState.value.objectIds &&
|
||||
(!newFieldInputState.viewerData || !newFieldInputState.objectIds)
|
||||
) {
|
||||
viewerReloadNeeded.value = true
|
||||
}
|
||||
|
||||
// if (!isViewerInitialized.value) {
|
||||
// if (
|
||||
// fieldInputState.value.viewerData &&
|
||||
// fieldInputState.value.objectIds &&
|
||||
// !fieldInputState.value.tooltipData &&
|
||||
// !fieldInputState.value.colorBy
|
||||
// ) {
|
||||
// viewerReloadNeeded.value = true
|
||||
// }
|
||||
// }
|
||||
|
||||
fieldInputState.value = newFieldInputState
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets input status as flags `viewerReloadNeeded` if the new status is not 'valid'
|
||||
*/
|
||||
const setInputStatus = (newValue: InputState) => {
|
||||
console.log('❓ Data input statues changed to:', newValue)
|
||||
|
||||
dataInputStatus.value = newValue
|
||||
if (dataInputStatus.value !== 'valid') {
|
||||
viewerReloadNeeded.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const clearDataInput = () => {
|
||||
dataInput.value = null
|
||||
}
|
||||
|
||||
const setViewerReadyToLoad = () => {
|
||||
isViewerReadyToLoad.value = true
|
||||
}
|
||||
|
||||
return {
|
||||
host,
|
||||
objectsFromStore,
|
||||
isViewerInitialized,
|
||||
isViewerReadyToLoad,
|
||||
isViewerObjectsLoaded,
|
||||
viewerReloadNeeded,
|
||||
dataInput,
|
||||
dataInputStatus,
|
||||
viewerEmit,
|
||||
fieldInputState,
|
||||
loadObjectsFromFile,
|
||||
loadObjectsFromStore,
|
||||
setHost,
|
||||
setObjectsFromStore,
|
||||
setViewerEmitter,
|
||||
setDataInput,
|
||||
setFieldInputState,
|
||||
setInputStatus,
|
||||
clearDataInput
|
||||
clearDataInput,
|
||||
setViewerReadyToLoad
|
||||
}
|
||||
})
|
||||
|
||||
@@ -12,14 +12,12 @@ import { FieldInputState, useVisualStore } from '@src/store/visualStore'
|
||||
export function validateMatrixView(options: VisualUpdateOptions): FieldInputState {
|
||||
const matrixVew = options.dataViews[0].matrix
|
||||
|
||||
let hasViewerData = false,
|
||||
hasObjectIds = false,
|
||||
let hasObjectIds = false,
|
||||
hasColorFilter = false,
|
||||
hasTooltipData = false
|
||||
|
||||
matrixVew.rows.levels.forEach((level) => {
|
||||
level.sources.forEach((source) => {
|
||||
if (!hasViewerData) hasViewerData = source.roles['viewerData'] != undefined
|
||||
if (!hasObjectIds) hasObjectIds = source.roles['objectIds'] != undefined
|
||||
if (!hasColorFilter) hasColorFilter = source.roles['objectColorBy'] != undefined
|
||||
})
|
||||
@@ -32,7 +30,6 @@ export function validateMatrixView(options: VisualUpdateOptions): FieldInputStat
|
||||
})
|
||||
|
||||
return {
|
||||
viewerData: hasViewerData,
|
||||
objectIds: hasObjectIds,
|
||||
colorBy: hasColorFilter,
|
||||
tooltipData: hasTooltipData
|
||||
@@ -110,9 +107,7 @@ function processObjectIdLevel(
|
||||
host: powerbi.extensibility.visual.IVisualHost,
|
||||
matrixView: powerbi.DataViewMatrix
|
||||
) {
|
||||
return parentObjectIdChild.children?.map((objectIdChild) =>
|
||||
processObjectNode(objectIdChild, host, matrixView)
|
||||
)
|
||||
return processObjectNode(parentObjectIdChild, host, matrixView)
|
||||
}
|
||||
|
||||
export let previousPalette = null
|
||||
@@ -138,35 +133,56 @@ export function processMatrixView(
|
||||
|
||||
const objects: Record<string, string[]> = {}
|
||||
|
||||
// let objectsString = ''
|
||||
|
||||
// NOTE: matrix view gave us already filtered out rows from tooltip data if it is assigned
|
||||
matrixView.rows.root.children.forEach((obj) => {
|
||||
// otherwise there is no point to collect objects
|
||||
if (visualStore.viewerReloadNeeded) {
|
||||
const id = obj.children[0].value as unknown as string
|
||||
const value = (obj.value as unknown as string).slice(9)
|
||||
|
||||
const existingObjectId = Object.keys(objects).find((k) => id.includes(k))
|
||||
if (!existingObjectId) {
|
||||
objects[id] = [value]
|
||||
} else {
|
||||
objects[existingObjectId].push(value)
|
||||
}
|
||||
}
|
||||
// const id = obj.children[0].value as unknown as string
|
||||
// if (visualStore.viewerReloadNeeded) {
|
||||
// const viewerDataValue = obj.value as unknown as string
|
||||
// if (!viewerDataValue.startsWith('z_')) {
|
||||
// objectsString += viewerDataValue.slice(9)
|
||||
// }
|
||||
// }
|
||||
|
||||
// before row optimization
|
||||
// if (visualStore.viewerReloadNeeded) {
|
||||
// const id = obj.children[0].value as unknown as string
|
||||
// const value = (obj.value as unknown as string).slice(9)
|
||||
// const existingObjectId = Object.keys(objects).find((k) => id.includes(k))
|
||||
// if (!existingObjectId) {
|
||||
// objects[id] = [value]
|
||||
// } else {
|
||||
// objects[existingObjectId].push(value)
|
||||
// }
|
||||
// }
|
||||
|
||||
const processedObjectIdLevels = processObjectIdLevel(obj, host, matrixView)
|
||||
|
||||
processedObjectIdLevels.forEach((objRes) => {
|
||||
objectIds.push(objRes.id)
|
||||
onSelectionPair(objRes.id, objRes.selectionId)
|
||||
if (objRes.shouldSelect) {
|
||||
selectedIds.push(objRes.id)
|
||||
}
|
||||
objectTooltipData.set(objRes.id, {
|
||||
selectionId: objRes.selectionId,
|
||||
data: objRes.data
|
||||
})
|
||||
objectIds.push(processedObjectIdLevels.id)
|
||||
onSelectionPair(processedObjectIdLevels.id, processedObjectIdLevels.selectionId)
|
||||
if (processedObjectIdLevels.shouldSelect) {
|
||||
selectedIds.push(processedObjectIdLevels.id)
|
||||
}
|
||||
objectTooltipData.set(processedObjectIdLevels.id, {
|
||||
selectionId: processedObjectIdLevels.selectionId,
|
||||
data: processedObjectIdLevels.data
|
||||
})
|
||||
|
||||
// processedObjectIdLevels.forEach((objRes) => {
|
||||
// objectIds.push(objRes.id)
|
||||
// onSelectionPair(objRes.id, objRes.selectionId)
|
||||
// if (objRes.shouldSelect) {
|
||||
// selectedIds.push(objRes.id)
|
||||
// }
|
||||
// objectTooltipData.set(objRes.id, {
|
||||
// selectionId: objRes.selectionId,
|
||||
// data: objRes.data
|
||||
// })
|
||||
// })
|
||||
|
||||
if (hasColorFilter) {
|
||||
obj.children.forEach((child) => {
|
||||
const colorSelectionId = host
|
||||
@@ -191,33 +207,51 @@ export function processMatrixView(
|
||||
objectIds: []
|
||||
}
|
||||
|
||||
processObjectIdLevel(child, host, matrixView).forEach((objRes) => {
|
||||
objectIds.push(objRes.id)
|
||||
onSelectionPair(objRes.id, objRes.selectionId)
|
||||
if (objRes.shouldSelect) selectedIds.push(objRes.id)
|
||||
if (objRes.shouldColor) {
|
||||
colorGroup.objectIds.push(objRes.id)
|
||||
}
|
||||
objectTooltipData.set(objRes.id, {
|
||||
selectionId: objRes.selectionId,
|
||||
data: objRes.data
|
||||
})
|
||||
const processedObjectIdLevels = processObjectIdLevel(child, host, matrixView)
|
||||
|
||||
objectIds.push(processedObjectIdLevels.id)
|
||||
onSelectionPair(processedObjectIdLevels.id, processedObjectIdLevels.selectionId)
|
||||
if (processedObjectIdLevels.shouldSelect) selectedIds.push(processedObjectIdLevels.id)
|
||||
if (processedObjectIdLevels.shouldColor) {
|
||||
colorGroup.objectIds.push(processedObjectIdLevels.id)
|
||||
}
|
||||
objectTooltipData.set(processedObjectIdLevels.id, {
|
||||
selectionId: processedObjectIdLevels.selectionId,
|
||||
data: processedObjectIdLevels.data
|
||||
})
|
||||
|
||||
// processObjectIdLevel(child, host, matrixView).forEach((objRes) => {
|
||||
// objectIds.push(objRes.id)
|
||||
// onSelectionPair(objRes.id, objRes.selectionId)
|
||||
// if (objRes.shouldSelect) selectedIds.push(objRes.id)
|
||||
// if (objRes.shouldColor) {
|
||||
// colorGroup.objectIds.push(objRes.id)
|
||||
// }
|
||||
// objectTooltipData.set(objRes.id, {
|
||||
// selectionId: objRes.selectionId,
|
||||
// data: objRes.data
|
||||
// })
|
||||
// })
|
||||
if (colorGroup.objectIds.length > 0) colorByIds.push(colorGroup)
|
||||
})
|
||||
}
|
||||
})
|
||||
const jsonObjects: object[] = []
|
||||
try {
|
||||
// otherwise there is no point to join collected objects
|
||||
if (visualStore.viewerReloadNeeded) {
|
||||
for (const objs of Object.values(objects)) {
|
||||
jsonObjects.push(JSON.parse(objs.join('')))
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
const jsonObjects = []
|
||||
// if (visualStore.viewerReloadNeeded) {
|
||||
// jsonObjects = JSON.parse(objectsString)
|
||||
// }
|
||||
// // const jsonObjects: object[] = JSON.parse(objectsString)
|
||||
// try {
|
||||
// // otherwise there is no point to join collected objects
|
||||
// // if (visualStore.viewerReloadNeeded) {
|
||||
// // for (const objs of Object.values(objects)) {
|
||||
// // jsonObjects.push(JSON.parse(objs.join('')))
|
||||
// // }
|
||||
// // }
|
||||
// jsonObjects = JSON.parse(objectsString)
|
||||
// } catch (error) {
|
||||
// console.error(error)
|
||||
// }
|
||||
|
||||
previousPalette = host.colorPalette['colorPalette']
|
||||
|
||||
|
||||
@@ -10,10 +10,12 @@
|
||||
<div class="flex justify-center mt-2 gap-1">
|
||||
<button :class="buttonClass" @click="goToForum">Help</button>
|
||||
<button :class="buttonClass" @click="goToGuide">Getting started</button>
|
||||
<button :class="buttonClass" @click="triggerFileInput">Upload File</button>
|
||||
<!-- TODO: dependency issue need to be resolved to be able to use ui-components library-->
|
||||
<!-- <FormButton color="subtle" @click="goToForum">Help</FormButton>
|
||||
<FormButton color="subtle" @click="goToGuide">Getting started</FormButton> -->
|
||||
</div>
|
||||
<input ref="fileInput" type="file" style="display: none" @change="handleFileChange" />
|
||||
<!-- <CommonLoadingBar :loading="true"/> -->
|
||||
</div>
|
||||
</template>
|
||||
@@ -34,4 +36,44 @@ function goToForum() {
|
||||
function goToGuide() {
|
||||
visualStore.host.launchUrl('https://speckle.guide/user/powerbi')
|
||||
}
|
||||
|
||||
// Method to programmatically trigger the file input
|
||||
function triggerFileInput() {
|
||||
const fileInput = document.querySelector<HTMLInputElement>('input[type="file"]')
|
||||
fileInput?.click()
|
||||
}
|
||||
|
||||
// Method to handle file selection
|
||||
function handleFileChange(event: Event) {
|
||||
const input = event.target as HTMLInputElement
|
||||
const file = input.files?.[0]
|
||||
if (file) {
|
||||
console.log('Selected file:', file.name)
|
||||
console.log(file)
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
const fileContent = e.target?.result
|
||||
if (fileContent) {
|
||||
const visualStore = useVisualStore()
|
||||
visualStore.setViewerReadyToLoad()
|
||||
setTimeout(() => {
|
||||
const objects = JSON.parse(fileContent as string)
|
||||
console.log('File content:', objects)
|
||||
visualStore.loadObjectsFromFile(objects)
|
||||
}, 250)
|
||||
|
||||
// Process the file content (e.g., parse JSON, display text, etc.)
|
||||
}
|
||||
}
|
||||
// Handle errors if any occur
|
||||
reader.onerror = (e) => {
|
||||
console.error('Error reading file:', e)
|
||||
}
|
||||
|
||||
// Read the file as text (you can also use readAsDataURL, readAsBinaryString, etc.)
|
||||
reader.readAsText(file)
|
||||
|
||||
// Add logic to process the file as needed
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -2,14 +2,50 @@
|
||||
<div class="absolute top-0 left-0 z-10" @click="goToSpeckleWebsite">
|
||||
<img class="w-16 h-auto mt-1 mr-1 cursor-pointer" src="@assets/powered-by-speckle.png" />
|
||||
</div>
|
||||
<div
|
||||
v-if="isInteractive"
|
||||
class="absolute top-2 left-1/2 -translate-x-1/2 z-20 bg-white bg-opacity-70 text-black text-center text-sm px-4 py-2 rounded shadow"
|
||||
>
|
||||
<div v-if="bothFieldsMissing">
|
||||
<strong>Object IDs</strong>
|
||||
and
|
||||
<strong>Tooltip Data</strong>
|
||||
fields are needed for interactivity.
|
||||
</div>
|
||||
<div v-else-if="onlyObjectIdsMissing">
|
||||
<strong>Object IDs</strong>
|
||||
field is needed for interactivity.
|
||||
</div>
|
||||
<div v-else-if="onlyTooltipDataMissing">
|
||||
<strong>Tooltip Data</strong>
|
||||
field is needed for interactivity.
|
||||
</div>
|
||||
</div>
|
||||
<viewer-wrapper id="speckle-3d-view" class="h-full w-full"></viewer-wrapper>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ViewerWrapper from 'src/components/ViewerWrapper.vue'
|
||||
import { useVisualStore } from '../store/visualStore'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const visualStore = useVisualStore()
|
||||
|
||||
const onlyObjectIdsMissing = computed(
|
||||
() => !visualStore.fieldInputState.objectIds && visualStore.fieldInputState.tooltipData
|
||||
)
|
||||
|
||||
const onlyTooltipDataMissing = computed(
|
||||
() => visualStore.fieldInputState.objectIds && !visualStore.fieldInputState.tooltipData
|
||||
)
|
||||
|
||||
const bothFieldsMissing = computed(
|
||||
() => !visualStore.fieldInputState.objectIds && !visualStore.fieldInputState.tooltipData
|
||||
)
|
||||
|
||||
const isInteractive = computed(
|
||||
() => !visualStore.fieldInputState.objectIds || !visualStore.fieldInputState.tooltipData
|
||||
)
|
||||
|
||||
const goToSpeckleWebsite = () => visualStore.host.launchUrl('https://speckle.systems')
|
||||
</script>
|
||||
|
||||
@@ -79,11 +79,14 @@ export class Visual implements IVisual {
|
||||
console.log('Selector colors', this.formattingSettings.colorSelector)
|
||||
|
||||
try {
|
||||
const matrixVew = options.dataViews[0].matrix
|
||||
if (!matrixVew) throw new Error('Data does not contain a matrix data view') // TODO: Could be toast notificiation too!
|
||||
const matrixView = options.dataViews[0].matrix
|
||||
if (!matrixView) throw new Error('Data does not contain a matrix data view') // TODO: Could be toast notificiation too!
|
||||
console.log(matrixView)
|
||||
|
||||
// we first need to check which inputs user provided to decide our strategy
|
||||
const validationResult = validateMatrixView(options)
|
||||
console.log(validationResult)
|
||||
|
||||
visualStore.setFieldInputState(validationResult)
|
||||
|
||||
// read saved data from file if any
|
||||
@@ -105,7 +108,7 @@ export class Visual implements IVisual {
|
||||
case powerbi.VisualUpdateType.Data:
|
||||
try {
|
||||
const input = processMatrixView(
|
||||
matrixVew,
|
||||
matrixView,
|
||||
this.host,
|
||||
validationResult.colorBy,
|
||||
this.formattingSettings,
|
||||
@@ -128,8 +131,7 @@ export class Visual implements IVisual {
|
||||
console.warn(
|
||||
`Incomplete data input. "Viewer Data", "Object IDs" data inputs are mandatory. If your data connector does not output all these columns, please update it.`
|
||||
)
|
||||
|
||||
visualStore.setInputStatus('incomplete')
|
||||
visualStore.setFieldInputState({ objectIds: false, colorBy: false, tooltipData: false })
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -153,21 +155,7 @@ export class Visual implements IVisual {
|
||||
input.objects = visualStore.objectsFromStore
|
||||
input.isFromStore = true
|
||||
}
|
||||
|
||||
// if (!visualStore.isViewerInitialized && visualStore.viewerReloadNeeded) {
|
||||
// this.host.persistProperties({
|
||||
// merge: [
|
||||
// {
|
||||
// objectName: 'storedData',
|
||||
// properties: {
|
||||
// fullData: JSON.stringify(input.objects)
|
||||
// },
|
||||
// selector: null
|
||||
// }
|
||||
// ]
|
||||
// })
|
||||
// }
|
||||
// visualStore.setDataInput(input)
|
||||
visualStore.setViewerReadyToLoad()
|
||||
|
||||
if (visualStore.isViewerInitialized && !visualStore.viewerReloadNeeded) {
|
||||
visualStore.setDataInput(input)
|
||||
|
||||
Reference in New Issue
Block a user