Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f85c029765 | |||
| 8aeb882e9e | |||
| f01e0d3e89 | |||
| 47170c0241 | |||
| 4c898679ad | |||
| 809ea7bb8d | |||
| 4995ecf28e | |||
| 99cc44f518 | |||
| 925ece5fa6 | |||
| b547b9e237 | |||
| 208d937698 | |||
| 488deca574 | |||
| cf9112c714 | |||
| f11378e1d2 | |||
| 95be883b16 | |||
| 985064287b | |||
| f5f99ba3b2 | |||
| 22fd03125a | |||
| ce193b41ad | |||
| e3af320f4d | |||
| dfb5fd444a | |||
| ffdb9cd2b5 | |||
| 872c2552c0 | |||
| 56f9f2747a | |||
| 5a407d45f0 | |||
| 89e6706ee4 | |||
| 1dee2e50fb | |||
| 9fba4d6207 | |||
| f1968ac990 | |||
| 5425bfae33 | |||
| c1c48eeb1e | |||
| 2dcb21bbd6 | |||
| 0dc2d8abc6 | |||
| f55d410911 | |||
| 1a5837e28a | |||
| 79d3d15e9e | |||
| cfe0078fc6 | |||
| b9cacf8440 | |||
| 7002f75c6d | |||
| 6d3fd718ba | |||
| 3a361a9e16 | |||
| f751c45cf6 | |||
| 9c7e5f5276 | |||
| e135471d4a | |||
| e345c2aef8 | |||
| a2b523aadc | |||
| 17e484301c | |||
| 7f73317bc4 | |||
| ebc7707b01 | |||
| 11dcaa6bfd | |||
| 5e057b2ed0 | |||
| 288e113d57 | |||
| 6f2d4b0dd5 | |||
| bdb78f2d2f | |||
| 5b902578fc | |||
| 4579ead966 | |||
| 9dc13940eb | |||
| 6ec73b1aad | |||
| 0830c4c818 | |||
| 71d76a239a | |||
| 3170255986 | |||
| dc25e59ada | |||
| 5d82e2ab5f | |||
| f8c91ec062 | |||
| 17d0e93ec7 | |||
| b3bc596e84 | |||
| d0fbb350d3 | |||
| f8c1d9f96e | |||
| 5cb0a1eafd | |||
| ceccc31ec7 | |||
| 1d0798081d | |||
| 5b40aab84c | |||
| 4e8a6015cb | |||
| 8b9d950376 | |||
| 9eef750890 | |||
| 92dd0583f9 | |||
| 2cb1207999 | |||
| 1289a2a842 | |||
| a44fa41bbf |
@@ -1,78 +0,0 @@
|
|||||||
name: Update issue Status
|
|
||||||
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types: [closed]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update_issue:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Get project data
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
|
|
||||||
ORGANIZATION: specklesystems
|
|
||||||
PROJECT_NUMBER: 9
|
|
||||||
run: |
|
|
||||||
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
|
|
||||||
query($org: String!, $number: Int!) {
|
|
||||||
organization(login: $org){
|
|
||||||
projectNext(number: $number) {
|
|
||||||
id
|
|
||||||
fields(first:20) {
|
|
||||||
nodes {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
settings
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
|
|
||||||
|
|
||||||
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
|
|
||||||
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
|
|
||||||
|
|
||||||
echo "$PROJECT_ID"
|
|
||||||
echo "$STATUS_FIELD_ID"
|
|
||||||
|
|
||||||
echo 'DONE_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .settings | fromjson | .options[] | select(.name== "Done") | .id' project_data.json) >> $GITHUB_ENV
|
|
||||||
echo "$DONE_ID"
|
|
||||||
|
|
||||||
- name: Add Issue to project #it's already in the project, but we do this to get its node id!
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
|
|
||||||
ISSUE_ID: ${{ github.event.issue.node_id }}
|
|
||||||
run: |
|
|
||||||
item_id="$( gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
|
|
||||||
mutation($project:ID!, $id:ID!) {
|
|
||||||
addProjectNextItem(input: {projectId: $project, contentId: $id}) {
|
|
||||||
projectNextItem {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}' -f project=$PROJECT_ID -f id=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
|
|
||||||
|
|
||||||
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Update Status
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
|
|
||||||
ISSUE_ID: ${{ github.event.issue.node_id }}
|
|
||||||
run: |
|
|
||||||
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
|
|
||||||
mutation($project:ID!, $status:ID!, $id:ID!, $value:String!) {
|
|
||||||
set_status: updateProjectNextItemField(
|
|
||||||
input: {
|
|
||||||
projectId: $project
|
|
||||||
itemId: $id
|
|
||||||
fieldId: $status
|
|
||||||
value: $value
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
projectNextItem {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}' -f project=$PROJECT_ID -f status=$STATUS_FIELD_ID -f id=$ITEM_ID -f value=${{ env.DONE_ID }}
|
|
||||||
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
name: Move new issues into Project
|
|
||||||
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types: [opened]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
track_issue:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Get project data
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
|
|
||||||
ORGANIZATION: specklesystems
|
|
||||||
PROJECT_NUMBER: 9
|
|
||||||
run: |
|
|
||||||
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
|
|
||||||
query($org: String!, $number: Int!) {
|
|
||||||
organization(login: $org){
|
|
||||||
projectNext(number: $number) {
|
|
||||||
id
|
|
||||||
fields(first:20) {
|
|
||||||
nodes {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
settings
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
|
|
||||||
|
|
||||||
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
|
|
||||||
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Add Issue to project
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
|
|
||||||
ISSUE_ID: ${{ github.event.issue.node_id }}
|
|
||||||
run: |
|
|
||||||
item_id="$( gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
|
|
||||||
mutation($project:ID!, $id:ID!) {
|
|
||||||
addProjectNextItem(input: {projectId: $project, contentId: $id}) {
|
|
||||||
projectNextItem {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}' -f project=$PROJECT_ID -f id=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
|
|
||||||
|
|
||||||
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
|
|
||||||
@@ -15,7 +15,6 @@ pnpm-debug.log*
|
|||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
|
||||||
*.suo
|
*.suo
|
||||||
*.ntvs*
|
*.ntvs*
|
||||||
*.njsproj
|
*.njsproj
|
||||||
|
|||||||
Vendored
+23
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Excel Desktop",
|
||||||
|
"type": "msedge",
|
||||||
|
"request": "attach",
|
||||||
|
"useWebView": true,
|
||||||
|
"port": 9229,
|
||||||
|
"timeout": 600000,
|
||||||
|
"webRoot": "${workspaceFolder}/src",
|
||||||
|
"preLaunchTask": "npm: excel",
|
||||||
|
"sourceMapPathOverrides": {
|
||||||
|
"webpack:///src/*": "${webRoot}/*",
|
||||||
|
"webpack:///src/*.vue": "${webRoot}/*.vue",
|
||||||
|
"webpack:///./src/*.js": "${webRoot}/*.js",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ Comprehensive developer and user documentation can be found in our:
|
|||||||
For developing and debugging this connector you'll need to set up a Speckle App.
|
For developing and debugging this connector you'll need to set up a Speckle App.
|
||||||
The server on which the app runs must be on `https`, so **do not use a local Speckle server** on `http://localhost:3000/` as it will not work.
|
The server on which the app runs must be on `https`, so **do not use a local Speckle server** on `http://localhost:3000/` as it will not work.
|
||||||
|
|
||||||
You can use `https://latest.speckle.dev/` or `https://speckle.xyz/`.
|
You can use `https://latest.speckle.systems/` or `https://app.speckle.systems/`.
|
||||||
|
|
||||||
Now open up its frontend, and under your profile register a new app.
|
Now open up its frontend, and under your profile register a new app.
|
||||||
|
|
||||||
|
|||||||
Generated
+915
-118
File diff suppressed because it is too large
Load Diff
+7
-3
@@ -6,23 +6,27 @@
|
|||||||
"build": "vue-cli-service build",
|
"build": "vue-cli-service build",
|
||||||
"lint": "vue-cli-service lint",
|
"lint": "vue-cli-service lint",
|
||||||
"excel": "office-addin-debugging start public/manifest.xml",
|
"excel": "office-addin-debugging start public/manifest.xml",
|
||||||
"excel:web": "office-addin-debugging start public/manifest.xml web --document https://teocomi-my.sharepoint.com/:x:/g/personal/teocomi_teocomi_onmicrosoft_com/EdxKPPFhnMdDoGclr4J-xJAB7H6-TRJ5s5ZnXzVdrdFyUg?e=VAFmBN",
|
"excel:web": "office-addin-debugging start public/manifest.xml web --document https://skfn3-my.sharepoint.com/:x:/r/personal/connorisadmin_skfn3_onmicrosoft_com/_layouts/15/Doc.aspx?sourcedoc=%7B46999CFC-9B5A-4716-9F71-FECAEA59A6E8%7D",
|
||||||
|
"excel:web2": "office-addin-debugging start public/manifest.xml web --document https://teocomi-my.sharepoint.com/:x:/g/personal/teocomi_teocomi_onmicrosoft_com/EdxKPPFhnMdDoGclr4J-xJABLf-Ao6CJ902W95kcFPL2fA?e=mD0B8a",
|
||||||
"excel:prod": "office-addin-debugging start public/manifest-prod.xml",
|
"excel:prod": "office-addin-debugging start public/manifest-prod.xml",
|
||||||
"excel:web-prod": "office-addin-debugging start public/manifest-prod.xml web --document https://teocomi-my.sharepoint.com/:x:/g/personal/teocomi_teocomi_onmicrosoft_com/EdxKPPFhnMdDoGclr4J-xJAB7H6-TRJ5s5ZnXzVdrdFyUg?e=VAFmBN",
|
"excel:web-prod": "office-addin-debugging start public/manifest-prod.xml web --document https://teocomi-my.sharepoint.com/:x:/g/personal/teocomi_teocomi_onmicrosoft_com/EdxKPPFhnMdDoGclr4J-xJAB7H6-TRJ5s5ZnXzVdrdFyUg?e=VAFmBN",
|
||||||
"stop": "office-addin-debugging stop public/manifest.xml",
|
"stop": "office-addin-debugging stop public/manifest.xml",
|
||||||
"validate": "office-toolbox validate -m public/manifest.xml"
|
"validate": "office-toolbox validate -m public/manifest.xml"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@speckle/objectloader": "^2.3.0",
|
"@mdi/font": "^7.2.96",
|
||||||
|
"@speckle/objectloader": "^2.9.0",
|
||||||
|
"@speckle/viewer": "^2.14.7",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
|
"crypto-js": "^4.1.1",
|
||||||
"flat": "^5.0.2",
|
"flat": "^5.0.2",
|
||||||
"v-tooltip": "^2.1.3",
|
"v-tooltip": "^2.1.3",
|
||||||
"vue": "^2.6.12",
|
"vue": "^2.6.12",
|
||||||
"vue-apollo": "^3.0.5",
|
"vue-apollo": "^3.0.5",
|
||||||
"vue-infinite-loading": "^2.4.5",
|
"vue-infinite-loading": "^2.4.5",
|
||||||
|
"vue-mixpanel": "1.0.7",
|
||||||
"vue-router": "^3.5.1",
|
"vue-router": "^3.5.1",
|
||||||
"vue-timeago": "^5.1.3",
|
"vue-timeago": "^5.1.3",
|
||||||
"vue-mixpanel": "1.0.7",
|
|
||||||
"vuetify": "^2.5.0",
|
"vuetify": "^2.5.0",
|
||||||
"vuex": "^3.6.2",
|
"vuex": "^3.6.2",
|
||||||
"vuex-persist": "^3.1.3",
|
"vuex-persist": "^3.1.3",
|
||||||
|
|||||||
+4
-5
@@ -11,10 +11,6 @@
|
|||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"
|
href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"
|
||||||
/>
|
/>
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css"
|
|
||||||
/>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
@@ -25,7 +21,10 @@
|
|||||||
</noscript>
|
</noscript>
|
||||||
<!-- built files will be auto injected -->
|
<!-- built files will be auto injected -->
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
if (window.navigator.userAgent.indexOf('Trident') > -1 || window.navigator.userAgent.indexOf('Edge') > -1) {
|
if (
|
||||||
|
window.navigator.userAgent.indexOf('Trident') > -1 ||
|
||||||
|
window.navigator.userAgent.indexOf('Edge') > -1
|
||||||
|
) {
|
||||||
//render unsupported message
|
//render unsupported message
|
||||||
document.write(
|
document.write(
|
||||||
'<h1><br/>Sorry, this Excel version is not supported as it still uses IE 11/Legacy Edge.<br/>Please update it or use Excel Online.</h1>'
|
'<h1><br/>Sorry, this Excel version is not supported as it still uses IE 11/Legacy Edge.<br/>Please update it or use Excel Online.</h1>'
|
||||||
|
|||||||
+2
-2
@@ -22,8 +22,8 @@
|
|||||||
<SupportUrl DefaultValue="https://speckle.guide/user/excel.html" />
|
<SupportUrl DefaultValue="https://speckle.guide/user/excel.html" />
|
||||||
|
|
||||||
<AppDomains>
|
<AppDomains>
|
||||||
<AppDomain>https://latest.speckle.dev</AppDomain>
|
<AppDomain>https://latest.speckle.systems</AppDomain>
|
||||||
<AppDomain>https://speckle.xyz</AppDomain>
|
<AppDomain>https://app.speckle.systems</AppDomain>
|
||||||
<AppDomain>https://localhost:3000</AppDomain>
|
<AppDomain>https://localhost:3000</AppDomain>
|
||||||
</AppDomains>
|
</AppDomains>
|
||||||
|
|
||||||
|
|||||||
@@ -110,11 +110,6 @@ export default {
|
|||||||
drawer: null,
|
drawer: null,
|
||||||
showSnackbar: false,
|
showSnackbar: false,
|
||||||
items: [
|
items: [
|
||||||
{
|
|
||||||
name: 'Add stream',
|
|
||||||
icon: '➕',
|
|
||||||
to: '/add'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'Streams',
|
name: 'Streams',
|
||||||
icon: '📃',
|
icon: '📃',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card class="pa-5 mb-3" style="transition: all 0.2s" @click="addStream">
|
<v-card class="pa-5 mb-3" style="transition: all 0.2s" @click="openStream">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="8" class="align-self-center">
|
<v-col cols="12" sm="8" class="align-self-center">
|
||||||
<div class="subtitle-1">
|
<div class="subtitle-1">
|
||||||
@@ -47,14 +47,16 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="4" class="text-sm-center text-md-right align-self-center">
|
<v-col cols="12" sm="4" class="text-sm-center text-md-right align-self-center">
|
||||||
<div>
|
<div>
|
||||||
<user-avatar
|
<span v-for="user in collaboratorsSlice" :key="user.id">
|
||||||
v-for="user in collaboratorsSlice"
|
<user-avatar
|
||||||
:id="user.id"
|
v-if="user.id"
|
||||||
:key="user.id"
|
:id="user.id"
|
||||||
:avatar="user.avatar"
|
:avatar="user.avatar"
|
||||||
:size="30"
|
:size="30"
|
||||||
:name="user.name"
|
:name="user.name"
|
||||||
/>
|
/>
|
||||||
|
</span>
|
||||||
|
|
||||||
<div v-if="stream.collaborators.length > collaboratorsSlice.length" class="d-inline">
|
<div v-if="stream.collaborators.length > collaboratorsSlice.length" class="d-inline">
|
||||||
<v-avatar class="ma-1 grey--text text--darken-2" color="grey lighten-3" size="30">
|
<v-avatar class="ma-1 grey--text text--darken-2" color="grey lighten-3" size="30">
|
||||||
<b>+{{ stream.collaborators.length - collaboratorsSlice.length }}</b>
|
<b>+{{ stream.collaborators.length - collaboratorsSlice.length }}</b>
|
||||||
@@ -80,22 +82,22 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
collaboratorsSlice() {
|
collaboratorsSlice() {
|
||||||
let limit = 18
|
let limit = 6
|
||||||
switch (this.$vuetify.breakpoint.name) {
|
switch (this.$vuetify.breakpoint.name) {
|
||||||
case 'xs':
|
case 'xs':
|
||||||
limit = 10
|
limit = 5
|
||||||
break
|
break
|
||||||
case 'sm':
|
case 'sm':
|
||||||
limit = 9
|
limit = 5
|
||||||
break
|
break
|
||||||
case 'md':
|
case 'md':
|
||||||
limit = 8
|
limit = 4
|
||||||
break
|
break
|
||||||
case 'lg':
|
case 'lg':
|
||||||
limit = 12
|
limit = 6
|
||||||
break
|
break
|
||||||
case 'xl':
|
case 'xl':
|
||||||
limit = 18
|
limit = 6
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,6 +122,9 @@ export default {
|
|||||||
selectedCommitId: null
|
selectedCommitId: null
|
||||||
})
|
})
|
||||||
this.$router.push('/')
|
this.$router.push('/')
|
||||||
|
},
|
||||||
|
openStream() {
|
||||||
|
this.$router.push(`/streams/${this.stream.id}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,8 @@
|
|||||||
:stream-id="streamId"
|
:stream-id="streamId"
|
||||||
:commit-id="commitId"
|
:commit-id="commitId"
|
||||||
:commit-msg="commitMsg"
|
:commit-msg="commitMsg"
|
||||||
|
:nearest-object-id="nearestObjectId"
|
||||||
|
:path-from-nearest-object="`${entry.pathFromNearestObject}`"
|
||||||
></component>
|
></component>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-text v-if="localExpand && currentLimit < value.length">
|
<v-card-text v-if="localExpand && currentLimit < value.length">
|
||||||
@@ -77,6 +79,14 @@ export default {
|
|||||||
commitMsg: {
|
commitMsg: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null
|
||||||
|
},
|
||||||
|
nearestObjectId: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
pathFromNearestObject: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -91,13 +101,17 @@ export default {
|
|||||||
rangeEntries() {
|
rangeEntries() {
|
||||||
let arr = []
|
let arr = []
|
||||||
let index = 0
|
let index = 0
|
||||||
|
const delimiter = ':::'
|
||||||
for (let val of this.range) {
|
for (let val of this.range) {
|
||||||
index++
|
index++
|
||||||
if (Array.isArray(val)) {
|
if (Array.isArray(val)) {
|
||||||
arr.push({
|
arr.push({
|
||||||
key: `${index}`,
|
key: `${index}`,
|
||||||
value: val,
|
value: val,
|
||||||
type: 'ObjectListViewer'
|
type: 'ObjectListViewer',
|
||||||
|
pathFromNearestObject: this.pathFromNearestObject
|
||||||
|
? this.pathFromNearestObject + (index - 1) + delimiter
|
||||||
|
: index - 1 + delimiter
|
||||||
})
|
})
|
||||||
} else if (typeof val === 'object' && val !== null) {
|
} else if (typeof val === 'object' && val !== null) {
|
||||||
if (val.speckle_type && val.speckle_type === 'reference') {
|
if (val.speckle_type && val.speckle_type === 'reference') {
|
||||||
@@ -155,8 +169,11 @@ export default {
|
|||||||
this.commitId,
|
this.commitId,
|
||||||
this.commitMsg,
|
this.commitMsg,
|
||||||
this.$refs.modal,
|
this.$refs.modal,
|
||||||
ac.signal
|
ac.signal,
|
||||||
|
this.nearestObjectId,
|
||||||
|
this.pathFromNearestObject
|
||||||
)
|
)
|
||||||
|
|
||||||
if (receiverSelection) {
|
if (receiverSelection) {
|
||||||
receiverSelection.fullKeyName = this.fullKeyName
|
receiverSelection.fullKeyName = this.fullKeyName
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,14 @@ export default {
|
|||||||
commitMsg: {
|
commitMsg: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null
|
||||||
|
},
|
||||||
|
nearestObjectId: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
pathFromNearestObject: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|||||||
@@ -41,6 +41,8 @@
|
|||||||
:stream-id="streamId"
|
:stream-id="streamId"
|
||||||
:commit-id="commitId"
|
:commit-id="commitId"
|
||||||
:commit-msg="commitMsg"
|
:commit-msg="commitMsg"
|
||||||
|
:nearest-object-id="updatedObjectId"
|
||||||
|
:path-from-nearest-object="`${entry.pathFromNearestObject}`"
|
||||||
></component>
|
></component>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<filter-modal ref="modal" />
|
<filter-modal ref="modal" />
|
||||||
@@ -93,12 +95,22 @@ export default {
|
|||||||
commitMsg: {
|
commitMsg: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null
|
||||||
|
},
|
||||||
|
nearestObjectId: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
pathFromNearestObject: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
localExpand: false,
|
localExpand: false,
|
||||||
progress: false
|
progress: false,
|
||||||
|
objectEntries: null,
|
||||||
|
updatedObjectId: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
apollo: {
|
apollo: {
|
||||||
@@ -111,6 +123,9 @@ export default {
|
|||||||
id: this.value.referencedId
|
id: this.value.referencedId
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
result() {
|
||||||
|
this.objectEntries = this.getObjectEntries()
|
||||||
|
},
|
||||||
skip() {
|
skip() {
|
||||||
return !this.localExpand
|
return !this.localExpand
|
||||||
},
|
},
|
||||||
@@ -120,11 +135,24 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
mounted() {
|
||||||
objectEntries() {
|
this.localExpand = this.expand
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleLoadExpand() {
|
||||||
|
this.localExpand = !this.localExpand
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
ac.abort()
|
||||||
|
},
|
||||||
|
getObjectEntries() {
|
||||||
if (!this.object) return []
|
if (!this.object) return []
|
||||||
let entries = Object.entries(this.object.data)
|
let entries = Object.entries(this.object.data)
|
||||||
let arr = []
|
let arr = []
|
||||||
|
this.updatedObjectId = this.object.data.id ?? this.nearestObjectId
|
||||||
|
const delimiter = ':::'
|
||||||
|
console.log(this.updatedObjectId, delimiter)
|
||||||
|
console.log(this.nearestObjectId)
|
||||||
for (let [key, val] of entries) {
|
for (let [key, val] of entries) {
|
||||||
let name = key
|
let name = key
|
||||||
if (key.startsWith('__')) continue
|
if (key.startsWith('__')) continue
|
||||||
@@ -138,7 +166,7 @@ export default {
|
|||||||
name,
|
name,
|
||||||
value: val,
|
value: val,
|
||||||
type: 'ObjectListViewer',
|
type: 'ObjectListViewer',
|
||||||
description: `List (${val.length} elements)`
|
pathFromNearestObject: key + delimiter
|
||||||
})
|
})
|
||||||
} else if (typeof val === 'object' && val !== null) {
|
} else if (typeof val === 'object' && val !== null) {
|
||||||
if (val.speckle_type && val.speckle_type === 'reference') {
|
if (val.speckle_type && val.speckle_type === 'reference') {
|
||||||
@@ -171,17 +199,6 @@ export default {
|
|||||||
return 0
|
return 0
|
||||||
})
|
})
|
||||||
return arr
|
return arr
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.localExpand = this.expand
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
toggleLoadExpand() {
|
|
||||||
this.localExpand = !this.localExpand
|
|
||||||
},
|
|
||||||
cancel() {
|
|
||||||
ac.abort()
|
|
||||||
},
|
},
|
||||||
async bake() {
|
async bake() {
|
||||||
this.progress = true
|
this.progress = true
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ export default {
|
|||||||
commitMsg: {
|
commitMsg: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null
|
||||||
|
},
|
||||||
|
nearestObjectId: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,521 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<v-card v-if="error" class="pa-5 mb-3" style="transition: all 0.2s">
|
|
||||||
<v-card-title class="subtitle-1 px-0 pt-0">
|
|
||||||
{{ error }} ⚠️
|
|
||||||
<div class="floating">
|
|
||||||
<v-btn
|
|
||||||
v-tooltip="`Remove this stream from the document`"
|
|
||||||
small
|
|
||||||
icon
|
|
||||||
color="red"
|
|
||||||
@click="remove"
|
|
||||||
>
|
|
||||||
<v-icon small>mdi-minus-circle-outline</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn
|
|
||||||
v-tooltip="`Open this stream in a new window`"
|
|
||||||
small
|
|
||||||
icon
|
|
||||||
color="primary"
|
|
||||||
:href="`${serverUrl}/streams/${savedStream.id}`"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<v-icon small>mdi-open-in-new</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</div>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text class="px-0">
|
|
||||||
<span v-if="error == 'Stream not found'">
|
|
||||||
The stream might have been deleted or belog to another Speckle server
|
|
||||||
</span>
|
|
||||||
<span v-if="error == 'You do not have access to this resource.'">
|
|
||||||
Please ask the stream owner for access or to make it public
|
|
||||||
</span>
|
|
||||||
<br />
|
|
||||||
Stream Id: {{ savedStream.id }}
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
<div v-else-if="$apollo.queries.stream.loading" class="mx-0 mb-3">
|
|
||||||
<v-skeleton-loader type="article"></v-skeleton-loader>
|
|
||||||
</div>
|
|
||||||
<v-card v-else-if="stream" class="pa-5 mb-3" style="transition: all 0.2s">
|
|
||||||
<v-row>
|
|
||||||
<v-col class="align-self-center">
|
|
||||||
<div class="subtitle-1">
|
|
||||||
{{ stream.name }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="floating">
|
|
||||||
<v-btn
|
|
||||||
v-tooltip="`Remove this stream from the document`"
|
|
||||||
small
|
|
||||||
icon
|
|
||||||
color="red"
|
|
||||||
@click="remove"
|
|
||||||
>
|
|
||||||
<v-icon small>mdi-minus-circle-outline</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn
|
|
||||||
v-tooltip="`Open this stream in a new window`"
|
|
||||||
small
|
|
||||||
icon
|
|
||||||
color="primary"
|
|
||||||
:href="`${serverUrl}/streams/${stream.id}/branches/${selectedBranch.name}`"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<v-icon small>mdi-open-in-new</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
|
|
||||||
<v-btn
|
|
||||||
v-if="stream.role != 'stream:reviewer'"
|
|
||||||
v-tooltip="`Click to make this a ` + (savedStream.isReceiver ? `sender` : `receiver`)"
|
|
||||||
small
|
|
||||||
icon
|
|
||||||
color="primary"
|
|
||||||
@click="swapReceiver"
|
|
||||||
>
|
|
||||||
<v-icon small>mdi-swap-horizontal</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</div>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row class="stream-card-select">
|
|
||||||
<v-col cols="6" class="pa-0 align-self-center">
|
|
||||||
<v-select
|
|
||||||
v-if="stream.branches"
|
|
||||||
v-model="selectedBranch"
|
|
||||||
:items="stream.branches.items"
|
|
||||||
item-value="name"
|
|
||||||
solo
|
|
||||||
flat
|
|
||||||
style="width: 100%"
|
|
||||||
dense
|
|
||||||
return-object
|
|
||||||
class="d-inline-block mb-0 pb-0"
|
|
||||||
>
|
|
||||||
<template #selection="{ item }">
|
|
||||||
<v-icon color="primary" small class="mr-1">mdi-source-branch</v-icon>
|
|
||||||
<span class="text-truncate caption primary--text">{{ item.name }}</span>
|
|
||||||
</template>
|
|
||||||
<template #item="{ item }">
|
|
||||||
<div class="pa-2">
|
|
||||||
<p class="pa-0 ma-0 caption">{{ item.name }}</p>
|
|
||||||
<p class="caption pa-0 ma-0 grey--text font-weight-light">
|
|
||||||
{{ item.description }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="6" class="pa-0 align-self-start">
|
|
||||||
<div v-if="savedStream.isReceiver">
|
|
||||||
<v-select
|
|
||||||
v-if="
|
|
||||||
selectedBranch && selectedBranch.commits && selectedBranch.commits.items.length > 0
|
|
||||||
"
|
|
||||||
v-model="selectedCommit"
|
|
||||||
:items="selectedBranch.commits.items"
|
|
||||||
item-value="id"
|
|
||||||
solo
|
|
||||||
flat
|
|
||||||
dense
|
|
||||||
style="width: 100%"
|
|
||||||
return-object
|
|
||||||
class="d-inline-block mb-0 pb-0"
|
|
||||||
>
|
|
||||||
<template #selection="{ item }">
|
|
||||||
<v-icon color="primary" small class="mr-1">mdi-source-commit</v-icon>
|
|
||||||
<span class="text-truncate caption primary--text">
|
|
||||||
{{ formatCommitName(item.id) }}
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<template #item="{ item }">
|
|
||||||
<div class="pa-2">
|
|
||||||
<p class="pa-0 ma-0 caption">
|
|
||||||
{{ item.id }}
|
|
||||||
<span v-if="formatCommitName(item.id) == 'latest'">(latest)</span>
|
|
||||||
</p>
|
|
||||||
<p class="caption pa-0 ma-0 grey--text font-weight-light">
|
|
||||||
{{ item.message }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</v-select>
|
|
||||||
<div v-else class="text-truncate caption mt-2 ml-3">
|
|
||||||
<span>No commits to receive</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="caption d-inline-flex" style="width: 100%">
|
|
||||||
<span
|
|
||||||
v-if="savedStream.selection"
|
|
||||||
v-tooltip="savedStream.selection"
|
|
||||||
class="mt-2 ml-2 text-truncate"
|
|
||||||
>
|
|
||||||
{{ savedStream.selection }}
|
|
||||||
</span>
|
|
||||||
<span v-else class="mt-2 ml-2">No range set</span>
|
|
||||||
|
|
||||||
<v-menu>
|
|
||||||
<template #activator="{ on, attrs }">
|
|
||||||
<v-btn
|
|
||||||
v-tooltip="`Set or clear range`"
|
|
||||||
icon
|
|
||||||
color="primary"
|
|
||||||
small
|
|
||||||
text
|
|
||||||
v-bind="attrs"
|
|
||||||
class="ml-1 mt-1"
|
|
||||||
v-on="on"
|
|
||||||
>
|
|
||||||
<v-icon small>mdi-dots-vertical</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<v-list>
|
|
||||||
<v-list-item @click="setRange(true)">
|
|
||||||
<v-list-item-title
|
|
||||||
v-tooltip="`Ranges with headers will be sent as objects`"
|
|
||||||
class="caption"
|
|
||||||
>
|
|
||||||
Set range with headers
|
|
||||||
</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item @click="setRange(false)">
|
|
||||||
<v-list-item-title
|
|
||||||
v-tooltip="`Ranges without headers will be sent as data arrays`"
|
|
||||||
class="caption"
|
|
||||||
>
|
|
||||||
Set range
|
|
||||||
</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item v-if="savedStream.selection" @click="clearSelection">
|
|
||||||
<v-list-item-title class="caption">Clear</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-menu>
|
|
||||||
</div>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="savedStream.isReceiver">
|
|
||||||
<v-col class="align-self-center">
|
|
||||||
<v-dialog v-model="progress" persistent>
|
|
||||||
<v-card class="pt-3">
|
|
||||||
<v-card-text class="caption">
|
|
||||||
Receiving data from the Speckleverse...
|
|
||||||
<v-progress-linear class="mt-2" indeterminate color="primary"></v-progress-linear>
|
|
||||||
<v-btn class="mt-3" outlined x-small color="primary" @click="cancel">Cancel</v-btn>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
<v-menu top offset-y>
|
|
||||||
<template #activator="{ on, attrs }">
|
|
||||||
<v-btn
|
|
||||||
:disabled="!selectedCommit"
|
|
||||||
color="primary"
|
|
||||||
class="lower"
|
|
||||||
v-bind="attrs"
|
|
||||||
small
|
|
||||||
v-on="on"
|
|
||||||
>
|
|
||||||
<v-img class="mr-2" width="30" height="30" src="../assets/ReceiverWhite@32.png" />
|
|
||||||
|
|
||||||
Receive
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<v-list v-if="selectedCommit" dense>
|
|
||||||
<v-list-item :to="`/streams/${stream.id}/commits/${selectedCommit.id}`">
|
|
||||||
<v-list-item-action class="mr-2">
|
|
||||||
<v-icon small>mdi-filter-variant</v-icon>
|
|
||||||
</v-list-item-action>
|
|
||||||
<v-list-item-content class="caption">Filter and receive</v-list-item-content>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item :disabled="!savedStream.receiverSelection" @click="receiveLatest">
|
|
||||||
<v-list-item-action class="mr-2">
|
|
||||||
<v-icon small>mdi-update</v-icon>
|
|
||||||
</v-list-item-action>
|
|
||||||
<v-list-item-content class="caption">Receive last selection</v-list-item-content>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-menu>
|
|
||||||
<!-- <span v-if="savedStream.receiverSelection">
|
|
||||||
{{ savedStream.receiverSelection.fullKeyName }}
|
|
||||||
</span> -->
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<v-row v-else>
|
|
||||||
<v-col class="align-self-center d-inline-flex">
|
|
||||||
<v-btn
|
|
||||||
color="primary"
|
|
||||||
small
|
|
||||||
:disabled="!savedStream.selection"
|
|
||||||
class="mt-1"
|
|
||||||
@click="send"
|
|
||||||
>
|
|
||||||
<v-img class="mr-2" width="30" height="30" src="../assets/SenderWhite@32.png" />
|
|
||||||
|
|
||||||
Send
|
|
||||||
</v-btn>
|
|
||||||
|
|
||||||
<v-text-field
|
|
||||||
v-model="message"
|
|
||||||
class="pt-0 mt-0 ml-3 caption"
|
|
||||||
placeholder="Data from Excel"
|
|
||||||
></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import streamQuery from '../graphql/stream.gql'
|
|
||||||
import { send, receiveLatest } from '../plugins/excel'
|
|
||||||
import gql from 'graphql-tag'
|
|
||||||
import { createClient } from '../vue-apollo'
|
|
||||||
|
|
||||||
let ac = new AbortController()
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
savedStream: {
|
|
||||||
type: Object,
|
|
||||||
default: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
error: null,
|
|
||||||
progress: false,
|
|
||||||
message: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
apollo: {
|
|
||||||
stream: {
|
|
||||||
prefetch: true,
|
|
||||||
query: streamQuery,
|
|
||||||
fetchPolicy: 'network-only',
|
|
||||||
variables() {
|
|
||||||
return {
|
|
||||||
id: this.savedStream.id
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error(error) {
|
|
||||||
console.log(this.error)
|
|
||||||
this.error = JSON.stringify(error.message)
|
|
||||||
.replaceAll('"', '')
|
|
||||||
.replace('GraphQL error: ', '')
|
|
||||||
console.log(this.error)
|
|
||||||
},
|
|
||||||
skip() {
|
|
||||||
return this.savedStream === null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
$client: createClient(),
|
|
||||||
$subscribe: {
|
|
||||||
streamUpdated: {
|
|
||||||
query: gql`
|
|
||||||
subscription($id: String!) {
|
|
||||||
streamUpdated(streamId: $id)
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
variables() {
|
|
||||||
return { id: this.savedStream.id }
|
|
||||||
},
|
|
||||||
result() {
|
|
||||||
this.$apollo.queries.stream.refetch()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
commitCreated: {
|
|
||||||
query: gql`
|
|
||||||
subscription($streamId: String!) {
|
|
||||||
commitCreated(streamId: $streamId)
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
variables() {
|
|
||||||
return { streamId: this.savedStream.id }
|
|
||||||
},
|
|
||||||
result(commitInfo) {
|
|
||||||
this.$apollo.queries.stream.refetch()
|
|
||||||
if (this.savedStream.isReceiver)
|
|
||||||
this.$store.dispatch('showSnackbar', {
|
|
||||||
message: `New commit on ${this.stream.name} @ ${commitInfo.data.commitCreated.branchName}`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
commitUpdated: {
|
|
||||||
query: gql`
|
|
||||||
subscription($id: String!) {
|
|
||||||
commitUpdated(streamId: $id)
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
variables() {
|
|
||||||
return { id: this.savedStream.id }
|
|
||||||
},
|
|
||||||
result() {
|
|
||||||
this.$apollo.queries.stream.refetch()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
branchCreated: {
|
|
||||||
query: gql`
|
|
||||||
subscription($id: String!) {
|
|
||||||
branchCreated(streamId: $id)
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
variables() {
|
|
||||||
return { id: this.savedStream.id }
|
|
||||||
},
|
|
||||||
result() {
|
|
||||||
this.$apollo.queries.stream.refetch()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
branchDeleted: {
|
|
||||||
query: gql`
|
|
||||||
subscription($id: String!) {
|
|
||||||
branchDeleted(streamId: $id)
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
variables() {
|
|
||||||
return { id: this.savedStream.id }
|
|
||||||
},
|
|
||||||
result() {
|
|
||||||
this.$apollo.queries.stream.refetch()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
branchUpdated: {
|
|
||||||
query: gql`
|
|
||||||
subscription($id: String!) {
|
|
||||||
branchUpdated(streamId: $id)
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
variables() {
|
|
||||||
return { id: this.savedStream.id }
|
|
||||||
},
|
|
||||||
result() {
|
|
||||||
this.$apollo.queries.stream.refetch()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
serverUrl() {
|
|
||||||
return this.$store.getters.serverUrl
|
|
||||||
},
|
|
||||||
selectedBranch: {
|
|
||||||
get() {
|
|
||||||
if (!this.stream || !this.stream.branches) return null
|
|
||||||
|
|
||||||
let selectedBranchName = this.savedStream.selectedBranchName
|
|
||||||
? this.savedStream.selectedBranchName
|
|
||||||
: 'main'
|
|
||||||
const index = this.stream.branches.items.findIndex((x) => x.name === selectedBranchName)
|
|
||||||
if (index > -1) return this.stream.branches.items[index]
|
|
||||||
return this.stream.branches.items[0]
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
let s = { ...this.savedStream }
|
|
||||||
s.selectedBranchName = value.name
|
|
||||||
this.$store.dispatch('updateStream', s)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectedCommit: {
|
|
||||||
get() {
|
|
||||||
if (!this.selectedBranch || !this.selectedBranch.commits) return null
|
|
||||||
|
|
||||||
//not set or latest, return first
|
|
||||||
if (!this.savedStream.selectedCommitId || this.savedStream.selectedCommitId === 'latest')
|
|
||||||
return this.selectedBranch.commits.items[0]
|
|
||||||
|
|
||||||
//try match by id
|
|
||||||
const index = this.selectedBranch.commits.items.findIndex(
|
|
||||||
(x) => x.id === this.savedStream.selectedCommitId
|
|
||||||
)
|
|
||||||
if (index > -1) return this.selectedBranch.commits.items[index]
|
|
||||||
|
|
||||||
return this.selectedBranch.commits.items[0]
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
let s = { ...this.savedStream }
|
|
||||||
const index = this.selectedBranch.commits.items.findIndex((x) => x.id === value.id)
|
|
||||||
s.selectedCommitId = index === 0 ? 'latest' : value.id
|
|
||||||
this.$store.dispatch('updateStream', s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
swapReceiver() {
|
|
||||||
let s = { ...this.savedStream }
|
|
||||||
s.isReceiver = !s.isReceiver
|
|
||||||
this.$mixpanel.track('Connector Action', { name: 'Stream Swap Receive/Send', type: 'action' })
|
|
||||||
this.$store.dispatch('updateStream', s)
|
|
||||||
},
|
|
||||||
|
|
||||||
async setRange(headers) {
|
|
||||||
await window.Excel.run(async (context) => {
|
|
||||||
let range = context.workbook.getSelectedRange()
|
|
||||||
range.load('address')
|
|
||||||
|
|
||||||
await context.sync()
|
|
||||||
|
|
||||||
let s = { ...this.savedStream }
|
|
||||||
s.selection = range.address
|
|
||||||
s.hasHeaders = headers
|
|
||||||
this.$store.dispatch('updateStream', s)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
clearSelection() {
|
|
||||||
let s = { ...this.savedStream }
|
|
||||||
s.selection = ''
|
|
||||||
this.$store.dispatch('updateStream', s)
|
|
||||||
},
|
|
||||||
|
|
||||||
remove() {
|
|
||||||
this.$mixpanel.track('Connector Action', { name: 'Stream Remove' })
|
|
||||||
return this.$store.dispatch('removeStream', this.savedStream.id)
|
|
||||||
},
|
|
||||||
cancel() {
|
|
||||||
ac.abort()
|
|
||||||
},
|
|
||||||
async send() {
|
|
||||||
this.$mixpanel.track('Send')
|
|
||||||
send(this.savedStream, this.stream.id, this.selectedBranch.name, this.message)
|
|
||||||
},
|
|
||||||
async receiveLatest() {
|
|
||||||
this.$mixpanel.track('Receive')
|
|
||||||
ac = new AbortController()
|
|
||||||
|
|
||||||
console.log(this.savedStream.receiverSelection)
|
|
||||||
|
|
||||||
this.progress = true
|
|
||||||
await receiveLatest(
|
|
||||||
this.selectedCommit.referencedObject,
|
|
||||||
this.stream.id,
|
|
||||||
this.selectedCommit.id,
|
|
||||||
this.selectedCommit.message,
|
|
||||||
this.savedStream.receiverSelection,
|
|
||||||
ac.signal
|
|
||||||
)
|
|
||||||
this.progress = false
|
|
||||||
},
|
|
||||||
formatCommitName(id) {
|
|
||||||
if (this.selectedBranch.commits.items[0].id == id) return 'latest'
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
.stream-card-select .v-text-field__details {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
.v-btn .lower {
|
|
||||||
text-transform: none;
|
|
||||||
}
|
|
||||||
.floating {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -0,0 +1,436 @@
|
|||||||
|
<template>
|
||||||
|
<v-card id="stream-info" class="pa-5 ma-3" style="transition: all 0.2s">
|
||||||
|
<v-row>
|
||||||
|
<v-col class="align-self-center">
|
||||||
|
<div class="subtitle-1">
|
||||||
|
{{ stream.name }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="floating">
|
||||||
|
<v-btn
|
||||||
|
v-tooltip="`Open this stream in a new window`"
|
||||||
|
small
|
||||||
|
icon
|
||||||
|
color="primary"
|
||||||
|
:href="`${serverUrl}/streams/${stream.id}/branches/${selectedBranch.name}`"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<v-icon small>mdi-open-in-new</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
v-if="stream.role != 'stream:reviewer'"
|
||||||
|
v-tooltip="`Click to make this a ` + (isReceiver ? `sender` : `receiver`)"
|
||||||
|
small
|
||||||
|
icon
|
||||||
|
color="primary"
|
||||||
|
@click="swapReceiver"
|
||||||
|
>
|
||||||
|
<v-icon small>mdi-swap-horizontal</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row class="stream-card-select">
|
||||||
|
<v-col cols="6" class="pa-0 align-self-center">
|
||||||
|
<v-select
|
||||||
|
v-if="stream.branches"
|
||||||
|
v-model="selectedBranch"
|
||||||
|
:items="stream.branches.items"
|
||||||
|
item-value="name"
|
||||||
|
solo
|
||||||
|
flat
|
||||||
|
style="width: 100%"
|
||||||
|
dense
|
||||||
|
return-object
|
||||||
|
class="d-inline-block mb-0 pb-0"
|
||||||
|
>
|
||||||
|
<template #selection="{ item }">
|
||||||
|
<v-icon color="primary" small class="mr-1">mdi-source-branch</v-icon>
|
||||||
|
<span class="text-truncate caption primary--text">{{ item.name }}</span>
|
||||||
|
</template>
|
||||||
|
<template #item="{ item }">
|
||||||
|
<div class="pa-2">
|
||||||
|
<p class="pa-0 ma-0 caption">{{ item.name }}</p>
|
||||||
|
<p class="caption pa-0 ma-0 grey--text font-weight-light">
|
||||||
|
{{ item.description }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="6" class="pa-0 align-self-start">
|
||||||
|
<div v-if="isReceiver">
|
||||||
|
<v-select
|
||||||
|
v-if="
|
||||||
|
selectedBranch && selectedBranch.commits && selectedBranch.commits.items.length > 0
|
||||||
|
"
|
||||||
|
v-model="selectedCommit"
|
||||||
|
:items="selectedBranch.commits.items"
|
||||||
|
item-value="id"
|
||||||
|
solo
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
style="width: 100%"
|
||||||
|
return-object
|
||||||
|
class="d-inline-block mb-0 pb-0"
|
||||||
|
>
|
||||||
|
<template #selection="{ item }">
|
||||||
|
<v-icon color="primary" small class="mr-1">mdi-source-commit</v-icon>
|
||||||
|
<span class="text-truncate caption primary--text">
|
||||||
|
{{ formatCommitName(item.id) }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template #item="{ item }">
|
||||||
|
<div class="pa-2">
|
||||||
|
<p class="pa-0 ma-0 caption">
|
||||||
|
{{ item.id }}
|
||||||
|
<span v-if="formatCommitName(item.id) == 'latest'">(latest)</span>
|
||||||
|
</p>
|
||||||
|
<p class="caption pa-0 ma-0 grey--text font-weight-light">
|
||||||
|
{{ item.message }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-select>
|
||||||
|
<div v-else class="text-truncate caption mt-2 ml-3">
|
||||||
|
<span>No commits to receive</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="caption d-inline-flex" style="width: 100%">
|
||||||
|
<span v-if="selection" v-tooltip="selection" class="mt-2 ml-2 text-truncate">
|
||||||
|
{{ selection }}
|
||||||
|
</span>
|
||||||
|
<span v-else class="mt-2 ml-2">No range set</span>
|
||||||
|
|
||||||
|
<v-menu>
|
||||||
|
<template #activator="{ on, attrs }">
|
||||||
|
<v-btn
|
||||||
|
v-tooltip="`Set or clear range`"
|
||||||
|
icon
|
||||||
|
color="primary"
|
||||||
|
small
|
||||||
|
text
|
||||||
|
v-bind="attrs"
|
||||||
|
class="ml-1 mt-1"
|
||||||
|
v-on="on"
|
||||||
|
>
|
||||||
|
<v-icon small>mdi-dots-vertical</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-list>
|
||||||
|
<v-list-item @click="setRange(true)">
|
||||||
|
<v-list-item-title
|
||||||
|
v-tooltip="`Ranges with headers will be sent as objects`"
|
||||||
|
class="caption"
|
||||||
|
>
|
||||||
|
Set range with headers
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item @click="setRange(false)">
|
||||||
|
<v-list-item-title
|
||||||
|
v-tooltip="`Ranges without headers will be sent as data arrays`"
|
||||||
|
class="caption"
|
||||||
|
>
|
||||||
|
Set range
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item v-if="selection" @click="clearSelection">
|
||||||
|
<v-list-item-title class="caption">Clear</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="isReceiver">
|
||||||
|
<v-col class="align-self-center">
|
||||||
|
<v-dialog v-model="progress" persistent>
|
||||||
|
<v-card class="pt-3">
|
||||||
|
<v-card-text class="caption">
|
||||||
|
Receiving data from the Speckleverse...
|
||||||
|
<v-progress-linear class="mt-2" indeterminate color="primary"></v-progress-linear>
|
||||||
|
<v-btn class="mt-3" outlined x-small color="primary" @click="cancel">Cancel</v-btn>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
<v-btn
|
||||||
|
v-if="determinedConversion"
|
||||||
|
color="primary"
|
||||||
|
class="lower"
|
||||||
|
v-bind="attrs"
|
||||||
|
small
|
||||||
|
@click="receiveDeterminedConversion"
|
||||||
|
>
|
||||||
|
<v-img class="mr-2" width="30" height="30" src="../assets/ReceiverWhite@32.png" />
|
||||||
|
Receive {{ determinedConversion }}
|
||||||
|
</v-btn>
|
||||||
|
<v-menu v-else top offset-y>
|
||||||
|
<template #activator="{ on, attrs }">
|
||||||
|
<v-btn
|
||||||
|
:disabled="!selectedCommit"
|
||||||
|
color="primary"
|
||||||
|
class="lower"
|
||||||
|
v-bind="attrs"
|
||||||
|
small
|
||||||
|
v-on="on"
|
||||||
|
>
|
||||||
|
<v-img class="mr-2" width="30" height="30" src="../assets/ReceiverWhite@32.png" />
|
||||||
|
Receive {{ determinedConversion }}
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-list v-if="selectedCommit" dense>
|
||||||
|
<!-- <v-list-item :to="`/streams/${stream.id}/commits/${selectedCommit.id}`"> -->
|
||||||
|
<v-list-item @click="filterAndReceive">
|
||||||
|
<v-list-item-action class="mr-2">
|
||||||
|
<v-icon small>mdi-filter-variant</v-icon>
|
||||||
|
</v-list-item-action>
|
||||||
|
<v-list-item-content class="caption">Filter and receive</v-list-item-content>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item :disabled="!savedStream.receiverSelection" @click="receiveLatest">
|
||||||
|
<v-list-item-action class="mr-2">
|
||||||
|
<v-icon small>mdi-update</v-icon>
|
||||||
|
</v-list-item-action>
|
||||||
|
<v-list-item-content class="caption">Receive last selection</v-list-item-content>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
<!-- <span v-if="savedStream.receiverSelection">
|
||||||
|
{{ savedStream.receiverSelection.fullKeyName }}
|
||||||
|
</span> -->
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-row v-else>
|
||||||
|
<v-col class="align-self-center d-inline-flex">
|
||||||
|
<v-btn color="primary" small :disabled="!selection" class="mt-1" @click="send">
|
||||||
|
<v-img class="mr-2" width="30" height="30" src="../assets/SenderWhite@32.png" />
|
||||||
|
|
||||||
|
Send
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<v-text-field
|
||||||
|
v-model="message"
|
||||||
|
class="pt-0 mt-0 ml-3 caption"
|
||||||
|
placeholder="Data from Excel"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { send, receiveLatest, bakeSchedule } from '../plugins/excel'
|
||||||
|
import { getReferencedObject } from '../plugins/excel'
|
||||||
|
|
||||||
|
let ac = new AbortController()
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
stream: {
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
determinedConversion: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
error: null,
|
||||||
|
progress: false,
|
||||||
|
message: '',
|
||||||
|
viewer: null,
|
||||||
|
objectIds: null,
|
||||||
|
selectedBranchName: null,
|
||||||
|
selectedCommitId: null,
|
||||||
|
isReceiver: true,
|
||||||
|
selection: null,
|
||||||
|
hasHeaders: false,
|
||||||
|
receiverSelection: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
serverUrl() {
|
||||||
|
return this.$store.getters.serverUrl
|
||||||
|
},
|
||||||
|
selectedBranch: {
|
||||||
|
get() {
|
||||||
|
if (!this.stream || !this.stream.branches) return null
|
||||||
|
|
||||||
|
let selectedBranchName = this.selectedBranchName ? this.selectedBranchName : 'main'
|
||||||
|
const index = this.stream.branches.items.findIndex((x) => x.name === selectedBranchName)
|
||||||
|
if (index > -1) return this.stream.branches.items[index]
|
||||||
|
return this.stream.branches.items[0]
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.selectedBranchName = value.name
|
||||||
|
this.$emit('loadByReferencedId', this.selectedBranch.commits.items[0].referencedObject)
|
||||||
|
this.$store.dispatch('updateStream', this.savedStream)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectedCommit: {
|
||||||
|
get() {
|
||||||
|
if (!this.selectedBranch || !this.selectedBranch.commits) return null
|
||||||
|
var commit = null
|
||||||
|
//not set or latest, return first
|
||||||
|
if (!this.selectedCommitId || this.selectedCommitId === 'latest')
|
||||||
|
commit = this.selectedBranch.commits.items[0]
|
||||||
|
//try match by id
|
||||||
|
else {
|
||||||
|
const index = this.selectedBranch.commits.items.findIndex(
|
||||||
|
(x) => x.id === this.selectedCommitId
|
||||||
|
)
|
||||||
|
if (index > -1) commit = this.selectedBranch.commits.items[index]
|
||||||
|
else commit = this.selectedBranch.commits.items[0]
|
||||||
|
}
|
||||||
|
return commit
|
||||||
|
},
|
||||||
|
async set(value) {
|
||||||
|
const index = this.selectedBranch.commits.items.findIndex((x) => x.id === value.id)
|
||||||
|
this.selectedCommitId = value.id
|
||||||
|
this.$emit('loadByReferencedId', this.selectedBranch.commits.items[index].referencedObject)
|
||||||
|
this.$store.dispatch('updateStream', this.savedStream)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
savedStream() {
|
||||||
|
return {
|
||||||
|
id: this.stream.Id,
|
||||||
|
isReceiver: this.isReceiver,
|
||||||
|
selection: this.selection,
|
||||||
|
hasHeaders: this.hasHeaders,
|
||||||
|
selectedBranchName: this.selectedBranchName,
|
||||||
|
selectedCommitId: this.selectedCommitId,
|
||||||
|
receiverSelection: this.receiverSelection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(function () {
|
||||||
|
this.$emit('loadByReferencedId', this.selectedCommit.referencedObject)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
swapReceiver() {
|
||||||
|
this.isReceiver = !this.isReceiver
|
||||||
|
this.$emit('loadByCommitId', this.selectedCommitId)
|
||||||
|
this.$store.dispatch('updateStream', this.savedStream)
|
||||||
|
this.$mixpanel.track('Connector Action', { name: 'Stream Swap Receive/Send', type: 'action' })
|
||||||
|
},
|
||||||
|
async filterAndReceive() {
|
||||||
|
this.$store.dispatch('addStream', this.savedStream)
|
||||||
|
this.$router.push(`/streams/${this.stream.id}/commits/${this.selectedCommit.id}`)
|
||||||
|
},
|
||||||
|
async setRange(headers) {
|
||||||
|
await window.Excel.run(async (context) => {
|
||||||
|
let range = context.workbook.getSelectedRange()
|
||||||
|
range.load('address')
|
||||||
|
|
||||||
|
await context.sync()
|
||||||
|
|
||||||
|
this.selection = range.address
|
||||||
|
this.hasHeaders = headers
|
||||||
|
this.$store.dispatch('updateStream', this.savedStream)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
clearSelection() {
|
||||||
|
this.selection = ''
|
||||||
|
this.$store.dispatch('updateStream', this.savedStream)
|
||||||
|
},
|
||||||
|
remove() {
|
||||||
|
this.$mixpanel.track('Connector Action', { name: 'Stream Remove' })
|
||||||
|
return this.$store.dispatch('removeStream', this.savedStream.id)
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
ac.abort()
|
||||||
|
},
|
||||||
|
async send() {
|
||||||
|
// these values need to be set to null or the models will not load
|
||||||
|
// when switching back to the receive mode
|
||||||
|
this.viewer = null
|
||||||
|
this.referencedObject = null
|
||||||
|
this.$store.dispatch('addStream', this.savedStream)
|
||||||
|
|
||||||
|
this.$mixpanel.track('Send')
|
||||||
|
send(this.savedStream, this.stream.id, this.selectedBranch.name, this.message)
|
||||||
|
},
|
||||||
|
async receiveDeterminedConversion() {
|
||||||
|
let data = await getReferencedObject(this.stream.id, this.selectedCommit.referencedObject, {})
|
||||||
|
let ac = new AbortController()
|
||||||
|
if (this.determinedConversion == 'Schedule') {
|
||||||
|
this.receiverSelection = await bakeSchedule(
|
||||||
|
data,
|
||||||
|
this.stream.id,
|
||||||
|
this.selectedCommit.id,
|
||||||
|
this.selectedCommit.message,
|
||||||
|
ac.signal,
|
||||||
|
null, // nearestObjectId,
|
||||||
|
null, // pathFromNearestObj,
|
||||||
|
null, // previousHeaders,
|
||||||
|
null // previousRange
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
this.$emit('clearDeterminedConversion')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.receiverSelection) {
|
||||||
|
this.$store.dispatch('setReceiverSelection', {
|
||||||
|
id: this.stream.id,
|
||||||
|
receiverSelection: this.receiverSelection
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async receiveLatest() {
|
||||||
|
this.$mixpanel.track('Receive')
|
||||||
|
ac = new AbortController()
|
||||||
|
|
||||||
|
console.log(this.receiverSelection)
|
||||||
|
|
||||||
|
this.progress = true
|
||||||
|
await receiveLatest(
|
||||||
|
this.selectedCommit.referencedObject,
|
||||||
|
this.stream.id,
|
||||||
|
this.selectedCommit.id,
|
||||||
|
this.selectedCommit.message,
|
||||||
|
this.receiverSelection,
|
||||||
|
ac.signal
|
||||||
|
)
|
||||||
|
this.progress = false
|
||||||
|
},
|
||||||
|
formatCommitName(id) {
|
||||||
|
if (this.selectedBranch.commits.items[0].id == id) {
|
||||||
|
console.log(id)
|
||||||
|
return 'latest'
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.stream-card-select .v-text-field__details {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.v-btn .lower {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
.floating {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
.background-light {
|
||||||
|
background: #8e9eab;
|
||||||
|
background: -webkit-linear-gradient(to top right, #eeeeee, #c8e8ff) !important;
|
||||||
|
background: linear-gradient(to top right, #ffffff, #c8e8ff) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background-dark {
|
||||||
|
background: #141e30;
|
||||||
|
background: -webkit-linear-gradient(to top left, #243b55, #141e30) !important;
|
||||||
|
background: linear-gradient(to top left, #243b55, #141e30) !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div style="display: inline-block">
|
<div v-if="otherUser && otherUser.name" style="display: inline-block">
|
||||||
<v-menu v-if="loggedIn" offset-x open-on-hover>
|
<v-menu v-if="loggedIn" offset-x open-on-hover>
|
||||||
<template #activator="{ on, attrs }">
|
<template #activator="{ on, attrs }">
|
||||||
<v-avatar class="ma-1" color="grey lighten-3" :size="size" v-bind="attrs" v-on="on">
|
<v-avatar class="ma-1" color="grey lighten-3" :size="size" v-bind="attrs" v-on="on">
|
||||||
@@ -14,12 +14,14 @@
|
|||||||
<v-img v-else :src="`https://robohash.org/` + id + `.png?size=40x40`" />
|
<v-img v-else :src="`https://robohash.org/` + id + `.png?size=40x40`" />
|
||||||
</v-avatar>
|
</v-avatar>
|
||||||
<br />
|
<br />
|
||||||
<b>{{ user.name }}</b>
|
<b>{{ otherUser.name }}</b>
|
||||||
<v-divider class="ma-4"></v-divider>
|
<v-divider class="ma-4"></v-divider>
|
||||||
{{ user.company }}
|
{{ otherUser.company }}
|
||||||
<br />
|
<br />
|
||||||
{{
|
{{
|
||||||
user.bio ? user.bio : 'This user prefers to keep an air of mystery around themselves.'
|
otherUser.bio
|
||||||
|
? otherUser.bio
|
||||||
|
: 'This user prefers to keep an air of mystery around themselves.'
|
||||||
}}
|
}}
|
||||||
<br />
|
<br />
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
@@ -58,7 +60,7 @@ export default {
|
|||||||
},
|
},
|
||||||
apollo: {
|
apollo: {
|
||||||
$client: createClient(),
|
$client: createClient(),
|
||||||
user: {
|
otherUser: {
|
||||||
query: userQuery,
|
query: userQuery,
|
||||||
variables() {
|
variables() {
|
||||||
return {
|
return {
|
||||||
@@ -67,6 +69,9 @@ export default {
|
|||||||
},
|
},
|
||||||
skip() {
|
skip() {
|
||||||
return !this.loggedIn
|
return !this.loggedIn
|
||||||
|
},
|
||||||
|
error(error) {
|
||||||
|
console.log('Could not get user', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
<template>
|
||||||
|
<!-- DIALOG: Create Branch -->
|
||||||
|
<v-dialog v-model="showCreateBranch">
|
||||||
|
<template #activator="{ on: dialog, attrs }">
|
||||||
|
<v-btn
|
||||||
|
v-tooltip="'Create Branch'"
|
||||||
|
icon
|
||||||
|
x-small
|
||||||
|
class="ml-0 mr-1"
|
||||||
|
v-bind="attrs"
|
||||||
|
v-on="{ ...dialog }"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-plus-circle</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-card>
|
||||||
|
<v-card-title class="text-h5 mb-1">Create a New Branch</v-card-title>
|
||||||
|
<v-card-subtitle class="py-0 my-0 font-italic">under {{ streamName }} stream</v-card-subtitle>
|
||||||
|
<v-container class="px-6" pb-0>
|
||||||
|
<v-text-field
|
||||||
|
v-model="branchName"
|
||||||
|
xxxclass="small-text-field"
|
||||||
|
hide-details
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
placeholder="Branch Name"
|
||||||
|
/>
|
||||||
|
<v-text-field
|
||||||
|
v-model="description"
|
||||||
|
xxxclass="small-text-field"
|
||||||
|
hide-details
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
placeholder="Description (Optional)"
|
||||||
|
/>
|
||||||
|
</v-container>
|
||||||
|
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="blue darken-1" text @click="showCreateBranch = false">Cancel</v-btn>
|
||||||
|
<v-btn :disabled="branchName === ''" color="blue darken-1" text @click="createBranch">
|
||||||
|
Create
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import gql from 'graphql-tag'
|
||||||
|
// import { bus } from '@/main'
|
||||||
|
export default {
|
||||||
|
name: 'CreateBranchDialog',
|
||||||
|
props: {
|
||||||
|
streamId: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
streamName: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showCreateBranch: false,
|
||||||
|
branchName: '',
|
||||||
|
description: '',
|
||||||
|
defaultDescription: 'Stream created from SketchUp',
|
||||||
|
accountToCreateStream: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
loggedIn() {
|
||||||
|
if (!this.$store.state) return false
|
||||||
|
return this.$store.getters.isAuthenticated
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async createBranch() {
|
||||||
|
let res = await this.$apollo.mutate({
|
||||||
|
mutation: gql`
|
||||||
|
mutation branchCreate($branch: BranchCreateInput!) {
|
||||||
|
branchCreate(branch: $branch)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
branch: {
|
||||||
|
streamId: this.streamId,
|
||||||
|
name: this.branchName,
|
||||||
|
description: this.description === '' ? this.defaultDescription : this.description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// bus.$emit(`create-branch-${this.streamId}`, this.branchName)
|
||||||
|
this.showCreateBranch = false
|
||||||
|
this.branchName = ''
|
||||||
|
this.description = ''
|
||||||
|
this.$mixpanel.track('Connector Action', { name: 'Create Branch' })
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.v-dialog {
|
||||||
|
max-width: 390px;
|
||||||
|
}
|
||||||
|
.v-text-field >>> input {
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
.v-text-field >>> label {
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
<template>
|
||||||
|
<v-container fluid class="px-1 pb-0 pt-1">
|
||||||
|
<v-row>
|
||||||
|
<v-col class="center-content">
|
||||||
|
<!-- DIALOG: Create New Stream -->
|
||||||
|
<v-dialog v-model="showCreateNewStream">
|
||||||
|
<template #activator="{ on, attrs }">
|
||||||
|
<v-btn class="ma-2 pa-3" x-small v-bind="attrs" v-on="on">
|
||||||
|
<v-icon dark left>mdi-plus-circle</v-icon>
|
||||||
|
Create New Stream
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-card>
|
||||||
|
<v-card-title class="text-h5">Create a New Stream</v-card-title>
|
||||||
|
<v-container class="px-6" pb-0>
|
||||||
|
<v-text-field
|
||||||
|
v-model="streamName"
|
||||||
|
xxxclass="small-text-field"
|
||||||
|
hide-details
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
placeholder="Stream Name (Optional)"
|
||||||
|
/>
|
||||||
|
<v-text-field
|
||||||
|
v-model="description"
|
||||||
|
xxxclass="small-text-field"
|
||||||
|
hide-details
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
placeholder="Description (Optional)"
|
||||||
|
/>
|
||||||
|
<v-switch v-model="privateStream" :label="'Private Stream'"></v-switch>
|
||||||
|
</v-container>
|
||||||
|
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="blue darken-1" text @click="showCreateNewStream = false">Cancel</v-btn>
|
||||||
|
<v-btn color="blue darken-1" text @click="createStream">Create</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
<!-- DIALOG: Add a Stream by ID or URL -->
|
||||||
|
<v-dialog v-model="showCreateStreamById">
|
||||||
|
<template #activator="{ on, attrs }">
|
||||||
|
<v-btn class="ma-2 pa-3" x-small min-width="163" v-bind="attrs" v-on="on">
|
||||||
|
<v-icon dark left>mdi-link-plus</v-icon>
|
||||||
|
Add By ID or URL
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-card>
|
||||||
|
<v-card-title class="text-h5">Add a Stream by ID or URL</v-card-title>
|
||||||
|
<v-card-text>Stream IDs and Stream/Branch/Commit URLs are supported.</v-card-text>
|
||||||
|
<v-container class="px-6">
|
||||||
|
<v-text-field
|
||||||
|
v-model="createStreamByIdText"
|
||||||
|
xxxclass="small-text-field"
|
||||||
|
hide-details
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
placeholder="Stream URL"
|
||||||
|
/>
|
||||||
|
</v-container>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="blue darken-1" text @click="showCreateStreamById = false">Cancel</v-btn>
|
||||||
|
<v-btn
|
||||||
|
:disabled="createStreamByIdText === ''"
|
||||||
|
color="blue darken-1"
|
||||||
|
text
|
||||||
|
@click="getStream"
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import gql from 'graphql-tag'
|
||||||
|
import { StreamWrapper } from '@/utils/streamWrapper'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'CreateStreamDialog',
|
||||||
|
props: {
|
||||||
|
accountId: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
serverUrl: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showCreateNewStream: false,
|
||||||
|
showCreateStreamById: false,
|
||||||
|
createStreamByIdText: '',
|
||||||
|
privateStream: false,
|
||||||
|
streamName: '',
|
||||||
|
description: '',
|
||||||
|
defaultDescription: 'Stream created from Excel',
|
||||||
|
accountToCreateStream: null,
|
||||||
|
streamId: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
loggedIn() {
|
||||||
|
if (!this.$store.state) return false
|
||||||
|
return this.$store.getters.isAuthenticated
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async getStream() {
|
||||||
|
try {
|
||||||
|
const streamWrapper = new StreamWrapper(
|
||||||
|
this.createStreamByIdText,
|
||||||
|
this.accountId,
|
||||||
|
this.serverUrl
|
||||||
|
)
|
||||||
|
this.$router.push(`/streams/${streamWrapper.streamId}/${streamWrapper.commitId}`)
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
this.showCreateStreamById = false
|
||||||
|
},
|
||||||
|
async createStream() {
|
||||||
|
let res = await this.$apollo.mutate({
|
||||||
|
mutation: gql`
|
||||||
|
mutation streamCreate($stream: StreamCreateInput!) {
|
||||||
|
streamCreate(stream: $stream)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
stream: {
|
||||||
|
name: this.streamName,
|
||||||
|
description: this.description === '' ? this.defaultDescription : this.description,
|
||||||
|
isPublic: !this.privateStream
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.showCreateNewStream = false
|
||||||
|
this.streamName = ''
|
||||||
|
this.description = ''
|
||||||
|
this.$mixpanel.track('Connector Action', { name: 'Create Stream' })
|
||||||
|
this.refresh()
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.center-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.v-dialog {
|
||||||
|
max-width: 390px;
|
||||||
|
}
|
||||||
|
.v-text-field >>> input {
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
.v-text-field >>> label {
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
query User($id: String!) {
|
query LimitedUser($id: String!) {
|
||||||
user(id: $id) {
|
otherUser(id: $id) {
|
||||||
id
|
id
|
||||||
email
|
|
||||||
name
|
name
|
||||||
bio
|
bio
|
||||||
company
|
company
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import router from './router'
|
|||||||
import store from './store'
|
import store from './store'
|
||||||
import { apolloProvider } from './vue-apollo'
|
import { apolloProvider } from './vue-apollo'
|
||||||
import vuetify from './plugins/vuetify'
|
import vuetify from './plugins/vuetify'
|
||||||
|
import '@mdi/font/css/materialdesignicons.css'
|
||||||
|
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,463 @@
|
|||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
import {
|
||||||
|
bakeTable,
|
||||||
|
bakeArray,
|
||||||
|
hideRowOrColumn,
|
||||||
|
removeNonAlphanumericCharacters,
|
||||||
|
getIndiciesFromRangeAddress
|
||||||
|
} from './excel'
|
||||||
|
export const tableName = 'SpeckleDataTable'
|
||||||
|
|
||||||
|
export function checkIfReceivingDataTable(item) {
|
||||||
|
if (!Array.isArray(item)) {
|
||||||
|
return checkIfSingleDataTable(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.length < 1) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
//it's a flat list
|
||||||
|
else if (!Array.isArray(item[0])) {
|
||||||
|
return checkIfSingleDataTable(item[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkIfSingleDataTable(item) {
|
||||||
|
if (!(item.speckle_type && item.speckle_type.split('.').at(-1) == 'DataTable')) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatArrayDataForTable(item, arrayData) {
|
||||||
|
// TODO: support receiving multiple tables
|
||||||
|
if (Array.isArray(item)) {
|
||||||
|
item = item[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
arrayData[0].push('SpeckleColumnMetadataRow')
|
||||||
|
for (let i = 0; i < item.columnCount; i++) {
|
||||||
|
arrayData[0].push(JSON.stringify(item.columnMetadata[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < item.rowCount; i++) {
|
||||||
|
let row = []
|
||||||
|
row.push(JSON.stringify(item.rowMetadata[i]))
|
||||||
|
row.push(...item.data[i])
|
||||||
|
arrayData.push(row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function bakeDataTable(item, arrayData, context, sheet, rowStart, colStart) {
|
||||||
|
// TODO: support receiving multiple tables
|
||||||
|
if (Array.isArray(item)) {
|
||||||
|
item = item[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
var tableToUpdate = await getExistingTableInLocation(context, sheet, rowStart, colStart)
|
||||||
|
if (tableToUpdate) {
|
||||||
|
let existingAppId = await getSpeckleIdFromTable(tableToUpdate, sheet, context)
|
||||||
|
if (existingAppId != item.applicationId) {
|
||||||
|
throw new Error('Trying receive a datatable where a different datatable already exists')
|
||||||
|
}
|
||||||
|
|
||||||
|
let indicies = await getMetadataIndex(tableToUpdate, sheet, context)
|
||||||
|
if (indicies[0] != -1 && indicies[1] != -1) {
|
||||||
|
rowStart = indicies[0]
|
||||||
|
colStart = indicies[1]
|
||||||
|
|
||||||
|
await cleanUpTableMetadata(tableToUpdate.id, context)
|
||||||
|
tableToUpdate.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add one to headerRowIndex because we've added the column metadata as a new first row
|
||||||
|
let headerRowIndex = 1
|
||||||
|
if (item.headerRowIndex) {
|
||||||
|
headerRowIndex = item.headerRowIndex + 1
|
||||||
|
}
|
||||||
|
let name = 'DataTable'
|
||||||
|
if (item.name) {
|
||||||
|
name = item.name
|
||||||
|
}
|
||||||
|
hideRowOrColumn(sheet, colStart, rowStart)
|
||||||
|
await bakeArray(arrayData.splice(0, headerRowIndex), rowStart, colStart, context)
|
||||||
|
|
||||||
|
// set table applicationId in the top left cell
|
||||||
|
arrayData[0][0] = `{"id":"","speckle_type":"Objects.Organization.DataTable","applicationId":"${item.applicationId}","totalChildrenCount":0}`
|
||||||
|
await bakeTable(arrayData, context, sheet, name, rowStart, colStart, headerRowIndex)
|
||||||
|
greyOutReadOnlyColumns(
|
||||||
|
item.columnMetadata,
|
||||||
|
rowStart + 1 + headerRowIndex,
|
||||||
|
colStart + 1,
|
||||||
|
arrayData.length - 1,
|
||||||
|
sheet,
|
||||||
|
context
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getExistingTableInLocation(context, sheet, rowStart, colStart) {
|
||||||
|
let range = sheet.getRangeByIndexes(rowStart, colStart, 1, 1)
|
||||||
|
var speckleTableContainingRange = await getDataTableContainingRange(range, sheet, context)
|
||||||
|
if (!speckleTableContainingRange) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return speckleTableContainingRange
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSpeckleIdFromTable(table, sheet, context) {
|
||||||
|
let range = table.getRange()
|
||||||
|
range.load('rowIndex, columnIndex')
|
||||||
|
await context.sync()
|
||||||
|
|
||||||
|
let firstCell = sheet.getRangeByIndexes(range.rowIndex, range.columnIndex, 1, 1)
|
||||||
|
firstCell.load('values')
|
||||||
|
await context.sync()
|
||||||
|
|
||||||
|
let appIdObj = JSON.parse(firstCell.values[0][0])
|
||||||
|
return appIdObj.applicationId
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getMetadataIndex(table, sheet, context) {
|
||||||
|
table.load('id')
|
||||||
|
await context.sync()
|
||||||
|
let tableId = removeNonAlphanumericCharacters(table.id)
|
||||||
|
let speckleRowMeta = sheet.names.getItemOrNullObject(`speckleRowMetadata_${tableId}`)
|
||||||
|
await context.sync()
|
||||||
|
if (speckleRowMeta.isNullObject) {
|
||||||
|
return [-1, -1]
|
||||||
|
}
|
||||||
|
|
||||||
|
let speckleRowMetaRange = speckleRowMeta.getRange()
|
||||||
|
speckleRowMetaRange.load('columnIndex, rowIndex')
|
||||||
|
await context.sync()
|
||||||
|
|
||||||
|
return [speckleRowMetaRange.rowIndex, speckleRowMetaRange.columnIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDataTableContainingRange(range, sheet, context) {
|
||||||
|
let selectedTable = null
|
||||||
|
sheet.tables.load('count')
|
||||||
|
await context.sync()
|
||||||
|
|
||||||
|
for (let i = 0; i < sheet.tables.count; i++) {
|
||||||
|
let table = sheet.tables.getItemAt(i)
|
||||||
|
let tableMetataIndicies = await getMetadataIndex(table, sheet, context)
|
||||||
|
|
||||||
|
if (tableMetataIndicies[0] == -1 && tableMetataIndicies[1] == -1) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let tableRange = table.getRange()
|
||||||
|
tableRange.load('address')
|
||||||
|
range.load('address')
|
||||||
|
await context.sync()
|
||||||
|
|
||||||
|
let tableRangeIndicies = getIndiciesFromRangeAddress(tableRange.address)
|
||||||
|
let rangeIndicies = getIndiciesFromRangeAddress(range.address)
|
||||||
|
if (
|
||||||
|
// sorry these indicies don't cooperate very well. Get metadata index return [rowindex, colindex]
|
||||||
|
// while getIndiciesFromRangeAddress return indicies as excel addresses display them [colindex, rowindex, endcolindex, endRowIndex]
|
||||||
|
tableMetataIndicies[0] <= rangeIndicies[1] &&
|
||||||
|
tableMetataIndicies[1] <= rangeIndicies[0] &&
|
||||||
|
tableRangeIndicies[3] >= rangeIndicies[3] &&
|
||||||
|
tableRangeIndicies[2] >= rangeIndicies[2]
|
||||||
|
) {
|
||||||
|
selectedTable = sheet.tables.getItemAt(i)
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
"Are you trying to send a data table? Make sure your selection doesn't extended beyond the table"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedTable == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDataTable = await isSpeckleDataTable(selectedTable, sheet, context)
|
||||||
|
if (selectedTable && isDataTable) {
|
||||||
|
return selectedTable
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isSpeckleDataTable(table, sheet, context) {
|
||||||
|
let tableRange = table.getRange()
|
||||||
|
tableRange.load('rowIndex, columnIndex')
|
||||||
|
await context.sync()
|
||||||
|
// let tableRangeAddress = tableRange.address
|
||||||
|
// let rangeIndicies = getIndiciesFromRangeAddress(tableRangeAddress)
|
||||||
|
// let firstCellRange = sheet.getRangeByIndexes(rangeIndicies[1], rangeIndicies[0], 1, 1)
|
||||||
|
let firstCellRange = sheet.getRangeByIndexes(tableRange.rowIndex, tableRange.columnIndex, 1, 1)
|
||||||
|
|
||||||
|
firstCellRange.load('values')
|
||||||
|
await context.sync()
|
||||||
|
|
||||||
|
if (firstCellRange.values[0][0].includes('Objects.Organization.DataTable')) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function greyOutReadOnlyColumns(
|
||||||
|
columnMetadata,
|
||||||
|
rowStartIndex,
|
||||||
|
colStartIndex,
|
||||||
|
rowCount,
|
||||||
|
sheet,
|
||||||
|
context
|
||||||
|
) {
|
||||||
|
for (let i = 0; i < columnMetadata.length; i++) {
|
||||||
|
if (columnMetadata[i].IsReadOnly) {
|
||||||
|
let range = sheet.getRangeByIndexes(rowStartIndex, colStartIndex + i, rowCount, 1)
|
||||||
|
range.format.fill.color = '#AEAAAA'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await context.sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function BuildDataTableObject(sendingRange, values, table, sheet, context) {
|
||||||
|
let metaRowIndex = await GetColumnMetadataRowIndex(table, sheet, context)
|
||||||
|
let metaColIndex = await GetRowMetadataColumnIndex(table, context)
|
||||||
|
|
||||||
|
let speckleTable = new DataTable()
|
||||||
|
speckleTable.applicationId = await GetTableApplicationId(table, context)
|
||||||
|
|
||||||
|
sendingRange.load('rowIndex, columnIndex, rowCount, columnCount')
|
||||||
|
await context.sync()
|
||||||
|
|
||||||
|
let metaRowRange = sheet.getRangeByIndexes(
|
||||||
|
metaRowIndex,
|
||||||
|
sendingRange.columnIndex,
|
||||||
|
1,
|
||||||
|
sendingRange.columnCount
|
||||||
|
)
|
||||||
|
let metaColumnRange = sheet.getRangeByIndexes(
|
||||||
|
sendingRange.rowIndex,
|
||||||
|
metaColIndex,
|
||||||
|
sendingRange.rowCount,
|
||||||
|
1
|
||||||
|
)
|
||||||
|
|
||||||
|
metaRowRange.load('values')
|
||||||
|
metaColumnRange.load('values')
|
||||||
|
await context.sync()
|
||||||
|
|
||||||
|
var rangeColumnStart = 0
|
||||||
|
if (sendingRange.columnIndex == metaColIndex) {
|
||||||
|
rangeColumnStart = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = rangeColumnStart; i < sendingRange.columnCount; i++) {
|
||||||
|
speckleTable.defineColumn(JSON.parse(metaRowRange.values[0][i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < sendingRange.rowCount; i++) {
|
||||||
|
speckleTable.addRow(JSON.parse(metaColumnRange.values[i][0]), values[i].slice(rangeColumnStart))
|
||||||
|
}
|
||||||
|
|
||||||
|
return speckleTable
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GetColumnMetadataRowIndex(table, sheet, context) {
|
||||||
|
let tableRange = table.getRange()
|
||||||
|
tableRange.load('columnCount, columnIndex, rowCount, rowIndex')
|
||||||
|
await context.sync()
|
||||||
|
|
||||||
|
let extendedRowIndex = Math.max(0, tableRange.rowIndex - 5)
|
||||||
|
|
||||||
|
let possibleMetadataRowRange = sheet.getRangeByIndexes(
|
||||||
|
extendedRowIndex,
|
||||||
|
tableRange.columnIndex,
|
||||||
|
tableRange.rowIndex - extendedRowIndex,
|
||||||
|
tableRange.columnCount
|
||||||
|
)
|
||||||
|
|
||||||
|
return await findMetadataRowIndex(possibleMetadataRowRange, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function findMetadataRowIndex(range, context) {
|
||||||
|
var found = range.findOrNullObject('SpeckleColumnMetadataRow', {
|
||||||
|
completeMatch: false, // Match the whole cell value.
|
||||||
|
matchCase: true, // Don't match case.
|
||||||
|
searchDirection: window.Excel.SearchDirection.forward // Start search at the beginning of the range.
|
||||||
|
})
|
||||||
|
found.load('rowIndex')
|
||||||
|
await context.sync()
|
||||||
|
if (found.isNullObject) {
|
||||||
|
found = range.findOrNullObject('speckle_type', {
|
||||||
|
completeMatch: false, // Match the whole cell value.
|
||||||
|
matchCase: true, // Match case.
|
||||||
|
searchDirection: window.Excel.SearchDirection.forward // Start search at the beginning of the range.
|
||||||
|
})
|
||||||
|
found.load('rowIndex')
|
||||||
|
await context.sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found.isNullObject) {
|
||||||
|
throw new Error('Could not find column metadata')
|
||||||
|
}
|
||||||
|
|
||||||
|
return found.rowIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GetRowMetadataColumnIndex(table, context) {
|
||||||
|
let tableRange = table.getRange()
|
||||||
|
tableRange.load('columnIndex, rowCount, rowIndex')
|
||||||
|
await context.sync()
|
||||||
|
|
||||||
|
return tableRange.columnIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
async function GetTableApplicationId(table, context) {
|
||||||
|
const headerRange = table.getHeaderRowRange()
|
||||||
|
headerRange.load('values')
|
||||||
|
await context.sync()
|
||||||
|
|
||||||
|
const tableMetadata = JSON.parse(headerRange.values[0][0])
|
||||||
|
if (!tableMetadata.hasOwnProperty('applicationId'))
|
||||||
|
throw new Error('Cannot find TableApplicationId in table header metadata')
|
||||||
|
|
||||||
|
return tableMetadata.applicationId
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function onTableChanged(eventArgs) {
|
||||||
|
if (eventArgs.changeType != 'ColumnDeleted') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await window.Excel.run(async (context) => {
|
||||||
|
const tableId = removeNonAlphanumericCharacters(eventArgs.tableId)
|
||||||
|
let sheet = context.workbook.worksheets.getActiveWorksheet()
|
||||||
|
let speckleRowMeta = sheet.names.getItemOrNullObject(`speckleRowMetadata_${tableId}`)
|
||||||
|
await context.sync()
|
||||||
|
if (speckleRowMeta.isNullObject) {
|
||||||
|
console.log('speckle row metadata was null')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let table = sheet.tables.getItem(eventArgs.tableId)
|
||||||
|
let tableRange = table.getRange()
|
||||||
|
|
||||||
|
let speckleRowMetaRange = speckleRowMeta.getRange()
|
||||||
|
speckleRowMetaRange.load('columnIndex')
|
||||||
|
tableRange.load('columnCount, columnIndex')
|
||||||
|
await context.sync()
|
||||||
|
|
||||||
|
if (tableRange.columnCount == 1 && tableRange.columnIndex == speckleRowMetaRange.columnIndex) {
|
||||||
|
let speckleColumnMeta = sheet.names.getItemOrNullObject(`speckleColumnMetadata_${tableId}`)
|
||||||
|
await deleteMetadata(speckleRowMeta, false, context)
|
||||||
|
await deleteMetadata(speckleColumnMeta, true, context)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export async function onTableDeleted(eventArgs) {
|
||||||
|
await window.Excel.run(async (context) => {
|
||||||
|
await cleanUpTableMetadata(eventArgs.tableId, context)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cleanUpTableMetadata(originalTableId, context) {
|
||||||
|
const tableId = removeNonAlphanumericCharacters(originalTableId)
|
||||||
|
let sheet = context.workbook.worksheets.getActiveWorksheet()
|
||||||
|
let speckleColumnMeta = sheet.names.getItemOrNullObject(`speckleColumnMetadata_${tableId}`)
|
||||||
|
let speckleRowMeta = sheet.names.getItemOrNullObject(`speckleRowMetadata_${tableId}`)
|
||||||
|
await context.sync()
|
||||||
|
|
||||||
|
// warning: must delete rowMetadata (which is actually a column) before deleting the colMetadata
|
||||||
|
await deleteMetadata(speckleRowMeta, false, context)
|
||||||
|
await deleteMetadata(speckleColumnMeta, true, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteMetadata(namedRange, isRow, context) {
|
||||||
|
if (namedRange.isNullObject) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let speckleMetaRange = namedRange.getRange()
|
||||||
|
|
||||||
|
try {
|
||||||
|
await findMetadataRowIndex(speckleMetaRange, context)
|
||||||
|
} catch {
|
||||||
|
console.log('speckle metadata doesnt exist in this range anymore')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRow) {
|
||||||
|
speckleMetaRange.load('rowHidden')
|
||||||
|
await context.sync()
|
||||||
|
|
||||||
|
if (speckleMetaRange.rowHidden) {
|
||||||
|
speckleMetaRange.getEntireRow().delete('Up')
|
||||||
|
} else {
|
||||||
|
speckleMetaRange.clear('Contents')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
speckleMetaRange.load('columnHidden')
|
||||||
|
await context.sync()
|
||||||
|
|
||||||
|
if (speckleMetaRange.columnHidden) {
|
||||||
|
speckleMetaRange.getEntireColumn().delete('Left')
|
||||||
|
} else {
|
||||||
|
speckleMetaRange.clear('Contents')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namedRange.delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Base {
|
||||||
|
id
|
||||||
|
totalChildrenCount
|
||||||
|
applicationId
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DataTable extends Base {
|
||||||
|
get columnCount() {
|
||||||
|
return this.columnMetadata.length
|
||||||
|
}
|
||||||
|
get rowCount() {
|
||||||
|
return this.rowMetadata.length
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
get speckle_type() {
|
||||||
|
return 'Objects.Organization.DataTable'
|
||||||
|
}
|
||||||
|
headerRowIndex
|
||||||
|
columnMetadata = []
|
||||||
|
rowMetadata = []
|
||||||
|
data = []
|
||||||
|
|
||||||
|
addRow(metadata, objects) {
|
||||||
|
if (objects.length != this.columnCount)
|
||||||
|
throw new Error(
|
||||||
|
`object length of ${objects.length} does not match the column count, ${this.columnCount}`
|
||||||
|
)
|
||||||
|
this.rowMetadata.push(metadata)
|
||||||
|
this.data.push(objects)
|
||||||
|
}
|
||||||
|
|
||||||
|
defineColumn(metadata) {
|
||||||
|
this.columnMetadata.push(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
const jsonObj = Object.assign({}, this)
|
||||||
|
const proto = Object.getPrototypeOf(this)
|
||||||
|
for (const key of Object.getOwnPropertyNames(proto)) {
|
||||||
|
const desc = Object.getOwnPropertyDescriptor(proto, key)
|
||||||
|
const hasGetter = desc && typeof desc.get === 'function'
|
||||||
|
if (hasGetter) {
|
||||||
|
jsonObj[key] = this[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return jsonObj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// export function checkIfSendingDataTable(item, arrayData) {
|
||||||
|
|
||||||
|
// }
|
||||||
+498
-116
@@ -1,35 +1,57 @@
|
|||||||
/* eslint-disable no-unreachable */
|
/* eslint-disable no-unreachable */
|
||||||
import flatten from 'flat'
|
import flatten from 'flat'
|
||||||
import store from '../store/index.js'
|
import store from '../store/index.js'
|
||||||
|
import { MD5, enc } from 'crypto-js'
|
||||||
|
import {
|
||||||
|
checkIfReceivingDataTable,
|
||||||
|
getDataTableContainingRange,
|
||||||
|
bakeDataTable,
|
||||||
|
formatArrayDataForTable,
|
||||||
|
BuildDataTableObject,
|
||||||
|
onTableChanged,
|
||||||
|
onTableDeleted
|
||||||
|
} from './dataTable.js'
|
||||||
|
|
||||||
const unflatten = require('flat').unflatten
|
const unflatten = require('flat').unflatten
|
||||||
|
|
||||||
let ignoreEndsWithProps = ['id', 'totalChildrenCount']
|
let ignoreEndsWithProps = ['totalChildrenCount', 'elements']
|
||||||
|
|
||||||
let streamId, sheet, rowStart, colStart, arrayData, isTabularData
|
let displayValues = ['displayValue', '@displayValue', 'displayMesh']
|
||||||
let headerIndices = []
|
let speckleTypesWithGeometry = ['Objects.Geometry']
|
||||||
|
|
||||||
|
let streamId, sheet, rowStart, colStart, arrayData, isTabularData, arrayIdData
|
||||||
|
|
||||||
async function flattenData(item, signal) {
|
async function flattenData(item, signal) {
|
||||||
if (signal.aborted) return
|
if (signal.aborted) return
|
||||||
if (Array.isArray(item)) {
|
if (Array.isArray(item)) {
|
||||||
for (let o of item) {
|
let localItems = [...item]
|
||||||
if (signal.aborted) return
|
const batchSize = 35
|
||||||
await flattenSingle(o, signal)
|
while (localItems.length > 0) {
|
||||||
|
let batch = localItems.splice(0, batchSize)
|
||||||
|
await Promise.all(batch.map((i) => flattenSingle(i, signal)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await flattenSingle(item, signal)
|
await flattenSingle(item, signal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getReferencedObject(reference, signal) {
|
export async function getReferencedObject(
|
||||||
|
streamId,
|
||||||
|
reference,
|
||||||
|
signal,
|
||||||
|
excludeElementsFromConstruction = true
|
||||||
|
) {
|
||||||
if (signal.aborted) return
|
if (signal.aborted) return
|
||||||
|
|
||||||
|
let excludeProps = ['__closure']
|
||||||
|
if (excludeElementsFromConstruction) excludeProps.push('elements')
|
||||||
|
|
||||||
let loader = await store.dispatch('getObject', {
|
let loader = await store.dispatch('getObject', {
|
||||||
streamId: streamId,
|
streamId: streamId,
|
||||||
objectId: reference,
|
objectId: reference,
|
||||||
options: {
|
options: {
|
||||||
fullyTraverseArrays: false,
|
fullyTraverseArrays: false,
|
||||||
excludeProps: ['displayValue', 'displayMesh', '__closure', 'elements']
|
excludeProps: excludeProps
|
||||||
},
|
},
|
||||||
signal
|
signal
|
||||||
})
|
})
|
||||||
@@ -39,27 +61,54 @@ async function getReferencedObject(reference, signal) {
|
|||||||
|
|
||||||
async function flattenSingle(item, signal) {
|
async function flattenSingle(item, signal) {
|
||||||
if (item.speckle_type && item.speckle_type == 'reference') {
|
if (item.speckle_type && item.speckle_type == 'reference') {
|
||||||
item = await getReferencedObject(item.referencedId, signal)
|
item = await getReferencedObject(streamId, item.referencedId, signal)
|
||||||
}
|
}
|
||||||
|
|
||||||
let flat = flatten(item)
|
let flat = flatten(item)
|
||||||
let rowData = []
|
let rowData = []
|
||||||
|
let rowIdData = ''
|
||||||
for (const [key, value] of Object.entries(flat)) {
|
for (const [key, value] of Object.entries(flat)) {
|
||||||
if (key === null || value === null) continue
|
if (key === null || value === null) continue
|
||||||
if (ignoreEndsWithProps.findIndex((x) => key.endsWith(x)) !== -1) continue
|
if (ignoreEndsWithProps.findIndex((x) => key.endsWith(x)) !== -1) continue
|
||||||
|
if (key.endsWith('id')) {
|
||||||
|
rowIdData += idsToBeAdded(key, value, flat)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (displayValues.some((v) => key.includes(v))) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
let colIndex = arrayData[0].findIndex((x) => x === key)
|
let colIndex = arrayData[0].findIndex((x) => x === key)
|
||||||
if (colIndex === -1) {
|
if (colIndex === -1) {
|
||||||
colIndex = arrayData[0].length
|
colIndex = arrayData[0].length
|
||||||
arrayData[0].push(key)
|
arrayData[0].push(key)
|
||||||
}
|
}
|
||||||
rowData[colIndex] = value
|
rowData[colIndex] = Array.isArray(value) ? JSON.stringify(value) : value
|
||||||
}
|
}
|
||||||
arrayData.push(rowData)
|
arrayData.push(rowData)
|
||||||
|
arrayIdData.push(rowIdData)
|
||||||
|
}
|
||||||
|
|
||||||
|
function idsToBeAdded(key, value, flat) {
|
||||||
|
for (let i = 0; i < displayValues.length; i++) {
|
||||||
|
if (flat[key.slice(0, -2).concat(displayValues[i]).concat('.0.id')]) {
|
||||||
|
return value + ','
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let speckleType = flat[key.slice(0, -2).concat('speckle_type')]
|
||||||
|
if (!speckleType) return ''
|
||||||
|
|
||||||
|
for (let i = 0; i < speckleTypesWithGeometry.length; i++) {
|
||||||
|
if (speckleType.startsWith(speckleTypesWithGeometry[i])) {
|
||||||
|
return value + ','
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
//called if the received data does not contain objects => it's a table, a list or a single value
|
//called if the received data does not contain objects => it's a table, a list or a single value
|
||||||
async function bakeArray(data, context) {
|
export async function bakeArray(data, rowStart, colStart, context) {
|
||||||
//it's a single value
|
//it's a single value
|
||||||
if (!Array.isArray(data)) {
|
if (!Array.isArray(data)) {
|
||||||
let valueRange = sheet.getCell(rowStart, colStart)
|
let valueRange = sheet.getCell(rowStart, colStart)
|
||||||
@@ -74,33 +123,117 @@ async function bakeArray(data, context) {
|
|||||||
colIndex++
|
colIndex++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//it's a list of lists aka table
|
// it's a list of lists aka table
|
||||||
else {
|
else {
|
||||||
let counter = 0
|
|
||||||
let rowIndex = 0
|
let rowIndex = 0
|
||||||
for (let array of data) {
|
let batchSize = 50
|
||||||
let colIndex = 0
|
while (rowIndex < data.length) {
|
||||||
let actualColIndex = 0
|
let dataBatch = data.slice(rowIndex, rowIndex + batchSize)
|
||||||
for (let item of array) {
|
let numRows = dataBatch.length
|
||||||
if (headerIndices.length === 0 || headerIndices.includes(colIndex)) {
|
let rangeAddress = getRangeAddressFromIndicies(
|
||||||
let valueRange = sheet.getCell(rowIndex + rowStart, actualColIndex + colStart)
|
rowStart + rowIndex,
|
||||||
valueRange.values = Array.isArray(item) ? JSON.stringify(item) : item
|
colStart,
|
||||||
actualColIndex++
|
rowStart + rowIndex + numRows - 1,
|
||||||
counter++
|
colStart + data[0].length - 1
|
||||||
}
|
)
|
||||||
colIndex++
|
let valueRange = sheet.getRange(rangeAddress)
|
||||||
//sync in batches to avoid a RequestPayloadSizeLimitExceeded
|
valueRange.values = dataBatch
|
||||||
if (counter > 5000) {
|
rowIndex += numRows
|
||||||
counter = 0
|
await context.sync()
|
||||||
await context.sync()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rowIndex++
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function bakeTable(data, context, sheet, name, rowStart, colStart, headerRowIndex) {
|
||||||
|
let rowIndex = 0
|
||||||
|
let batchSize = 50
|
||||||
|
while (rowIndex < data.length) {
|
||||||
|
let dataBatch = data.slice(rowIndex, rowIndex + batchSize)
|
||||||
|
let numRows = dataBatch.length
|
||||||
|
let rangeAddress = getRangeAddressFromIndicies(
|
||||||
|
rowStart + headerRowIndex + rowIndex,
|
||||||
|
colStart,
|
||||||
|
rowStart + headerRowIndex + rowIndex + numRows - 1,
|
||||||
|
colStart + data[0].length - 1
|
||||||
|
)
|
||||||
|
let valueRange = sheet.getRange(rangeAddress)
|
||||||
|
valueRange.values = dataBatch
|
||||||
|
rowIndex += numRows
|
||||||
|
await context.sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalRangeAddress = getRangeAddressFromIndicies(
|
||||||
|
rowStart + headerRowIndex,
|
||||||
|
colStart,
|
||||||
|
rowStart + headerRowIndex + data.length - 1,
|
||||||
|
colStart + data[0].length - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
sheet.activate()
|
||||||
|
let table = sheet.tables.add(totalRangeAddress, true)
|
||||||
|
table.load('id')
|
||||||
|
await context.sync()
|
||||||
|
|
||||||
|
let tableId = removeNonAlphanumericCharacters(table.id)
|
||||||
|
let columnMetaAddress = getRangeAddressFromIndicies(
|
||||||
|
rowStart,
|
||||||
|
colStart + 1,
|
||||||
|
rowStart,
|
||||||
|
colStart + data[0].length - 1
|
||||||
|
)
|
||||||
|
let columnMetaRange = sheet.getRange(columnMetaAddress)
|
||||||
|
let rowMetaAddress = getRangeAddressFromIndicies(
|
||||||
|
rowStart,
|
||||||
|
colStart,
|
||||||
|
rowStart + headerRowIndex + rowIndex - 1,
|
||||||
|
colStart
|
||||||
|
)
|
||||||
|
let rowMetaRange = sheet.getRange(rowMetaAddress)
|
||||||
|
|
||||||
|
sheet.names.add(`speckleColumnMetadata_${tableId}`, columnMetaRange)
|
||||||
|
sheet.names.add(`speckleRowMetadata_${tableId}`, rowMetaRange)
|
||||||
|
context.workbook.tables.onDeleted.add(onTableDeleted)
|
||||||
|
context.workbook.tables.onChanged.add(onTableChanged)
|
||||||
|
|
||||||
|
await context.sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeNonAlphanumericCharacters(s) {
|
||||||
|
return s.replace(/\W/g, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hideRowOrColumn(sheet, columnIndex = -1, rowIndex = -1) {
|
||||||
|
if (columnIndex > -1) {
|
||||||
|
let columnLetter = numberToLetters(columnIndex)
|
||||||
|
sheet.getRange(`${columnLetter}:${columnLetter}`).columnHidden = true
|
||||||
|
}
|
||||||
|
if (rowIndex > -1) {
|
||||||
|
let rowRange = sheet.getRange(`${rowIndex + 1}:${rowIndex + 1}`)
|
||||||
|
rowRange.rowHidden = true
|
||||||
|
rowRange.format.wrapText = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addIdDataToObjectData() {
|
||||||
|
if (
|
||||||
|
arrayData.length != arrayIdData.length ||
|
||||||
|
arrayData.length <= 1 ||
|
||||||
|
!Array.isArray(arrayData[0])
|
||||||
|
) {
|
||||||
|
console.log('Could not attach object ids to table')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// if all speckleIds are empty strings then don't add the column
|
||||||
|
if (arrayIdData.slice(1, -1).every((val) => !val)) return
|
||||||
|
|
||||||
|
for (let i = 0; i < arrayData.length; i++) {
|
||||||
|
arrayData[i].push(arrayIdData[i])
|
||||||
|
// push an empty space at the end of each array because it will trim the overflow from the
|
||||||
|
// speckleIds in the previous column
|
||||||
|
arrayData[i].push(' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function headerListToTree(headers) {
|
function headerListToTree(headers) {
|
||||||
let tree = [{ id: 0, name: 'all fields', fullname: '', children: [] }]
|
let tree = [{ id: 0, name: 'all fields', fullname: '', children: [] }]
|
||||||
let i = 1
|
let i = 1
|
||||||
@@ -136,6 +269,114 @@ function hasObjects(data) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function constructRefObjectData(data, nearestObjectId, pathFromNearestObj, signal) {
|
||||||
|
if (!Array.isArray(data)) {
|
||||||
|
if (data.speckle_type == 'reference') {
|
||||||
|
return await getReferencedObject(streamId, data.referencedId, signal)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nearestObjectId) return data
|
||||||
|
|
||||||
|
const refIndex = data.findIndex((o) => o.speckle_type === 'reference')
|
||||||
|
|
||||||
|
// no referenced Ids, objects are already constructed
|
||||||
|
if (refIndex == -1) return data
|
||||||
|
|
||||||
|
var parent = await getReferencedObject(
|
||||||
|
streamId,
|
||||||
|
nearestObjectId,
|
||||||
|
signal,
|
||||||
|
!pathFromNearestObj.toLowerCase().includes('elements')
|
||||||
|
)
|
||||||
|
if (signal.aborted) return data
|
||||||
|
|
||||||
|
const delimiter = ':::'
|
||||||
|
var pathArray = pathFromNearestObj.split(delimiter)
|
||||||
|
for (let i = 0; i < pathArray.length; i++) {
|
||||||
|
if (!pathArray[i]) continue
|
||||||
|
parent = parent[pathArray[i]]
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
Array.isArray(parent) &&
|
||||||
|
data.length == parent.length &&
|
||||||
|
data[refIndex].referencedId == parent[refIndex].id
|
||||||
|
) {
|
||||||
|
return parent
|
||||||
|
}
|
||||||
|
//TODO: add logging here. If this line is reached then I'm pretty sure I did the traversal logic wrong
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// this function is brought to you by chatGPT
|
||||||
|
// it takes in an excel column index and outputs the column string
|
||||||
|
// 0 -> A
|
||||||
|
// 1 -> B
|
||||||
|
// ...
|
||||||
|
// 26 -> AA
|
||||||
|
// 27 -> AB
|
||||||
|
// ...
|
||||||
|
function numberToLetters(number) {
|
||||||
|
const base = 26
|
||||||
|
let letters = ''
|
||||||
|
do {
|
||||||
|
const remainder = number % base
|
||||||
|
letters = String.fromCharCode(65 + remainder) + letters
|
||||||
|
number = Math.floor(number / base) - 1
|
||||||
|
} while (number >= 0)
|
||||||
|
return letters
|
||||||
|
}
|
||||||
|
|
||||||
|
// this function is also the intellectual property of chatGPT
|
||||||
|
// it does the opposite of the numberToLetters function
|
||||||
|
// A -> 0
|
||||||
|
// B -> 1
|
||||||
|
// ...
|
||||||
|
// AA -> 26
|
||||||
|
// ...
|
||||||
|
function lettersToNumber(letters) {
|
||||||
|
const base = 26
|
||||||
|
let number = 0
|
||||||
|
for (let i = 0; i < letters.length; i++) {
|
||||||
|
const charCode = letters.charCodeAt(i) - 65 + 1
|
||||||
|
number = number * base + charCode
|
||||||
|
}
|
||||||
|
return number - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRangeAddressFromIndicies(startRow, startCol, endRow, endCol) {
|
||||||
|
let range = ''
|
||||||
|
|
||||||
|
range += numberToLetters(startCol) + String(startRow + 1)
|
||||||
|
range += ':'
|
||||||
|
range += numberToLetters(endCol) + String(endRow + 1)
|
||||||
|
|
||||||
|
return range
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getIndiciesFromRangeAddress(address) {
|
||||||
|
let parts = address.match(/[a-zA-Z]+|[0-9]+/g)
|
||||||
|
|
||||||
|
// if the sheet is part of the address, then get rid of it
|
||||||
|
if (parts[0] === 'Sheet') parts.splice(0, 2)
|
||||||
|
|
||||||
|
let output = []
|
||||||
|
for (let part of parts) {
|
||||||
|
let numValue = parseInt(part)
|
||||||
|
if (numValue) numValue -= 1
|
||||||
|
else numValue = lettersToNumber(part)
|
||||||
|
output.push(numValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.length == 2) {
|
||||||
|
output[2] = output[0]
|
||||||
|
output[3] = output[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
export async function receiveLatest(
|
export async function receiveLatest(
|
||||||
reference,
|
reference,
|
||||||
_streamId,
|
_streamId,
|
||||||
@@ -147,13 +388,21 @@ export async function receiveLatest(
|
|||||||
try {
|
try {
|
||||||
//TODO: only get objs that are needed?
|
//TODO: only get objs that are needed?
|
||||||
streamId = _streamId
|
streamId = _streamId
|
||||||
let item = await getReferencedObject(reference, signal)
|
let item = await getReferencedObject(streamId, reference, signal)
|
||||||
let parts = receiverSelection.fullKeyName.split('.')
|
let parts = receiverSelection.fullKeyName.split('.')
|
||||||
//picks the sub-object on which the user previously clicked `bake`
|
//picks the sub-object on which the user previously clicked `bake`
|
||||||
for (let part of parts) {
|
for (let part of parts) {
|
||||||
item = item[part]
|
item = item[part]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
store.dispatch('showSnackbar', {
|
||||||
|
message: 'Could not match the previous data structure',
|
||||||
|
color: 'error'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
await bake(
|
await bake(
|
||||||
item,
|
item,
|
||||||
_streamId,
|
_streamId,
|
||||||
@@ -161,6 +410,8 @@ export async function receiveLatest(
|
|||||||
_commitMsg,
|
_commitMsg,
|
||||||
null,
|
null,
|
||||||
signal,
|
signal,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
receiverSelection.headers,
|
receiverSelection.headers,
|
||||||
receiverSelection.range
|
receiverSelection.range
|
||||||
)
|
)
|
||||||
@@ -173,6 +424,126 @@ export async function receiveLatest(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function getAddress(_streamId, signal, previousRange, context) {
|
||||||
|
let address, range
|
||||||
|
// await window.Excel.run(async (context) => {
|
||||||
|
if (previousRange) {
|
||||||
|
let sheetName = previousRange.split('!')[0].replace(/'/g, '')
|
||||||
|
let rangeAddress = previousRange.split('!')[1]
|
||||||
|
sheet = context.workbook.worksheets.getItem(sheetName)
|
||||||
|
range = sheet.getRange(rangeAddress)
|
||||||
|
} else {
|
||||||
|
range = context.workbook.getSelectedRange()
|
||||||
|
}
|
||||||
|
range.load('address, worksheet, columnIndex, rowIndex')
|
||||||
|
await context.sync()
|
||||||
|
// })
|
||||||
|
|
||||||
|
sheet = range.worksheet
|
||||||
|
sheet.load('items/name')
|
||||||
|
|
||||||
|
address = range.address
|
||||||
|
rowStart = range.rowIndex
|
||||||
|
colStart = range.columnIndex
|
||||||
|
|
||||||
|
streamId = _streamId
|
||||||
|
arrayData = [[]]
|
||||||
|
arrayIdData = ['speckleIDs']
|
||||||
|
|
||||||
|
//if the incoming data has objects we need to flatten them to an array
|
||||||
|
//otherwise we just output it
|
||||||
|
isTabularData = true
|
||||||
|
|
||||||
|
if (signal.aborted) return
|
||||||
|
|
||||||
|
return address
|
||||||
|
}
|
||||||
|
export async function bakeSchedule(
|
||||||
|
data,
|
||||||
|
_streamId,
|
||||||
|
_commitId,
|
||||||
|
_commitMsg,
|
||||||
|
signal,
|
||||||
|
nearestObjectId,
|
||||||
|
pathFromNearestObj,
|
||||||
|
previousHeaders,
|
||||||
|
previousRange
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
let selectedHeaders = previousHeaders
|
||||||
|
let address
|
||||||
|
await window.Excel.run(async (context) => {
|
||||||
|
address = await getAddress(_streamId, signal, previousRange, context)
|
||||||
|
data = await constructRefObjectData(data, nearestObjectId, pathFromNearestObj, signal)
|
||||||
|
|
||||||
|
let schedulePaths = []
|
||||||
|
let flat = flatten(data, { maxDepth: 4 })
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(flat)) {
|
||||||
|
if (key.endsWith('speckle_type') && value.endsWith('DataTable')) {
|
||||||
|
schedulePaths.push(
|
||||||
|
key.replace('.speckle_type', '').replace('speckle_type', '').split('.')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.workbook.worksheets.load('items')
|
||||||
|
await context.sync()
|
||||||
|
|
||||||
|
for (let i = 0; i < schedulePaths.length; i++) {
|
||||||
|
if (i != 0) {
|
||||||
|
context.workbook.worksheets.add()
|
||||||
|
sheet = context.workbook.worksheets.items[-1]
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
let filteredData = { ...data }
|
||||||
|
schedulePaths[i].forEach((step) => {
|
||||||
|
if (step) {
|
||||||
|
filteredData = filteredData[step]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (signal.aborted) return
|
||||||
|
|
||||||
|
formatArrayDataForTable(filteredData, arrayData)
|
||||||
|
|
||||||
|
if (signal.aborted) return
|
||||||
|
await bakeDataTable(filteredData, arrayData, context, sheet, rowStart, colStart)
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await context.sync()
|
||||||
|
|
||||||
|
await store.dispatch('receiveCommit', {
|
||||||
|
sourceApplication: 'Excel',
|
||||||
|
streamId: _streamId,
|
||||||
|
commitId: _commitId,
|
||||||
|
message: _commitMsg
|
||||||
|
})
|
||||||
|
|
||||||
|
store.dispatch('showSnackbar', {
|
||||||
|
message: 'Data received successfully'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
let receiverSelection = { headers: selectedHeaders, range: address }
|
||||||
|
|
||||||
|
return receiverSelection
|
||||||
|
// eslint-disable-next-line no-unreachable
|
||||||
|
} catch (e) {
|
||||||
|
//pokemon
|
||||||
|
console.log(e)
|
||||||
|
|
||||||
|
let m = 'Something went wrong: ' + e
|
||||||
|
if (e.name !== 'AbortError') m = 'Operation cancelled'
|
||||||
|
|
||||||
|
store.dispatch('showSnackbar', {
|
||||||
|
message: m,
|
||||||
|
color: 'error'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
export async function bake(
|
export async function bake(
|
||||||
data,
|
data,
|
||||||
_streamId,
|
_streamId,
|
||||||
@@ -180,84 +551,64 @@ export async function bake(
|
|||||||
_commitMsg,
|
_commitMsg,
|
||||||
modal,
|
modal,
|
||||||
signal,
|
signal,
|
||||||
|
nearestObjectId,
|
||||||
|
pathFromNearestObj,
|
||||||
previousHeaders,
|
previousHeaders,
|
||||||
previousRange
|
previousRange
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
let address, range
|
|
||||||
let selectedHeaders = previousHeaders
|
let selectedHeaders = previousHeaders
|
||||||
|
let address
|
||||||
await window.Excel.run(async (context) => {
|
await window.Excel.run(async (context) => {
|
||||||
if (previousRange) {
|
address = await getAddress(_streamId, signal, previousRange, context)
|
||||||
let sheetName = previousRange.split('!')[0].replace(/'/g, '')
|
data = await constructRefObjectData(data, nearestObjectId, pathFromNearestObj, signal)
|
||||||
let rangeAddress = previousRange.split('!')[1]
|
|
||||||
sheet = context.workbook.worksheets.getItem(sheetName)
|
if (signal.aborted) return
|
||||||
range = sheet.getRange(rangeAddress)
|
// check for specific conversions
|
||||||
|
if (checkIfReceivingDataTable(data)) {
|
||||||
|
formatArrayDataForTable(data, arrayData)
|
||||||
|
await bakeDataTable(data, arrayData, context, sheet, rowStart, colStart)
|
||||||
} else {
|
} else {
|
||||||
range = context.workbook.getSelectedRange()
|
if (hasObjects(data, signal)) {
|
||||||
}
|
isTabularData = false
|
||||||
range.load('address, worksheet, columnIndex, rowIndex')
|
await flattenData(data, signal)
|
||||||
await context.sync()
|
//transpose 2d array, sort alphabetically, then transpose again
|
||||||
|
//this helps ensure the order of the baked columns is the same across streams
|
||||||
|
arrayData = arrayData[0].map((_, colIndex) => arrayData.map((row) => row[colIndex]))
|
||||||
|
arrayData = arrayData.sort((a, b) => (a[0] > b[0] ? 1 : -1))
|
||||||
|
arrayData = arrayData[0].map((_, colIndex) => arrayData.map((row) => row[colIndex]))
|
||||||
|
} else arrayData = data
|
||||||
|
|
||||||
sheet = range.worksheet
|
if (signal.aborted) return
|
||||||
sheet.load('items/name')
|
|
||||||
|
|
||||||
address = range.address
|
if (!isTabularData && arrayData[0].length > 25) {
|
||||||
rowStart = range.rowIndex
|
if (!previousHeaders && modal) {
|
||||||
colStart = range.columnIndex
|
let headers = headerListToTree(arrayData[0], signal)
|
||||||
|
let dialog = await modal.open(
|
||||||
streamId = _streamId
|
headers,
|
||||||
arrayData = [[]]
|
`You are about to receive ${arrayData[0].length} columns and ${arrayData.length} rows, you can filter them below.`
|
||||||
headerIndices = []
|
)
|
||||||
|
console.log(dialog)
|
||||||
//if the incoming data has objects we need to flatten them to an array
|
if (!dialog.result) {
|
||||||
//otherwise we just output it
|
store.dispatch('showSnackbar', {
|
||||||
isTabularData = true
|
message: 'Operation cancelled'
|
||||||
|
})
|
||||||
if (signal.aborted) return
|
return
|
||||||
if (hasObjects(data, signal)) {
|
}
|
||||||
isTabularData = false
|
selectedHeaders = filterArrayData(dialog.items, arrayData)
|
||||||
await flattenData(data, signal)
|
} else if (previousHeaders) {
|
||||||
//transpose 2d array, sort alphabetically, then transpose again
|
selectedHeaders = filterArrayData(previousHeaders, arrayData)
|
||||||
//this helps ensure the order of the baked columns is the same across streams
|
|
||||||
arrayData = arrayData[0].map((_, colIndex) => arrayData.map((row) => row[colIndex]))
|
|
||||||
arrayData = arrayData.sort((a, b) => (a[0] > b[0] ? 1 : -1))
|
|
||||||
arrayData = arrayData[0].map((_, colIndex) => arrayData.map((row) => row[colIndex]))
|
|
||||||
} else arrayData = data
|
|
||||||
|
|
||||||
if (signal.aborted) return
|
|
||||||
|
|
||||||
if (!isTabularData && arrayData[0].length > 25) {
|
|
||||||
//it's manual run
|
|
||||||
if (!previousHeaders && modal) {
|
|
||||||
let headers = headerListToTree(arrayData[0], signal)
|
|
||||||
let dialog = await modal.open(
|
|
||||||
headers,
|
|
||||||
`You are about to receive ${arrayData[0].length} columns and ${arrayData.length} rows, you can filter them below.`
|
|
||||||
)
|
|
||||||
console.log(dialog)
|
|
||||||
if (!dialog.result) {
|
|
||||||
store.dispatch('showSnackbar', {
|
|
||||||
message: 'Operation cancelled'
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (arrayData[0].length !== dialog.items.length) {
|
|
||||||
selectedHeaders = dialog.items
|
|
||||||
//construct a list of the index of each header to include
|
|
||||||
for (let item of selectedHeaders) headerIndices.push(arrayData[0].indexOf(item))
|
|
||||||
}
|
|
||||||
} else if (previousHeaders) {
|
|
||||||
for (let item of previousHeaders) {
|
|
||||||
let index = arrayData[0].indexOf(item)
|
|
||||||
if (index !== -1) headerIndices.push(index)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(arrayData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (signal.aborted) return
|
||||||
|
|
||||||
|
await addIdDataToObjectData()
|
||||||
|
await bakeArray(arrayData, rowStart, colStart, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (signal.aborted) return
|
|
||||||
|
|
||||||
await bakeArray(arrayData, context)
|
|
||||||
await context.sync()
|
await context.sync()
|
||||||
|
|
||||||
await store.dispatch('receiveCommit', {
|
await store.dispatch('receiveCommit', {
|
||||||
@@ -290,6 +641,27 @@ export async function bake(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function filterArrayData(headers, allData) {
|
||||||
|
if (allData[0].length == headers.length) return allData
|
||||||
|
|
||||||
|
let filteredData = [[]]
|
||||||
|
// initialize filteredData array with empty arrays
|
||||||
|
for (let i = 0; i < allData.length; i++) {
|
||||||
|
filteredData[i] = []
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let item of headers) {
|
||||||
|
let index = allData[0].indexOf(item)
|
||||||
|
if (index === -1) continue
|
||||||
|
|
||||||
|
for (let i = 0; i < allData.length; i++) {
|
||||||
|
filteredData[i].push(allData[i][index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filteredData
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
export async function send(savedStream, streamId, branchName, message) {
|
export async function send(savedStream, streamId, branchName, message) {
|
||||||
try {
|
try {
|
||||||
await window.Excel.run(async (context) => {
|
await window.Excel.run(async (context) => {
|
||||||
@@ -303,26 +675,36 @@ export async function send(savedStream, streamId, branchName, message) {
|
|||||||
let values = range.values
|
let values = range.values
|
||||||
|
|
||||||
let data = []
|
let data = []
|
||||||
if (savedStream.hasHeaders) {
|
// check for specific conversion
|
||||||
for (let row = 1; row < values.length; row++) {
|
let table = await getDataTableContainingRange(range, sheet, context)
|
||||||
let object = {}
|
if (table) {
|
||||||
for (let col = 0; col < values[0].length; col++) {
|
data = await BuildDataTableObject(range, values, table, sheet, context)
|
||||||
let propName = values[0][col]
|
|
||||||
//if (propName !== 'id' && propName.endsWith('.id')) continue
|
|
||||||
let propValue = values[row][col]
|
|
||||||
object[propName] = propValue
|
|
||||||
}
|
|
||||||
let unlattened = unflatten(object)
|
|
||||||
data.push(unlattened)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
for (let row = 0; row < values.length; row++) {
|
if (savedStream.hasHeaders) {
|
||||||
let rowArray = []
|
for (let row = 1; row < values.length; row++) {
|
||||||
for (let col = 0; col < values[0].length; col++) {
|
let object = {}
|
||||||
rowArray.push(values[row][col])
|
for (let col = 0; col < values[0].length; col++) {
|
||||||
|
let propName = values[0][col]
|
||||||
|
//if (propName !== 'id' && propName.endsWith('.id')) continue
|
||||||
|
let propValue = values[row][col]
|
||||||
|
object[propName] = propValue
|
||||||
|
}
|
||||||
|
// generate a hash if none is present
|
||||||
|
object.id = object.id || MD5(JSON.stringify(object)).toString(enc.Hex)
|
||||||
|
let unlattened = unflatten(object)
|
||||||
|
data.push(unlattened)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let row = 0; row < values.length; row++) {
|
||||||
|
let rowArray = []
|
||||||
|
for (let col = 0; col < values[0].length; col++) {
|
||||||
|
rowArray.push(values[row][col])
|
||||||
|
}
|
||||||
|
data.push(rowArray)
|
||||||
}
|
}
|
||||||
data.push(rowArray)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data = { data: data, speckle_type: 'Base' }
|
||||||
}
|
}
|
||||||
|
|
||||||
await store.dispatch('createCommit', {
|
await store.dispatch('createCommit', {
|
||||||
|
|||||||
+14
-5
@@ -11,11 +11,6 @@ const routes = [
|
|||||||
name: 'streams',
|
name: 'streams',
|
||||||
component: () => import('../views/Streams.vue')
|
component: () => import('../views/Streams.vue')
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/add',
|
|
||||||
name: 'add',
|
|
||||||
component: () => import('../views/Add.vue')
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/streams/:streamId/commits/:commitId',
|
path: '/streams/:streamId/commits/:commitId',
|
||||||
name: 'commit',
|
name: 'commit',
|
||||||
@@ -24,11 +19,25 @@ const routes = [
|
|||||||
},
|
},
|
||||||
component: () => import('../views/Commit.vue')
|
component: () => import('../views/Commit.vue')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/streams/:streamId/:commitId?',
|
||||||
|
name: 'stream',
|
||||||
|
meta: {
|
||||||
|
title: 'Stream | Speckle'
|
||||||
|
},
|
||||||
|
component: () => import('../views/SingleStream.vue'),
|
||||||
|
props: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: '/login',
|
||||||
name: 'login',
|
name: 'login',
|
||||||
component: () => import('../views/Login.vue')
|
component: () => import('../views/Login.vue')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/singleStream',
|
||||||
|
name: 'singleStream',
|
||||||
|
component: () => import('../views/SingleStream.vue')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/redirect',
|
path: '/redirect',
|
||||||
name: 'redirect'
|
name: 'redirect'
|
||||||
|
|||||||
+1
-1
@@ -209,7 +209,7 @@ export default new Vuex.Store({
|
|||||||
variables: {
|
variables: {
|
||||||
object: {
|
object: {
|
||||||
streamId: streamId,
|
streamId: streamId,
|
||||||
objects: [{ data: object, speckle_type: 'Base' }]
|
objects: [object]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ export default {
|
|||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
ADD_STREAM(state, value) {
|
ADD_STREAM(state, value) {
|
||||||
state.streams.unshift(value)
|
const index = state.streams.findIndex((x) => x.id === value.id)
|
||||||
|
if (index == -1) state.streams.unshift(value)
|
||||||
},
|
},
|
||||||
REMOVE_STREAM(state, value) {
|
REMOVE_STREAM(state, value) {
|
||||||
const index = state.streams.findIndex((x) => x.id === value)
|
const index = state.streams.findIndex((x) => x.id === value)
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
require('url')
|
||||||
|
|
||||||
|
export class StreamWrapper {
|
||||||
|
constructor(streamIdOrUrl, accountId, serverUrl) {
|
||||||
|
this.originalOutput = streamIdOrUrl
|
||||||
|
try {
|
||||||
|
this.streamWrapperFromUrl(streamIdOrUrl)
|
||||||
|
} catch (e) {
|
||||||
|
this.serverUrl = serverUrl
|
||||||
|
this.userId = accountId
|
||||||
|
this.streamId = streamIdOrUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
streamWrapperFromUrl(streamUrl) {
|
||||||
|
this.url = new URL(streamUrl)
|
||||||
|
this.segments = this.url.pathname.split('/').map((segment) => segment + '/')
|
||||||
|
this.serverUrl = this.url.origin
|
||||||
|
|
||||||
|
if (this.segments.length >= 4 && this.segments[3]?.toLowerCase() === 'branches/') {
|
||||||
|
this.streamId = this.segments[2].replace('/', '')
|
||||||
|
if (this.segments.length > 5) {
|
||||||
|
let branchSegments = this.segments.slice(4, this.segments.length - 1)
|
||||||
|
this.branchName = branchSegments.join('')
|
||||||
|
} else {
|
||||||
|
this.branchName = this.segments[4]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (this.segments.length) {
|
||||||
|
case 3: // ie http://speckle.server/streams/8fecc9aa6d
|
||||||
|
if (this.segments[1].toLowerCase() === 'streams/')
|
||||||
|
this.streamId = this.segments[2].replace('/', '')
|
||||||
|
else throw new Error(`Cannot parse ${this.originalOutput} into a stream wrapper class`)
|
||||||
|
break
|
||||||
|
case 4: // ie https://speckle.server/streams/0c6ad366c4/globals/
|
||||||
|
if (this.segments[3].toLowerCase().startsWith('globals')) {
|
||||||
|
this.streamId = this.segments[2].replace('/', '')
|
||||||
|
this.branchName = this.segments[3].replace('/', '')
|
||||||
|
} else throw new Error(`Cannot parse ${this.originalOutput} into a stream wrapper class`)
|
||||||
|
break
|
||||||
|
case 5: // ie http://speckle.server/streams/8fecc9aa6d/commits/76a23d7179
|
||||||
|
switch (this.segments[3].toLowerCase()) {
|
||||||
|
case 'commits/':
|
||||||
|
this.streamId = this.segments[2].replace('/', '')
|
||||||
|
this.commitId = this.segments[4].replace('/', '')
|
||||||
|
break
|
||||||
|
case 'globals/':
|
||||||
|
this.streamId = this.segments[2].replace('/', '')
|
||||||
|
this.branchName = this.segments[3].replace('/', '')
|
||||||
|
this.commitId = this.segments[4].replace('/', '')
|
||||||
|
break
|
||||||
|
case 'branches/':
|
||||||
|
this.streamId = this.segments[2].replace('/', '')
|
||||||
|
this.branchName = this.segments[4].replace('/', '')
|
||||||
|
break
|
||||||
|
case 'objects/':
|
||||||
|
this.streamId = this.segments[2].replace('/', '')
|
||||||
|
this.objectId = this.segments[4].replace('/', '')
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw new Error(`Cannot parse ${this.originalOutput} into a stream wrapper class`)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw new Error(`Cannot parse ${this.originalOutput} into a stream wrapper class`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-container>
|
|
||||||
<v-row align="center">
|
|
||||||
<v-col cols="12" align="center" class="mt-5">
|
|
||||||
<p v-if="filteredStreams && filteredStreams.length > 0" class="subtitle">
|
|
||||||
Click on a stream to add it to this document. 👇
|
|
||||||
</p>
|
|
||||||
<p v-else-if="search" class="subtitle">No streams found 🧐</p>
|
|
||||||
<div v-else-if="!$apollo.loading">
|
|
||||||
<p class="subtitle">
|
|
||||||
You don't have any streams 😟,
|
|
||||||
<a :href="serverUrl" target="_blank">visit Speckle web to create one</a>
|
|
||||||
!
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<v-row align="center" class="my-0 py-0">
|
|
||||||
<v-col cols="12" class="my-0 py-0" align="center">
|
|
||||||
<v-text-field
|
|
||||||
v-model="search"
|
|
||||||
rounded
|
|
||||||
filled
|
|
||||||
clearable
|
|
||||||
label="Search"
|
|
||||||
class="mx-5 search"
|
|
||||||
prepend-inner-icon="mdi-magnify"
|
|
||||||
></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<v-row class="mt-0 pt-0">
|
|
||||||
<v-col cols="12" class="mt-0 pt-0">
|
|
||||||
<v-card elevation="0" color="transparent">
|
|
||||||
<div v-if="$apollo.loading" class="mx-4">
|
|
||||||
<v-skeleton-loader class="mt-3" type="article"></v-skeleton-loader>
|
|
||||||
<v-skeleton-loader class="mt-3" type="article"></v-skeleton-loader>
|
|
||||||
<v-skeleton-loader class="mt-3" type="article"></v-skeleton-loader>
|
|
||||||
</div>
|
|
||||||
<v-card-text v-if="streams" class="mt-0 pt-3">
|
|
||||||
<div v-for="(stream, i) in filteredStreams" :key="i">
|
|
||||||
<list-item-stream :stream="stream"></list-item-stream>
|
|
||||||
</div>
|
|
||||||
<infinite-loading
|
|
||||||
v-if="streams && streams.items && streams.items.length < streams.totalCount"
|
|
||||||
@infinite="infiniteHandler"
|
|
||||||
>
|
|
||||||
<div slot="no-more">These are all your streams!</div>
|
|
||||||
<div slot="no-results">There are no streams to load</div>
|
|
||||||
</infinite-loading>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import ListItemStream from '../components/ListItemStream'
|
|
||||||
import gql from 'graphql-tag'
|
|
||||||
import streamsQuery from '../graphql/streams.gql'
|
|
||||||
import InfiniteLoading from 'vue-infinite-loading'
|
|
||||||
import { createClient } from '../vue-apollo'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Add',
|
|
||||||
|
|
||||||
components: {
|
|
||||||
ListItemStream,
|
|
||||||
InfiniteLoading
|
|
||||||
},
|
|
||||||
data: () => ({
|
|
||||||
streams: [],
|
|
||||||
search: ''
|
|
||||||
}),
|
|
||||||
apollo: {
|
|
||||||
$client: createClient(),
|
|
||||||
streams: {
|
|
||||||
prefetch: true,
|
|
||||||
query: streamsQuery,
|
|
||||||
fetchPolicy: 'cache-and-network', //https://www.apollographql.com/docs/react/data/queries/
|
|
||||||
variables() {
|
|
||||||
return {
|
|
||||||
query: this.search
|
|
||||||
}
|
|
||||||
},
|
|
||||||
skip() {
|
|
||||||
return this.search && this.search.length > 0 && this.search.length < 3
|
|
||||||
},
|
|
||||||
debounce: 300
|
|
||||||
},
|
|
||||||
$subscribe: {
|
|
||||||
userStreamAdded: {
|
|
||||||
query: gql`
|
|
||||||
subscription {
|
|
||||||
userStreamAdded
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
result() {
|
|
||||||
this.$apollo.queries.streams.refetch()
|
|
||||||
},
|
|
||||||
skip() {
|
|
||||||
return !this.user
|
|
||||||
}
|
|
||||||
},
|
|
||||||
userStreamRemoved: {
|
|
||||||
query: gql`
|
|
||||||
subscription {
|
|
||||||
userStreamRemoved
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
result() {
|
|
||||||
this.$apollo.queries.streams.refetch()
|
|
||||||
}
|
|
||||||
// skip() {
|
|
||||||
// return !this.isAuthenticated
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
isAuthenticated() {
|
|
||||||
return this.$store.getters.isAuthenticated
|
|
||||||
},
|
|
||||||
serverUrl() {
|
|
||||||
return this.$store.getters.serverUrl
|
|
||||||
},
|
|
||||||
filteredStreams() {
|
|
||||||
if (!this.streams.items) return null
|
|
||||||
return this.streams.items.filter(
|
|
||||||
(x) => this.$store.state.streams.streams.findIndex((y) => y.id === x.id) === -1
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.$mixpanel.track('Connector Action', { name: 'Stream List' })
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
infiniteHandler($state) {
|
|
||||||
this.$apollo.queries.streams.fetchMore({
|
|
||||||
variables: {
|
|
||||||
cursor: this.streams.cursor
|
|
||||||
},
|
|
||||||
// Transform the previous result with new data
|
|
||||||
updateQuery: (previousResult, { fetchMoreResult }) => {
|
|
||||||
const newItems = fetchMoreResult.streams.items
|
|
||||||
|
|
||||||
//set vue-infinite state
|
|
||||||
if (newItems.length === 0) $state.complete()
|
|
||||||
else $state.loaded()
|
|
||||||
|
|
||||||
return {
|
|
||||||
streams: {
|
|
||||||
__typename: previousResult.streams.__typename,
|
|
||||||
totalCount: fetchMoreResult.streams.totalCount,
|
|
||||||
cursor: fetchMoreResult.streams.cursor,
|
|
||||||
// Merging the new streams
|
|
||||||
items: [...previousResult.streams.items, ...newItems]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
.search .v-text-field__details {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<v-container>
|
<v-container>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col class="py-2">
|
<v-col class="py-2">
|
||||||
<v-btn text large color="primary" to="/">
|
<v-btn text large color="primary" :to="`/streams/${streamId}`">
|
||||||
<v-icon dark>mdi-chevron-left</v-icon>
|
<v-icon dark>mdi-chevron-left</v-icon>
|
||||||
back
|
back
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
<object-speckle-viewer
|
<object-speckle-viewer
|
||||||
v-if="stream"
|
v-if="stream"
|
||||||
:stream-id="stream.id"
|
:stream-id="stream.id"
|
||||||
:object-id="stream.commit.referencedObject"
|
:nearest-object-id="stream.commit.referencedObject"
|
||||||
:value="commitObject"
|
:value="commitObject"
|
||||||
:downloadable="false"
|
:downloadable="false"
|
||||||
:expand="true"
|
:expand="true"
|
||||||
@@ -90,6 +90,9 @@ export default {
|
|||||||
speckle_type: 'reference',
|
speckle_type: 'reference',
|
||||||
referencedId: this.stream.commit.referencedObject
|
referencedId: this.stream.commit.referencedObject
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
streamId() {
|
||||||
|
return this.$route.params.streamId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,590 @@
|
|||||||
|
<template>
|
||||||
|
<v-card v-if="error" class="pa-5 mb-3" style="transition: all 0.2s">
|
||||||
|
<v-card-title class="subtitle-1 px-0 pt-0">
|
||||||
|
{{ error }} ⚠️
|
||||||
|
<div class="floating">
|
||||||
|
<!-- <v-btn
|
||||||
|
v-tooltip="`Remove this stream from the document`"
|
||||||
|
small
|
||||||
|
icon
|
||||||
|
color="red"
|
||||||
|
@click="remove"
|
||||||
|
>
|
||||||
|
<v-icon small>mdi-minus-circle-outline</v-icon>
|
||||||
|
</v-btn> -->
|
||||||
|
<v-btn
|
||||||
|
v-tooltip="`Open this stream in a new window`"
|
||||||
|
small
|
||||||
|
icon
|
||||||
|
color="primary"
|
||||||
|
:href="`${serverUrl}/streams/${savedStream.id}`"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<v-icon small>mdi-open-in-new</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text class="px-0">
|
||||||
|
<span v-if="error == 'Stream not found'">
|
||||||
|
The stream might have been deleted or belog to another Speckle server
|
||||||
|
</span>
|
||||||
|
<span v-if="error == 'You do not have access to this resource.'">
|
||||||
|
Please ask the stream owner for access or to make it public
|
||||||
|
</span>
|
||||||
|
<br />
|
||||||
|
Stream Id: {{ savedStream.id }}
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
<div v-else-if="$apollo.queries.stream.loading" class="mx-0 mb-3 fill-height background-light">
|
||||||
|
<div class="progress-parent">
|
||||||
|
<v-progress-circular
|
||||||
|
indeterminate
|
||||||
|
color="primary"
|
||||||
|
:size="150"
|
||||||
|
class="fill-height"
|
||||||
|
></v-progress-circular>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="stream" id="viewer-parent" class="background-light">
|
||||||
|
<div v-if="viewerLoading" class="progress-parent">
|
||||||
|
<v-progress-circular
|
||||||
|
indeterminate
|
||||||
|
color="primary"
|
||||||
|
:size="150"
|
||||||
|
class="fill-height"
|
||||||
|
></v-progress-circular>
|
||||||
|
</div>
|
||||||
|
<div id="viewer"></div>
|
||||||
|
<div id="stream-info-parent">
|
||||||
|
<StreamController
|
||||||
|
ref="streamController"
|
||||||
|
:stream="stream"
|
||||||
|
:determined-conversion="determinedConversion"
|
||||||
|
@loadByReferencedId="loadViewerObjectByReferencedId"
|
||||||
|
@loadByCommitId="loadViewerObjectByCommitId"
|
||||||
|
@clearDeterminedConversion="clearDeterminedConversion"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import StreamController from '../components/StreamController.vue'
|
||||||
|
import streamQuery from '../graphql/stream.gql'
|
||||||
|
import {
|
||||||
|
send,
|
||||||
|
receiveLatest,
|
||||||
|
getIndiciesFromRangeAddress,
|
||||||
|
getRangeAddressFromIndicies
|
||||||
|
} from '../plugins/excel'
|
||||||
|
import gql from 'graphql-tag'
|
||||||
|
import { createClient } from '../vue-apollo'
|
||||||
|
import { Viewer, ViewerEvent } from '@speckle/viewer'
|
||||||
|
// import router from '../router'
|
||||||
|
|
||||||
|
let ac = new AbortController()
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
StreamController
|
||||||
|
},
|
||||||
|
async beforeRouteLeave(to, from, next) {
|
||||||
|
// remove on selection changed event that is tied to the viewer
|
||||||
|
if (this.onSelectionChangedEvent) {
|
||||||
|
await window.Excel.run(this.onSelectionChangedEvent.context, async (context) => {
|
||||||
|
this.onSelectionChangedEvent.remove()
|
||||||
|
await context.sync()
|
||||||
|
this.onSelectionChangedEvent = null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
next()
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
streamId: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
commitId: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
error: null,
|
||||||
|
progress: false,
|
||||||
|
message: '',
|
||||||
|
viewer: null,
|
||||||
|
objectIds: null,
|
||||||
|
selectedObjectIds: null,
|
||||||
|
filterViewer: true,
|
||||||
|
isReceiver: true,
|
||||||
|
selection: null,
|
||||||
|
hasHeaders: false,
|
||||||
|
viewerLoading: false,
|
||||||
|
referencedObject: null,
|
||||||
|
onSelectionChangedEvent: null,
|
||||||
|
determinedConversion: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
apollo: {
|
||||||
|
stream: {
|
||||||
|
prefetch: true,
|
||||||
|
query: streamQuery,
|
||||||
|
fetchPolicy: 'network-only',
|
||||||
|
variables() {
|
||||||
|
return {
|
||||||
|
id: this.streamId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
result() {
|
||||||
|
const index = this.$store.state.streams.streams.findIndex((x) => x.id === this.streamId)
|
||||||
|
let savedStream = null
|
||||||
|
if (index > -1) {
|
||||||
|
savedStream = this.$store.state.streams.streams[index]
|
||||||
|
this.$nextTick(function () {
|
||||||
|
this.$refs.streamController.isReceiver = savedStream.isReceiver
|
||||||
|
this.$refs.streamController.selection = savedStream.selection
|
||||||
|
this.$refs.streamController.hasHeaders = savedStream.hasHeaders
|
||||||
|
this.$refs.streamController.selectedBranchName = savedStream.selectedBranchName
|
||||||
|
this.$refs.streamController.selectedCommitId = savedStream.selectedCommitId
|
||||||
|
this.$refs.streamController.receiverSelection = savedStream.receiverSelection
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.$nextTick(function () {
|
||||||
|
this.$refs.streamController.isReceiver = true
|
||||||
|
this.$refs.streamController.selectedBranchName = this.$refs.streamController.selectedBranch.name
|
||||||
|
this.$refs.streamController.selectedCommitId = this.selectedCommit.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// if this page is reached via a link with a commit id, this set the branch and id on the card
|
||||||
|
if (this.commitId) {
|
||||||
|
this.$nextTick(function () {
|
||||||
|
this.$refs.streamController.selectedCommitId = this.commitId
|
||||||
|
const branch = this.stream.branches.items.find((x) =>
|
||||||
|
x.commits.items.findIndex(
|
||||||
|
(y) => y.id == this.$refs.streamController.selectedCommitId - 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
this.$refs.streamController.selectedBranchName = branch.name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error(error) {
|
||||||
|
console.log(this.error)
|
||||||
|
this.error = JSON.stringify(error.message)
|
||||||
|
.replaceAll('"', '')
|
||||||
|
.replace('GraphQL error: ', '')
|
||||||
|
console.log(this.error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
$client: createClient(),
|
||||||
|
$subscribe: {
|
||||||
|
streamUpdated: {
|
||||||
|
query: gql`
|
||||||
|
subscription($id: String!) {
|
||||||
|
streamUpdated(streamId: $id)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables() {
|
||||||
|
return { id: this.streamId }
|
||||||
|
},
|
||||||
|
result() {
|
||||||
|
this.$apollo.queries.stream.refetch()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
commitCreated: {
|
||||||
|
query: gql`
|
||||||
|
subscription($streamId: String!) {
|
||||||
|
commitCreated(streamId: $streamId)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables() {
|
||||||
|
return { streamId: this.streamId }
|
||||||
|
},
|
||||||
|
result(commitInfo) {
|
||||||
|
this.$apollo.queries.stream.refetch()
|
||||||
|
if (this.isReceiver)
|
||||||
|
this.$store.dispatch('showSnackbar', {
|
||||||
|
message: `New commit on ${this.stream.name} @ ${commitInfo.data.commitCreated.branchName}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
commitUpdated: {
|
||||||
|
query: gql`
|
||||||
|
subscription($id: String!) {
|
||||||
|
commitUpdated(streamId: $id)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables() {
|
||||||
|
return { id: this.streamId }
|
||||||
|
},
|
||||||
|
result() {
|
||||||
|
this.$apollo.queries.stream.refetch()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
branchCreated: {
|
||||||
|
query: gql`
|
||||||
|
subscription($id: String!) {
|
||||||
|
branchCreated(streamId: $id)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables() {
|
||||||
|
return { id: this.streamId }
|
||||||
|
},
|
||||||
|
result() {
|
||||||
|
this.$apollo.queries.stream.refetch()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
branchDeleted: {
|
||||||
|
query: gql`
|
||||||
|
subscription($id: String!) {
|
||||||
|
branchDeleted(streamId: $id)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables() {
|
||||||
|
return { id: this.streamId }
|
||||||
|
},
|
||||||
|
result() {
|
||||||
|
this.$apollo.queries.stream.refetch()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
branchUpdated: {
|
||||||
|
query: gql`
|
||||||
|
subscription($id: String!) {
|
||||||
|
branchUpdated(streamId: $id)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables() {
|
||||||
|
return { id: this.streamId }
|
||||||
|
},
|
||||||
|
result() {
|
||||||
|
this.$apollo.queries.stream.refetch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
serverUrl() {
|
||||||
|
return this.$store.getters.serverUrl
|
||||||
|
},
|
||||||
|
savedStream() {
|
||||||
|
return {
|
||||||
|
id: this.streamId,
|
||||||
|
isReceiver: this.isReceiver,
|
||||||
|
selection: this.selection,
|
||||||
|
hasHeaders: this.hasHeaders,
|
||||||
|
selectedBranchName: this.$refs.streamController.selectedBranchName,
|
||||||
|
selectedCommitId: this.$refs.streamController.selectedCommitId,
|
||||||
|
receiverSelection: this.receiverSelection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
checkForDeterminedConversions() {
|
||||||
|
const dataTree = this.viewer.getDataTree()
|
||||||
|
// Get all mesh speckle objects
|
||||||
|
try {
|
||||||
|
const schedule = dataTree.findFirst((guid, obj) => {
|
||||||
|
return obj.speckle_type.endsWith('DataTable')
|
||||||
|
})
|
||||||
|
|
||||||
|
if (schedule) {
|
||||||
|
this.determinedConversion = 'Schedule'
|
||||||
|
} else {
|
||||||
|
this.determinedConversion = ''
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
this.determinedConversion = ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clearDeterminedConversion() {
|
||||||
|
this.determinedConversion = ''
|
||||||
|
},
|
||||||
|
async initViewer() {
|
||||||
|
if (this.viewer) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var container = document.getElementById('viewer')
|
||||||
|
var v = new Viewer(container)
|
||||||
|
await v.init()
|
||||||
|
|
||||||
|
// highlight selected objects in sheet
|
||||||
|
v.on(ViewerEvent.ObjectClicked, async (data) => {
|
||||||
|
console.log(data?.hits[0]?.object.id)
|
||||||
|
var speckleId = data?.hits[0]?.object.id
|
||||||
|
if (speckleId == undefined) v.resetFilters()
|
||||||
|
else {
|
||||||
|
v.selectObjects(new Array(data?.hits[0]?.object.id))
|
||||||
|
await window.Excel.run(async (context) => {
|
||||||
|
var sheet = context.workbook.worksheets.getActiveWorksheet()
|
||||||
|
var range = sheet.getUsedRange()
|
||||||
|
var found = range.findOrNullObject(speckleId, {
|
||||||
|
completeMatch: false, // Match the whole cell value.
|
||||||
|
matchCase: false, // Don't match case.
|
||||||
|
searchDirection: window.Excel.SearchDirection.forward // Start search at the beginning of the range.
|
||||||
|
})
|
||||||
|
|
||||||
|
found.load('address')
|
||||||
|
// Update the fill color
|
||||||
|
if (found) {
|
||||||
|
// var extendedRange = found.get
|
||||||
|
this.filterViewer = false
|
||||||
|
found.getExtendedRange(window.Excel.KeyboardDirection.left, found).select()
|
||||||
|
}
|
||||||
|
await context.sync()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// highlight selected objects in viewer
|
||||||
|
await window.Excel.run(async (context) => {
|
||||||
|
var sheet = context.workbook.worksheets.getActiveWorksheet()
|
||||||
|
this.onSelectionChangedEvent ??= sheet.onSelectionChanged.add(this.checkModelForSelection)
|
||||||
|
await context.sync()
|
||||||
|
})
|
||||||
|
|
||||||
|
v.setLightConfiguration({
|
||||||
|
enabled: true,
|
||||||
|
castShadow: false, // there is a bug involving the shadows so turn them off for now
|
||||||
|
intensity: 5,
|
||||||
|
color: 0xffffff,
|
||||||
|
elevation: 1.33,
|
||||||
|
azimuth: 0.75,
|
||||||
|
radius: 0,
|
||||||
|
indirectLightIntensity: 1.2,
|
||||||
|
shadowcatcher: true
|
||||||
|
})
|
||||||
|
this.viewer = v
|
||||||
|
},
|
||||||
|
async loadViewerObjectByReferencedId(referencedObject) {
|
||||||
|
if (referencedObject === this.referencedObject) return
|
||||||
|
if (this.viewerLoading) {
|
||||||
|
await this.viewer?.cancelLoad(
|
||||||
|
`${this.serverUrl}/streams/${this.streamId}/objects/${this.referencedObject}`,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
this.viewerLoading = false
|
||||||
|
}
|
||||||
|
this.referencedObject = referencedObject
|
||||||
|
await this.initViewer()
|
||||||
|
await this.viewer?.unloadAll()
|
||||||
|
this.viewerLoading = true
|
||||||
|
|
||||||
|
const APP_NAME = process.env.VUE_APP_SPECKLE_NAME
|
||||||
|
const TOKEN = `${APP_NAME}.AuthToken`
|
||||||
|
try {
|
||||||
|
await this.viewer?.loadObject(
|
||||||
|
`${this.serverUrl}/streams/${this.streamId}/objects/${referencedObject}`,
|
||||||
|
localStorage.getItem(TOKEN)
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
if (referencedObject == this.referencedObject) this.viewerLoading = false
|
||||||
|
}
|
||||||
|
this.checkForDeterminedConversions()
|
||||||
|
},
|
||||||
|
async loadViewerObjectByCommitId(commitId) {
|
||||||
|
const index = this.$refs.streamController.selectedBranch.commits.items.findIndex(
|
||||||
|
(x) => x.id === commitId
|
||||||
|
)
|
||||||
|
|
||||||
|
await this.loadViewerObjectByReferencedId(
|
||||||
|
this.$refs.streamController.selectedBranch.commits.items[index].referencedObject
|
||||||
|
)
|
||||||
|
},
|
||||||
|
async checkModelForSelection() {
|
||||||
|
if (!this.filterViewer) {
|
||||||
|
this.filterViewer = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let speckleIdColIndex = await this.getSpeckleIdsColIndex()
|
||||||
|
await window.Excel.run(async (context) => {
|
||||||
|
// Get the selected range.
|
||||||
|
let range = context.workbook.getSelectedRange()
|
||||||
|
range.load('address')
|
||||||
|
await context.sync()
|
||||||
|
let selectedRangeIndicies = getIndiciesFromRangeAddress(range.address)
|
||||||
|
|
||||||
|
if (selectedRangeIndicies[0] > speckleIdColIndex) {
|
||||||
|
this.unisolateObjects()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let speckleIdRangeAddress = getRangeAddressFromIndicies(
|
||||||
|
selectedRangeIndicies[1],
|
||||||
|
speckleIdColIndex,
|
||||||
|
selectedRangeIndicies[3],
|
||||||
|
speckleIdColIndex
|
||||||
|
)
|
||||||
|
let speckleIdRange = context.workbook.worksheets
|
||||||
|
.getActiveWorksheet()
|
||||||
|
.getRange(speckleIdRangeAddress)
|
||||||
|
speckleIdRange.load('text')
|
||||||
|
await context.sync()
|
||||||
|
|
||||||
|
let idsInViewer = new Array()
|
||||||
|
for (let i = 0; i < speckleIdRange.text?.length; i++) {
|
||||||
|
for (let j = 0; j < speckleIdRange.text[i].length; j++) {
|
||||||
|
if (speckleIdRange.text[i][j].length < 32) continue
|
||||||
|
let splitIDs = speckleIdRange.text[i][j].split(',')
|
||||||
|
for (let id = 0; id < splitIDs.length; id++) {
|
||||||
|
if (splitIDs[id].length == 32) idsInViewer.push(splitIDs[id])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.unisolateObjects()
|
||||||
|
|
||||||
|
if (idsInViewer.length > 0) {
|
||||||
|
this.viewer?.isolateObjects(idsInViewer)
|
||||||
|
this.selectedObjectIds = idsInViewer
|
||||||
|
// this.viewer?.zoom(idsInViewer)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
unisolateObjects() {
|
||||||
|
// unisolate previous objects
|
||||||
|
if (this.selectedObjectIds?.length > 0) {
|
||||||
|
this.viewer?.resetFilters()
|
||||||
|
this.viewer?.unIsolateObjects(this.selectedObjectIds)
|
||||||
|
this.selectedObjectIds = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getSpeckleIdsColIndex() {
|
||||||
|
return await window.Excel.run(async (context) => {
|
||||||
|
let sheet = context.workbook.worksheets.getActiveWorksheet()
|
||||||
|
let usedRange = sheet.getUsedRange()
|
||||||
|
|
||||||
|
// TODO: we may need to narrow the search field for large wbs
|
||||||
|
// or if we want to have more stable support for multiple tables in the same sheet
|
||||||
|
|
||||||
|
var found = usedRange.findOrNullObject('speckleIDs', {
|
||||||
|
completeMatch: true, // Match the whole cell value.
|
||||||
|
matchCase: true, // Don't match case.
|
||||||
|
searchDirection: window.Excel.SearchDirection.forward // Start search at the beginning of the range.
|
||||||
|
})
|
||||||
|
found.load('address')
|
||||||
|
await context.sync()
|
||||||
|
|
||||||
|
var idHeaderAddressIndicies = getIndiciesFromRangeAddress(found.address)
|
||||||
|
return idHeaderAddressIndicies[0]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
ac.abort()
|
||||||
|
},
|
||||||
|
async send() {
|
||||||
|
// these values need to be set to null or the models will not load
|
||||||
|
// when switching back to the receive mode
|
||||||
|
this.viewer = null
|
||||||
|
this.referencedObject = null
|
||||||
|
this.$store.dispatch('addStream', this.savedStream)
|
||||||
|
|
||||||
|
this.$mixpanel.track('Send')
|
||||||
|
send(
|
||||||
|
this.savedStream,
|
||||||
|
this.stream.id,
|
||||||
|
this.$refs.streamController.selectedBranch.name,
|
||||||
|
this.message
|
||||||
|
)
|
||||||
|
},
|
||||||
|
async receiveLatest() {
|
||||||
|
// this.$mixpanel.track('Receive')
|
||||||
|
ac = new AbortController()
|
||||||
|
|
||||||
|
console.log(this.savedStream.receiverSelection)
|
||||||
|
|
||||||
|
this.progress = true
|
||||||
|
await receiveLatest(
|
||||||
|
this.selectedCommit.referencedObject,
|
||||||
|
this.stream.id,
|
||||||
|
this.selectedCommit.id,
|
||||||
|
this.selectedCommit.message,
|
||||||
|
this.savedStream.receiverSelection,
|
||||||
|
ac.signal
|
||||||
|
)
|
||||||
|
this.progress = false
|
||||||
|
},
|
||||||
|
formatCommitName(id) {
|
||||||
|
if (this.$refs.streamController.selectedBranch.commits.items[0].id == id) {
|
||||||
|
return 'latest'
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.stream-card-select .v-text-field__details {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.v-btn .lower {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
.floating {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
#stream-info-parent {
|
||||||
|
/* max-width: 600px; */
|
||||||
|
/* left: 50%;
|
||||||
|
transform: translateX(-50%); */
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: 0 auto;
|
||||||
|
bottom: 0px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
#stream-info {
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 15px;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
#stream-info .row {
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
#viewer {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#viewer-parent {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.background-light {
|
||||||
|
background: #8e9eab;
|
||||||
|
background: -webkit-linear-gradient(to top right, #eeeeee, #c8e8ff) !important;
|
||||||
|
background: linear-gradient(to top right, #ffffff, #c8e8ff) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background-dark {
|
||||||
|
background: #141e30;
|
||||||
|
background: -webkit-linear-gradient(to top left, #243b55, #141e30) !important;
|
||||||
|
background: linear-gradient(to top left, #243b55, #141e30) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-parent {
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.v-progress-circular {
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+161
-21
@@ -2,25 +2,52 @@
|
|||||||
<v-container>
|
<v-container>
|
||||||
<v-row align="center">
|
<v-row align="center">
|
||||||
<v-col cols="12" align="center" class="mt-5">
|
<v-col cols="12" align="center" class="mt-5">
|
||||||
<span v-if="streams && streams.length > 0" class="subtitle">
|
<p v-if="search" class="subtitle">No streams found 🧐</p>
|
||||||
You have {{ streams.length }} stream{{ streams.length === 1 ? '' : 's' }}
|
<div v-else-if="!$apollo.loading && filteredStreams && filteredStreams.length == 0">
|
||||||
in this document 🙌
|
<p class="subtitle">
|
||||||
</span>
|
You don't have any streams 😟,
|
||||||
|
<a :href="serverUrl" target="_blank">visit Speckle web to create one</a>
|
||||||
<div v-else>
|
!
|
||||||
<p class="subtitle">You have no streams in this document</p>
|
</p>
|
||||||
<v-btn large class="mt-5" color="primary" to="add">Add a stream</v-btn>
|
|
||||||
</div>
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
<v-row>
|
<v-row align="center" class="my-0 py-0">
|
||||||
<v-col cols="12">
|
<v-col cols="12" class="my-0 py-0" align="center">
|
||||||
|
<v-text-field
|
||||||
|
v-model="search"
|
||||||
|
rounded
|
||||||
|
filled
|
||||||
|
clearable
|
||||||
|
label="Search"
|
||||||
|
class="mx-5 search"
|
||||||
|
prepend-inner-icon="mdi-magnify"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<create-stream-dialog :account-id="user.id" :server-url="serverUrl" />
|
||||||
|
|
||||||
|
<v-row class="mt-0 pt-0">
|
||||||
|
<v-col cols="12" class="mt-0 pt-0">
|
||||||
<v-card elevation="0" color="transparent">
|
<v-card elevation="0" color="transparent">
|
||||||
<v-card-text class="mt-0 pt-3">
|
<div v-if="$apollo.loading" class="mx-4">
|
||||||
<div v-for="stream in streams" :key="stream.id">
|
<v-skeleton-loader class="mt-3" type="article"></v-skeleton-loader>
|
||||||
<stream-card :saved-stream="stream"></stream-card>
|
<v-skeleton-loader class="mt-3" type="article"></v-skeleton-loader>
|
||||||
|
<v-skeleton-loader class="mt-3" type="article"></v-skeleton-loader>
|
||||||
|
</div>
|
||||||
|
<v-card-text v-if="streams" class="mt-0 pt-3">
|
||||||
|
<div v-for="(stream, i) in filteredStreams" :key="i">
|
||||||
|
<list-item-stream :stream="stream"></list-item-stream>
|
||||||
</div>
|
</div>
|
||||||
|
<infinite-loading
|
||||||
|
v-if="streams && streams.items && streams.items.length < streams.totalCount"
|
||||||
|
@infinite="infiniteHandler"
|
||||||
|
>
|
||||||
|
<div slot="no-more">These are all your streams!</div>
|
||||||
|
<div slot="no-results">There are no streams to load</div>
|
||||||
|
</infinite-loading>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
@@ -29,20 +56,133 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import StreamCard from '../components/StreamCard'
|
import ListItemStream from '../components/ListItemStream'
|
||||||
|
import gql from 'graphql-tag'
|
||||||
|
import streamsQuery from '../graphql/streams.gql'
|
||||||
|
import InfiniteLoading from 'vue-infinite-loading'
|
||||||
|
import { createClient } from '../vue-apollo'
|
||||||
|
import CreateStreamDialog from '../components/dialogs/CreateStreamDialog.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Streams',
|
name: 'Add',
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
StreamCard
|
ListItemStream,
|
||||||
|
InfiniteLoading,
|
||||||
|
CreateStreamDialog
|
||||||
},
|
},
|
||||||
computed: {
|
data: () => ({
|
||||||
streams() {
|
streams: [],
|
||||||
if (!this.$store.state) return []
|
search: ''
|
||||||
return this.$store.state.streams.streams
|
}),
|
||||||
|
apollo: {
|
||||||
|
$client: createClient(),
|
||||||
|
streams: {
|
||||||
|
prefetch: true,
|
||||||
|
query: streamsQuery,
|
||||||
|
fetchPolicy: 'cache-and-network', //https://www.apollographql.com/docs/react/data/queries/
|
||||||
|
variables() {
|
||||||
|
return {
|
||||||
|
query: this.search
|
||||||
|
}
|
||||||
|
},
|
||||||
|
skip() {
|
||||||
|
return this.search && this.search.length > 0 && this.search.length < 3
|
||||||
|
},
|
||||||
|
debounce: 300
|
||||||
|
},
|
||||||
|
$subscribe: {
|
||||||
|
userStreamAdded: {
|
||||||
|
query: gql`
|
||||||
|
subscription {
|
||||||
|
userStreamAdded
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
result() {
|
||||||
|
this.$apollo.queries.streams.refetch()
|
||||||
|
},
|
||||||
|
skip() {
|
||||||
|
return !this.user
|
||||||
|
}
|
||||||
|
},
|
||||||
|
userStreamRemoved: {
|
||||||
|
query: gql`
|
||||||
|
subscription {
|
||||||
|
userStreamRemoved
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
result() {
|
||||||
|
this.$apollo.queries.streams.refetch()
|
||||||
|
}
|
||||||
|
// skip() {
|
||||||
|
// return !this.isAuthenticated
|
||||||
|
// }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {}
|
computed: {
|
||||||
|
isAuthenticated() {
|
||||||
|
console.log(this.user)
|
||||||
|
return this.$store.getters.isAuthenticated
|
||||||
|
},
|
||||||
|
user() {
|
||||||
|
return this.$store.state.user.user
|
||||||
|
},
|
||||||
|
serverUrl() {
|
||||||
|
return this.$store.getters.serverUrl
|
||||||
|
},
|
||||||
|
filteredStreams() {
|
||||||
|
console.log('user', this.$store.state.user.user)
|
||||||
|
if (!this.streams.items) return null
|
||||||
|
|
||||||
|
let savedStreams = this.streams.items.filter(
|
||||||
|
(x) => this.$store.state.streams.streams.findIndex((y) => y.id === x.id) !== -1
|
||||||
|
)
|
||||||
|
let otherStreams = this.streams.items.filter(
|
||||||
|
(x) => this.$store.state.streams.streams.findIndex((y) => y.id === x.id) === -1
|
||||||
|
)
|
||||||
|
|
||||||
|
savedStreams.push(...otherStreams)
|
||||||
|
return savedStreams
|
||||||
|
// return this.streams.items.filter(
|
||||||
|
// (x) => this.$store.state.streams.streams.findIndex((y) => y.id === x.id) === -1
|
||||||
|
// )
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$mixpanel.track('Connector Action', { name: 'Stream List' })
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
infiniteHandler($state) {
|
||||||
|
this.$apollo.queries.streams.fetchMore({
|
||||||
|
variables: {
|
||||||
|
cursor: this.streams.cursor
|
||||||
|
},
|
||||||
|
// Transform the previous result with new data
|
||||||
|
updateQuery: (previousResult, { fetchMoreResult }) => {
|
||||||
|
const newItems = fetchMoreResult.streams.items
|
||||||
|
|
||||||
|
//set vue-infinite state
|
||||||
|
if (newItems.length === 0) $state.complete()
|
||||||
|
else $state.loaded()
|
||||||
|
|
||||||
|
return {
|
||||||
|
streams: {
|
||||||
|
__typename: previousResult.streams.__typename,
|
||||||
|
totalCount: fetchMoreResult.streams.totalCount,
|
||||||
|
cursor: fetchMoreResult.streams.cursor,
|
||||||
|
// Merging the new streams
|
||||||
|
items: [...previousResult.streams.items, ...newItems]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<style>
|
||||||
|
.search .v-text-field__details {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
+4
-1
@@ -10,5 +10,8 @@ module.exports = {
|
|||||||
'Access-Control-Allow-Origin': '*'
|
'Access-Control-Allow-Origin': '*'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
transpileDependencies: ['vuetify', '@speckle/objectloader', 'flatted', 'vuex-persist']
|
transpileDependencies: ['vuetify', '@speckle/objectloader', 'flatted', 'vuex-persist'],
|
||||||
|
configureWebpack: (config) => {
|
||||||
|
config.devtool = 'source-map'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user