Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b9ff8ee5f7 | |||
| 30c2a2002c | |||
| 4502a48098 | |||
| f2ab186bd8 | |||
| fbe9672f81 | |||
| 2691902533 | |||
| 95294c6e6f | |||
| e70aa8ae4b | |||
| e65bf83995 | |||
| 74ade84015 | |||
| abc4bf11fe | |||
| 0bd1218d49 | |||
| e277ce686e | |||
| f3f5eddb7b | |||
| 28ce6b6a76 | |||
| fe483b7b2b | |||
| 72b4b9b589 | |||
| 57c0f198bd | |||
| 439bf56f47 | |||
| 3ecae7f493 | |||
| 843174f5b6 | |||
| 83cfa39be0 | |||
| e4401da357 | |||
| 222a6f8987 | |||
| c44b54616e | |||
| 7b22e929e0 | |||
| e1e6d4e640 | |||
| 7bdb80f801 | |||
| da774b631a | |||
| ca0765c862 | |||
| 87b64b7a11 | |||
| 41c4e642fe | |||
| 76febcfce6 | |||
| 8fbb5a3c9d | |||
| eecde37f8b | |||
| a21a960516 | |||
| 9e76f62ad0 | |||
| ab266fa410 |
@@ -0,0 +1,3 @@
|
||||
for /f "tokens=1 delims=-" %%i in ("%CIRCLE_TAG%") do set "TAG=%%i.%WORKFLOW_NUM%"
|
||||
for /f "tokens=1 delims=/" %%j in ("%CIRCLE_TAG%") do set "SEMVER=%%j"
|
||||
tools\InnoSetup\ISCC.exe tools\powerbi.iss /Sbyparam=$p /DINFO_VERSION=%TAG% /DVERSION=%SEMVER% %*
|
||||
+166
-11
@@ -1,16 +1,171 @@
|
||||
# Use the latest 2.1 version of CircleCI pipeline process engine.
|
||||
# See: https://circleci.com/docs/2.0/configuration-reference
|
||||
version: 2.1
|
||||
|
||||
# Define the jobs we want to run for this project
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: cimg/base:2023.03
|
||||
steps:
|
||||
- run: echo "so long and thanks for all the fish"
|
||||
orbs:
|
||||
win: circleci/windows@5.0
|
||||
|
||||
commands:
|
||||
setup_digicert:
|
||||
description: Set up Digicert Keylocker certificate for code-signing
|
||||
steps:
|
||||
- run:
|
||||
name: "Digicert Signing Manager Setup"
|
||||
command: |
|
||||
cd C:\
|
||||
curl.exe -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:$env:SM_API_KEY" -o smtools-windows-x64.msi
|
||||
msiexec.exe /i smtools-windows-x64.msi /quiet /qn | Wait-Process
|
||||
- run:
|
||||
name: Setup Digicert ONE Client Cert
|
||||
command: |
|
||||
cd C:\
|
||||
echo $env:SM_CLIENT_CERT_FILE_B64 > certificate.txt
|
||||
certutil -decode certificate.txt certificate.p12
|
||||
- run:
|
||||
name: Sync Certs
|
||||
command: |
|
||||
& $env:SSM\smksp_cert_sync.exe
|
||||
|
||||
jobs:
|
||||
build-visual:
|
||||
docker:
|
||||
- image: cimg/node:18.20.3
|
||||
steps:
|
||||
- checkout
|
||||
- run: node --version
|
||||
- run:
|
||||
name: "npm install"
|
||||
command: "npm i"
|
||||
working_directory: src/powerbi-visual
|
||||
- run:
|
||||
name: Set version
|
||||
command: |
|
||||
npm version ${CIRCLE_TAG:-2.0.0} --allow-same-version
|
||||
working_directory: src/powerbi-visual
|
||||
- run:
|
||||
name: "npm run build"
|
||||
command: "npm run build"
|
||||
working_directory: src/powerbi-visual
|
||||
- store_artifacts:
|
||||
path: dist/*.pbiviz
|
||||
- persist_to_workspace:
|
||||
root: ./
|
||||
paths:
|
||||
- src/powerbi-visual/dist/*.pbiviz
|
||||
|
||||
build-connector:
|
||||
executor:
|
||||
name: win/default
|
||||
shell: powershell.exe
|
||||
environment:
|
||||
SSM: 'C:\Program Files\DigiCert\DigiCert One Signing Manager Tools'
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: "Set connector internal version"
|
||||
command: |
|
||||
$env:VERSION = if([string]::IsNullOrEmpty($env:CIRCLE_TAG)) { "2.0.0" } else { $env:CIRCLE_TAG }
|
||||
(Get-Content ./Speckle.pq).replace('[Version = "2.0.0"]', '[Version = "'+$($env:VERSION)+'"]') | Set-Content ./Speckle.pq
|
||||
working_directory: src/powerbi-data-connector
|
||||
- run:
|
||||
name: "Build Data Connector"
|
||||
command: "msbuild Speckle.proj /restore /consoleloggerparameters:NoSummary /property:GenerateFullPaths=true"
|
||||
working_directory: src/powerbi-data-connector
|
||||
- run:
|
||||
name: Create PQX file
|
||||
command: .\tools\MakePQX\MakePQX.exe pack -mz src/powerbi-data-connector/bin/Speckle.mez -t src/powerbi-data-connector/bin/Speckle.pqx
|
||||
- persist_to_workspace:
|
||||
root: ./
|
||||
paths:
|
||||
- src/powerbi-data-connector/bin/Speckle.pqx
|
||||
|
||||
build-installer:
|
||||
executor:
|
||||
name: win/default
|
||||
shell: powershell.exe
|
||||
environment:
|
||||
SSM: 'C:\Program Files\DigiCert\DigiCert One Signing Manager Tools'
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: ./
|
||||
- unless: # Build installers unsigned on non-tagged builds
|
||||
condition: << pipeline.git.tag >>
|
||||
steps:
|
||||
- run:
|
||||
name: Build Installer
|
||||
shell: cmd.exe #does not work in powershell
|
||||
environment:
|
||||
WORKFLOW_NUM: << pipeline.number >>
|
||||
CIRCLE_TAG: 2.0.0
|
||||
command: .circleci\build-installer.bat
|
||||
- when: # Setup certificates and build installers signed for tagged builds
|
||||
condition: << pipeline.git.tag >>
|
||||
steps:
|
||||
- setup_digicert
|
||||
- run:
|
||||
name: Build Installer
|
||||
shell: cmd.exe #does not work in powershell
|
||||
environment:
|
||||
WORKFLOW_NUM: << pipeline.number >>
|
||||
command: .circleci\build-installer.bat /DSIGN_INSTALLER /DCODE_SIGNING_CERT_FINGERPRINT=%SM_CODE_SIGNING_CERT_SHA1_HASH%
|
||||
- store_artifacts:
|
||||
path: ./installer
|
||||
- persist_to_workspace:
|
||||
root: ./
|
||||
paths:
|
||||
- installer/*.exe
|
||||
|
||||
deploy-connector-to-feed:
|
||||
docker:
|
||||
- image: mcr.microsoft.com/dotnet/sdk:6.0
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ./
|
||||
- run:
|
||||
name: Install Manager Feed CLI
|
||||
command: dotnet tool install --global Speckle.Manager.Feed
|
||||
- run:
|
||||
name: Upload new version
|
||||
command: |
|
||||
TAG=$(if [ "${CIRCLE_TAG}" ]; then echo $CIRCLE_TAG; else echo "2.0.0"; fi;)
|
||||
SEMVER=$(echo "$TAG" | sed -e 's/\/[a-zA-Z-]*//')
|
||||
VER=$(echo "$SEMVER" | sed -e 's/-.*//')
|
||||
VERSION=$(echo $VER.$WORKFLOW_NUM)
|
||||
/root/.dotnet/tools/Speckle.Manager.Feed deploy -s powerbi -v ${SEMVER} -u https://releases.speckle.dev/installers/powerbi/powerbi-${SEMVER}.exe -o Win -a Any -f ./installer/powerbi-${SEMVER}.exe
|
||||
environment:
|
||||
WORKFLOW_NUM: << pipeline.number >>
|
||||
|
||||
# Orchestrate our job run sequence
|
||||
workflows:
|
||||
build_and_test:
|
||||
when: false
|
||||
build:
|
||||
jobs:
|
||||
- build
|
||||
- build-connector:
|
||||
context: digicert-keylocker
|
||||
- build-visual
|
||||
- build-installer:
|
||||
context: digicert-keylocker
|
||||
requires:
|
||||
- build-connector
|
||||
- build-visual
|
||||
deploy:
|
||||
jobs:
|
||||
- build-connector:
|
||||
filters: &deploy_filter
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /^([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w{1,10})?$/
|
||||
context: digicert-keylocker
|
||||
- build-visual:
|
||||
filters: *deploy_filter
|
||||
- build-installer:
|
||||
filters: *deploy_filter
|
||||
context: digicert-keylocker
|
||||
requires:
|
||||
- build-connector
|
||||
- build-visual
|
||||
- deploy-connector-to-feed:
|
||||
filters: *deploy_filter
|
||||
requires:
|
||||
- build-installer
|
||||
context: do-spaces-speckle-releases
|
||||
|
||||
@@ -8,39 +8,32 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
outputs:
|
||||
semver: ${{ steps.set-version.outputs.semver }}
|
||||
file-version: ${{ steps.set-version.outputs.file-version }}
|
||||
file-version: ${{ steps.set-info-version.outputs.file-version }}
|
||||
env:
|
||||
CertFile: "./speckle.pfx"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- id: set-version
|
||||
name: Set version to output
|
||||
shell: bash
|
||||
run: |
|
||||
TAG=${{ github.ref_name }}
|
||||
if [[ "${{ github.ref }}" != refs/tags/* ]]; then
|
||||
TAG="v3.0.99.${{ github.run_number }}"
|
||||
fi
|
||||
SEMVER="${TAG#v}"
|
||||
FILE_VERSION=$(echo "$TAG" | sed -E 's/^v([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
|
||||
FILE_VERSION="$FILE_VERSION.${{ github.run_number }}"
|
||||
- name: Install GitVersion
|
||||
uses: gittools/actions/gitversion/setup@v3.0.0
|
||||
with:
|
||||
versionSpec: 6.0.5 # github actions doesnt like 6.1.0 onwards https://github.com/GitTools/actions/blob/main/docs/versions.md
|
||||
|
||||
echo "semver=$SEMVER" >> "$GITHUB_OUTPUT"
|
||||
echo "file-version=$FILE_VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo $SEMVER
|
||||
echo $FILE_VERSION
|
||||
- name: Determine Version
|
||||
id: gitversion
|
||||
uses: gittools/actions/gitversion/execute@v3.0.0
|
||||
|
||||
- name: Set connector version
|
||||
run: |
|
||||
python patch_version.py ${{steps.set-version.outputs.file-version}}
|
||||
python patch_version.py ${{steps.gitversion.outputs.AssemblySemVer}}
|
||||
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
@@ -67,35 +60,37 @@ jobs:
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
- id: set-version
|
||||
name: Set version to output
|
||||
run: echo "semver=${{steps.gitversion.outputs.semVer}}" >> "$GITHUB_OUTPUT"
|
||||
shell: bash
|
||||
- id: set-info-version
|
||||
name: Set version to output
|
||||
run: echo "file-version=${{steps.gitversion.outputs.AssemblySemVer}}" >> "$GITHUB_OUTPUT"
|
||||
shell: bash
|
||||
|
||||
build-visual:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- id: set-version
|
||||
name: Set version to output
|
||||
shell: bash
|
||||
run: |
|
||||
TAG=${{ github.ref_name }}
|
||||
if [[ "${{ github.ref }}" != refs/tags/* ]]; then
|
||||
TAG="v3.0.99.${{ github.run_number }}"
|
||||
fi
|
||||
SEMVER="${TAG#v}"
|
||||
FILE_VERSION=$(echo "$TAG" | sed -E 's/^v([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
|
||||
FILE_VERSION="$FILE_VERSION.${{ github.run_number }}"
|
||||
- name: Install GitVersion
|
||||
uses: gittools/actions/gitversion/setup@v3.0.0
|
||||
with:
|
||||
versionSpec: 6.0.5 # github actions doesnt like 6.1.0 onwards https://github.com/GitTools/actions/blob/main/docs/versions.md
|
||||
|
||||
echo "semver=$SEMVER" >> "$GITHUB_OUTPUT"
|
||||
echo "file-version=$FILE_VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo $SEMVER
|
||||
echo $FILE_VERSION
|
||||
- name: Determine Version
|
||||
id: gitversion
|
||||
uses: gittools/actions/gitversion/execute@v3.0.0
|
||||
|
||||
- run: npm ci
|
||||
working-directory: src/powerbi-visual
|
||||
- run: npm version ${{steps.set-version.outputs.semver}} --allow-same-version
|
||||
- run: npm version ${{steps.gitversion.outputs.semVer}} --allow-same-version
|
||||
working-directory: src/powerbi-visual
|
||||
- run: npm run build
|
||||
working-directory: src/powerbi-visual
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
workflow: GitFlow/v1
|
||||
next-version: 3.0.0
|
||||
mode: ManualDeployment
|
||||
branches:
|
||||
main:
|
||||
label: rc
|
||||
develop:
|
||||
regex: ^dev$
|
||||
label: beta
|
||||
unknown:
|
||||
increment: None
|
||||
@@ -59,32 +59,16 @@
|
||||
)
|
||||
),
|
||||
|
||||
// Function to check if a row should be excluded based on speckle type
|
||||
ShouldExcludeRow = (row as record) as logical =>
|
||||
let
|
||||
speckleType = Record.FieldOrDefault(row[data], "speckle_type", "")
|
||||
in
|
||||
speckleType = "Speckle.Core.Models.DataChunk" or
|
||||
Text.Contains(speckleType, "Objects.Other.RawEncoding"),
|
||||
|
||||
// Filtering logic here
|
||||
// If model data contains any DataObject -> fetch only data objects (excluding unwanted types)
|
||||
// If there are no data objects in the data -> fetch everything but exclude DataChunks and RawEncoding
|
||||
// If, model data contains any DataObject -> fetch only data objects
|
||||
// If there are no data objects in the data -> fetch everything but DataChunks
|
||||
HasDataObjects = Table.RowCount(
|
||||
Table.SelectRows(
|
||||
FinalTable,
|
||||
each Text.Contains(Record.FieldOrDefault([data], "speckle_type", ""), "DataObject")
|
||||
and not ShouldExcludeRow(_)
|
||||
)
|
||||
Table.SelectRows(FinalTable, each Text.Contains(Record.FieldOrDefault([data], "speckle_type", ""), "DataObject"))
|
||||
) > 0,
|
||||
|
||||
FilteredTable = if HasDataObjects then
|
||||
Table.SelectRows(
|
||||
FinalTable,
|
||||
each Text.Contains(Record.FieldOrDefault([data], "speckle_type", ""), "DataObject")
|
||||
and not ShouldExcludeRow(_)
|
||||
)
|
||||
Table.SelectRows(FinalTable, each Text.Contains(Record.FieldOrDefault([data], "speckle_type", ""), "DataObject"))
|
||||
else
|
||||
Table.SelectRows(FinalTable, each not ShouldExcludeRow(_))
|
||||
Table.SelectRows(FinalTable, each Record.FieldOrDefault([data], "speckle_type", "") <> "Speckle.Core.Models.DataChunk")
|
||||
in
|
||||
FilteredTable
|
||||
@@ -39,38 +39,29 @@
|
||||
projectResult = ApiFetch(server, projectQuery, projectVariables),
|
||||
workspaceId = projectResult[data][workspaceId],
|
||||
|
||||
// check if workspaceId is null (personal project)
|
||||
workspaceInfo = if workspaceId = null then
|
||||
[
|
||||
workspaceId = null,
|
||||
workspaceLogo = null,
|
||||
workspaceName = null,
|
||||
canHideBranding = false
|
||||
]
|
||||
else
|
||||
// query workspace only if workspaceId exists
|
||||
let
|
||||
workspaceQuery = "query Workspace($workspaceId: String!, $featureName: WorkspaceFeatureName!) {
|
||||
data:workspace(id: $workspaceId) {
|
||||
logo
|
||||
name
|
||||
hasAccessToFeature(featureName: $featureName)
|
||||
}
|
||||
}",
|
||||
// query to get workspace
|
||||
workspaceQuery = "query Workspace($workspaceId: String!, $featureName: WorkspaceFeatureName!) {
|
||||
data:workspace(id: $workspaceId) {
|
||||
logo
|
||||
name
|
||||
hasAccessToFeature(featureName: $featureName)
|
||||
}
|
||||
}",
|
||||
|
||||
workspaceVariables = [
|
||||
workspaceId = workspaceId,
|
||||
featureName = "hideSpeckleBranding"
|
||||
],
|
||||
|
||||
workspaceResult = ApiFetch(server, workspaceQuery, workspaceVariables),
|
||||
|
||||
workspaceVariables = [
|
||||
workspaceId = workspaceId,
|
||||
featureName = "hideSpeckleBranding"
|
||||
],
|
||||
|
||||
workspaceResult = ApiFetch(server, workspaceQuery, workspaceVariables),
|
||||
workspace = workspaceResult[data]
|
||||
in
|
||||
[
|
||||
workspaceId = workspaceId,
|
||||
workspaceLogo = workspace[logo],
|
||||
workspaceName = workspace[name],
|
||||
canHideBranding = workspace[hasAccessToFeature]
|
||||
]
|
||||
workspace = workspaceResult[data],
|
||||
|
||||
workspaceInfo = [
|
||||
workspaceId = workspaceId,
|
||||
workspaceLogo = workspace[logo],
|
||||
workspaceName = workspace[name],
|
||||
canHideBranding = workspace[hasAccessToFeature]
|
||||
]
|
||||
in
|
||||
workspaceInfo
|
||||
@@ -78,13 +78,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"properties": {
|
||||
"brandingHidden": {
|
||||
"type": { "bool": true }
|
||||
}
|
||||
}
|
||||
},
|
||||
"viewMode": {
|
||||
"properties": {
|
||||
"defaultViewMode": {
|
||||
@@ -97,11 +90,29 @@
|
||||
"defaultView": {
|
||||
"type": { "text": true }
|
||||
},
|
||||
"isOrtho": {
|
||||
"type": { "bool": true }
|
||||
"allowCameraUnder": {
|
||||
"type": {
|
||||
"bool": true
|
||||
}
|
||||
},
|
||||
"isGhost": {
|
||||
"type": { "bool": true }
|
||||
"zoomOnDataChange": {
|
||||
"type": {
|
||||
"bool": true
|
||||
}
|
||||
},
|
||||
"projection": {
|
||||
"type": {
|
||||
"enumeration": [
|
||||
{
|
||||
"displayName": "Perspective",
|
||||
"value": "perspective"
|
||||
},
|
||||
{
|
||||
"displayName": "Orthographic",
|
||||
"value": "orthographic"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Generated
+4
-6
@@ -31,8 +31,7 @@
|
||||
"powerbi-visuals-utils-formattingmodel": "^6.0.4",
|
||||
"powerbi-visuals-utils-interactivityutils": "^6.0.4",
|
||||
"powerbi-visuals-utils-tooltiputils": "^6.0.4",
|
||||
"regenerator-runtime": "^0.13.11",
|
||||
"vue-tippy": "^6.7.1"
|
||||
"regenerator-runtime": "^0.13.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.21.8",
|
||||
@@ -14715,10 +14714,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vue-tippy": {
|
||||
"version": "6.7.1",
|
||||
"resolved": "https://registry.npmjs.org/vue-tippy/-/vue-tippy-6.7.1.tgz",
|
||||
"integrity": "sha512-gdHbBV5/Vc8gH87hQHLA7TN1K4BlLco3MAPrTb70ZYGXxx+55rAU4a4mt0fIoP+gB3etu1khUZ6c29Br1n0CiA==",
|
||||
"license": "MIT",
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-tippy/-/vue-tippy-6.5.0.tgz",
|
||||
"integrity": "sha512-U44UDETTLuZWZGosagslEwgimWQdt1JVSxfWStVPnVdeqo2jo9X5zW3SB04k7JaTFosdgrDhFsUDrd6n42Nh7Q==",
|
||||
"dependencies": {
|
||||
"tippy.js": "^6.3.7"
|
||||
},
|
||||
|
||||
@@ -35,8 +35,7 @@
|
||||
"powerbi-visuals-utils-formattingmodel": "^6.0.4",
|
||||
"powerbi-visuals-utils-interactivityutils": "^6.0.4",
|
||||
"powerbi-visuals-utils-tooltiputils": "^6.0.4",
|
||||
"regenerator-runtime": "^0.13.11",
|
||||
"vue-tippy": "^6.7.1"
|
||||
"regenerator-runtime": "^0.13.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.21.8",
|
||||
|
||||
@@ -1,19 +1,4 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="visualStore.loadingProgress"
|
||||
class="absolute top-1/2 left-1/2 w-1/2 -translate-x-1/2 z-50 text-center text-sm"
|
||||
>
|
||||
<!-- Progress Bar -->
|
||||
<LoadingBar :progress="visualStore.loadingProgress"></LoadingBar>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="visualStore.commonError"
|
||||
class="absolute top-11 left-1/2 -translate-x-1/2 z-100 bg-white bg-opacity-70 text-black text-center text-sm px-4 py-1 rounded shadow font-medium cursor-default"
|
||||
>
|
||||
{{ visualStore.commonError }}
|
||||
</div>
|
||||
|
||||
<ViewerView v-if="visualStore.isViewerReadyToLoad" />
|
||||
<HomeView v-else />
|
||||
</template>
|
||||
@@ -23,7 +8,6 @@ import HomeView from './views/HomeView.vue'
|
||||
import ViewerView from './views/ViewerView.vue'
|
||||
import { onMounted } from 'vue'
|
||||
import { useVisualStore } from './store/visualStore'
|
||||
import LoadingBar from '@src/components/loading/LoadingBar.vue'
|
||||
|
||||
const visualStore = useVisualStore()
|
||||
|
||||
@@ -31,12 +15,3 @@ onMounted(() => {
|
||||
console.log('App mounted')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.tippy-box[data-theme~='custom'] {
|
||||
font-size: 10px;
|
||||
padding: 0px 0px;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,20 +2,12 @@
|
||||
<div class="space-y-2">
|
||||
<ViewerControlsButtonGroup>
|
||||
<!-- Zoom extend -->
|
||||
<ViewerControlsButtonToggle flat tooltip="Zoom extends" @click="onZoomExtentsClicked">
|
||||
<ViewerControlsButtonToggle v-tippy="'Zoom extends'" flat @click="onZoomExtentsClicked">
|
||||
<ArrowsPointingOutIcon class="h-4 w-4 md:h-5 md:w-5" />
|
||||
</ViewerControlsButtonToggle>
|
||||
<!-- Ghost / Hidden -->
|
||||
<ViewerControlsButtonToggle
|
||||
:tooltip="
|
||||
visualStore.isGhostActive
|
||||
? 'Hide ghosted objects on filter'
|
||||
: 'Show ghosted objects on filter'
|
||||
"
|
||||
flat
|
||||
@click="toggleGhostHidden"
|
||||
>
|
||||
<Ghost v-if="visualStore.isGhostActive" class="h-5 w-5" />
|
||||
<ViewerControlsButtonToggle flat @click="toggleGhostHidden">
|
||||
<Ghost v-if="isGhost" class="h-5 w-5" />
|
||||
<Ghost v-else class="h-5 w-5 opacity-30" />
|
||||
</ViewerControlsButtonToggle>
|
||||
</ViewerControlsButtonGroup>
|
||||
@@ -39,11 +31,10 @@
|
||||
<ViewerControlsButtonToggle
|
||||
flat
|
||||
secondary
|
||||
tooltip="Projection"
|
||||
:active="visualStore.isOrthoProjection"
|
||||
:active="isOrthoProjection"
|
||||
@click="toggleProjection"
|
||||
>
|
||||
<Perspective v-if="visualStore.isOrthoProjection" class="h-3.5 md:h-4 w-4" />
|
||||
<Perspective v-if="isOrthoProjection" class="h-3.5 md:h-4 w-4" />
|
||||
<PerspectiveMore v-else class="h-3.5 md:h-4 w-4" />
|
||||
</ViewerControlsButtonToggle>
|
||||
</ViewerControlsButtonGroup>
|
||||
@@ -79,6 +70,9 @@ withDefaults(defineProps<{ sectionBox: boolean; views: SpeckleView[] }>(), {
|
||||
sectionBox: false
|
||||
})
|
||||
|
||||
const isOrthoProjection = ref(false)
|
||||
const isGhost = ref(true)
|
||||
|
||||
type ActiveControl =
|
||||
| 'none'
|
||||
| 'viewModes'
|
||||
@@ -100,15 +94,13 @@ const toggleActiveControl = (control: ActiveControl) => {
|
||||
}
|
||||
|
||||
const toggleProjection = () => {
|
||||
isOrthoProjection.value = !isOrthoProjection.value
|
||||
visualStore.viewerEmit('toggleProjection')
|
||||
visualStore.setIsOrthoProjection(!visualStore.isOrthoProjection)
|
||||
visualStore.writeIsOrthoToFile()
|
||||
}
|
||||
|
||||
const toggleGhostHidden = () => {
|
||||
visualStore.setIsGhost(!visualStore.isGhostActive)
|
||||
visualStore.viewerEmit('toggleGhostHidden', visualStore.isGhostActive)
|
||||
visualStore.writeIsGhostToFile()
|
||||
isGhost.value = !isGhost.value
|
||||
visualStore.viewerEmit('toggleGhostHidden', isGhost.value)
|
||||
}
|
||||
|
||||
const viewModesOpen = computed({
|
||||
|
||||
@@ -1,130 +1,76 @@
|
||||
<template>
|
||||
<div class="border">
|
||||
<transition name="slide-fade">
|
||||
<nav
|
||||
v-show="!isNavbarCollapsed"
|
||||
class="fixed top-0 h-9 flex items-center bg-foundation border border-outline-2 w-full transition z-20 cursor-default"
|
||||
>
|
||||
<div class="flex items-center transition-all justify-between w-full">
|
||||
<div
|
||||
v-if="visualStore.receiveInfo.workspaceName"
|
||||
class="flex items-center gap-2 p-0.5 pr-1.5 hover:bg-highlight-2 rounded ml-2"
|
||||
<transition name="slide-fade">
|
||||
<nav
|
||||
v-show="!isNavbarCollapsed"
|
||||
class="fixed top-0 h-9 flex items-center bg-foundation border-b border-outline-2 w-full transition z-20 shadow-sm hover:shadow cursor-default"
|
||||
>
|
||||
<div class="flex items-center transition-all justify-between w-full">
|
||||
<div class="flex items-center hover:cursor-pointer" @click="goToSpeckleWebsite">
|
||||
<div class="max-[200px]:hidden block ml-2">
|
||||
<img class="w-6 h-auto ml-1 mr-2 my-1" src="@assets/logo-big.png" />
|
||||
</div>
|
||||
<div class="font-sans font-medium">Speckle</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="font-thin text-xs mr-2 text-gray-400">v1.0.0</div>
|
||||
<button
|
||||
class="text-gray-400 hover:text-gray-700 transition"
|
||||
title="Hide navbar"
|
||||
@click="isNavbarCollapsed = true"
|
||||
>
|
||||
<WorkspaceAvatar
|
||||
:name="visualStore.receiveInfo.workspaceName"
|
||||
:logo="visualStore.receiveInfo.workspaceLogo"
|
||||
></WorkspaceAvatar>
|
||||
<div class="min-w-0 truncate flex-grow text-left text-xs">
|
||||
<span>{{ visualStore.receiveInfo.workspaceName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="flex items-center hover:cursor-pointer" @click="goToSpeckleWebsite">
|
||||
<div class="max-[200px]:hidden block ml-2">
|
||||
<img class="w-6 h-auto ml-1 mr-2 my-1" src="@assets/logo-big.png" />
|
||||
</div>
|
||||
<div class="font-sans font-medium">Speckle</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-2">
|
||||
<FormButton
|
||||
v-if="visualStore.latestAvailableVersion && !visualStore.isConnectorUpToDate"
|
||||
v-tippy="{
|
||||
content: 'New connector version is available.<br>Click to download.',
|
||||
allowHTML: true
|
||||
}"
|
||||
color="outline"
|
||||
size="sm"
|
||||
@click="visualStore.downloadLatestVersion"
|
||||
>
|
||||
Update
|
||||
</FormButton>
|
||||
<div class="font-thin text-xs text-gray-400">
|
||||
v{{ visualStore.receiveInfo.version }}
|
||||
</div>
|
||||
<button
|
||||
class="text-gray-400 hover:text-gray-700 transition"
|
||||
title="Hide navbar"
|
||||
@click="isNavbarCollapsed = true"
|
||||
>
|
||||
<ChevronUpIcon class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
<ChevronUpIcon class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
</transition>
|
||||
</div>
|
||||
</nav>
|
||||
</transition>
|
||||
|
||||
<div
|
||||
v-if="!isInteractive"
|
||||
class="absolute left-1/2 -translate-x-1/2 z-20 bg-white bg-opacity-70 text-black text-center text-xs px-4 py-1 rounded shadow font-medium cursor-default transition-all duration-300"
|
||||
:class="isNavbarCollapsed ? 'top-1' : 'top-11'"
|
||||
>
|
||||
<strong>Object IDs</strong>
|
||||
field is needed for interactivity with other visuals.
|
||||
</div>
|
||||
<!-- TODO: another transition here needed that below components - but this time it will move to left -->
|
||||
|
||||
<div v-if="isNavbarCollapsed" class="fixed top-0 right-0 z-20">
|
||||
<button
|
||||
class="transition opacity-50 hover:opacity-100"
|
||||
title="Show navbar"
|
||||
@click="isNavbarCollapsed = false"
|
||||
>
|
||||
<ChevronDownIcon class="w-4 h-4 text-gray-400" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<transition name="slide-left">
|
||||
<ViewerControls
|
||||
v-show="!isNavbarCollapsed"
|
||||
v-model:section-box="bboxActive"
|
||||
:views="views"
|
||||
class="fixed top-11 left-2 z-30"
|
||||
@view-clicked="(view) => viewerHandler.setView(view)"
|
||||
@view-mode-clicked="(viewMode) => viewerHandler.setViewMode(viewMode)"
|
||||
/>
|
||||
</transition>
|
||||
|
||||
<div v-if="visualStore.isFilterActive" class="absolute bottom-5 left-1/2 -translate-x-1/2 z-50">
|
||||
<FormButton size="sm" @click="visualStore.resetFilters(), selectionHandler.reset()">
|
||||
Reset filters
|
||||
</FormButton>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="absolute z-10 flex items-center text-xs cursor-pointer"
|
||||
:class="visualStore.isBrandingHidden ? 'bottom-0 right-0' : 'bottom-2 right-2'"
|
||||
@click.stop="goToSpeckleWebsite"
|
||||
>
|
||||
<!-- TODO: fade bottom here as transition -->
|
||||
<transition name="fade-bottom">
|
||||
<div
|
||||
v-if="!visualStore.isBrandingHidden"
|
||||
class="flex items-center justify-center font-thin"
|
||||
>
|
||||
<div class="">Powered by</div>
|
||||
<img class="w-4 h-auto mx-1" src="@assets/logo-big.png" />
|
||||
<div class="font-medium">Speckle</div>
|
||||
</div>
|
||||
</transition>
|
||||
<button
|
||||
v-if="visualStore.receiveInfo && visualStore.receiveInfo.canHideBranding"
|
||||
class="transition opacity-50 hover:opacity-100 ml-1"
|
||||
:title="visualStore.isBrandingHidden ? '' : 'Hide branding'"
|
||||
@click.stop="visualStore.toggleBranding()"
|
||||
>
|
||||
<ChevronUpIcon v-if="visualStore.isBrandingHidden" class="w-4 h-4 text-gray-400" />
|
||||
<ChevronDownIcon v-else class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref="container"
|
||||
class="fixed h-full w-full z-0 cursor-default"
|
||||
@click="onCanvasClick"
|
||||
@auxclick="onCanvasAuxClick"
|
||||
/>
|
||||
<div
|
||||
v-if="!isInteractive"
|
||||
class="absolute top-1 left-1/2 -translate-x-1/2 z-20 bg-white bg-opacity-70 text-black text-center text-xs px-4 py-1 rounded shadow font-medium"
|
||||
>
|
||||
<strong>Object IDs</strong>
|
||||
field is needed for interactivity with other visuals.
|
||||
</div>
|
||||
|
||||
<div v-if="isNavbarCollapsed" class="fixed top-2 right-0 z-20">
|
||||
<button
|
||||
class="transition opacity-50 hover:opacity-100"
|
||||
title="Show navbar"
|
||||
@click="isNavbarCollapsed = false"
|
||||
>
|
||||
<ChevronDownIcon class="w-4 h-4 text-gray-400" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- till here -->
|
||||
|
||||
<transition name="slide-left">
|
||||
<ViewerControls
|
||||
v-show="!isNavbarCollapsed"
|
||||
v-model:section-box="bboxActive"
|
||||
:views="views"
|
||||
class="fixed top-11 left-1 z-30"
|
||||
@view-clicked="(view) => viewerHandler.setView(view)"
|
||||
@view-mode-clicked="(viewMode) => viewerHandler.setViewMode(viewMode)"
|
||||
/>
|
||||
</transition>
|
||||
|
||||
<div v-if="visualStore.isFilterActive" class="absolute bottom-5 left-1/2 -translate-x-1/2 z-50">
|
||||
<FormButton size="sm" @click="visualStore.resetFilters(), selectionHandler.reset()">
|
||||
Reset filters
|
||||
</FormButton>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref="container"
|
||||
class="fixed h-full w-full z-0"
|
||||
@click="onCanvasClick"
|
||||
@auxclick="onCanvasAuxClick"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script async setup lang="ts">
|
||||
@@ -138,7 +84,6 @@ import { useVisualStore } from '@src/store/visualStore'
|
||||
import { ViewerHandler } from '@src/plugins/viewer'
|
||||
import { selectionHandlerKey, tooltipHandlerKey } from '@src/injectionKeys'
|
||||
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/vue/24/outline'
|
||||
import WorkspaceAvatar from './workspace/WorkspaceAvatar.vue'
|
||||
|
||||
const visualStore = useVisualStore()
|
||||
const { dragged } = useClickDragged()
|
||||
@@ -236,19 +181,4 @@ async function onCanvasAuxClick(ev: MouseEvent) {
|
||||
opacity: 0;
|
||||
transform: translateX(-20px);
|
||||
}
|
||||
|
||||
.fade-bottom-enter-active,
|
||||
.fade-bottom-leave-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.fade-bottom-enter-from,
|
||||
.fade-bottom-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
.fade-bottom-enter-to,
|
||||
.fade-bottom-leave-from {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,46 +1,22 @@
|
||||
<template>
|
||||
<div class="w-full text-xs text-foreground-on-primary space-y-1">
|
||||
<!-- Bar container -->
|
||||
<div
|
||||
:class="[
|
||||
'w-full h-1 overflow-hidden rounded-xl bg-blue-500/30',
|
||||
showBar ? 'opacity-100' : 'opacity-0'
|
||||
]"
|
||||
>
|
||||
<!-- Swooshing animation -->
|
||||
<div v-if="isIndeterminate" class="swoosher top-0 left-0 h-full bg-blue-500/50"></div>
|
||||
|
||||
<!-- Determinate progress bar -->
|
||||
<div
|
||||
v-else
|
||||
class="top-0 left-0 h-full bg-blue-500 transition-all duration-300 ease-linear"
|
||||
:style="{ width: `${progressPercent + 20}%` }"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<!-- Progress text below -->
|
||||
<div v-if="isIndeterminate" class="text-[13px] text-center text-foreground-2">
|
||||
{{ props.progress.summary }}
|
||||
</div>
|
||||
<div v-else class="text-[13px] text-center text-foreground-2">
|
||||
{{ progressPercent.toFixed(0) }}% ({{ props.progress.summary }})
|
||||
</div>
|
||||
<div
|
||||
:class="[
|
||||
'relative w-full h-1 bg-blue-500/30 text-xs text-foreground-on-primary overflow-hidden rounded-xl',
|
||||
showBar ? 'opacity-100' : 'opacity-0'
|
||||
]"
|
||||
>
|
||||
<div class="swoosher relative top-0 bg-blue-500/50"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useMounted } from '@vueuse/core'
|
||||
import { LoadingProgress } from '@src/store/visualStore'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps<{ progress: LoadingProgress; clientOnly?: boolean }>()
|
||||
const props = defineProps<{ loading: boolean; clientOnly?: boolean }>()
|
||||
|
||||
const mounted = useMounted()
|
||||
const showBar = computed(() => (mounted.value || !props.clientOnly) && !!props.progress)
|
||||
const isIndeterminate = computed(() => props.progress.progress == null)
|
||||
const progressPercent = computed(() => (props.progress.progress ?? 0) * 100)
|
||||
const showBar = computed(() => (mounted.value || !props.clientOnly) && props.loading)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.swoosher {
|
||||
width: 100%;
|
||||
@@ -53,9 +29,11 @@ const progressPercent = computed(() => (props.progress.progress ?? 0) * 100)
|
||||
0% {
|
||||
transform: translateX(0) scaleX(0);
|
||||
}
|
||||
|
||||
40% {
|
||||
transform: translateX(0) scaleX(0.4);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(100%) scaleX(0.5);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<button
|
||||
:title="tooltip"
|
||||
:class="`transition rounded-lg w-8 md:w-10 h-8 md:h-10 shrink-0 flex items-center justify-center ${colorClasses} outline-none ${
|
||||
props.flat ? '!w-7 md:!w-9' : 'border border-outline-2 w-8 md:w-10 shadow'
|
||||
}`"
|
||||
@@ -16,7 +15,6 @@ const props = defineProps<{
|
||||
active?: boolean
|
||||
flat?: boolean
|
||||
secondary?: boolean
|
||||
tooltip?: string
|
||||
}>()
|
||||
|
||||
const colorClasses = computed(() => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!-- eslint-disable vuejs-accessibility/no-static-element-interactions -->
|
||||
<template>
|
||||
<ViewerMenu v-model:open="open" title="View modes">
|
||||
<ViewerMenu v-model:open="open" tooltip="View modes">
|
||||
<template #trigger-icon>
|
||||
<ViewModes class="h-5 w-5" />
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!-- eslint-disable vuejs-accessibility/no-static-element-interactions -->
|
||||
<template>
|
||||
<ViewerMenu v-model:open="open" title="Views">
|
||||
<ViewerMenu v-model:open="open" tooltip="Views">
|
||||
<template #trigger-icon>
|
||||
<Views class="w-5 h-5" />
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex shrink-0 overflow-hidden rounded-md border border-outline-2 bg-foundation-2">
|
||||
<div
|
||||
class="w-6 h-6 bg-center bg-contain bg-no-repeat flex items-center justify-center"
|
||||
class="h-full w-full bg-cover bg-center bg-no-repeat flex items-center justify-center"
|
||||
:style="logo ? { backgroundImage: `url('${logo}')` } : {}"
|
||||
>
|
||||
<span v-if="!logo" class="text-foreground-3 uppercase leading-none">
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { useVisualStore } from '@src/store/visualStore'
|
||||
import { ref } from 'vue'
|
||||
|
||||
type Versions = {
|
||||
Versions: Version[]
|
||||
}
|
||||
|
||||
export type Version = {
|
||||
Number: string
|
||||
Url: string
|
||||
Os: number
|
||||
Architecture: number
|
||||
Date: string
|
||||
Prerelease: boolean
|
||||
}
|
||||
|
||||
export function useUpdateConnector() {
|
||||
const versions = ref<Version[]>([])
|
||||
const latestAvailableVersion = ref<Version | null>(null)
|
||||
|
||||
async function checkUpdate() {
|
||||
try {
|
||||
await getVersions()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
async function getVersions() {
|
||||
const visualStore = useVisualStore()
|
||||
const response = await fetch(`https://releases.speckle.dev/manager2/feeds/powerbi-v3.json`, {
|
||||
method: 'GET'
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch versions')
|
||||
}
|
||||
|
||||
const data = (await response.json()) as unknown as Versions
|
||||
const sortedVersions = data.Versions.sort(function (a: Version, b: Version) {
|
||||
return new Date(b.Date).getTime() - new Date(a.Date).getTime()
|
||||
})
|
||||
versions.value = sortedVersions
|
||||
const sanitizedVersion = sanitizeVersion(sortedVersions[0].Number)
|
||||
latestAvailableVersion.value = { ...sortedVersions[0], Number: sanitizedVersion }
|
||||
visualStore.setLatestAvailableVersion(latestAvailableVersion.value)
|
||||
}
|
||||
|
||||
function sanitizeVersion(version: string): string {
|
||||
const match = version.match(/\d+\.\d+\.\d+/)
|
||||
return match ? match[0] : version // fallback to original version
|
||||
}
|
||||
|
||||
return { checkUpdate }
|
||||
}
|
||||
@@ -11,9 +11,7 @@ import {
|
||||
Viewer,
|
||||
HybridCameraController,
|
||||
SelectionExtension,
|
||||
FilteringExtension,
|
||||
UpdateFlags,
|
||||
ViewerEvent
|
||||
FilteringExtension
|
||||
} from '@speckle/viewer'
|
||||
import { SpeckleObjectsOfflineLoader } from '@src/laoder/SpeckleObjectsOfflineLoader'
|
||||
import { useVisualStore } from '@src/store/visualStore'
|
||||
@@ -37,7 +35,7 @@ export interface Hit {
|
||||
export interface IViewerEvents {
|
||||
ping: (message: string) => void
|
||||
setSelection: (objectIds: string[]) => void
|
||||
resetFilter: (objectIds: string[], ghost: boolean) => void
|
||||
resetFilter: (objectIds: string[]) => void
|
||||
filterSelection: (objectIds: string[], ghost: boolean) => void
|
||||
setViewMode: (viewMode: ViewMode) => void
|
||||
colorObjectsByGroup: (
|
||||
@@ -91,14 +89,15 @@ export class ViewerHandler {
|
||||
this.filtering = this.viewer.getExtension(FilteringExtension)
|
||||
this.selection = this.viewer.getExtension(SelectionExtension)
|
||||
|
||||
const store = useVisualStore()
|
||||
if (store.isOrthoProjection) {
|
||||
this.cameraControls.toggleCameras()
|
||||
}
|
||||
|
||||
this.viewer.on(ViewerEvent.LoadComplete, (arg: string) => {
|
||||
store.clearLoadingProgress()
|
||||
})
|
||||
// NOTE: storing camera position into file triggers `update` function. even if I early return according to flag - it slows down the usage a lot.
|
||||
// this.cameraControls.on(CameraEvent.Stationary, () => {
|
||||
// console.log('🎬 Storing the camera position into file')
|
||||
// const cameraController = this.viewer.getExtension(CameraController)
|
||||
// const position = cameraController.getPosition()
|
||||
// const target = cameraController.getTarget()
|
||||
// const store = useVisualStore()
|
||||
// store.writeCameraPositionToFile(position, target)
|
||||
// })
|
||||
}
|
||||
|
||||
emit<E extends keyof IViewerEvents>(event: E, ...payload: Parameters<IViewerEvents[E]>): void {
|
||||
@@ -110,16 +109,10 @@ export class ViewerHandler {
|
||||
this.cameraControls.setCameraView(objectIds, animate)
|
||||
}
|
||||
|
||||
public zoomExtends = () => {
|
||||
this.cameraControls.setCameraView(undefined, true)
|
||||
this.viewer.requestRender(UpdateFlags.RENDER_RESET)
|
||||
}
|
||||
public zoomExtends = () => this.cameraControls.setCameraView(undefined, false)
|
||||
public toggleProjection = () => this.cameraControls.toggleCameras()
|
||||
|
||||
public setView = (view: CanonicalView) => {
|
||||
this.cameraControls.setCameraView(view, false)
|
||||
this.snapshotCameraPositionAndStore()
|
||||
}
|
||||
public setView = (view: CanonicalView) => this.cameraControls.setCameraView(view, false)
|
||||
|
||||
public setSectionBox = (bboxActive: boolean, objectIds: string[]) => {
|
||||
// TODO
|
||||
@@ -131,15 +124,6 @@ export class ViewerHandler {
|
||||
viewModes.setViewMode(viewMode)
|
||||
}
|
||||
|
||||
public snapshotCameraPositionAndStore = () => {
|
||||
console.log('🎬 Storing the camera position into file')
|
||||
const cameraController = this.viewer.getExtension(CameraController)
|
||||
const position = cameraController.getPosition()
|
||||
const target = cameraController.getTarget()
|
||||
const store = useVisualStore()
|
||||
store.writeCameraPositionToFile(position, target)
|
||||
}
|
||||
|
||||
public selectObjects = (objectIds: string[]) => {
|
||||
console.log('🔗 Handling setSelection inside ViewerHandler:', objectIds)
|
||||
if (objectIds) {
|
||||
@@ -156,10 +140,10 @@ export class ViewerHandler {
|
||||
}
|
||||
}
|
||||
|
||||
public resetFilter = (objectIds: string[], ghost: boolean) => {
|
||||
public resetFilter = (objectIds: string[]) => {
|
||||
console.log('🔗 Handling filterSelection inside ViewerHandler')
|
||||
if (objectIds) {
|
||||
this.isolateObjects(objectIds, ghost)
|
||||
this.isolateObjects(objectIds, true)
|
||||
this.zoomObjects(objectIds, true)
|
||||
}
|
||||
}
|
||||
@@ -243,6 +227,7 @@ export class ViewerHandler {
|
||||
sourceHostApp: store.receiveInfo.sourceApplication,
|
||||
workspace_id: store.receiveInfo.workspaceId
|
||||
})
|
||||
// camera need to be set after objects loaded
|
||||
if (store.cameraPosition) {
|
||||
const position = new Vector3(
|
||||
store.cameraPosition[0],
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { CanonicalView, SpeckleView, ViewMode } from '@speckle/viewer'
|
||||
import { Version } from '@src/composables/useUpdateConnector'
|
||||
import { ColorBy, IViewerEvents } from '@src/plugins/viewer'
|
||||
import { SpeckleVisualSettingsModel } from '@src/settings/visualSettingsModel'
|
||||
import { SpeckleDataInput } from '@src/types'
|
||||
@@ -7,7 +6,7 @@ import { zipModelObjects } from '@src/utils/compression'
|
||||
import { ReceiveInfo } from '@src/utils/matrixViewUtils'
|
||||
import { defineStore } from 'pinia'
|
||||
import { Vector3 } from 'three'
|
||||
import { computed, ref, shallowRef } from 'vue'
|
||||
import { ref, shallowRef } from 'vue'
|
||||
|
||||
export type InputState = 'valid' | 'incomplete' | 'invalid'
|
||||
|
||||
@@ -18,25 +17,16 @@ export type FieldInputState = {
|
||||
tooltipData: boolean
|
||||
}
|
||||
|
||||
export type LoadingProgress = { summary: string; progress: number; step?: string }
|
||||
|
||||
export const useVisualStore = defineStore('visualStore', () => {
|
||||
const latestAvailableVersion = ref<Version | null>(null)
|
||||
|
||||
const host = shallowRef<powerbi.extensibility.visual.IVisualHost>()
|
||||
const formattingSettings = ref<SpeckleVisualSettingsModel>()
|
||||
const loadingProgress = ref<LoadingProgress>(undefined)
|
||||
const loadingProgress = ref<{ summary: string; progress: number }>(undefined)
|
||||
const objectsFromStore = ref<object[]>(undefined)
|
||||
|
||||
const postFileSaveSkipNeeded = ref<boolean>(false)
|
||||
const postClickSkipNeeded = ref<boolean>(false)
|
||||
|
||||
const isFilterActive = ref<boolean>(false)
|
||||
const isBrandingHidden = ref<boolean>(false)
|
||||
const isOrthoProjection = ref<boolean>(false)
|
||||
const isGhostActive = ref<boolean>(true)
|
||||
|
||||
const commonError = ref<string>(undefined)
|
||||
|
||||
// once you see this shit, you might freak out and you are right. All of them needed because of "update" function trigger by API.
|
||||
// most of the time we need to know what we are doing to treat operations accordingly. Ask for more to me (Ogu), but the answers will make both of us unhappy.
|
||||
@@ -83,17 +73,6 @@ export const useVisualStore = defineStore('visualStore', () => {
|
||||
|
||||
const setReceiveInfo = (newReceiveInfo: ReceiveInfo) => (receiveInfo.value = newReceiveInfo)
|
||||
|
||||
const setLatestAvailableVersion = (version: Version | null) => {
|
||||
latestAvailableVersion.value = version
|
||||
}
|
||||
|
||||
const isConnectorUpToDate = computed(() => {
|
||||
if (receiveInfo.value && receiveInfo.value.version) {
|
||||
return receiveInfo.value.version === latestAvailableVersion.value?.Number
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
/**
|
||||
* Ideally one time set when onMounted of `ViewerWrapper.vue` component
|
||||
* @param emit picky emit function to trigger events under `IViewerEvents` interface
|
||||
@@ -122,9 +101,7 @@ export const useVisualStore = defineStore('visualStore', () => {
|
||||
}
|
||||
}
|
||||
|
||||
const clearLoadingProgress = () => {
|
||||
loadingProgress.value = undefined
|
||||
}
|
||||
const clearLoadingProgress = () => (loadingProgress.value = undefined)
|
||||
|
||||
// MAKE TS HAPPY
|
||||
type SpeckleObject = {
|
||||
@@ -137,6 +114,7 @@ export const useVisualStore = defineStore('visualStore', () => {
|
||||
viewerReloadNeeded.value = false
|
||||
console.log(`📦 Loading viewer from cached data with ${lastLoadedRootObjectId.value} id.`)
|
||||
await viewerEmit.value('loadObjects', objects)
|
||||
clearLoadingProgress()
|
||||
objectsFromStore.value = objects
|
||||
isViewerObjectsLoaded.value = true
|
||||
viewerReloadNeeded.value = false
|
||||
@@ -155,20 +133,19 @@ export const useVisualStore = defineStore('visualStore', () => {
|
||||
lastLoadedRootObjectId.value = modelIds
|
||||
console.log(`🔄 Forcing viewer re-render for new root object id.`)
|
||||
await viewerEmit.value('loadObjects', dataInput.value.modelObjects)
|
||||
clearLoadingProgress()
|
||||
viewerReloadNeeded.value = false
|
||||
isViewerObjectsLoaded.value = true
|
||||
setLoadingProgress('Storing objects into file', null)
|
||||
writeObjectsToFile(dataInput.value.modelObjects)
|
||||
loadingProgress.value = undefined
|
||||
}
|
||||
|
||||
if (dataInput.value.selectedIds.length > 0) {
|
||||
isFilterActive.value = true
|
||||
viewerEmit.value('filterSelection', dataInput.value.selectedIds, isGhostActive.value)
|
||||
viewerEmit.value('filterSelection', dataInput.value.selectedIds, true)
|
||||
} else {
|
||||
isFilterActive.value = false
|
||||
latestColorBy.value = dataInput.value.colorByIds
|
||||
viewerEmit.value('resetFilter', dataInput.value.objectIds, isGhostActive.value)
|
||||
viewerEmit.value('resetFilter', dataInput.value.objectIds)
|
||||
}
|
||||
viewerEmit.value('colorObjectsByGroup', dataInput.value.colorByIds)
|
||||
}
|
||||
@@ -208,38 +185,6 @@ export const useVisualStore = defineStore('visualStore', () => {
|
||||
})
|
||||
}
|
||||
|
||||
const writeIsOrthoToFile = () => {
|
||||
// NOTE: need skipping the update function, it resets the viewer state unneccessarily.
|
||||
postFileSaveSkipNeeded.value = true
|
||||
host.value.persistProperties({
|
||||
merge: [
|
||||
{
|
||||
objectName: 'camera',
|
||||
properties: {
|
||||
isOrtho: isOrthoProjection.value
|
||||
},
|
||||
selector: null
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
const writeIsGhostToFile = () => {
|
||||
// NOTE: need skipping the update function, it resets the viewer state unneccessarily.
|
||||
postFileSaveSkipNeeded.value = true
|
||||
host.value.persistProperties({
|
||||
merge: [
|
||||
{
|
||||
objectName: 'camera',
|
||||
properties: {
|
||||
isGhost: isGhostActive.value
|
||||
},
|
||||
selector: null
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
const writeViewModeToFile = (viewMode: ViewMode) => {
|
||||
// NOTE: need skipping the update function, it resets the viewer state unneccessarily.
|
||||
postFileSaveSkipNeeded.value = true
|
||||
@@ -256,41 +201,25 @@ export const useVisualStore = defineStore('visualStore', () => {
|
||||
})
|
||||
}
|
||||
|
||||
const writeHideBrandingToFile = (brandingHidden: boolean) => {
|
||||
// NOTE: need skipping the update function, it resets the viewer state unneccessarily.
|
||||
postFileSaveSkipNeeded.value = true
|
||||
host.value.persistProperties({
|
||||
merge: [
|
||||
{
|
||||
objectName: 'workspace',
|
||||
properties: {
|
||||
brandingHidden: brandingHidden
|
||||
},
|
||||
selector: null
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
const writeCameraPositionToFile = (position: Vector3, target: Vector3) => {
|
||||
// NOTE: need skipping the update function, it resets the viewer state unneccessarily.
|
||||
postFileSaveSkipNeeded.value = true
|
||||
host.value.persistProperties({
|
||||
merge: [
|
||||
{
|
||||
objectName: 'cameraPosition',
|
||||
properties: {
|
||||
positionX: position.x,
|
||||
positionY: position.y,
|
||||
positionZ: position.z,
|
||||
targetX: target.x,
|
||||
targetY: target.y,
|
||||
targetZ: target.z
|
||||
},
|
||||
selector: null
|
||||
}
|
||||
]
|
||||
})
|
||||
// postFileSaveSkipNeeded.value = true
|
||||
// host.value.persistProperties({
|
||||
// merge: [
|
||||
// {
|
||||
// objectName: 'cameraPosition',
|
||||
// properties: {
|
||||
// positionX: position.x,
|
||||
// positionY: position.y,
|
||||
// positionZ: position.z,
|
||||
// targetX: target.x,
|
||||
// targetY: target.y,
|
||||
// targetZ: target.z
|
||||
// },
|
||||
// selector: null
|
||||
// }
|
||||
// ]
|
||||
// })
|
||||
}
|
||||
|
||||
const setFieldInputState = (newFieldInputState: FieldInputState) =>
|
||||
@@ -300,27 +229,10 @@ export const useVisualStore = defineStore('visualStore', () => {
|
||||
|
||||
const setIsLoadingFromFile = (newValue: boolean) => (isLoadingFromFile.value = newValue)
|
||||
|
||||
const setViewerReadyToLoad = (newValue: boolean) => (isViewerReadyToLoad.value = newValue)
|
||||
const setViewerReadyToLoad = () => (isViewerReadyToLoad.value = true)
|
||||
|
||||
const setViewerReloadNeeded = () => (viewerReloadNeeded.value = true)
|
||||
|
||||
const toggleBranding = () => {
|
||||
isBrandingHidden.value = !isBrandingHidden.value
|
||||
writeHideBrandingToFile(isBrandingHidden.value)
|
||||
}
|
||||
|
||||
const setBrandingHidden = (val: boolean) => {
|
||||
isBrandingHidden.value = val
|
||||
}
|
||||
|
||||
const setIsOrthoProjection = (val: boolean) => {
|
||||
isOrthoProjection.value = val
|
||||
}
|
||||
|
||||
const setIsGhost = (val: boolean) => {
|
||||
isGhostActive.value = val
|
||||
}
|
||||
|
||||
const setPostFileSaveSkipNeeded = (newValue: boolean) => (postFileSaveSkipNeeded.value = newValue)
|
||||
const setPostClickSkipNeeded = (newValue: boolean) => (postClickSkipNeeded.value = newValue)
|
||||
|
||||
@@ -332,21 +244,13 @@ export const useVisualStore = defineStore('visualStore', () => {
|
||||
(formattingSettings.value = newFormattingSettings)
|
||||
|
||||
const resetFilters = () => {
|
||||
viewerEmit.value('resetFilter', dataInput.value.objectIds, isGhostActive.value)
|
||||
viewerEmit.value('resetFilter', dataInput.value.objectIds)
|
||||
if (latestColorBy.value !== null) {
|
||||
viewerEmit.value('colorObjectsByGroup', latestColorBy.value)
|
||||
}
|
||||
isFilterActive.value = false
|
||||
}
|
||||
|
||||
const downloadLatestVersion = () => {
|
||||
host.value.launchUrl(latestAvailableVersion.value?.Url as string)
|
||||
}
|
||||
|
||||
const setCommonError = (error: string) => {
|
||||
commonError.value = error
|
||||
}
|
||||
|
||||
return {
|
||||
host,
|
||||
receiveInfo,
|
||||
@@ -370,18 +274,7 @@ export const useVisualStore = defineStore('visualStore', () => {
|
||||
isFilterActive,
|
||||
latestColorBy,
|
||||
formattingSettings,
|
||||
isBrandingHidden,
|
||||
isOrthoProjection,
|
||||
isGhostActive,
|
||||
latestAvailableVersion,
|
||||
isConnectorUpToDate,
|
||||
commonError,
|
||||
setCommonError,
|
||||
setLatestAvailableVersion,
|
||||
setIsOrthoProjection,
|
||||
setIsGhost,
|
||||
setFormattingSettings,
|
||||
setBrandingHidden,
|
||||
setPostClickSkipNeeded,
|
||||
setPostFileSaveSkipNeeded,
|
||||
setCameraPositionInFile,
|
||||
@@ -394,12 +287,8 @@ export const useVisualStore = defineStore('visualStore', () => {
|
||||
setObjectsFromStore,
|
||||
writeObjectsToFile,
|
||||
writeCameraViewToFile,
|
||||
writeIsGhostToFile,
|
||||
writeIsOrthoToFile,
|
||||
writeViewModeToFile,
|
||||
writeCameraPositionToFile,
|
||||
writeHideBrandingToFile,
|
||||
toggleBranding,
|
||||
setViewerEmitter,
|
||||
setDataInput,
|
||||
setFieldInputState,
|
||||
@@ -408,7 +297,6 @@ export const useVisualStore = defineStore('visualStore', () => {
|
||||
setLoadingProgress,
|
||||
clearLoadingProgress,
|
||||
setIsLoadingFromFile,
|
||||
resetFilters,
|
||||
downloadLatestVersion
|
||||
resetFilters
|
||||
}
|
||||
})
|
||||
|
||||
@@ -10,7 +10,6 @@ import { SpeckleVisualSettingsModel } from 'src/settings/visualSettingsModel'
|
||||
import { FieldInputState, useVisualStore } from '@src/store/visualStore'
|
||||
import { delay } from 'lodash'
|
||||
import { getSlugFromHostAppNameAndVersion } from './hostAppSlug'
|
||||
import { useUpdateConnector } from '@src/composables/useUpdateConnector'
|
||||
|
||||
export class AsyncPause {
|
||||
private lastPauseTime = 0
|
||||
@@ -160,40 +159,6 @@ export type ReceiveInfo = {
|
||||
version?: string
|
||||
}
|
||||
|
||||
export type PreGetObjects = {
|
||||
modelExists: boolean
|
||||
objectCount?: number
|
||||
}
|
||||
|
||||
async function getPreGetObjects(commaSeparatedModelIds: string): Promise<PreGetObjects[]> {
|
||||
const modelIds = (commaSeparatedModelIds as string).split(',')
|
||||
const preGetObjects = []
|
||||
|
||||
for await (const id of modelIds) {
|
||||
const res = await getPreGetObjectsForModel(id)
|
||||
preGetObjects.push(res)
|
||||
}
|
||||
return preGetObjects
|
||||
}
|
||||
|
||||
async function getPreGetObjectsForModel(id: string): Promise<PreGetObjects> {
|
||||
try {
|
||||
const preGetObjectsRes = await fetch(`http://localhost:29364/pre-get-objects/${id}`)
|
||||
|
||||
if (!preGetObjectsRes.body) {
|
||||
console.log('No response body for pre get objects')
|
||||
return {
|
||||
modelExists: false,
|
||||
objectCount: null
|
||||
} as PreGetObjects
|
||||
}
|
||||
|
||||
return (await preGetObjectsRes.json()) as PreGetObjects
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function getReceiveInfo(id) {
|
||||
try {
|
||||
const ids = (id as string).split(',')
|
||||
@@ -210,29 +175,19 @@ async function getReceiveInfo(id) {
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchStreamedData(commaSeparatedModelIds: string, totalObjectCount: number) {
|
||||
async function fetchStreamedData(commaSeparatedModelIds: string) {
|
||||
const modelIds = (commaSeparatedModelIds as string).split(',')
|
||||
const modelObjects = []
|
||||
|
||||
let loadedObjectCount = 0
|
||||
|
||||
for await (const id of modelIds) {
|
||||
const objects = await fetchStreamedDataForModel(id, totalObjectCount, loadedObjectCount)
|
||||
const objects = await fetchStreamedDataForModel(id)
|
||||
modelObjects.push(objects)
|
||||
loadedObjectCount += objects.length
|
||||
}
|
||||
return modelObjects
|
||||
}
|
||||
|
||||
async function fetchStreamedDataForModel(
|
||||
id: string,
|
||||
totalObjectCount: number,
|
||||
loadedObjectCount: number
|
||||
) {
|
||||
console.log(loadedObjectCount, totalObjectCount)
|
||||
|
||||
async function fetchStreamedDataForModel(id) {
|
||||
try {
|
||||
const visualStore = useVisualStore()
|
||||
const response = await fetch(`http://localhost:29364/get-objects/${id}`)
|
||||
|
||||
if (!response.body) {
|
||||
@@ -259,11 +214,6 @@ async function fetchStreamedDataForModel(
|
||||
try {
|
||||
const obj = JSON.parse(jsonString)
|
||||
objects.push(obj)
|
||||
visualStore.setLoadingProgress(
|
||||
'Loading objects from storage',
|
||||
(objects.length + loadedObjectCount) / totalObjectCount
|
||||
)
|
||||
// console.log('Loading', (objects.length + loadedObjectCount) / totalObjectCount)
|
||||
|
||||
// console.log('Received object:', jsonObject)
|
||||
} catch (e) {
|
||||
@@ -353,16 +303,11 @@ export async function processMatrixView(
|
||||
|
||||
if (visualStore.lastLoadedRootObjectId !== id && !visualStore.isLoadingFromFile) {
|
||||
const start = performance.now()
|
||||
visualStore.setViewerReadyToLoad()
|
||||
visualStore.setLoadingProgress('Loading', null)
|
||||
|
||||
const getPreGetObjectsRes: PreGetObjects[] = await getPreGetObjects(id)
|
||||
|
||||
if (getPreGetObjectsRes.some((preGetObjects) => preGetObjects.modelExists === false)) {
|
||||
visualStore.setCommonError(
|
||||
'Version Object ID is not found in storage. Please make sure you placed correct field or consider refreshing your data via data connector.'
|
||||
)
|
||||
visualStore.setViewerReadyToLoad(false)
|
||||
return
|
||||
}
|
||||
// stream data
|
||||
modelObjects = await fetchStreamedData(id)
|
||||
|
||||
const receiveInfo = await getReceiveInfo(id)
|
||||
if (receiveInfo) {
|
||||
@@ -376,25 +321,11 @@ export async function processMatrixView(
|
||||
version: receiveInfo.version,
|
||||
canHideBranding: receiveInfo.canHideBranding
|
||||
})
|
||||
console.log(`Receive info retrieved from desktop service`, receiveInfo)
|
||||
}
|
||||
|
||||
const totalObjectCount = getPreGetObjectsRes.reduce((sum, obj) => {
|
||||
return sum + (obj.objectCount ?? 0)
|
||||
}, 0)
|
||||
|
||||
visualStore.setViewerReadyToLoad(true)
|
||||
// stream data
|
||||
modelObjects = await fetchStreamedData(id, totalObjectCount)
|
||||
|
||||
visualStore.setViewerReloadNeeded() // they should be marked as deferred action bc of update function complexity.
|
||||
visualStore.setLoadingProgress('Loading objects into viewer', null)
|
||||
console.log(`🚀 Upload is completed in ${(performance.now() - start) / 1000} s!`)
|
||||
}
|
||||
|
||||
if (visualStore.receiveInfo && visualStore.receiveInfo.version) {
|
||||
const { checkUpdate } = useUpdateConnector()
|
||||
await checkUpdate()
|
||||
console.log(`🚀 Upload is completed in ${(performance.now() - start) / 1000} s!`)
|
||||
}
|
||||
|
||||
// If colors assigned, data arrives nested
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="visualStore.loadingProgress"
|
||||
class="absolute top-1/2 left-1/2 w-1/2 -translate-x-1/2 z-20 text-center text-sm"
|
||||
>
|
||||
<!-- Progress Bar -->
|
||||
<LoadingBar :loading="!!visualStore.loadingProgress"></LoadingBar>
|
||||
</div>
|
||||
|
||||
<viewer-wrapper id="speckle-3d-view" class="h-full w-full cursor-default"></viewer-wrapper>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ViewerWrapper from 'src/components/ViewerWrapper.vue'
|
||||
import { useVisualStore } from '../store/visualStore'
|
||||
import LoadingBar from '@src/components/loading/LoadingBar.vue'
|
||||
|
||||
const visualStore = useVisualStore()
|
||||
</script>
|
||||
|
||||
@@ -4,7 +4,6 @@ import '../style/visual.css'
|
||||
import { FormattingSettingsService } from 'powerbi-visuals-utils-formattingmodel'
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import VueTippy from 'vue-tippy'
|
||||
import { selectionHandlerKey, tooltipHandlerKey } from 'src/injectionKeys'
|
||||
|
||||
import { SpeckleDataInput } from './types'
|
||||
@@ -47,11 +46,6 @@ export class Visual implements IVisual {
|
||||
console.log('🚀 Init Vue App')
|
||||
createApp(App)
|
||||
.use(pinia)
|
||||
.use(VueTippy, {
|
||||
defaultProps: {
|
||||
theme: 'custom'
|
||||
}
|
||||
})
|
||||
// .use(store, storeKey)
|
||||
.provide(selectionHandlerKey, this.selectionHandler)
|
||||
.provide(tooltipHandlerKey, this.tooltipHandler)
|
||||
@@ -69,10 +63,6 @@ export class Visual implements IVisual {
|
||||
|
||||
public async update(options: VisualUpdateOptions) {
|
||||
const visualStore = useVisualStore()
|
||||
if (visualStore.commonError) {
|
||||
visualStore.setCommonError(undefined)
|
||||
visualStore.setViewerReadyToLoad(false)
|
||||
}
|
||||
|
||||
if (visualStore.postFileSaveSkipNeeded) {
|
||||
visualStore.setPostFileSaveSkipNeeded(false)
|
||||
@@ -136,20 +126,7 @@ export class Visual implements IVisual {
|
||||
)
|
||||
}
|
||||
|
||||
if (options.dataViews[0].metadata.objects.workspace?.brandingHidden as boolean) {
|
||||
console.log(
|
||||
`Branding Hidden: ${
|
||||
options.dataViews[0].metadata.objects.workspace?.brandingHidden as boolean
|
||||
}`
|
||||
)
|
||||
|
||||
visualStore.setBrandingHidden(
|
||||
options.dataViews[0].metadata.objects.workspace?.brandingHidden as boolean
|
||||
)
|
||||
}
|
||||
|
||||
if (options.dataViews[0].metadata.objects.cameraPosition?.positionX as string) {
|
||||
console.log(`Stored camera position is found`)
|
||||
visualStore.setCameraPositionInFile([
|
||||
Number(options.dataViews[0].metadata.objects.cameraPosition?.positionX),
|
||||
Number(options.dataViews[0].metadata.objects.cameraPosition?.positionY),
|
||||
@@ -159,31 +136,6 @@ export class Visual implements IVisual {
|
||||
Number(options.dataViews[0].metadata.objects.cameraPosition?.targetZ)
|
||||
])
|
||||
}
|
||||
|
||||
const camera = options.dataViews[0].metadata.objects.camera
|
||||
|
||||
if (camera && 'isOrtho' in camera) {
|
||||
console.log(
|
||||
`Projection is ortho?: ${
|
||||
options.dataViews[0].metadata.objects.camera?.isOrtho as boolean
|
||||
}`
|
||||
)
|
||||
|
||||
visualStore.setIsOrthoProjection(
|
||||
options.dataViews[0].metadata.objects.camera?.isOrtho as boolean
|
||||
)
|
||||
}
|
||||
|
||||
if (camera && 'isGhost' in camera) {
|
||||
console.log(
|
||||
`Is ghost?: ${options.dataViews[0].metadata.objects.camera?.isGhost as boolean}`
|
||||
)
|
||||
|
||||
visualStore.setIsGhost(
|
||||
options.dataViews[0].metadata.objects.camera?.isGhost as boolean
|
||||
)
|
||||
}
|
||||
|
||||
// get receive info from file for mixpanel
|
||||
try {
|
||||
const receiveInfoFromFile = JSON.parse(
|
||||
@@ -194,8 +146,8 @@ export class Visual implements IVisual {
|
||||
console.warn(error)
|
||||
console.log('missing mixpanel info')
|
||||
}
|
||||
|
||||
const savedVersionObjectId = objectsFromFile.map((o) => o[0].id).join(',')
|
||||
|
||||
if (visualStore.lastLoadedRootObjectId !== savedVersionObjectId) {
|
||||
this.tryReadFromFile(objectsFromFile, visualStore)
|
||||
}
|
||||
@@ -246,7 +198,7 @@ export class Visual implements IVisual {
|
||||
const visualStore = useVisualStore()
|
||||
|
||||
this.tooltipHandler.setup(input.objectTooltipData)
|
||||
visualStore.setViewerReadyToLoad(true)
|
||||
visualStore.setViewerReadyToLoad()
|
||||
|
||||
if (visualStore.isViewerInitialized && !visualStore.viewerReloadNeeded) {
|
||||
visualStore.setDataInput(input)
|
||||
@@ -260,7 +212,7 @@ export class Visual implements IVisual {
|
||||
}
|
||||
|
||||
private tryReadFromFile(objectsFromFile: object[][], visualStore) {
|
||||
visualStore.setViewerReadyToLoad(true)
|
||||
visualStore.setViewerReadyToLoad()
|
||||
visualStore.setIsLoadingFromFile(true) // to block unnecessary streaming data if bg service is running
|
||||
setTimeout(() => {
|
||||
visualStore.loadObjectsFromFile(objectsFromFile)
|
||||
|
||||
Reference in New Issue
Block a user