Compare commits

...

318 Commits

Author SHA1 Message Date
Dogukan Karatas a9fd34831c feat: move from embed token api to share token api (#230)
* new mutation for token exchange

* variable based
2026-04-15 14:09:12 +03:00
Dogukan Karatas dbac5c013b fix (auth): new auth endpoint (#229)
* new endpoint for auth

* fallback added

* remove appSecret
2026-04-02 16:54:20 +03:00
Mucahit Bilal GOKER a73e832816 feat(visual): add sectioning tool (#226)
* section box tool

* save section box

* force section outlines recomputation

* extract duplicate section box logic

* vector3 simplification

* extract section enable helper

* fix misleading type casts

* fix let const usage

* add section box error handling

* fix section box state sync

* capabilities

* replace section box bool pair with state enum

* change scissors icon behaviour

* add clarifying comment for toggleSectionBox
2026-02-20 12:55:45 +03:00
Mucahit Bilal GOKER 0b55013a84 feat: consolidate camera controls (#225) 2026-02-12 23:39:37 +03:00
Dogukan Karatas baa723287b Merge pull request #224 from specklesystems/dogukan/cnx-3009-data-connector
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
feat(data): add markReceived mutation to data connector
2026-01-30 08:53:27 +01:00
Dogukan Karatas 0976597db3 mark received data connector 2026-01-29 12:38:15 +01:00
Mucahit Bilal GOKER 40536a565f feat: add Application ID column (#223)
* add application id column

* use record
2026-01-26 12:06:13 +03:00
Dogukan Karatas 34115d9a5d feat (visual): align visual with FE2 (#222)
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
* view mode menu updated

* clean up
2026-01-19 23:19:51 +03:00
Mucahit Bilal GOKER 74ac3e3990 add object and application ids (#221) 2026-01-15 20:40:46 +03:00
Mucahit Bilal GOKER f9b5e250d8 feat: Issues helper function (#194)
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
* helper func

* register

* query: remove code duplicate

* discussions gone, issues in.

* add issue urls

* add optional replies input
2026-01-06 16:02:53 +03:00
Dogukan Karatas 0befca0200 Merge pull request #220 from specklesystems/dogukan/delimeter-fix
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
fix (data): federate helper function delimeter fix
2025-12-12 15:59:29 +01:00
Dogukan Karatas 1a74336e27 fixed delimeter 2025-12-12 15:57:05 +01:00
Dogukan Karatas 9f9b31d9ba Merge pull request #219 from specklesystems/dogukan/optional-version-object-id
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
feat (data): keep version object id for backwards compatability
2025-11-14 14:15:58 +01:00
Dogukan Karatas df3ad118e1 backwards compatible 2025-11-14 13:29:25 +01:00
Dogukan Karatas ec634be352 feat: auth flow without desktop service (#218)
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
* first pass

* column renaming

* mark received in visual

* some security measures

* renamed
2025-11-11 13:38:43 +03:00
Dogukan Karatas 0a4ae9340a Merge pull request #217 from specklesystems/dogukan/bump-ol2-version
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
fix: objectloader2 version update
2025-11-05 16:37:17 +01:00
Dogukan Karatas 92bcf4b5c0 bump ol2 version 2025-11-05 16:06:13 +01:00
Dogukan Karatas 2a22bbf0af Merge pull request #216 from specklesystems/dogukan/arrange-buttons
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
fix (visual): button rearrangements
2025-11-03 13:45:28 +01:00
Dogukan Karatas 7b5e5397b6 minor changes 2025-11-03 13:35:15 +01:00
Dogukan Karatas 24eeb44ff7 Merge pull request #215 from specklesystems/dogukan/direct-server-download
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
feat (data): direct server download
2025-10-30 20:41:55 +01:00
Dogukan Karatas b1f16c4005 modifies the download procedure 2025-10-30 17:14:29 +01:00
Jedd Morgan 2307d87735 fix version again (#212) 2025-10-20 17:12:09 +01:00
Jedd Morgan b80624396d Update deploy.yml (#211) 2025-10-20 16:55:42 +01:00
Oğuzhan Koral 098ef3d112 Bump viewer for proxy fix (#210)
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
2025-10-20 10:44:54 +03:00
Oğuzhan Koral 94fdc7a2c3 bump viewer (#209)
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
2025-10-16 17:33:36 +03:00
Dogukan Karatas 525857bd26 adds version id suffix (#207)
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
Co-authored-by: Oğuzhan Koral <45078678+oguzhankoral@users.noreply.github.com>
2025-10-09 22:24:40 +03:00
Dogukan Karatas 959bcaa671 added a env check (#208) 2025-10-09 22:22:03 +03:00
Dogukan Karatas 04b3aef829 Merge pull request #206 from specklesystems/oguzhan/objectloader2
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
feat (visual): objectloader2 integration
2025-10-01 14:10:40 +02:00
Dogukan Karatas 318dc6dbbe cleanup added 2025-10-01 13:46:54 +02:00
Dogukan Karatas 20577a1fdb version bump 2025-10-01 12:14:11 +02:00
Dogukan Karatas e74bad829e downloads missing objects 2025-10-01 11:59:44 +02:00
oguzhankoral dda04e49c2 get root object first 2025-09-30 14:03:29 +03:00
Dogukan Karatas 97983fb8aa Revert "loader integration"
This reverts commit 53e4cda456.
2025-09-25 12:02:26 +02:00
Mucahit Bilal GOKER 1cac02ae61 Merge pull request #205 from specklesystems/bilal/cnx-2596-auto-expand-properties
feat: Add Property Expansion Option
2025-09-25 12:29:58 +03:00
bimgeek 0a5001987e remove true from description 2025-09-25 11:01:26 +03:00
bimgeek 5ffb3ea1dd set default value to false 2025-09-25 10:49:40 +03:00
bimgeek 3461c48b11 try check 2025-09-25 10:26:24 +03:00
bimgeek 220946a611 property expansion option 2025-09-25 10:19:35 +03:00
Dogukan Karatas 53e4cda456 loader integration 2025-09-23 15:00:50 +02:00
oguzhankoral 4ca0ae0978 replace objectloader1 with 2 2025-09-18 15:46:46 +03:00
Dogukan Karatas 685a137531 Merge pull request #204 from specklesystems/dogukan/cnx-2103-filtering-from-other-visuals-doesnt-work-when-conditional
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
fix (visual): remove forcing conditional formatted objects always be visible
2025-09-17 20:30:53 +03:00
Dogukan Karatas 78af91f38a Merge pull request #202 from specklesystems/dogukan/cnx-2515-fallback-to-json-for-scheduled-refresh
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
fallback to json download
2025-09-11 15:02:39 +02:00
Dogukan Karatas 108a406bd5 clearer error message on visual 2025-09-11 15:00:00 +02:00
Mucahit Bilal GOKER d7ede2edcf Merge branch 'main' into dogukan/cnx-2515-fallback-to-json-for-scheduled-refresh 2025-09-11 07:13:22 +03:00
Mucahit Bilal GOKER a25d635ca1 Merge pull request #201 from specklesystems/bilal/cnx-2510-remove-internal-border
Bilal/cnx 2510 remove internal border
2025-09-11 06:57:59 +03:00
Dogukan Karatas 5a9add6d76 fallback to json download 2025-09-10 13:22:41 +02:00
Mucahit Bilal GOKER 89c8005dee remove border from main container 2025-09-09 19:19:21 +03:00
Mucahit Bilal GOKER a384370652 Merge branch 'dev' into bilal/cnx-2510-remove-internal-border 2025-09-09 19:07:56 +03:00
oguzhankoral 5ec90095f0 Merge branch 'dev'
# Conflicts:
#	README.md
2025-09-04 23:29:20 +03:00
Oğuzhan Koral 20fad26fef Merge pull request #200 from specklesystems/oguzhan/cherry-pick-readme
Update README.md (#147)
2025-09-04 23:19:32 +03:00
Jonathon Broughton 03215f79c4 Update README.md (#147)
* Update README.md

* Update README.md

(cherry picked from commit 85f8f72335)

# Conflicts:
#	README.md
2025-09-04 23:15:19 +03:00
Dogukan Karatas 6d17377ca2 Merge pull request #198 from specklesystems/dogukan/remove-access-token-auth
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
feat (data): remove token and anonymous authentication
2025-09-03 10:09:43 +02:00
Dogukan Karatas 256abaed0c remove token and anon auth 2025-09-02 21:46:35 +02:00
Dogukan Karatas 26409b4ea6 storing logic changes 2025-09-02 14:22:03 +02:00
Dogukan Karatas 865c4c1608 Merge pull request #197 from specklesystems/dogukan/server-url-in-request-data
fix (data): adds server url to exchange data
2025-09-02 12:53:44 +02:00
Dogukan Karatas 67836c2a7f case sensitive change 2025-09-02 11:00:35 +02:00
Dogukan Karatas 95d819f7f3 adds server url to exchange request data 2025-09-02 10:31:01 +02:00
Dogukan Karatas dee3ee6c4d Merge pull request #196 from specklesystems/dogukan/fix-conditional-formatting-card
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
fix (visual): bring back the conditional formatting
2025-08-28 17:17:27 +02:00
Dogukan Karatas 7ed612ec14 revert back for conditional formatting 2025-08-28 16:30:15 +02:00
Dogukan Karatas 4bd7af4c31 Merge pull request #195 from specklesystems/dogukan/online-mode-with-object-loader
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
feat (visual): dual-mode data loading
2025-08-27 20:40:40 +02:00
Dogukan Karatas 3ed2e977df removed unused function 2025-08-27 16:48:06 +02:00
Dogukan Karatas 788fa1c532 auto internalizing logic 2025-08-27 16:33:09 +02:00
Dogukan Karatas bafb7df6ed integrate two options together 2025-08-26 22:28:11 +02:00
Dogukan Karatas be4e4df983 integreates object loader instead of rest api 2025-08-20 15:25:37 +02:00
Dogukan Karatas b4830c80ab downloader with rest api 2025-08-20 13:16:50 +02:00
Dogukan Karatas a2d97facc5 adds token to the matrix 2025-08-19 17:02:58 +02:00
Dogukan Karatas aea344a46a updated sendtoserver for token exchange 2025-08-18 17:18:29 +02:00
Mucahit Bilal GOKER 13aa65bc2e Merge pull request #191 from specklesystems/bilal/cnx-2200-specklemodelsmaterialquantities-helper-function
Add Models.MaterialQuantities helper function
2025-08-12 10:28:44 +03:00
Mucahit Bilal GOKER 0a307c28e0 Merge pull request #190 from specklesystems/bilal/cnx-2169-properties-add-parent-dict-name-as-a-suffix
Parameter Group as suffix
2025-08-12 10:28:27 +03:00
Dogukan Karatas e0f4a4c02c Merge pull request #192 from specklesystems/dogukan/send-versionId-to-server
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
feat (data): send `versionId` to the server
2025-07-29 10:14:11 +02:00
Dogukan Karatas 29773f9492 versionId added 2025-07-28 16:28:53 +02:00
bimgeek 8f67ef4c84 add optional prefix 2025-07-25 21:37:49 +03:00
bimgeek 2c5f192403 first attempt 2025-07-25 20:25:50 +03:00
bimgeek 0c58789dd6 rename target fields to filterKeys 2025-07-23 16:45:55 +03:00
bimgeek 82acce2abb fix conflict resolution logic 2025-07-23 13:40:07 +03:00
bimgeek d83472c30b add parent name as a suffix 2025-07-23 13:15:13 +03:00
Oğuzhan Koral 634df47a25 Revert "Merge pull request #186 from specklesystems/dogukan/cnx-2136-tag-as-markreceived-after-download-in-dataconnector" (#189)
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
This reverts commit ffc0d8ef5e, reversing
changes made to c8d858d575.

Co-authored-by: bimgeek <mucahitbgoker@gmail.com>
2025-07-22 08:02:27 +01:00
Mucahit Bilal GOKER 9ad59bf1d3 Bilal/cnx 2115 configure oauth2 for data gateway usage (#183)
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
* oauth2 implementation

* change code challenge to plain

* Reorder error checks

* implement proper PKCE security with SHA256
2025-07-21 16:56:44 +01:00
Mucahit Bilal GOKER ffc0d8ef5e Merge pull request #186 from specklesystems/dogukan/cnx-2136-tag-as-markreceived-after-download-in-dataconnector
fix: mark stream as received
2025-07-21 15:59:26 +03:00
Mucahit Bilal GOKER 94c80857a0 Merge branch 'dev' into dogukan/cnx-2136-tag-as-markreceived-after-download-in-dataconnector 2025-07-21 14:47:07 +03:00
Mucahit Bilal GOKER c8d858d575 Merge pull request #185 from specklesystems/dogukan/cnx-1933-disable-selection-of-ghosted-objects
fix (visual): disable selection of ghosted objects
2025-07-21 14:46:54 +03:00
Mucahit Bilal GOKER 36b9787b66 Merge branch 'dev' into dogukan/cnx-1933-disable-selection-of-ghosted-objects 2025-07-21 14:44:23 +03:00
Mucahit Bilal GOKER bde7a42c44 Merge branch 'dev' into dogukan/cnx-2136-tag-as-markreceived-after-download-in-dataconnector 2025-07-21 14:36:24 +03:00
Mucahit Bilal GOKER 1040f4622d Bilal/cnx 2033 update readme (#184)
* update readme

* remove button styling

* added code

* Update README.md

* Update README.md
2025-07-21 14:23:33 +03:00
Dogukan Karatas 91e799d006 mark received function added 2025-07-16 15:45:44 +02:00
Dogukan Karatas 8694666874 remove legacy implementation 2025-07-14 15:02:46 +02:00
Dogukan Karatas fa6ad8ec40 extended filtered selection 2025-07-11 17:04:01 +02:00
Dogukan Karatas 8e60249291 update viewer for ghosted objects 2025-07-11 15:27:29 +02:00
Mucahit Bilal GOKER 68d6bf3d55 Merge pull request #180 from specklesystems/dogukan/cnx-1978-control-camera-animation-on-change
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
feat (visual): toogle for animation on action
2025-07-08 16:14:31 +03:00
Dogukan Karatas 2a8925c8ef icon updated 2025-07-08 15:08:47 +02:00
Mucahit Bilal GOKER f9b3d3db52 Merge pull request #181 from specklesystems/dogukan/cnx-2089-auto-extract-properties
feat (data): extract properties column by default
2025-07-08 15:41:06 +03:00
Mucahit Bilal GOKER bfd0c33373 Merge pull request #179 from specklesystems/bilal/cnx-2106-federate-models-helper-function
Helper Function: Federate Models
2025-07-08 15:39:36 +03:00
Dogukan Karatas d155a4b165 registered to the capabilities 2025-07-08 13:37:17 +02:00
Dogukan Karatas 2e9ece856f adds properties extractor by default 2025-07-07 15:49:38 +02:00
Dogukan Karatas 808e288848 adds option for zoom on select 2025-07-07 14:18:18 +02:00
bimgeek 701116c66c helper function: federate models 2025-07-07 10:48:05 +03:00
Dogukan Karatas e73d392013 Merge pull request #178 from specklesystems/dogukan/cnx-1932-saving-visual-with-filtered-data
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
fix (visual): persistent visual filters
2025-07-04 12:51:52 +02:00
Dogukan Karatas dd7f3fe95d cover initial states 2025-07-03 15:54:09 +02:00
Dogukan Karatas fdcc1f2cef listen load event 2025-07-03 15:42:06 +02:00
Dogukan Karatas 5e2f108e49 Merge pull request #177 from specklesystems/dogukan/cnx-2032-persistent-hide-nav-bar-toggle
fix (visual): persistent navbar toogle
2025-07-01 15:01:48 +02:00
Dogukan Karatas df334e95a2 workspace to viewmode 2025-07-01 14:53:03 +02:00
Dogukan Karatas ce733d1ced adds persistent navbar 2025-07-01 11:15:59 +02:00
Dogukan Karatas 8345258990 Merge pull request #176 from specklesystems/dogukan/fix-function-registration
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
fix: helper function registration
2025-06-24 15:22:12 +02:00
Dogukan Karatas dbd0f2f9ce fix registration 2025-06-24 15:19:11 +02:00
Dogukan Karatas c9b4155660 Merge pull request #174 from specklesystems/bilal/object-compound-structure
added objects composite structure
2025-06-24 14:56:26 +02:00
Dogukan Karatas c4d094d722 Merge branch 'dev' into bilal/object-compound-structure 2025-06-24 14:55:06 +02:00
Dogukan Karatas 5b003b182b Merge pull request #173 from specklesystems/bilal/object-materialquantities
object material quantities added
2025-06-24 14:54:36 +02:00
Dogukan Karatas 7eabd47f6d Merge pull request #175 from specklesystems/dogukan/cnx-2035-loading-got-veeeeeeery-sloooooooow
performance fix: remove `displayValue` exclusion
2025-06-24 14:54:24 +02:00
Dogukan Karatas 822b999be9 remove displayValue exclusion 2025-06-24 13:47:30 +02:00
Dogukan Karatas 2407b8758a Merge pull request #171 from specklesystems/dogukan/exclude-display-value
feat(data): exclude displayValue in data
2025-06-24 11:09:48 +02:00
Mucahit Bilal GOKER d6f5e65bd7 added objects composite structure 2025-06-19 15:28:41 +03:00
Mucahit Bilal GOKER 183cc36654 object material quantities added 2025-06-19 15:15:05 +03:00
Mucahit Bilal GOKER a740272585 Merge pull request #170 from specklesystems/bilal/expand-record-util
expand record helper function
2025-06-19 15:11:51 +03:00
Dogukan Karatas 72128a9f4e excludes displayValues in data 2025-06-19 13:55:41 +02:00
Mucahit Bilal GOKER 677c663ef3 Merge branch 'dev' into bilal/expand-record-util 2025-06-19 14:55:21 +03:00
Mucahit Bilal GOKER a077857c66 add new line 2025-06-19 14:40:16 +03:00
Mucahit Bilal GOKER 5897a286bc expand record helper function 2025-06-19 14:23:40 +03:00
Dogukan Karatas 5b49fb2a9a Merge pull request #169 from specklesystems/dogukan/revit-helper-functions
feat (data): get properties function
2025-06-19 13:07:57 +02:00
Dogukan Karatas 424404dd11 Merge branch 'dogukan/revit-helper-functions' of https://github.com/specklesystems/speckle-powerbi into dogukan/revit-helper-functions 2025-06-19 13:04:18 +02:00
Dogukan Karatas 31312522a7 adds objects.collections 2025-06-19 12:47:18 +02:00
Mucahit Bilal GOKER 932198dccf rename GetProperties to Objects.Properties 2025-06-19 13:47:13 +03:00
Dogukan Karatas 3770502ca4 adds function overload 2025-06-18 16:30:36 +02:00
Dogukan Karatas 93e8fcdd9d gets duplicated props 2025-06-18 16:13:42 +02:00
Dogukan Karatas 370052b2be GetProperties added 2025-06-18 14:41:14 +02:00
Oğuzhan Koral aa4a137a0d handle size in MB separately (#168)
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
2025-06-16 21:17:43 +03:00
Dogukan Karatas 4acdf30734 error handling (#167)
Co-authored-by: Oğuzhan Koral <45078678+oguzhankoral@users.noreply.github.com>
2025-06-16 12:05:21 +00:00
Dogukan Karatas b531446acd feat (visual): send connector version to mixpanel (#165)
* connector version added

* updated props
2025-06-16 15:03:54 +03:00
Jedd Morgan de1b2ca39c Run CI on PR (#164) 2025-06-06 10:14:00 +01:00
Jedd Morgan 5642da1e57 Ensure bash (#163)
build_powerbi / build-connector (push) Has been cancelled
build_powerbi / build-visual (push) Has been cancelled
build_powerbi / deploy-installers (push) Has been cancelled
2025-05-30 18:38:18 +03:00
Jedd Morgan 276d0c3a76 removed last trace of gitversion (#162) 2025-05-30 18:34:21 +03:00
Jedd Morgan 2d92b85687 fix(ci): replace gitversion with tag parsing (#161)
* remove git version

* delete unused gitversion

* disable circle ci
2025-05-30 16:27:57 +01:00
Oğuzhan Koral 82bd109b85 Feat: visual revamp (#156)
* delete debugger

* No need tooltip data as part of interactivity

* Fix coloring

* delete logging

* Fix messaging on interactivity for tooltip data

* Delete unused code

* Navbar and cursors

* Revamp viewer actions

* Hide viewer actions

* not a css master commit

* Toggle projection/orthi

* Remove console log

* Fix saved objects

* Remove console log

* Sort performance logging

* fix view mode cache

* Fix initial isolate issue

* Update README.md (#147)

* Update README.md

* Update README.md

* fix typo on conditions

* capabilities for object ids

* Revert isolating every setDataInput

* Fix selection issues

* Fix tooltip fckp

* Reset filters

* Fix reset filter

* Ghost hidden on filter

* Bring conditional formatting back

* Remove ghost hidden context from color card

* Disable shadow catcher

* Disable camera position persistence for performance reasons

* feat (data): sending version and branding info (#157)

* get version

* adds workspace info

* adds hideBranding

* adds workspace info

---------

Co-authored-by: Oğuzhan Koral <45078678+oguzhankoral@users.noreply.github.com>

* Enrich receive info from desktop service

* test

* fix path

* fix path again

* file version

* correct the version with assembly for file version

* sanitize tag

* Use version from receive info

* Fix clipped zoom extend

* Workspace name logo

* Add can hide branding logic

* Fix tooltip for toggle

* Fix capabilities for storing branding

* Tooltips

* Store is ortho in file

* Store camera position and target into file according to selected view

* Fix loading bar reactivity

* Update connector flow

* Fix ghost state

* Store is ghost in file

* Consider ghost value on reset filter

* More

* excludes rawencoding (#159)

* adds null check for personal (#158)

* Progress update and error handling

* Call pre get before

---------

Co-authored-by: Jonathon Broughton <760691+jsdbroughton@users.noreply.github.com>
Co-authored-by: Dogukan Karatas <61163577+dogukankaratas@users.noreply.github.com>
2025-05-30 17:43:21 +03:00
Kristaps Fabians Geikins 38fb5f7c26 fix: make webpack prefer module builds (#155)
* fix: make webpack prefer module builds

* dev screen
2025-05-24 00:27:27 +03:00
Dogukan Karatas dee021f0ef Merge pull request #154 from specklesystems/dogukan/cnx-1801-can-view-can-edit-in-power-bi-data-connector
feat(data connector): server-side permisson check integration
2025-05-14 13:46:56 +02:00
Dogukan Karatas 620bd22387 integrates permisson checks 2025-05-14 13:26:21 +02:00
Dogukan Karatas d364e096a8 Merge pull request #153 from specklesystems/dogukan/cnx-1551-reaching-end-of-the-buffer-in-power-bi
build_powerbi / build-connector (push) Has been cancelled
build_powerbi / build-visual (push) Has been cancelled
build_powerbi / deploy-installers (push) Has been cancelled
fix(data connector): reaching the end of the buffer
2025-05-14 11:43:32 +02:00
Dogukan Karatas 8e03bcf201 fix typo 2025-05-14 11:08:23 +02:00
Dogukan Karatas 1825a5ad4c update the query 2025-05-14 10:08:20 +02:00
Dogukan Karatas a626cdd45d updates acessing the token 2025-05-14 10:04:40 +02:00
Oğuzhan Koral 91871f8615 handle chunks not chunk (#152)
build_powerbi / build-connector (push) Has been cancelled
build_powerbi / build-visual (push) Has been cancelled
build_powerbi / deploy-installers (push) Has been cancelled
2025-05-14 10:04:54 +03:00
Dogukan Karatas 2da1602986 Merge pull request #146 from specklesystems/dogukan/cnx-1502-merge-tables-of-federated-models
build_powerbi / build-connector (push) Has been cancelled
build_powerbi / build-visual (push) Has been cancelled
build_powerbi / deploy-installers (push) Has been cancelled
feat: federated models
2025-04-30 16:18:44 +02:00
Dogukan Karatas a0372e1970 Merge pull request #149 from specklesystems/alex/no-more-legacy-viewer
Alex/no more legacy viewer
2025-04-30 16:17:39 +02:00
oguzhankoral 98f10bb344 Sort the new viewer after Alex and compression for federated models 2025-04-29 17:08:27 +03:00
oguzhankoral ee11e47af3 Merge remote-tracking branch 'origin/alex/no-more-legacy-viewer' into dogukan/cnx-1502-merge-tables-of-federated-models 2025-04-29 14:25:04 +03:00
AlexandruPopovici f57697d929 Got rid of LegacyViewer and adjusted the wrapper accordingly. Blindly 2025-04-29 10:25:07 +03:00
Jedd Morgan ac0db18d24 refactor(ci): Update workflow to use new consolidated deployment workflow (#148)
build_powerbi / build-connector (push) Has been cancelled
build_powerbi / build-visual (push) Has been cancelled
build_powerbi / deploy-installers (push) Has been cancelled
* Update workflow

* target main
2025-04-24 15:09:32 +02:00
Jonathon Broughton 85f8f72335 Update README.md (#147)
* Update README.md

* Update README.md
2025-04-08 21:32:04 +03:00
Dogukan Karatas 24d26dc49f adds support for federated models 2025-03-27 14:02:20 +01:00
Oğuzhan Koral 1fff59de85 Add flag for anonymous (#145)
build_powerbi / build-connector (push) Has been cancelled
build_powerbi / build-visual (push) Has been cancelled
build_powerbi / deploy-installers (push) Has been cancelled
2025-03-26 15:34:56 +03:00
Dogukan Karatas 771e34f6b5 null checks for anon user (#144) 2025-03-26 15:15:48 +03:00
Oğuzhan Koral f0b072d210 Feat(visual): persistent camera position (#143)
build_powerbi / build-connector (push) Has been cancelled
build_powerbi / build-visual (push) Has been cancelled
build_powerbi / deploy-installers (push) Has been cancelled
* Subscribe the camera position

* Remember the last camera position

* Add email to mixpanel track

* Add custom views
2025-03-21 18:22:38 +03:00
Oğuzhan Koral 97a72c966a Feat(visual): select default view to load in powerbi (#141)
build_powerbi / build-connector (push) Has been cancelled
build_powerbi / build-visual (push) Has been cancelled
build_powerbi / deploy-installers (push) Has been cancelled
* Add view modes and persist all camera settings into file

* Adjust z index

* sizing
2025-03-21 02:47:51 +03:00
Oğuzhan Koral 058c846770 Feat(visual): cleanup data (#140)
* cleanup data before loading to viewer

* only cleanup to properties and closures, good enough
2025-03-21 01:29:06 +03:00
Dogukan Karatas 3bb69f7aa8 removes beta flag (#139) 2025-03-19 14:59:37 +03:00
Dogukan Karatas c1dac7e663 Merge pull request #138 from specklesystems/dogukan/update-object-filtering
fix (data connector): object filtering logic
2025-03-19 10:39:35 +01:00
Dogukan Karatas 056815dad8 updated logic for filtering 2025-03-19 10:37:50 +01:00
Dogukan Karatas 929f06dbb7 update object filtering 2025-03-18 15:05:44 +01:00
Jedd Morgan 8cdfa97f41 Update build_powerbi.yml (#137) 2025-03-14 12:44:50 +03:00
Dogukan Karatas 28e9c43e5e fixes sourceApplication record (#136) 2025-03-03 20:30:31 +03:00
Oğuzhan Koral 380c60281b add ui property to track (#135)
build_powerbi / build-connector (push) Has been cancelled
build_powerbi / build-visual (push) Has been cancelled
build_powerbi / deploy-installers (push) Has been cancelled
2025-02-27 19:25:58 +03:00
Dogukan Karatas c4f7c6082e adds helper functions (#131)
build_powerbi / build-connector (push) Has been cancelled
build_powerbi / build-visual (push) Has been cancelled
build_powerbi / deploy-installers (push) Has been cancelled
2025-02-26 17:14:03 +00:00
Oğuzhan Koral 51a5261f1d more states.... (#134) 2025-02-26 16:53:28 +03:00
Oğuzhan Koral 5f6a032bf8 Fix: aligning colors across visuals (#133)
* Group by child value

* remove unused code
2025-02-26 15:04:05 +03:00
Jedd Morgan 8e1fe78af3 Remove desktop service from powerbi CI (#132) 2025-02-25 12:35:04 +00:00
Oğuzhan Koral 6dffa7dfa3 Feat(mixpanel): get user info from service (#130)
* get receive info from service

* rename userInfo to receiveInfo

* hijack download with more data

* gets email and token

* note for private project tests

---------

Co-authored-by: Dogukan Karatas <karatasdogukan@gmail.com>
2025-02-24 14:37:00 +03:00
Dogukan Karatas f61fae4284 feat (data): send user info to the service (#123)
* sends user info the service

* adds root object id

* fix port and casing

---------

Co-authored-by: oguzhankoral <oguzhankoral@gmail.com>
2025-02-21 16:14:13 +03:00
Oğuzhan Koral f97946a426 Update build_powerbi.yml (#129)
build_powerbi / build-connector (push) Has been cancelled
build_powerbi / build-visual (push) Has been cancelled
build_powerbi / deploy-installers (push) Has been cancelled
2025-02-19 23:26:20 +03:00
Dogukan Karatas 8949917f21 fix(visual): update viewer version (#128)
* updates viewer version

* make it consistent
2025-02-19 23:03:34 +03:00
Oğuzhan Koral 0a0abd70df Chore: update ports (#127)
* update port

* update port +1
2025-02-19 12:51:02 +03:00
Oğuzhan Koral 19c9252a4a Update build_powerbi.yml (#126)
* Update build_powerbi.yml

* bump version
2025-02-19 12:50:44 +03:00
Oğuzhan Koral e50b04b305 Feat(visual): UI fine tuning (#125)
build_powerbi / build-connector (push) Has been cancelled
build_powerbi / build-visual (push) Has been cancelled
build_powerbi / deploy-installers (push) Has been cancelled
* home view

* Update logo on viewer view

* progress bar while data is loading from bg service

* make home view speckle icon smaller

* put more gap between logo and speckle text
2025-02-17 22:19:02 +03:00
Dogukan Karatas 5cd0fe2b12 Merge pull request #124 from specklesystems/dogukan/visual-input-field-update
build_powerbi / build-connector (push) Has been cancelled
build_powerbi / build-visual (push) Has been cancelled
build_powerbi / deploy-installers (push) Has been cancelled
fix: rename the input field
2025-02-17 15:51:35 +01:00
Dogukan Karatas 59dd7ee9fc updates input field name 2025-02-17 15:36:47 +01:00
Jedd Morgan a6e7ed86d9 please work (#122)
build_powerbi / build-connector (push) Has been cancelled
build_powerbi / build-visual (push) Has been cancelled
build_powerbi / deploy-installers (push) Has been cancelled
2025-02-14 14:15:25 +00:00
Jedd Morgan 573e70df02 Single line command (#121)
* Add quotes

* cmd uses carets

* I hate windows
2025-02-14 14:12:44 +00:00
Jedd Morgan 52f406f3ac Fix CI multi-line command (#120)
* Add quotes

* cmd uses carets
2025-02-14 14:07:40 +00:00
Jedd Morgan fbe34d43b1 Add quotes (#119) 2025-02-14 13:59:26 +00:00
Dogukan Karatas 5ad8139a4c Merge pull request #118 from specklesystems/jedd/cxpla-125-sign-the-power-bi-installer
Jedd/cxpla 125 sign the power bi installer
2025-02-14 14:53:40 +01:00
Jedd Morgan ba43724443 Self sign connector 2025-02-14 13:51:12 +00:00
Dogukan Karatas 8a34685b00 Merge pull request #117 from specklesystems/dogukankaratas-patch-1
build_powerbi / build-connector (push) Has been cancelled
build_powerbi / build-visual (push) Has been cancelled
build_powerbi / deploy-installers (push) Has been cancelled
Update build_powerbi.yml
2025-02-14 13:51:00 +01:00
Dogukan Karatas e9c2293736 Update build_powerbi.yml 2025-02-14 13:49:11 +01:00
Dogukan Karatas d27adb243f Merge pull request #116 from specklesystems/oguzhan/streaming-data-poc
Feat(visual): streaming data
2025-02-14 13:27:07 +01:00
Dogukan Karatas e55a382d73 updates input field name 2025-02-14 13:24:23 +01:00
oguzhankoral 00af5b5abb compress data to write read 2025-02-14 03:09:03 +03:00
oguzhankoral cba5ccf254 handle ndjson streamed data 2025-02-13 20:29:40 +03:00
oguzhankoral ae3c480f02 bump only viewer 2025-02-13 15:27:25 +03:00
oguzhankoral 0b716eb2cd Merge remote-tracking branch 'origin/dev' into oguzhan/streaming-data-poc 2025-02-13 14:52:23 +03:00
oguzhankoral a88f3323b1 bump speckle packages 2025-02-13 14:47:40 +03:00
Dogukan Karatas 7b06aef1f8 feature (data connector): server integration (#115)
* adds version parsing in data connector

* integrate with server

* adds data cleaning
2025-02-13 13:50:55 +03:00
oguzhankoral a45a67ed38 Cleanup 2025-02-13 13:23:14 +03:00
oguzhankoral efd07d4ffb streaming data + progress 2025-02-12 22:23:27 +03:00
Jedd Morgan 33116083e8 bump-version
build_powerbi / build-connector (push) Has been cancelled
build_powerbi / build-visual (push) Has been cancelled
build_powerbi / deploy-installers (push) Has been cancelled
2025-02-07 11:03:47 +00:00
Dogukan Karatas 12f548bd76 Merge pull request #113 from specklesystems/oguzhan/post-with-id-param
Feat: post with id param
2025-02-07 10:42:44 +01:00
oguzhankoral 8b9c4b1cf2 pass root id as param to send 2025-02-05 13:06:09 +00:00
Dogukan Karatas deb34e9262 adds the root id to key (#112) 2025-02-05 10:11:11 +00:00
Oğuzhan Koral 4a4a7d70c0 Rename the event to Receive (#111) 2025-02-04 19:38:20 +00:00
Jedd Morgan 4e1ce9ce71 bump service to 0.4.0 (#110) 2025-02-04 18:26:53 +00:00
Dogukan Karatas c9c1820e4d fix (data connector): adds caching to data connector (#109)
* adds caching to data connector

* endpoint updated
2025-02-04 18:26:33 +00:00
Oğuzhan Koral 62110739fb bump service version to 0.3.0 (#108)
build_powerbi / build-connector (push) Has been cancelled
build_powerbi / build-visual (push) Has been cancelled
build_powerbi / deploy-installers (push) Has been cancelled
2025-02-02 21:33:53 +03:00
Oğuzhan Koral d5527c79b1 bump service tag (#107)
build_powerbi / build-connector (push) Has been cancelled
build_powerbi / build-visual (push) Has been cancelled
build_powerbi / deploy-installers (push) Has been cancelled
2025-01-31 12:27:54 +03:00
Dogukan Karatas 79ba5cd365 Merge pull request #106 from specklesystems/dogukan/get-user-info
feat: send to local server
2025-01-31 10:26:13 +01:00
Jedd Morgan 17c857d138 Pass service as artifact (#105) 2025-01-31 10:50:16 +03:00
oguzhankoral f0538c94c3 Proper mixpanel track 2025-01-29 22:27:43 +03:00
Dogukan Karatas b1f2c741ad gets user info 2025-01-29 18:57:58 +01:00
Oğuzhan Koral 64dc73411a Feat(service): viewer data from local server (#104)
* Join strings

* Join strings

* POC from local viewer data

* WIP

* more WIP

* Align states

* aligns with new visual

* sends json to server

* Remove unused references

* POC

* local server data added

* updated nav table

* status updated

* added root object id

* status hidden

* Have rootObjectId as field

* Extract writeObjectsToFile

* Working state of interactivity

* Fix load from file vs. from local server

* TODO note

* Enable camera views

* sendStatus added

* cleaner structure

* Align port number with service

* Remove handle file change input

* Remove unused function in viewer

* Update capabilities for port

---------

Co-authored-by: Dogukan Karatas <karatasdogukan@gmail.com>
2025-01-29 18:02:24 +03:00
Oğuzhan Koral 8816430f51 Remove menu buttons from viewer (#102)
build_powerbi / build-connector (push) Has been cancelled
build_powerbi / build-visual (push) Has been cancelled
build_powerbi / deploy-installers (push) Has been cancelled
2025-01-16 20:18:33 +03:00
Oğuzhan Koral d5e8cd8a90 Fix: opening a file that has a visual with tooltip (#101)
* WIP

* Remove speckle server urls from webaccess

* Offline support identifier for mixpanel tracks

* Do not select if null

* Remove throttle

* Simplfy logic

* Increase row limit to 150000 for pro users
2025-01-15 14:34:01 +03:00
Dogukan Karatas 3bc82f6b9e updates build yaml file (#100) 2025-01-14 18:51:24 +03:00
Dogukan Karatas dc4f4c19f4 Merge pull request #99 from specklesystems/dogukan/labeling-releases
fix: update version label
2025-01-10 10:25:58 +01:00
Dogukan Karatas 15ebfab50b update gitversion.yml 2025-01-10 10:18:53 +01:00
Dogukan Karatas 10cc50a5b8 creates gitversion.yml (#98) 2025-01-09 18:33:43 +03:00
Dogukan Karatas 049fab417f feat: github action installer (#97)
* rename data column

* create yaml

* update yaml

* gives directory to action

* adds connector to yml

* update yml

* update yml again

* update version

* changes the ref

* adds shell

* fixes typo

* zip the artifacts

* updates version

* adds fetch depth

* updates yml branch

* adds tags
2025-01-09 17:53:15 +03:00
Oğuzhan Koral efe1c7ea0f Capture hits for isolated objects (#96) 2025-01-08 21:32:55 +03:00
Dogukan Karatas 7d7f3b04c5 rename data column (#95) 2025-01-08 18:40:57 +03:00
Dogukan Karatas 8e03d4464e fix: align column and input names (#94)
* rename columns

* updates query
2025-01-08 17:10:27 +03:00
Oğuzhan Koral 41161a8739 Feat(colors): CNX-964 coloring by in power bi visual (#93)
* Group objects by color and with their id

* Have colors back

* Fix color by
2025-01-08 16:23:42 +03:00
Oğuzhan Koral 436d13475d Fix(camera): CNX-969 camera resets when tooltip input is setunset (#92)
* Fix reseting camera on set/unset toolip

* Remove console log

* Remove unnecessary store props methods
2025-01-06 23:43:27 +03:00
Dogukan Karatas 6618e5654b fixes the structure of data connector (#91) 2025-01-06 18:10:49 +03:00
Oğuzhan Koral ca34cd3adc Fix(selection): CNX-963 deselecting does not work (#90)
* Clear selection if no hit

* Remove console log
2025-01-06 17:44:56 +03:00
Oğuzhan Koral 5168516109 Unload objects whenever we load new model (#89) 2025-01-06 14:47:18 +03:00
Dogukan Karatas 2fa69873d1 feat: new data model (#88)
* gets user info

* gets structured data

* gets by url

* publish the function

* cleanup the fields

* creates navigation table

* improves publishing

* builds relation between two table

* expands records dynamically

* folder structure reorder

* adds ordered chunks

* viewer data alternatives

* adds model name to the table name

* adds support for versioned urls

* adds relationship
2024-12-19 12:37:37 +03:00
Oğuzhan Koral 4f15db5d9c Feat: offline support (#87)
* disable redundant steps for POC

* Fixed the getInstances issue. Changed the webpack devsserver port so  it doesn't conflict with the pbviz process

* speckle offline loader tests

* gets user info

* gets structured data

* gets by url

* publish the function

* cleanup the fields

* creates navigation table

* improves publishing

* builds relation between two table

* wip

* some log cleanup and notes

* get sorted data from powerbi

* aling R_ raw data

* WIP

* WIP

* Handle row raw data per object

* introduce pinia

* bump speckle libraries to 2.23.2

* Cleanup on injections

* remove vuex dependency

* extract out button classes

* Revamp store and viewer handler

* Detect reload needed

* Bump the version to 3

* Bump node project to 3

* prettier settings on save and co

* comments

* Enable zoom extends

* Node packages version bumps and selection handler saga

* Cleanup viewer wrapper as MVP

* bump viewer version for offline loader

* correct warning logs

---------

Co-authored-by: AlexandruPopovici <alexandrupopoviciioan@gmail.com>
Co-authored-by: Dogukan Karatas <karatasdogukan@gmail.com>
2024-12-19 12:27:34 +03:00
Kristaps Fabians Geikins e233aec607 chore: add extra instructions for straight up windows (#86) 2024-12-04 15:44:25 +03:00
Kristaps Fabians Geikins 52f4325619 chore: various DX improvements like working source maps (#85)
* init changes

* sourcemaps work

* eff off pbiviz

* more fixes

* moar fixes

* prod build fix

* remove yarn field

* minor scripts change

* moar readme stuff
2024-12-03 22:35:18 +03:00
Alan Rynne 6e92c857a7 Merge pull request #84 from specklesystems/dev
Update `main` with changes from `dev`
2024-11-05 11:53:21 +01:00
Alan Rynne 8edc01b7d6 fix: Update viewer to version 2.21.0 (#82) 2024-11-05 11:26:09 +01:00
Claire Kuang 6d5f638895 Merge pull request #83 from specklesystems/claire/cnx-699-update-github-links-to-point-to-v3
Update README.md to align with main github page
2024-11-01 18:51:47 +00:00
Claire Kuang ccecf7cb2b Update README.md to align with main github page 2024-11-01 18:49:03 +00:00
Mucahit Bilal GOKER 156c3a5c3a return model id instead of name (#81) 2024-10-01 18:02:00 +02:00
Alan Rynne 887dbb2344 Merge branch 'main' into dev 2024-09-09 16:19:13 +02:00
Mucahit Bilal GOKER 556196a45f fixed extension path (#79) 2024-09-05 15:38:02 +02:00
Jedd Morgan 1bf6e76252 Merge pull request #78 from specklesystems/dev
Merge dev -> main
2024-08-08 14:41:41 +01:00
Mucahit Bilal GOKER b26801ef8a Update README.md (#76) 2024-08-08 14:40:35 +01:00
Iain Sproat 74ab91be3b chore(domains): update speckle.xyz to app.speckle.systems (#77)
- remove reference to DO 1-click as this is deprecated
2024-08-08 14:39:07 +01:00
Alan Rynne 920b346175 Merge branch 'main' into dev 2024-06-30 23:24:53 +02:00
Mucahit Bilal GOKER 9c42f06ea4 Merge pull request #75 from specklesystems/bimgeek-patch-1
Update README.md
2024-06-04 17:03:09 +03:00
Mucahit Bilal GOKER 6af64d8499 Update README.md 2024-06-04 17:02:58 +03:00
Alan Rynne 46ff9acd64 fix(visual): Give user facing pbiviz a better name 2024-05-30 11:03:10 +02:00
Alan Rynne 8031c7da4f feat: CNX-9628 Add visual to installer and CI script (#74)
* feat: Add visual to installer and CI script (attempt 1)

* fix(ci): Working directory set incorrectly

* fix(ci): Run build before pack and set data connector path correctly

* Install visual tools into dev dependencies

* fix(ci): Try fix MakePQX not found

* fix(ci): MakePQX args need correct subfolder

* fix(visual): Rename visual to not have guid

* feat(ci): Set default version to 2.0.0 everywhere, and overwrite on CI jobs

* fix(ci): Missed one version location

* fix(visual): Always use package.json version

This allows us to `npm version` instead of doing some weirder logic

* fix(visual): Allow same version when bumping
2024-05-29 22:34:03 +02:00
Alan Rynne 08554fc864 Merge branch 'main' into dev 2024-05-29 18:20:16 +02:00
Alan Rynne 9168174747 feat: CNX-9627 Merge data connector and visual into the same repo (#73)
* chore: Moved data connector into nested `src/powerbi-data-connector` folder

* feat(visual): Moved powerbi visual from speckle-powerbi-visuals repo

Copied from 2.19 tagged commit 0f034c17d04acd3fa5130a91779576efde7793a9

* chore: Created vscode workspace and unified settings

* fix: Update iss file and CI scripts

+ added workspace settings

* ci: Minor path fixes

* ci: And some extra tweaks

* ci: Typo in powerbi.iss
2024-05-29 12:16:40 +02:00
Alan Rynne fd9054a387 fix: Ensure old .mez connector is removed upon installation (#72) 2024-05-23 00:00:42 +02:00
Alan Rynne 84efce792b Merge pull request #71 from specklesystems/dev
Update `main` with changes from `dev`
2024-05-14 17:20:43 +02:00
Alan Rynne 0bc4da1864 fix: User Shell constant from Innosetup instead of ENV var from system (#70)
Lower admin rights to minimum as we don't need any more.
2024-05-13 16:28:49 +02:00
Alan Rynne d7ea4f217f fix: Installer doesn't need admin rights unless we sign the PQX (currently blocked) (#69) 2024-04-29 12:17:35 +02:00
Alan Rynne 3971adafd6 Merge pull request #68 from specklesystems/dev
Update main with changes from dev
2024-04-09 22:06:30 +02:00
Alan Rynne 1392070b31 CNX-9189 Installer for PowerBI Data Connector (without signing PQX) (#67)
* feat: Added logic to add thumbprint to registry

As well as delete it on uninstall, without compromising the rest of the values that other connectors may have added.

* fix: Re-enable pqx

* fix: Minor edits to install logic

* feat: CI config with conditional signing

* fix: Set versions correctly

* fix: Disable github release upload

* fix: Missing SSM env var and fast exit if external pr

* fix: Set version env vars correctly

* just a test

* another test 🧪

* with AI help

* circleCI wth

* 🤔🤕

* Why does it refuse to work!?!?!

* Onelining

* Onelining real command

* too fast :D

* 🤞🏼

* final touch?

* I really hope this does it

* 🥲

* 💥

* Revert "💥"

This reverts commit ac570614a9.

* Jonathon's magic 🪄

* Jonathon's magic v2

* starts to seem less magical

* maybe PEBKAC

* 🤞🏼

* just one loop?

* try escape \

* no double %%

* try escape with ""

* try bat file

* try bat file differently

* 🤦🏼‍♂️

* double %% in bat file

* try relative path

* fix code-signing error

* move includes

* 🚀

* fix: Do not sign PQX but do sign installers

* fix: Use actual tagname 🙇🏼‍♂️🤦🏼‍♂️

* ci: Minor tweaks

* ci: Push to real feed

* fix: Use exe not pqx to upload installer 🤦🏼‍♂️
2024-04-08 12:06:44 +02:00
Alan Rynne 85b9e88fc1 Merge pull request #65 from specklesystems/dev
Update `dev` with changes from `main`
2024-03-14 11:40:48 +01:00
Alan Rynne 92fc894d4b fix: Throw error when multi-model URL is input (#64) 2024-02-26 18:21:23 +01:00
Alan Rynne b033cfa82b CNX-9076: first round of new web app terminology rename (#63)
* fix: first round of new web app terminology rename

* fix: Last refs to commit/branch/stream gone

* fix: Table headers

* ci: Removed unnecessary step and bumped ci ghr image

* Modified publicly visible names on resource file
2024-02-26 17:03:20 +01:00
KatKatKateryna 86cba3ce2b Update README.md (#62)
Updated install instructions as here: https://learn.microsoft.com/en-gb/power-bi/connect-data/desktop-connector-extensibility#certified-connectors
2024-02-26 10:44:10 +01:00
Alan Rynne c752487f9f fix: CNX-7606 Fixes missing Data column error (#61)
* fix: Result will now include parent commit object and no longer assume it will have children

* fix: Keep metadata in joined table
2024-01-12 13:29:42 +01:00
Alan Rynne 95f51b0d32 fix(CNX-8322): Separated completely frontend2 parsing (#59)
* fix: Separated completely frontend2 parsing

This prevents logic from Fe2 leaking into Fe1 and viceversa, which could cause `null` exceptions in some cases.

* CNX-8322 Track in Jira
2023-11-30 10:26:53 +01:00
Alan Rynne 9a5c1283ce Adds Frontend2 URL Support for single model urls (#58)
* feat: Adds FE2 Url support for single model urls

* fix: Minor formatting changes
2023-11-09 16:48:36 +01:00
Alan Rynne da1f0c7b7e feat: Added extensions.json (#57) 2023-10-30 10:58:17 +01:00
Alan Rynne 0bb72f0d9d fix: Get back metrics by using old specific Receive log method (#56) 2023-10-30 10:56:04 +01:00
Ryan Haunfelder 19e64fd4b2 Parses the stream URL in a slightly more robust way. (#55) 2023-10-25 11:26:46 +02:00
Alan Rynne 63f286cd0b bump: 2.15.0-rc 2023-07-07 14:54:01 +02:00
Alan Rynne e89ccbbe1a feat: Adds new ToNameValueRecord function for revit params and DynamicExpand for table columns (#49) 2023-07-07 10:35:38 +02:00
garylzimmer f77e315623 Update README.md (#47)
added note about expected use case and link to relevant github
2023-06-14 13:11:13 +02:00
Alan Rynne 9db161b3f2 Update main with changes in dev (#42)
* feat: Added isMultiplayer to receive log

* Adds universal OAuth, preliminary connector installer and new column in GetByURL table (#41)

* ci: Added step to sign MEZ file

* ci: Fix output dir

* ci: Added innosetup installer test

* ci: Fix path

* ci: Fix sign tool path 🤦🏼‍♂️

* ci: Don't sign

* feat: Added parent object column to GetByUrl result

* feat: Universal OAuth login

* fix: Installer function fail, non-critical. Reverted to simple implementation
2023-05-11 12:37:20 +02:00
Alan Rynne 83ec9a7888 ci: Updated issue automation 2023-01-09 20:26:28 +01:00
Alan Rynne 6128b4293f fix: Use filename with no . 2023-01-09 17:15:36 +01:00
Alan Rynne 6f68ec9c29 revert no branch change 2023-01-09 17:12:48 +01:00
Alan Rynne 10c1009430 fix: Try with no branch 2023-01-09 17:12:20 +01:00
Alan Rynne c635995bf6 fix: Add reopen event 2023-01-09 17:09:43 +01:00
Alan Rynne 1198450ea4 fix: Use non-nested path 2023-01-09 17:08:22 +01:00
Alan Rynne 3d4545be64 fix: Use correct location 2023-01-09 17:04:52 +01:00
Alan Rynne 363c389928 test: Use reusable workflow for issue open event 2023-01-09 17:02:02 +01:00
Alan Rynne addc3f9d86 feat: using contexts in CI config (#36)
* feat: using contexts in CI config

* fix: Wrong context in the wrong place
2023-01-09 11:55:40 +01:00
Alan Rynne 44fc99ecb7 fix(ci): Version setting on CI now picks up correct version 2023-01-02 11:08:35 +01:00
Alan Rynne f52d6c46cb Changes login from latest to XYZ (#35)
* feat: Adds XYZ login instead + minor cleanup

* fix: Added tracking to both exposed functions
2022-12-20 19:00:02 +01:00
Alan Rynne f558d5dce3 Adds support for REST endpoint to get objects (#34)
* feat: Working REST endpoint to fetch objects instead of GraphQL

* hack: Temporarily exposed rest function

* feat: Added OAuth test login

* chore: Cleanup for release
2022-12-15 12:06:19 +01:00
Alan Rynne f9093a34e5 fixes #33 2022-12-06 12:06:42 +01:00
Alan Rynne 23cb40c4ab Fixes slow behaviour in Speckle.GetByUrl (#31)
* fix: Swapped slow RemoveItems for faster Select

* feat: Added `select` input to `GetObjectChildren` function

* fix: Increased pagination to 1000
2022-12-05 22:53:56 +01:00
Alan Rynne 9bd6140065 CI: Adds build and deploy configs (#30)
* Add .circleci/config.yml

* First attempt

* fix: Missing win orb

* fix: Wrong persist path

* ci: Added version updating step

* ci: Fixed artifacts paths

* ci: Added publish to github release step

* Fixing ci paths to persist

* fix: Artifacts path

* ci: Fix deploy dependency order

* ci: Adds tags to ghr inputs
2022-12-05 17:59:20 +01:00
Alan Rynne a14bb06d4e Merge pull request #29 from specklesystems/unit-testing
General improvements in DataConnector
2022-12-05 13:51:11 +01:00
Alan Rynne 01576e77df chore: Reorganised tests 2022-12-05 10:19:32 +01:00
Alan Rynne f1e16e9d55 feat: More refactoring, tests and new exposed functions 2022-12-04 22:50:42 +01:00
Alan Rynne 4f210dae04 feat: Refactor of helper methods 2022-12-02 00:00:04 +01:00
Alan Rynne 8669cf59f8 fix: Minor fixes in imports + test.query.pq file 2022-12-01 17:03:15 +01:00
Alan Rynne 6e6402cb38 Merge pull request #18 from specklesystems/refactor/vs-code-extension
Refactor: Use new VSCode Extension + Modularise data connector
2022-11-29 12:34:08 +01:00
Alan Rynne 7b62630fd1 fix: MIssing getUser function in LogEvent 2022-11-29 12:28:24 +01:00
Alan Rynne eee4f0f8c2 feat: Added utility functions from powerquery docs 2022-11-28 23:05:41 +01:00
Alan Rynne 23fa79bdd9 fix: Renamed commitReceived function 2022-11-28 22:53:23 +01:00
Alan Rynne edc9917a20 feat: Split down into modules 2022-11-28 22:38:59 +01:00
Alan Rynne 99cffeba8c feat: Working with new VSCode extension 2022-11-28 20:47:28 +01:00
Alan Rynne 7e92e7a2aa Fixes #16 2022-11-25 13:48:06 +01:00
Alan Rynne 15aa627d7f fix: Failed receive on public streams due to read receipts (fixes #15) 2022-10-27 23:23:37 +02:00
Alan Rynne 8844f1837e feat: Updated mixpanel logging with sourceApp variables 2022-10-07 12:58:35 +02:00
Alan Rynne 6d1b85a14f Revert "fix: Read receipt was using hardcoded token? "
This reverts commit b2d6568b66.
2022-09-01 10:39:01 +02:00
Alan Rynne b2d6568b66 fix: Read receipt was using hardcoded token? 2022-09-01 10:33:05 +02:00
Alan Rynne 72f455393b fix: Prevent read receipt when no credentials are found 2022-09-01 10:13:20 +02:00
Alan Rynne bd3fe6a47c feat: Refactored output table for viewer compatibility 2022-08-31 11:25:46 +02:00
Alan Rynne e7710f0f8e fix: Remove null row at the end or result table 2022-08-25 11:08:09 +02:00
Alan Rynne e9034027eb feat: Added columns to FetchByUrl result table for plug-and-play with the viewer 2022-08-25 10:42:03 +02:00
Alan Rynne 685384b147 feat: Added mixpanel user and server hashed 2022-04-07 15:37:52 +02:00
Alan Rynne 5973aaea10 feat: Added user fetch function 2022-04-07 12:17:17 +02:00
Alan Rynne 639287f69c feat: Added mixpanel tracking 2022-04-06 12:55:49 +02:00
Alan Rynne a43decd473 Merge pull request #12 from specklesystems/fix/branch-special-chars
Fix: Uses powerquery `buildQueryString` method to ensure branch name is url safe first
2021-12-03 12:18:19 +01:00
Alan Rynne a532061879 Fix: Uses powerquery buildQueryString method to ensure branch name is url safe first 2021-12-03 12:16:50 +01:00
Alan Rynne 15ac7c041e Merge pull request #10 from specklesystems/hotfix/url-parsing
fix: Fixes wrong implementation assignment and optional input removal
2021-12-02 12:17:03 +01:00
Alan Rynne 770d45f981 fix: Fixes wrong implementation assignment and optional input removal 2021-12-02 12:15:58 +01:00
Alan Rynne 50b59a88c3 Merge pull request #9 from specklesystems/alan/error-reporting
Stable release final touches 🪄
2021-11-29 12:41:06 +01:00
Alan Rynne 0bad9914c8 fix: Removed shared tag from API function 2021-11-29 12:40:54 +01:00
Alan Rynne aee00f3f21 fix: Removed __closure and totalChildrenCount fields. 2021-11-29 12:31:45 +01:00
Alan Rynne 2abdd4b645 fix: Removed duplicated icons and fixed folder path 2021-11-29 12:31:19 +01:00
Alan Rynne 71ef1acd8b feat: Final polish!! 2021-11-29 11:39:39 +01:00
Alan Rynne 23114a2912 chore: Pending file deletion 2021-11-25 10:02:42 +01:00
Alan Rynne ee989f927d feat: Better error handling and initial function documentation 2021-11-25 10:01:09 +01:00
Alan Rynne 1ff15c7b44 chore: Moved logos into subfolder 2021-11-25 10:00:45 +01:00
Alan Rynne 6e9fb590a0 chore: Removed duplicated api call 2021-11-24 18:58:53 +01:00
Alan Rynne 9700865934 Merge pull request #7 from specklesystems/alan/table-pagination
Adds pagination to object queries
2021-11-24 10:58:48 +01:00
Alan Rynne cf9e5a70b6 chore: Reordering of methods for clarity 2021-11-24 09:52:04 +01:00
Alan Rynne 50c360ae79 feat: Pagination works!! Plus query formating 2021-11-24 09:31:39 +01:00
Alan Rynne b671945fa7 feat: More re-structuring + pagination boilerplate 2021-11-24 09:05:52 +01:00
Alan Rynne eb4787ddfa fix: Removed redundant implementation 2021-11-23 16:40:31 +01:00
Alan Rynne 3ffd6c63e0 feat: Fetch refactoring 2021-11-23 16:33:08 +01:00
219 changed files with 40609 additions and 650 deletions
+16
View File
@@ -0,0 +1,16 @@
version: 2.1
# Define the jobs we want to run for this project
jobs:
build:
docker:
- image: cimg/base:2023.03
steps:
- run: echo "so long and thanks for all the fish"
# Orchestrate our job run sequence
workflows:
build_and_test:
when: false
jobs:
- build
-78
View File
@@ -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 }}
+159
View File
@@ -0,0 +1,159 @@
name: Build and deploy Connector and Visual
on:
push:
branches: ["installer-test/**"]
tags: ["v3.*.*"] # Manual delivery on every 3.x tag
jobs:
build-connector:
runs-on: windows-latest
outputs:
semver: ${{ steps.set-version.outputs.semver }}
file-version: ${{ steps.set-version.outputs.file-version }}
env:
CertFile: "./speckle.pfx"
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python 3.10
uses: actions/setup-python@v3
with:
python-version: "3.10"
- id: set-version
name: Set version to output
shell: bash
run: |
TAG=${{ github.ref_name }}
if [[ "${{ github.ref }}" != refs/tags/* ]]; then
TAG="v3.0.99"
fi
SEMVER="${TAG#v}"
FILE_VERSION=$(echo "$TAG" | sed -E 's/^v([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
FILE_VERSION="$FILE_VERSION.${{ github.run_number }}"
echo "semver=$SEMVER" >> "$GITHUB_OUTPUT"
echo "file-version=$FILE_VERSION" >> "$GITHUB_OUTPUT"
echo $SEMVER
echo $FILE_VERSION
- name: Set connector version
run: |
python patch_version.py ${{steps.set-version.outputs.file-version}}
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v2
- name: Build Data Connector
working-directory: src/powerbi-data-connector
run: |
msbuild Speckle.proj /restore /consoleloggerparameters:NoSummary /property:GenerateFullPaths=true
- name: Setup Self-sign certificate
run: |
echo "${{ secrets.SELF_CERT_FILE_B64 }}" > "certificate.txt"
certutil -decode certificate.txt ${{ env.CertFile }}
- name: Create PQX file
run: |
.\tools\MakePQX\MakePQX.exe pack --mez src/powerbi-data-connector/bin/Speckle.mez --target src/powerbi-data-connector/bin/Speckle.pqx --certificate ${{env.CertFile}} --password ${{secrets.SELF_CERT_PASSWORD}}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: powerbi-connector
path: src/powerbi-data-connector/bin/Speckle.pqx
if-no-files-found: error
retention-days: 1
build-visual:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- id: set-version
name: Set version to output
shell: bash
run: |
TAG=${{ github.ref_name }}
if [[ "${{ github.ref }}" != refs/tags/* ]]; then
TAG="v3.0.99"
fi
SEMVER="${TAG#v}"
FILE_VERSION=$(echo "$TAG" | sed -E 's/^v([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
FILE_VERSION="$FILE_VERSION.${{ github.run_number }}"
echo "semver=$SEMVER" >> "$GITHUB_OUTPUT"
echo "file-version=$FILE_VERSION" >> "$GITHUB_OUTPUT"
echo $SEMVER
echo $FILE_VERSION
- run: npm ci
working-directory: src/powerbi-visual
- run: npm version ${{steps.set-version.outputs.semver}} --allow-same-version
working-directory: src/powerbi-visual
- run: npm run build
working-directory: src/powerbi-visual
- uses: actions/upload-artifact@v4
with:
name: powerbi-visual
path: src/powerbi-visual/dist/*.pbiviz
if-no-files-found: error
retention-days: 1
deploy-installers:
runs-on: ubuntu-latest
needs:
- build-connector
- build-visual
env:
IS_TAG_BUILD: ${{ github.ref_type == 'tag' }}
steps:
- name: download artifacts
uses: actions/download-artifact@v4
with:
name: powerbi-connector
path: artifacts/
- name: download artifacts visual
uses: actions/download-artifact@v4
with:
name: powerbi-visual
path: artifacts/
- name: Zip artifacts
run: |
cd artifacts && zip -r ../powerbi.zip .
- name: upload artifacts
uses: actions/upload-artifact@v4
with:
name: output-${{needs.build-connector.outputs.semver}}
path: powerbi.zip
if-no-files-found: error
retention-days: 1
- name: 🔫 Trigger Build Installer(s)
uses: the-actions-org/workflow-dispatch@v4.0.0
with:
workflow: Build Installers
repo: specklesystems/connector-installers
token: ${{ secrets.CONNECTORS_GH_TOKEN }}
inputs: '{
"run_id": "${{ github.run_id }}",
"semver": "${{ needs.build-connector.outputs.semver }}",
"file_version": "${{ needs.build-connector.outputs.file-version }}",
"repo": "${{ github.repository }}",
"is_public_release": ${{ env.IS_TAG_BUILD }}
}'
ref: main
wait-for-completion: true
wait-for-completion-interval: 10s
wait-for-completion-timeout: 10m
display-workflow-run-url: true
display-workflow-run-url-interval: 10s
- uses: geekyeggo/delete-artifact@v5
with:
name: output-*
-50
View File
@@ -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
+30
View File
@@ -0,0 +1,30 @@
name: Test Build Connector and Visual
on: pull_request
jobs:
build-connector:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v2
- name: Build Data Connector
working-directory: src/powerbi-data-connector
run: |
msbuild Speckle.proj /restore /consoleloggerparameters:NoSummary /property:GenerateFullPaths=true
build-visual:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
working-directory: src/powerbi-visual
- run: npm run build
working-directory: src/powerbi-visual
+10
View File
@@ -334,3 +334,13 @@ ASALocalRun/
.localhistory/
**/.DS_Store
**/dist/
**/.tmp/
**/webpack.statistics.*.html
**/webpack.statistics.html
**/Thumbs.db
installer/
localhost.pem
localhost-key.pem
+105 -64
View File
@@ -1,16 +1,112 @@
<h1 align="center">
<<h1 align="center">
<img src="https://user-images.githubusercontent.com/2679513/131189167-18ea5fe1-c578-47f6-9785-3748178e4312.png" width="150px"/><br/>
Speckle | PowerBI
Speckle | Power BI
</h1>
<p align="center"><a href="https://twitter.com/SpeckleSystems"><img src="https://img.shields.io/twitter/follow/SpeckleSystems?style=social" alt="Twitter Follow"></a> <a href="https://speckle.community"><img src="https://img.shields.io/discourse/users?server=https%3A%2F%2Fspeckle.community&amp;style=flat-square&amp;logo=discourse&amp;logoColor=white" alt="Community forum users"></a> <a href="https://speckle.systems"><img src="https://img.shields.io/badge/https://-speckle.systems-royalblue?style=flat-square" alt="website"></a> <a href="https://docs.speckle.systems/"><img src="https://img.shields.io/badge/docs-speckle.systems-orange?style=flat-square&amp;logo=read-the-docs&amp;logoColor=white" alt="docs"></a></p>
> Speckle is the first AEC data hub that connects with your favorite AEC tools. Speckle exists to overcome the challenges of working in a fragmented industry where communication, creative workflows, and the exchange of data are often hindered by siloed software and processes. It is here to make the industry better.
<h3 align="center">
Data Connector for Microsoft's PowerBI platform
Speckle Connector and 3D Visual for Power BI
</h3>
<p align="center"><b>Speckle</b> is the data infrastructure for the AEC industry.</p><br/>
<p align="center"><a href="https://twitter.com/SpeckleSystems"><img src="https://img.shields.io/twitter/follow/SpeckleSystems?style=social" alt="Twitter Follow"></a> <a href="https://speckle.community"><img src="https://img.shields.io/discourse/users?server=https%3A%2F%2Fspeckle.community&amp;style=flat-square&amp;logo=discourse&amp;logoColor=white" alt="Community forum users"></a> <a href="https://speckle.systems"><img src="https://img.shields.io/badge/https://-speckle.systems-royalblue?style=flat-square" alt="website"></a> <a href="https://speckle.guide/dev/"><img src="https://img.shields.io/badge/docs-speckle.guide-orange?style=flat-square&amp;logo=read-the-docs&amp;logoColor=white" alt="docs"></a></p>
<p align="center"></p>
## Features
# About Speckle
Speckle Power BI Data Connector lets you easily get data from Speckle into Power BI reports and visualizations. You can access and analyze data from various AEC apps (like Revit, Archicad, Grasshopper, and more) and open-source files (IFC, STL, OBJ, etc.) into Power BI with ease.
<p align="center">
<div align="center">
<a href="https://app.speckle.systems/connectors/">
Download Power BI Connector
</a>
</div>
</p>
Speckles connection to Power BI consists of two parts:
- **Data Connector** fetches the data you uploaded from AEC apps to Speckle.
- **3D Visual** allows you to see those models in 3D within Power BI.
![Desktop - 1 (1)](https://github.com/specklesystems/speckle-powerbi/assets/51519350/6d2c5224-965f-4eae-b869-be26cb48c6b2)
## Repository Structure
This repository is home to our Power BI connector. The Speckle Server provides all the web-facing functionality and can be found [here](https://github.com/specklesystems/Server).
`src/powerbi-data-connector` contains all the code for the Data connector.
`src/powerbi-visual` contains all the code for 3D Visual.
## Installation
Power BI connector installer can be downloaded from the [connectors portal](https://app.speckle.systems/connectors/). Full instructions for [installation](https://docs.speckle.systems/connectors/power-bi#setup) and [configuration](https://docs.speckle.systems/connectors/power-bi#why-dont-i-see-speckle-as-a-data-source-in-power-bi) can be found on our docs.
### 3D Visual
3D Visual can be imported as any other Power BI custom visual.
1. Navigate to the Visualization Pane.
2. Click the three dots (…) and select “Import a visual from a file”.
3. Go to `Documents/Power BI Desktop/Custom Visuals` and import `Speckle 3D Visual.pbiviz` file.
4. Speckle cube will appear in the Visualization pane.
For more on how to use the visual, [check our docs](https://docs.speckle.systems/connectors/power-bi).
## Quick Start
To get started with Power BI connector, please take a look at the [documentation](https://docs.speckle.systems/connectors/power-bi) and extensive [tutorials](https://www.youtube.com/@SpeckleSystems) published.
## Development Setup
### For local development of the 3D Visual
1. **Clone the repository**:
```bash
git clone https://github.com/specklesystems/speckle-powerbi.git
cd speckle-powerbi
```
2. **Navigate to the visual directory**:
```bash
cd src/powerbi-visual
```
3. **Install dependencies**:
```bash
npm install
# or
yarn install
```
4. **Start development server**:
```bash
npm run dev
```
5. **Build the visual**:
```bash
# Development build
npm run build:dev
# Production build
npm run build
```
### For local development of the Data Connector
1. **Install PowerQuery SDK**:
Follow the instructions from the [official docs](https://docs.microsoft.com/en-us/power-query/installingsdk)
2. **Open the project in Visual Studio Code**:
- Open `src/powerbi-data-connector/Speckle.proj`
- Build the project to generate the `.mez` file
3. **Testing the connector**:
- Visual Studio will automatically copy the `.mez` file to the appropriate location
- Restart Power BI Desktop to see the latest changes
## About Speckle
What is Speckle? Check our ![YouTube Video Views](https://img.shields.io/youtube/views/B9humiSpHzM?label=Speckle%20in%201%20minute%20video&style=social)
@@ -31,65 +127,10 @@ What is Speckle? Check our ![YouTube Video Views](https://img.shields.io/youtube
Give Speckle a try in no time by:
- [![speckle XYZ](https://img.shields.io/badge/https://-speckle.xyz-0069ff?style=flat-square&logo=hackthebox&logoColor=white)](https://speckle.xyz) ⇒ creating an account at our public server
- [![create a droplet](https://img.shields.io/badge/Create%20a%20Droplet-0069ff?style=flat-square&logo=digitalocean&logoColor=white)](https://marketplace.digitalocean.com/apps/speckle-server?refcode=947a2b5d7dc1) ⇒ deploying an instance in 1 click
- [![app.speckle.systems](https://img.shields.io/badge/https://-app.speckle.systems-0069ff?style=flat-square&logo=hackthebox&logoColor=white)](https://app.speckle.systems) ⇒ creating an account at our public server
### Resources
- [![Community forum users](https://img.shields.io/badge/community-forum-green?style=for-the-badge&logo=discourse&logoColor=white)](https://speckle.community) for help, feature requests or just to hang with other speckle enthusiasts, check out our community forum!
- [![website](https://img.shields.io/badge/tutorials-speckle.systems-royalblue?style=for-the-badge&logo=youtube)](https://speckle.systems) our tutorials portal is full of resources to get you started using Speckle
- [![docs](https://img.shields.io/badge/docs-speckle.guide-orange?style=for-the-badge&logo=read-the-docs&logoColor=white)](https://speckle.guide/dev/) reference on almost any end-user and developer functionality
![Untitled](https://user-images.githubusercontent.com/2679513/132021739-15140299-624d-4410-98dc-b6ae6d9027ab.png)
# Repo structure
This repo is the home to our Speckle 2.0 PowerBI project. The [Speckle Server](https://github.com/specklesystems/Server) is providing all the web-facing functionality and can be found [here](https://github.com/specklesystems/Server).
## Install
Go to the [Releases](https://github.com/specklesystems/speckle-powerbi/releases) page, downlad the `.mez` file of the latest release and copy it into the following folder in your computer:
```
YOUR_USER_FOLDER\Documents\Power BI Desktop\Custom Connectors\
```
### Allow custom extensions to run
Go to `Settings -> Security -> Data Extensions` and activate the following option:
![Allow extensions to run](https://user-images.githubusercontent.com/2316535/130931149-074cf6a8-1910-41f1-99c7-b8b08168f473.png)
### Checking the connector is loaded
Now open PowerBI and you should see `Speckle (beta)` appear in the data source.
![PowerBI](https://user-images.githubusercontent.com/2316535/129580913-02e5e662-f344-419c-9894-e97055930c58.png)
## Usage
> More detailed instructions on how to use the connector will be added shortly!
### Current limitations
Chunked data currently is not automatically de-chunked when received, we are aware of this limitation and are working to resolve it!
## Developing & Debugging
We encourage everyone interested to debug / hack / contribute / give feedback to this project.
### Setup
#### Install PowerQuery SDK
Follow the instructions from the [official docs](https://docs.microsoft.com/en-us/power-query/installingsdk)
#### Build with Visual Studio
Every time you build the connector, VisualStudio will copy the latest `.mez` connector file to the appropriate location. Just restart PowerBI to see the latest changes.
#### Debug
You can start the PowerQuery connector in VisualStudio, this will open a standalone connector you can use for testing purposes.
We don't know of a way to debug the connector live in PowerBI, but we'd be happy to hear about it.
- [![docs](https://img.shields.io/badge/docs-speckle.systems-orange?style=for-the-badge&logo=read-the-docs&logoColor=white)](https://docs.speckle.systems) reference on almost any end-user and developer functionality
-25
View File
@@ -1,25 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30804.86
MinimumVisualStudioVersion = 10.0.40219.1
Project("{4DF76451-A46A-4C0B-BE03-459FAAFA07E6}") = "Speckle", "Speckle\Speckle.mproj", "{D61F9470-E614-45C7-8047-E354FF219408}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D61F9470-E614-45C7-8047-E354FF219408}.Debug|x86.ActiveCfg = Debug|x86
{D61F9470-E614-45C7-8047-E354FF219408}.Debug|x86.Build.0 = Debug|x86
{D61F9470-E614-45C7-8047-E354FF219408}.Release|x86.ActiveCfg = Release|x86
{D61F9470-E614-45C7-8047-E354FF219408}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {765772BB-45FA-4661-9573-F0F182FEB7C1}
EndGlobalSection
EndGlobal
-121
View File
@@ -1,121 +0,0 @@
<Project DefaultTargets="BuildExtension" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{0b5fe03d-aad9-4bc4-9edf-b543d5e50d29}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>MyRootNamespace</RootNamespace>
<AssemblyName>MyAssemblyName</AssemblyName>
<EnableUnmanagedDebugging>False</EnableUnmanagedDebugging>
<AllowNativeQuery>False</AllowNativeQuery>
<AsAction>False</AsAction>
<FastCombine>False</FastCombine>
<ClearLog>False</ClearLog>
<ShowEngineTraces>False</ShowEngineTraces>
<ShowUserTraces>False</ShowUserTraces>
<LegacyRedirects>False</LegacyRedirects>
<SuppressRowErrors>False</SuppressRowErrors>
<SuppressCellErrors>False</SuppressCellErrors>
<MaxRows>1000</MaxRows>
<ExtensionProject>Yes</ExtensionProject>
<Name>Speckle</Name>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>false</DebugSymbols>
<!--Should be true, fix this when the debugger is implemented -->
<OutputPath>bin\Debug\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugSymbols>false</DebugSymbols>
<OutputPath>bin\Release\</OutputPath>
</PropertyGroup>
<ItemGroup>
<Reference Include="mscorlib" />
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Speckle.pq">
<SubType>Code</SubType>
</Compile>
<Compile Include="SpeckleLogo16.png">
<SubType>Code</SubType>
</Compile>
<Compile Include="SpeckleLogo20.png">
<SubType>Code</SubType>
</Compile>
<Compile Include="SpeckleLogo24.png">
<SubType>Code</SubType>
</Compile>
<Compile Include="SpeckleLogo32.png">
<SubType>Code</SubType>
</Compile>
<Compile Include="SpeckleLogo40.png">
<SubType>Code</SubType>
</Compile>
<Compile Include="SpeckleLogo48.png">
<SubType>Code</SubType>
</Compile>
<Compile Include="SpeckleLogo64.png">
<SubType>Code</SubType>
</Compile>
<Compile Include="SpeckleLogo80.png">
<SubType>Code</SubType>
</Compile>
<Compile Include="resources.resx">
<SubType>Code</SubType>
</Compile>
<Content Include="Speckle.query.pq">
<SubType>Code</SubType>
</Content>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<UsingTask TaskName="BuildExtension" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v12.0.dll">
<ParameterGroup>
<InputDirectory ParameterType="System.String" Required="true" />
<OutputFile ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Reference Include="System.IO.Compression" />
<Reference Include="System.IO.Compression.FileSystem" />
<Using Namespace="System.Globalization" />
<Using Namespace="System.IO.Compression " />
<Code Type="Fragment" Language="cs"><![CDATA[
using(FileStream fileStream = File.Create(OutputFile))
using(ZipArchive archiveOut = new ZipArchive(fileStream, ZipArchiveMode.Create, false))
{
foreach(string fullPath in Directory.EnumerateFiles(InputDirectory))
{
string filename = Path.GetFileName(fullPath);
archiveOut.CreateEntryFromFile(fullPath, filename, CompressionLevel.Optimal);
}
}
]]></Code>
</Task>
</UsingTask>
<Target Name="BuildExtension" DependsOnTargets="ExtensionClean">
<ItemGroup>
<PQFiles Include="@(Compile)" Condition="'%(Extension)' == '.pq'" />
</ItemGroup>
<ItemGroup>
<NonPQFiles Include="@(Compile)" Condition="'%(Extension)' != '.pq'" />
</ItemGroup>
<MakeDir Directories="$(IntermediateOutputPath)" />
<MakeDir Directories="$(OutputPath)" />
<Copy SourceFiles="@(NonPQFiles)" DestinationFolder="$(IntermediateOutputPath)" />
<Copy SourceFiles="@(PQFiles)" DestinationFiles="@(PQFiles->'$(IntermediateOutputPath)%(RecursiveDir)%(FileName).m')" />
<BuildExtension InputDirectory="$(IntermediateOutputPath)" OutputFile="$(OutputPath)\$(ProjectName).mez" />
<Message Text="Copying .mez file to: $(UserProfile)\Documents\Power BI Desktop\Custom Connectors" Importance="High" />
<MakeDir Directories="$(UserProfile)\Documents\Power BI Desktop\Custom Connectors\" />
<Copy SourceFiles="$(OutputPath)\$(ProjectName).mez" DestinationFolder="$(UserProfile)\Documents\Power BI Desktop\Custom Connectors">
</Copy>
</Target>
<Target Name="ExtensionClean">
<!-- Remove obj folder -->
<RemoveDir Directories="$(BaseIntermediateOutputPath)" />
<!-- Remove bin folder -->
<RemoveDir Directories="$(OutputPath)" />
</Target>
</Project>
-293
View File
@@ -1,293 +0,0 @@
section Speckle;
/* This is an additional nav bar that can display branches of a stream: not using this for now
[DataSource.Kind="Speckle", Publish="Speckle.Publish"]
shared Speckle.Contents = Value.ReplaceType(NavigationTable.Simple, type function (url as Uri.Type) as any);
// set up nav table
shared NavigationTable.Simple = (url) as table =>
let
baseUrl = Uri.Parts(url)[Host] as text,
streamId = Text.Split(Uri.Parts(url)[Path], "/"){2},
objects = Speckle.GetBranches(baseUrl, streamId),
table = #table(
{"Name", "Key", "Data", "ItemKind", "ItemName", "IsLeaf"},
List.InsertRange(objects, List.Count(objects), {{"GetCommit", "GetCommit", Speckle.GetObjectFromObject(baseUrl, streamId), "Function", "Function", true}})
),
NavTable = Table.ToNavigationTable(table, {"Key"}, "Name", "Data", "ItemKind", "ItemName", "IsLeaf")
in
NavTable;
Speckle.GetBranches = (url, id) =>
let
Source = Web.Contents(
Text.Combine({"https:/", url, "graphql"}, "/"),
[
Headers=[
#"Method"="POST",
#"Content-Type"="application/json"
],
Content=Text.ToBinary("{""query"": ""query { stream( id: \"""&id&"\"" ) { branches { items { name commits { items { id message sourceApplication authorName createdAt } } } } } }""}")
]
),
#"JSON" = Json.Document(Source),
branches = #"JSON"[data][stream][branches][items],
branchList = List.Generate(
() => [x = 0, y = Speckle.GetBranchAsList(branches{x})],
each [x] < List.Count(branches),
each [x = [x] + 1, y = Speckle.GetBranchAsList(branches{x})],
each [y]
)
in
branchList;
Speckle.GetBranchAsList = (branchRecord) =>
let
commits = Table.FromRecords(branchRecord[commits][items]),
list = {branchRecord[name], branchRecord[name], commits, "Table", "Table", true}
in
list;
*/
[DataSource.Kind="Speckle", Publish="Speckle.Publish"]
shared Speckle.Contents = Value.ReplaceType(CommitTable, type function (StreamUrl as Uri.Type) as any);
/* INFO: Variables will not be instantiated (or any code run) until they are used */
shared CommitTable = (url) as table =>
let
// Get server and streamId, and branchName / commitId / objectid from the input url
s = Text.Combine({"https://", Uri.Parts(url)[Host]}),
server = Speckle.LogToMatomo(s),
segments = Text.Split(Text.AfterDelimiter(Uri.Parts(url)[Path], "/", 0), "/"),
streamId = segments{1},
branchName = if( List.Count(segments) = 4 and segments{2} = "branches" ) then segments{3} else null,
commitId = if (List.Count(segments) = 4 and segments{2} = "commits" ) then segments{3} else null,
objectId = if (List.Count(segments) = 4 and segments{2} = "objects" ) then segments{3} else null,
commitTable = if (commitId <> null) then Speckle.GetObjectFromCommit(server, streamId, commitId)
else if (objectId <> null) then Speckle.GetObjectFromObject(server, streamId, objectId, false)
else if (branchName <> null) then Speckle.GetObjectFromBranch(server,streamId,branchName)
else Speckle.GetObjectFromStream(server, streamId)
in
commitTable;
Speckle.CommitReceived = (server, streamId, commitId) =>
let
app= "PowerBI",
apiKey = try Extension.CurrentCredential()[Key] otherwise "",
Source = if apiKey = null then "wohoo" else Web.Contents(
Text.Combine({server, "graphql"}, "/"),
[
Headers=[
#"Method"="POST",
#"Content-Type"="application/json",
#"Authorization"= if apiKey = null then "" else Text.Format("Bearer #{0}",{apiKey})
],
ManualStatusHandling = {400},
Content=Text.ToBinary("{""query"": ""mutation { commitReceive(input: { streamId: \"""&streamId&"\"", commitId: \"""&commitId&"\"", sourceApplication: \"""&app&"\""}) }""}") ]
),
#"JSON" = Json.Document(Source),
Result = #"JSON"[data][commitReceive]
in
if apiKey = "" then [data="skipped"] else [data=Text.From(Result)];
/* Since everything is lazily evaluated, we must join and split the result of the matomo call with the server, and spit back the server url for PowerBI to actually log the calls to Matomo */
Speckle.LogToMatomo = (server) =>
let
matomoUrl = "https://speckle.matomo.cloud/matomo.php",
action = "receive/manual",
appName = "Power BI",
userId = "powerBIuser",
params = [
idsite = "2",
rec = "1",
apiv = "1",
uid = userId,
action_name = action,
url = Text.Combine({"http://connectors/PowerBI/", action}),
urlref = Text.Combine({"http://connectors/PowerBI/", action}),
_cvar = Text.FromBinary(Json.FromValue([hostApplication = appName]))
],
visitQuery = Uri.BuildQueryString(params),
visitRes = Web.Contents(Text.Combine({matomoUrl, "?", visitQuery}),
[
Headers=[
#"Method"="POST"
],
Content=Text.ToBinary(server)
]),
eventParams = [
idsite = "2",
rec = "1",
apiv = "1",
uid = userId,
_cvar = Text.FromBinary(Json.FromValue([hostApplication = appName])),
e_c = appName,
e_a = action
],
eventQuery = Uri.BuildQueryString(eventParams),
eventRes = Web.Contents(Text.Combine({ matomoUrl, "?", eventQuery}),
[
Headers=[
#"Method"="POST"
],
Content=Text.ToBinary(server)
]),
Result = Text.FromBinary(visitRes) & Text.FromBinary(eventRes),
Combined = Text.Combine({server,Result},"___"),
Split = Text.Split(Combined,"___"){0}
in
Split;
Speckle.GetObjectFromStream = (server, streamId) =>
let
apiKey = try Extension.CurrentCredential()[Key] otherwise null,
branchName = "main",
Source = Web.Contents(
Text.Combine({server, "graphql"}, "/"),
[
Headers=[
#"Method"="POST",
#"Content-Type"="application/json",
#"Authorization"= if apiKey = null then "" else Text.Format("Bearer #{0}",{apiKey})
],
Content=Text.ToBinary("{""query"": ""query { stream( id: \"""&streamId&"\"" ) { branch (name: \"""&branchName&"\""){ commits (limit: 1) { items { referencedObject, id } } } } }""}")
]
),
#"JSON" = Json.Document(Source),
objectId = #"JSON"[data][stream][branch][commits][items]{0}[referencedObject],
commitId = #"JSON"[data][stream][branch][commits][items]{0}[id],
objectsTable = Speckle.GetObjectFromObject(server, streamId, objectId, true),
commitReceivedRes = Speckle.CommitReceived(server, streamId, commitId)
in
if commitReceivedRes[data] = "true" then objectsTable else objectsTable;
Speckle.GetObjectFromBranch = (server, streamId, branchName) =>
let
decodedBranchName = Record.Field(Record.Field(Uri.Parts("http://www.dummy.com?A=" & branchName),"Query"),"A"),
apiKey = try Extension.CurrentCredential()[Key] otherwise null,
Source = Web.Contents(
Text.Combine({server, "graphql"}, "/"),
[
Headers=[
#"Method"="POST",
#"Content-Type"="application/json",
#"Authorization"= if apiKey = null then "" else Text.Format("Bearer #{0}",{apiKey})
],
Content=Text.ToBinary("{""query"": ""query { stream( id: \"""&streamId&"\"" ) { branch (name: \"""&decodedBranchName&"\""){ commits (limit: 1) { items { id referencedObject } } } } }""}")
]
),
#"JSON" = Json.Document(Source),
objectId = #"JSON"[data][stream][branch][commits][items]{0}[referencedObject],
commitId = #"JSON"[data][stream][branch][commits][items]{0}[id],
objectsTable = Speckle.GetObjectFromObject(server, streamId, objectId, true),
commitReceivedRes = Speckle.CommitReceived(server, streamId, commitId)
in
if commitReceivedRes[data] = "true" then objectsTable else objectsTable;
shared Speckle.GetObjectFromCommit = (server, streamId, commitId) =>
let
apiKey = try Extension.CurrentCredential()[Key] otherwise null,
Source = Web.Contents(
Text.Combine({server, "graphql"}, "/"),
[
Headers=[
#"Method"="POST",
#"Content-Type"="application/json",
#"Authorization"= if apiKey = null then "" else Text.Format("Bearer #{0}",{apiKey})
],
Content=Text.ToBinary("{""query"": ""query { stream( id: \"""&streamId&"\"" ) { commit (id: \"""&commitId&"\""){ referencedObject } } }""}")
]
),
#"JSON" = Json.Document(Source),
objectId = #"JSON"[data][stream][commit][referencedObject],
objectsTable = Speckle.GetObjectFromObject(server, streamId, objectId, true),
commitReceivedRes = Speckle.CommitReceived(server, streamId, commitId),
forceCommitReceived = Table.RemoveFirstN(Table.InsertRows(objectsTable, 0, {commitReceivedRes}), 1)
in
if commitReceivedRes[data] = "true" then objectsTable else objectsTable;
Speckle.GetObjectFromObject = (server, streamId, objectId, IsCommitObject) =>
let
query = if (IsCommitObject) then "{""query"": ""query { stream( id: \"""&streamId&"\"" ) { object (id: \"""&objectId&"\"") { children { objects { data } } } } }""}"
else "{""query"": ""query { stream( id: \"""&streamId&"\"" ) { object (id: \"""&objectId&"\"") { data } } }""}",
apiKey = try Extension.CurrentCredential()[Key] otherwise null,
Source = Web.Contents(
Text.Combine({server, "graphql"}, "/"),
[
Headers=[
#"Method"="POST",
#"Content-Type"="application/json",
#"Authorization"= if apiKey = null then "" else Text.Format("Bearer #{0}",{apiKey})
],
Content=Text.ToBinary(query)
]
),
#"JSON" = Json.Document(Source),
objects = if (IsCommitObject) then #"JSON"[data][stream][object][children][objects]
else {#"JSON"[data][stream][object][data]},
// remove closures from records, and remove DataChunk records
removeClosureField = List.Transform(objects, each Record.RemoveFields(_, "__closure", MissingField.Ignore)),
removeDatachunkRecords = List.RemoveItems(removeClosureField, List.FindText(removeClosureField, "Speckle.Core.Models.DataChunk")),
objectsTable = Table.FromRecords(removeDatachunkRecords)
in
objectsTable;
// Data Source Kind description
Speckle = [
Authentication = [
Key = [
KeyLabel="Personal Access Token",
Label = "Private stream"
],
Implicit = [
Label = "Public stream"
]
],
Label = Extension.LoadString("Speckle Connector")
];
// Data Source UI publishing description
Speckle.Publish = [
Beta = true,
Category = "Other",
ButtonText = { Extension.LoadString("ButtonTitle"), Extension.LoadString("ButtonHelp") },
LearnMoreUrl = "https://speckle.guide",
SourceImage = Speckle.Icons,
SourceTypeImage = Speckle.Icons
];
Speckle.Icons = [
Icon16 = { Extension.Contents("SpeckleLogo16.png"), Extension.Contents("SpeckleLogo20.png"), Extension.Contents("SpeckleLogo24.png"), Extension.Contents("SpeckleLogo32.png") },
Icon32 = { Extension.Contents("SpeckleLogo32.png"), Extension.Contents("SpeckleLogo40.png"), Extension.Contents("SpeckleLogo48.png"), Extension.Contents("SpeckleLogo64.png") }
];
// copy and pasted function from microsoft docs since it's not included yet in M standard lib
Table.ToNavigationTable = (
table as table,
keyColumns as list,
nameColumn as text,
dataColumn as text,
itemKindColumn as text,
itemNameColumn as text,
isLeafColumn as text
) as table =>
let
tableType = Value.Type(table),
newTableType = Type.AddTableKey(tableType, keyColumns, true) meta
[
NavigationTable.NameColumn = nameColumn,
NavigationTable.DataColumn = dataColumn,
NavigationTable.ItemKindColumn = itemKindColumn,
Preview.DelayColumn = itemNameColumn,
NavigationTable.IsLeafColumn = isLeafColumn
],
navigationTable = Value.ReplaceType(table, newTableType)
in
navigationTable;
-5
View File
@@ -1,5 +0,0 @@
// Use this file to write queries to test your data connector
let
result = Speckle.Contents("https://speckle.xyz/streams/6ead215ec5/branches/main")
in
result
+6
View File
@@ -0,0 +1,6 @@
{
"name": "speckle-powerbi",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}
+42
View File
@@ -0,0 +1,42 @@
import re
import sys
import os
def sanitize_version(tag):
"""Extracts the first three numeric segments from a tag string, because PowerBI is..."""
parts = re.findall(r"\d+", tag)
return ".".join(parts[:3]) if len(parts) >= 3 else tag
def patch_connector(tag):
"""Patches the connector version within the data connector file"""
sanitized_tag = sanitize_version(tag)
pq_file = os.path.join(os.path.dirname(__file__), "src", "powerbi-data-connector", "Speckle.pq")
with open(pq_file, "r") as file:
lines = file.readlines()
for (index, line) in enumerate(lines):
if '[Version = "3.0.0"]' in line:
lines[index] = f'[Version = "{sanitized_tag}"]\n'
print(f"Patched connector version number in {pq_file}")
break
with open(pq_file, "w") as file:
file.writelines(lines)
def main():
if len(sys.argv) < 2:
return
tag = sys.argv[1]
if not re.match(r"([0-9]+)\.([0-9]+)\.([0-9]+)", tag):
raise ValueError(f"Invalid tag provided: {tag}")
print(f"Patching version: {tag}")
patch_connector(tag)
if __name__ == "__main__":
main()
+63
View File
@@ -0,0 +1,63 @@
{
"folders": [
{
"name": "🏠 root",
"path": "."
},
{
"name": "➡️ powerbi-data-connector",
"path": "src/powerbi-data-connector"
},
{
"name": "👀 powerbi-visual",
"path": "src/powerbi-visual"
}
],
"settings": {
"powerquery.general.mode": "SDK",
"powerquery.sdk.defaultQueryFile": "${workspaceFolder}\\src\\powerbi-data-connector\\Speckle.query.pq",
"powerquery.sdk.defaultExtension": "${workspaceFolder}\\src\\powerbi-data-connector\\bin\\Speckle.mez",
"files.eol": "\n",
"files.watcherExclude": {
"**/.git/objects/**": true,
"**/node_modules/**": true,
".tmp": true
},
"editor.formatOnPaste": true,
"editor.multiCursorModifier": "ctrlCmd",
"editor.snippetSuggestions": "top",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"search.exclude": {
".tmp": true,
"typings": true,
"dist": true,
"wepbpack.statistics.dev.html": true,
"wepbpack.statistics.html": true
},
"json.schemas": [
{
"fileMatch": ["/pbiviz.json"],
"url": "./src/powerbi-visual/node_modules/powerbi-visuals-api/schema.pbiviz.json"
},
{
"fileMatch": ["/capabilities.json"],
"url": "./src/powerbi-visual/node_modules/powerbi-visuals-api/schema.capabilities.json"
},
{
"fileMatch": ["/dependencies.json"],
"url": "./src/powerbi-visual/node_modules/powerbi-visuals-api/schema.dependencies.json"
}
]
},
"extensions": {
"recommendations": [
"esbenp.prettier-vscode",
"ms-dotnettools.csharp",
"powerquery.vscode-powerquery-sdk"
]
}
}
+12
View File
@@ -0,0 +1,12 @@
{
"configurations": [
{
"type": "powerquery",
"request": "launch",
"name": "Test powerquery file",
"program": "${workspaceFolder}/${command:AskForPowerQueryFileName}",
"additionalArgs": ["--logMashupEngineTraces", "user"],
"preLaunchTask": "build"
}
]
}
+32
View File
@@ -0,0 +1,32 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "powerquery",
"operation": "msbuild",
"additionalArgs": [
"/restore",
"/consoleloggerparameters:NoSummary",
"/property:GenerateFullPaths=true"
],
"problemMatcher": ["$msCompile"],
"group": "build",
"label": "build"
},
{
"type": "shell",
"label": "tests",
"command": "${config:powerquery.sdk.tools.location}\\PQTest.exe",
"args": [
"run-test",
"--extension",
"${config:powerquery.sdk.defaultExtension}",
"--queryFile",
"${workspaceFolder}\\tests",
"--prettyPrint"
],
"problemMatcher": [],
"dependsOn": ["build"]
}
]
}
+397
View File
@@ -0,0 +1,397 @@
[Version = "3.0.0"]
section Speckle;
AuthAppId = "spklpwerbi";
AuthAppSecret = "spklpwerbi";
// PKCE helper functions for enhanced OAuth2 security
Base64UrlEncode = (binaryData as binary) =>
let
// Convert binary to base64
base64 = Binary.ToText(binaryData, BinaryEncoding.Base64),
// Convert to base64url by replacing characters and removing padding
base64url = Text.Replace(Text.Replace(Text.Replace(base64, "+", "-"), "/", "_"), "=", "")
in
base64url;
GeneratePKCEVerifier = () =>
let
// Generate cryptographically secure random string using allowed characters
// RFC 7636: [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
allowedChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~",
// Generate multiple GUIDs to create entropy
guid1 = Text.Replace(Text.Replace(Text.NewGuid(), "-", ""), "{", ""),
guid2 = Text.Replace(Text.Replace(Text.NewGuid(), "-", ""), "}", ""),
guid3 = Text.Replace(Text.Replace(Text.NewGuid(), "-", ""), "{", ""),
guid4 = Text.Replace(Text.Replace(Text.NewGuid(), "-", ""), "}", ""),
// Combine and convert to allowed characters
combined = guid1 & guid2 & guid3 & guid4,
// Map hex characters to allowed PKCE characters
mapped = Text.Replace(
Text.Replace(
Text.Replace(
Text.Replace(
Text.Replace(
Text.Replace(combined, "0", "A"),
"1", "B"),
"2", "C"),
"3", "D"),
"4", "E"),
"5", "F"),
// Continue mapping remaining hex chars to allowed chars
verifier = Text.Replace(
Text.Replace(
Text.Replace(
Text.Replace(
Text.Replace(
Text.Replace(
Text.Replace(
Text.Replace(
Text.Replace(
Text.Replace(mapped, "6", "G"),
"7", "H"),
"8", "I"),
"9", "J"),
"a", "K"),
"b", "L"),
"c", "M"),
"d", "N"),
"e", "O"),
"f", "P"),
// Ensure length is between 43-128 characters as per RFC 7636
finalVerifier = Text.Start(verifier, 43)
in
finalVerifier;
GeneratePKCEChallenge = (verifier as text) =>
let
// Create SHA256 hash of the verifier as required by RFC 7636
hash = Crypto.CreateHash(CryptoAlgorithm.SHA256, Text.ToBinary(verifier, TextEncoding.Utf8)),
// Convert to base64url encoding
challenge = Base64UrlEncode(hash)
in
challenge;
// function to load `pqm` files - this is essential and must be kept
shared Speckle.LoadFunction = (fileName as text) =>
let
binary = Extension.Contents(fileName),
asText = Text.FromBinary(binary)
in
try
Expression.Evaluate(asText, #shared)
catch (e) =>
error
[
Reason = "Speckle.LoadFunction Failure",
Message.Format = "Loading '#{0}' failed - '#{1}': '#{2}'",
Message.Parameters = {fileName, e[Reason], e[Message]},
Detail = [File = fileName, Error = e]
];
// here we register the functions to expose them globally
[DataSource.Kind = "Speckle"]
shared Speckle.Parser = Value.ReplaceType(
Speckle.LoadFunction("Parser.pqm"),
type function (url as Uri.Type) as record
);
[DataSource.Kind = "Speckle"]
shared Speckle.Api.Fetch = Value.ReplaceType(
Speckle.LoadFunction("Api.Fetch.pqm"),
type function (url as Uri.Type, optional query as text, optional variables as record) as record
);
[DataSource.Kind = "Speckle"]
shared Speckle.CheckPermissions = Value.ReplaceType(
Speckle.LoadFunction("CheckPermissions.pqm"),
type function (url as Uri.Type) as record
);
[DataSource.Kind = "Speckle"]
shared Speckle.GetUser = Value.ReplaceType(
Speckle.LoadFunction("GetUser.pqm"),
type function (url as Uri.Type) as record
);
[DataSource.Kind = "Speckle"]
shared Speckle.GetModel = Value.ReplaceType(
Speckle.LoadFunction("GetModel.pqm"),
type function (url as Uri.Type) as record
);
[DataSource.Kind = "Speckle"]
shared Speckle.GetStructuredData = Value.ReplaceType(
Speckle.LoadFunction("GetStructuredData.pqm"),
type function (url as Uri.Type) as table
);
shared Speckle.GetVersion = Value.ReplaceType(
Speckle.LoadFunction("GetVersion.pqm"),
type function () as text
);
[DataSource.Kind = "Speckle"]
shared Speckle.SendToServer = Value.ReplaceType(
Speckle.LoadFunction("SendToServer.pqm"),
type function (url as Uri.Type) as table
);
[DataSource.Kind = "Speckle"]
shared Speckle.GetWorkspace = Value.ReplaceType(
Speckle.LoadFunction("GetWorkspace.pqm"),
type function (url as Uri.Type) as record
);
shared Speckle.Objects.Properties = Value.ReplaceType(
Speckle.LoadFunction("Objects.Properties.pqm"),
type function (inputRecord as any, optional filterKeys as list, optional parentPath as text, optional existingFields as list) as record
);
shared Speckle.Utils.ExpandRecord = Value.ReplaceType(
Speckle.LoadFunction("Utils.ExpandRecord.pqm"),
type function (
table as table,
columnName as text,
optional FieldNames as list,
optional UseCombinedNames as logical
) as table
);
shared Speckle.Objects.Collections = Value.ReplaceType(
Speckle.LoadFunction("Objects.Collections.pqm"),
type function (inputData as table) as table
);
shared Speckle.Objects.CompositeStructure = Value.ReplaceType(
Speckle.LoadFunction("Objects.CompositeStructure.pqm"),
type function (objectRecord as record, optional outputAsList as nullable logical) as any
);
shared Speckle.Objects.MaterialQuantities = Value.ReplaceType(
Speckle.LoadFunction("Objects.MaterialQuantities.pqm"),
type function (objectRecord as record, optional outputAsList as logical) as any
);
shared Speckle.Models.Federate = Value.ReplaceType(
Speckle.LoadFunction("Models.Federate.pqm"),
type function (tables as list, optional excludeData as logical) as table
);
shared Speckle.Models.MaterialQuantities = Value.ReplaceType(
Speckle.LoadFunction("Models.MaterialQuantities.pqm"),
type function (inputTable as table, optional addPrefix as logical) as table
);
[DataSource.Kind = "Speckle"]
shared Speckle.Project.Issues = Value.ReplaceType(
Speckle.LoadFunction("Project.Issues.pqm"),
type function (url as Uri.Type, optional getReplies as logical) as table
);
[DataSource.Kind = "Speckle", Publish="GetByUrl.Publish"]
shared Speckle.GetByUrl = Value.ReplaceType(
Speckle.LoadFunction("GetByUrl.pqm"),
type function (
url as (
Uri.Type meta [
Documentation.FieldCaption = "Speckle Model URL",
Documentation.FieldDescription = "The URL of a model in a Speckle server project. You can copy it directly from your browser.",
Documentation.SampleValues = {"https://app.speckle.systems/projects/7902de1f57/models/7f890a65df"}
]
),
optional ExpandProperties as (
type logical meta [
Documentation.FieldCaption = "Expand Properties (may slow query)",
Documentation.FieldDescription = "Expand the properties column into individual columns for easier analysis. When checked, each property from the 'properties' record column will have its own column. This can slow down the query if you have a lot of properties.",
Documentation.AllowedValues = {true, false}
]
)
) as table meta [
Documentation.Name = "Speckle - Get Data by URL",
Documentation.DisplayName = "Speckle - Get Data by URL",
Documentation.LongDescription = "Returns structured data from a Speckle model URL.#(lf)
Supports the following URL formats:#(lf)
- Model URL: Gets the latest version of the specified model#(lf)
(e.g., 'https://app.speckle.systems/projects/PROJECT_ID/models/MODEL_ID')#(lf)
- Version URL: Gets a specific version from the project#(lf)
(e.g., 'https://app.speckle.systems/projects/PROJECT_ID/models/MODEL_ID@VERSION_ID')"
]
);
shared Speckle.Revit.Parameters.ToNameValueRecord = (r as record, optional exclude as list) as record =>
let
defaultExclude = {"id", "speckle_type", "applicationId", "totalChildrenCount"},
fullExclusion = if exclude = null then defaultExclude else List.Union(defaultExclude, exclude),
clean = Record.RemoveFields(r, fullExclusion, MissingField.Ignore),
recTable = Record.ToTable(clean),
cleanTable = Table.RemoveColumns(recTable, "Name"),
expanded = Table.ExpandRecordColumn(
cleanTable, "Value", {"name", "value", "applicationInternalName"}, {"Name", "Value", "UID"}
),
joined = Table.AddColumn(expanded, "Combo", each [Name] & " [" & [UID] & "]"),
renamed = Table.RenameColumns(joined, {{"Name", "x"}, {"Combo", "Name"}}),
result = Record.FromTable(renamed)
in
result;
// here we register the GetByUrl function to power bi ui
GetByUrl.Publish = [
Cateogry = "Other",
ButtonText = {"Connect to Speckle"},
LearnMoreUrl = "https://speckle.guide/user/powerbi/introduction.html",
SourceImage = GetByUrl.Icons,
SourceTypeImage = GetByUrl.Icons
];
GetByUrl.Icons = [
Icon16 = { Extension.Contents("SpeckleLogo16.png"), Extension.Contents("SpeckleLogo20.png"), Extension.Contents("SpeckleLogo24.png"), Extension.Contents("SpeckleLogo32.png") },
Icon32 = { Extension.Contents("SpeckleLogo32.png"), Extension.Contents("SpeckleLogo40.png"), Extension.Contents("SpeckleLogo48.png"), Extension.Contents("SpeckleLogo64.png") }
];
// The data source definition
Speckle = [
// This is used when running the connector on an on-premises data gateway
TestConnection = (path) => {"Speckle.GetUser", path},
// Authentication strategy - OAuth only
Authentication = [
OAuth = [
Label = "Speckle Account",
StartLogin = (clientApplication, dataSourcePath, state, display) =>
let
server = Text.Combine(
{Uri.Parts(dataSourcePath)[Scheme], "://", Uri.Parts(dataSourcePath)[Host]}
),
// Generate PKCE parameters for enhanced security
codeVerifier = GeneratePKCEVerifier(),
codeChallenge = GeneratePKCEChallenge(codeVerifier),
// Detect if server supports /oauth/token
oauthCheck = try Web.Contents(
Text.Combine({server, "oauth", "token"}, "/"),
[ManualStatusHandling = {400, 401, 403, 404, 405, 500}]
) otherwise null,
useNewOAuth = oauthCheck <> null and Value.Metadata(oauthCheck)[Response.Status] = 200,
// Build auth URL based on server capabilities
authUrl = if useNewOAuth then
Text.Combine({server, "authn", "verify", AuthAppId, codeChallenge}, "/") &
"?code_challenge_method=S256" &
"&pbiNew=true"
else
// Legacy
Text.Combine({server, "authn", "verify", AuthAppId, codeVerifier}, "/")
in
[
LoginUri = authUrl,
CallbackUri = "https://oauth.powerbi.com/views/oauthredirect.html",
WindowHeight = 800,
WindowWidth = 600,
Context = [code_verifier = codeVerifier, use_new_oauth = useNewOAuth]
],
FinishLogin = (clientApplication, dataSourcePath, context, callbackUri, state) =>
let
server = Text.Combine(
{Uri.Parts(dataSourcePath)[Scheme], "://", Uri.Parts(dataSourcePath)[Host]}
),
Parts = Uri.Parts(callbackUri)[Query],
codeVerifier = if context <> null then context[code_verifier] else null,
useNewOAuth = if context <> null and Record.HasFields(context, "use_new_oauth") then context[use_new_oauth] else false,
// Single token exchange call based on server capability
Source = if useNewOAuth then
Web.Contents(
Text.Combine({server, "oauth", "token"}, "/"),
[
Headers = [#"Content-Type" = "application/json"],
Content = Json.FromValue([
appId = AuthAppId,
accessCode = Parts[access_code],
codeVerifier = codeVerifier
])
]
)
else
// Legacy
Web.Contents(
Text.Combine({server, "auth", "token"}, "/"),
[
Headers = [#"Content-Type" = "application/json"],
Content = Json.FromValue([
appId = AuthAppId,
appSecret = AuthAppSecret,
accessCode = Parts[access_code],
challenge = codeVerifier
])
]
),
json = Json.Document(Source)
in
[
access_token = json[token],
scope = null,
token_type = "bearer",
refresh_token = json[refreshToken]
],
Refresh = (dataSourcePath, refreshToken) =>
let
server = Text.Combine(
{Uri.Parts(dataSourcePath)[Scheme], "://", Uri.Parts(dataSourcePath)[Host]}
),
// Enhanced refresh with error handling for gateway compatibility
Source = try Web.Contents(
Text.Combine({server, "auth", "token"}, "/"),
[
Headers = [
#"Content-Type" = "application/json"
],
Content = Json.FromValue(
[
refreshToken = refreshToken,
appId = AuthAppId,
appSecret = AuthAppSecret
]
),
ManualStatusHandling = {400, 401, 403, 500, 502, 503, 504}
]
) otherwise null,
// Check if request was successful
IsSuccess = Source <> null,
// If successful, parse the response
json = if IsSuccess then
try Json.Document(Source) otherwise null
else
null,
// Validate the response contains expected fields
IsValidResponse = json <> null and Record.HasFields(json, {"token"}),
// Return result with enhanced error handling
result = if IsValidResponse then
[
access_token = json[token],
scope = null,
token_type = "bearer",
refresh_token = json[refreshToken]
]
else
error [
Reason = "TokenRefreshFailed",
Message = "Failed to refresh OAuth token - please re-authenticate",
Detail = [
Server = server,
RefreshToken = if refreshToken = null then "null" else "present"
]
]
in
result
]
],
Label = "Speckle"
];
+46
View File
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
DefaultTargets="BuildMez">
<PropertyGroup>
<Version Condition="'$(Version)' == ''">2.0.0-wip</Version>
<OutputPath Condition="'$(OutputPath)' == ''">$(MSBuildProjectDirectory)\bin\</OutputPath>
<IntermediateOutputPath Condition="'$(IntermediateOutputPath)' == ''">
$(MSBuildProjectDirectory)\obj\</IntermediateOutputPath>
<MezIntermediatePath>$(IntermediateOutputPath)MEZ\</MezIntermediatePath>
<MezOutputPath>$(OutputPath)$(MsBuildProjectName).mez</MezOutputPath>
<IsContinuousIntegrationBuild>false</IsContinuousIntegrationBuild>
</PropertyGroup>
<ItemGroup>
<MezContent Include="Speckle.pq" />
<MezContent Include="speckle\**\*.pqm" />
<MezContent Include="assets\SpeckleLogo16.png" />
<MezContent Include="assets\SpeckleLogo20.png" />
<MezContent Include="assets\SpeckleLogo24.png" />
<MezContent Include="assets\SpeckleLogo32.png" />
<MezContent Include="assets\SpeckleLogo40.png" />
<MezContent Include="assets\SpeckleLogo48.png" />
<MezContent Include="assets\SpeckleLogo64.png" />
<MezContent Include="assets\SpeckleLogo80.png" />
<MezContent Include="assets\resources.resx" />
</ItemGroup>
<Target Name="BuildMez" AfterTargets="Build" Inputs="@(MezContent)" Outputs="$(MezOutputPath)">
<RemoveDir Directories="$(MezIntermediatePath)" />
<Copy SourceFiles="@(MezContent)" DestinationFolder="$(MezIntermediatePath)" />
<MakeDir Directories="$(OutputPath)" Condition="!Exists('$(OutputPath)')" />
<ZipDirectory SourceDirectory="$(MezIntermediatePath)" DestinationFile="$(MezOutputPath)"
Overwrite="true" />
</Target>
<Target Name="CopyToConnectors" AfterTargets="BuildMez"
Condition="$(IsContinuousIntegrationBuild) == 'false'">
<Message
Text="Copying .mez file to: $(UserProfile)\Documents\Power BI Desktop\Custom Connectors"
Importance="High" />
<MakeDir Directories="$(UserProfile)\Documents\Power BI Desktop\Custom Connectors\" />
<Copy SourceFiles="$(MezOutputPath)"
DestinationFolder="$(UserProfile)\Documents\Power BI Desktop\Custom Connectors\" />
</Target>
<Target Name="Clean">
<RemoveDir Directories="$(MezIntermediatePath)" />
<Delete Files="$(MezOutputPath)" />
</Target>
</Project>
@@ -0,0 +1,9 @@
// use this file to write queries to test your data connector
// NOTE! for tests, be make sure you put here a model that in private project to make sure all good.
let
result = Speckle.GetByUrl(
"https://app.speckle.systems/projects/b61ab234b0/models/a8166255b5"
)
in
result

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

@@ -22,8 +22,10 @@
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing"
mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework
object]</value>
<comment>This is a comment</comment>
</data>
@@ -59,7 +61,8 @@
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
@@ -112,18 +115,65 @@
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ButtonHelp" xml:space="preserve">
<value>Connect to Speckle</value>
</data>
<data name="ButtonTitle" xml:space="preserve">
<value>Speckle</value>
</data>
<data name="DataSourceLabel" xml:space="preserve">
<value>Speckle</value>
</data>
<data name="GetByUrl.Help" xml:space="preserve">
<value>Connect to Speckle by Model URL</value>
</data>
<data name="GetByUrl.Label" xml:space="preserve">
<value>Speckle</value>
</data>
<data name="GetByUrl.Title" xml:space="preserve">
<value>Speckle - Get Model by URL</value>
</data>
<data name="GetObjFromBranch.Help" xml:space="preserve">
<value>Connect to Speckle by server URL, stream ID and branch name</value>
</data>
<data name="GetObjFromBranch.Label" xml:space="preserve">
<value>Get the latest commit from a stream's branch</value>
</data>
<data name="GetObjFromBranch.Title" xml:space="preserve">
<value>Speckle - Get Stream branch</value>
</data>
<data name="GetObjFromCommit.Help" xml:space="preserve">
<value>Connect to Speckle by server URL, stream ID and commit ID</value>
</data>
<data name="GetObjFromCommit.Label" xml:space="preserve">
<value>A label</value>
</data>
<data name="GetObjFromCommit.Title" xml:space="preserve">
<value>Speckle - Get Stream commit</value>
</data>
<data name="GetStream.Help" xml:space="preserve">
<value>Connect to Speckle by server URL and stream ID</value>
</data>
<data name="GetStream.Label" xml:space="preserve">
<value>Speckle</value>
</data>
<data name="GetStream.Title" xml:space="preserve">
<value>Speckle - Get Model by URL [Structured]</value>
</data>
<data name="GetObjectAsNavTable.Title" xml:space="preserve">
<value>Speckle - Get Object as NavTable</value>
</data>
<data name="GetObjectAsNavTable.Label" xml:space="preserve">
<value>Speckle</value>
</data>
<data name="GetObjectAsNavTable.Help" xml:space="preserve">
<value>Returns a navigation table for a given object</value>
</data>
<data name="Traverse.Title" xml:space="preserve">
<value>Traverse an object and populate refs</value>
</data>
<data name="Traverse.Label" xml:space="preserve">
<value>Traverse</value>
</data>
<data name="Traverse.Help" xml:space="preserve">
<value>Traverse help</value>
</data>
</root>
@@ -0,0 +1,300 @@
(url as text, optional ExpandProperties as logical) as table =>
let
// set default value for ExpandProperties
shouldExpandProperties = if ExpandProperties = null then false else ExpandProperties,
// import required functions
GetStructuredData = Extension.LoadFunction("GetStructuredData.pqm"),
SendToServer = Extension.LoadFunction("SendToServer.pqm"),
GetModel = Extension.LoadFunction("GetModel.pqm"),
Parser = Extension.LoadFunction("Parser.pqm"),
CheckPermissions = Extension.LoadFunction("CheckPermissions.pqm"),
ExchangeToken = Extension.LoadFunction("ExchangeToken.pqm"),
EncodeUserInfo = Extension.LoadFunction("EncodeUserInfo.pqm"),
GetUser = Extension.LoadFunction("GetUser.pqm"),
GetVersion = Extension.LoadFunction("GetVersion.pqm"),
GetWorkspace = Extension.LoadFunction("GetWorkspace.pqm"),
MarkReceived = Extension.LoadFunction("MarkReceived.pqm"),
// the logic for importing functions from other files
Extension.LoadFunction = (fileName as text) =>
let
binary = Extension.Contents(fileName),
asText = Text.FromBinary(binary)
in
try
Expression.Evaluate(asText, #shared)
catch (e) =>
error
[
Reason = "Extension.LoadFunction Failure",
Message.Format = "Loading '#{0}' failed - '#{1}': '#{2}'",
Message.Parameters = {fileName, e[Reason], e[Message]},
Detail = [File = fileName, Error = e]
],
// parse the URL to determine if it's a federated model
parsedUrl = Parser(url),
// check if user has permission to load the model
permissionCheck = CheckPermissions(url),
// assert that permission check returned a valid result
permissionAssert = if not Record.HasFields(permissionCheck, {"authorized", "code", "message"}) then
error "Invalid permission check result"
else
null,
// if not authorized, throw an error with the message from the server
authCheck = if not permissionCheck[authorized] then
error Text.Format(
"Permission denied: #{0} (Error code: #{1})",
{permissionCheck[message], permissionCheck[code]}
)
else
null,
// get user info, connector version, and workspace info for encoding
userInfo = GetUser(url),
powerfulToken = userInfo[Token],
userEmail = userInfo[UserEmail],
connectorVersion = GetVersion(),
workspaceInfo = GetWorkspace(url),
// exchange powerful token for weak token with limited scopes
tokenExchangeResult = ExchangeToken(
powerfulToken,
{"profile:read", "streams:read", "users:read"},
parsedUrl[projectId],
parsedUrl[baseUrl],
parsedUrl[resourceIdString]
),
// throw error if token exchange failed - do NOT use powerful token as fallback
tokenToUse = if tokenExchangeResult[Success] then
tokenExchangeResult[Token]
else
error [
Reason = "TokenExchangeFailed",
Message.Format = "Failed to exchange token for limited scope token: #{0}",
Message.Parameters = {tokenExchangeResult[ErrorMessage]},
Detail = [
ErrorMessage = tokenExchangeResult[ErrorMessage],
ProjectId = parsedUrl[projectId],
ServerUrl = parsedUrl[baseUrl]
]
],
// only proceed if user has permisson to load
results = if permissionCheck[authorized] then
if parsedUrl[isFederated] = true then
// process each model in the federation
let
modelsData = List.Transform(
parsedUrl[federatedModels],
each ProcessSingleModel(
parsedUrl[baseUrl],
parsedUrl[projectId],
[modelId],
[versionId]
)
),
// extract all data tables
allTables = List.Transform(modelsData, each [Data]),
// extract all root object IDs
allRootIds = List.Transform(modelsData, each [RootObjectId]),
// extract all encoded userInfo strings
allEncodedUserInfos = List.Transform(modelsData, each [EncodedUserInfo]),
// combine all root object IDs into a comma-separated string
combinedRootIds = Text.Combine(allRootIds, ","),
// combine all encoded userInfo strings with delimiter |||
// (delimiter chosen to avoid conflicts with base64 characters)
combinedEncodedUserInfos = Text.Combine(allEncodedUserInfos, "|||"),
// combine all data tables
combinedData = Table.Combine(allTables),
// replace both columns with combined values
transformedData = Table.TransformColumns(
combinedData,
{
{"Version Object ID", each combinedRootIds},
{"Model Info", each combinedEncodedUserInfos}
}
),
// expand properties column if requested and if it exists
finalData = if shouldExpandProperties and Table.HasColumns(transformedData, {"properties"}) then
try
Speckle.Utils.ExpandRecord(transformedData, "properties")
otherwise
transformedData // fallback to original data if expansion fails
else
transformedData
in
finalData
else
// use existing functionality for single models
let
// get model info
modelInfo = GetModel(url),
modelName = modelInfo[modelName],
rootObjectId = modelInfo[rootObjectId],
sourceApplication = modelInfo[sourceApplication],
versionId = modelInfo[versionId],
// mark version as received
markReceivedResult = MarkReceived(powerfulToken, versionId, parsedUrl[projectId], parsedUrl[baseUrl]),
// get structured data
structuredData = GetStructuredData(url),
// build userInfoData record for this model
userInfoData = [
rootObjectId = rootObjectId,
server = parsedUrl[baseUrl],
email = userEmail,
projectId = parsedUrl[projectId],
token = tokenToUse,
workspaceId = workspaceInfo[workspaceId],
workspaceName = workspaceInfo[workspaceName],
workspaceLogo = workspaceInfo[workspaceLogo],
version = connectorVersion,
sourceApplication = sourceApplication,
canHideBranding = workspaceInfo[canHideBranding],
versionId = versionId,
url = url
],
// try to send to desktop service for backward compatibility (non-blocking)
// must be called BEFORE encoding to ensure it executes
desktopServiceSent = TrySendToDesktopService(userInfoData),
// encode userInfoData as base64 JSON string
encodedUserInfo = EncodeUserInfo(userInfoData),
// replace both columns with appropriate values
transformedData = Table.TransformColumns(
structuredData,
{
{"Version Object ID", each rootObjectId},
{"Model Info", each if (desktopServiceSent or not desktopServiceSent) and (markReceivedResult or not markReceivedResult) then encodedUserInfo else encodedUserInfo}
}
),
// expand properties column if requested and if it exists
result = if shouldExpandProperties and Table.HasColumns(transformedData, {"properties"}) then
try
Speckle.Utils.ExpandRecord(transformedData, "properties")
otherwise
transformedData // fallback to original data if expansion fails
else
transformedData
in
result
else
error Text.Format(
"Permission denied: #{0} (Error code: #{1})",
{permissionCheck[message], permissionCheck[code]}
),
// helper function to try sending user info to desktop service for backward compatibility
// returns true if successful, false otherwise (non-blocking)
TrySendToDesktopService = (userInfoData as record) =>
try
let
userInfoJson = Json.FromValue(userInfoData),
response = Web.Contents(
"http://127.0.0.1:29364/store-user-info",
[
Headers = [
#"Content-Type" = "application/json",
#"Method" = "POST"
],
Content = userInfoJson,
ManualStatusHandling = {400, 401, 403, 404, 500, 502, 503, 504},
Timeout = #duration(0, 0, 0, 2)
]
),
statusCode = Value.Metadata(response)[Response.Status]
in
statusCode >= 200 and statusCode < 300
otherwise
false,
// function to process a single model and get its data
ProcessSingleModel = (baseUrl, projectId, modelId, versionId) =>
let
// construct a standard URL for the model
singleModelUrl = Text.Combine({
baseUrl,
"/projects/",
projectId,
"/models/",
modelId,
if versionId <> null then Text.Combine({"@", versionId}) else ""
}),
// get model info
modelInfo = GetModel(singleModelUrl),
rootObjectId = modelInfo[rootObjectId],
modelName = modelInfo[modelName],
sourceApplication = modelInfo[sourceApplication],
federatedVersionId = if versionId <> null then versionId else modelInfo[versionId],
// mark version as received (non-blocking, best-effort)
markReceivedResult = MarkReceived(powerfulToken, federatedVersionId, projectId, baseUrl),
// get structured data
structuredData = GetStructuredData(singleModelUrl),
// build userInfoData record for this model
userInfoData = [
rootObjectId = rootObjectId,
server = baseUrl,
email = userEmail,
projectId = projectId,
token = tokenToUse,
workspaceId = workspaceInfo[workspaceId],
workspaceName = workspaceInfo[workspaceName],
workspaceLogo = workspaceInfo[workspaceLogo],
version = connectorVersion,
sourceApplication = sourceApplication,
canHideBranding = workspaceInfo[canHideBranding],
versionId = if versionId <> null then versionId else modelInfo[versionId],
url = singleModelUrl
],
// try to send to desktop service for backward compatibility (non-blocking)
// must be called BEFORE encoding to ensure it executes
desktopServiceSent = TrySendToDesktopService(userInfoData),
// encode userInfoData as base64 JSON string
encodedUserInfo = EncodeUserInfo(userInfoData),
// add the model name as context - with version id if exists
// reference desktopServiceSent and markReceivedResult to force evaluation
result = Table.AddColumn(
structuredData,
"Source Model",
each if versionId <> null then
if (markReceivedResult or not markReceivedResult) then Text.Combine({modelName, "-", versionId}) else Text.Combine({modelName, "-", versionId})
else if (desktopServiceSent or not desktopServiceSent) and (markReceivedResult or not markReceivedResult) then
modelName
else
modelName,
type text
)
in
[
Data = result,
RootObjectId = rootObjectId,
EncodedUserInfo = encodedUserInfo
]
in
results
@@ -0,0 +1,79 @@
(server as text, optional query as text, optional variables as record) as record =>
let
// Enhanced credential retrieval with OAuth2 support
apiKey = try Extension.CurrentCredential()[access_token] otherwise null,
defaultQuery = "query {
activeUser {
email
name
}
serverInfo {
name
company
version
}
}",
// Enhanced API call with comprehensive error handling
Source = try Web.Contents(
Text.Combine({server, "graphql"}, "/"),
[
Headers = [
#"Method" = "POST",
#"Content-Type" = "application/json",
#"Authorization" = if apiKey = null then "" else Text.Format("Bearer #{0}", {apiKey})
],
ManualStatusHandling = {400, 401, 403, 404, 500, 502, 503, 504},
Content = Json.FromValue([query = Text.From(query ?? defaultQuery), variables = variables])
]
) otherwise null,
// Check if the HTTP request was successful
IsHttpSuccess = Source <> null,
// Get HTTP status code for detailed error handling
StatusCode = if IsHttpSuccess then Value.Metadata(Source)[Response.Status] else null,
// Parse JSON response if HTTP request was successful
#"JSON" = if IsHttpSuccess then
try Json.Document(Source) otherwise null
else
null,
// Comprehensive error handling
// Comprehensive error handling
result = if not IsHttpSuccess then
error [
Reason = "HttpRequestFailed",
Message = "Failed to connect to Speckle server",
Detail = [Server = server, StatusCode = StatusCode]
]
else if StatusCode = 401 then
error [
Reason = "AuthenticationFailed",
Message = "Invalid or expired authentication token",
Detail = [Server = server, HasToken = apiKey <> null]
]
else if StatusCode = 403 then
error [
Reason = "AuthorizationFailed",
Message = "Insufficient permissions for this operation",
Detail = [Server = server]
]
else if #"JSON" = null then
error [
Reason = "InvalidJsonResponse",
Message = "Server returned invalid JSON response",
Detail = [Server = server, StatusCode = StatusCode]
]
else if Record.HasFields(#"JSON", {"errors"}) then
error [
Reason = "GraphQLError",
Message = #"JSON"[errors]{0}[message],
Detail = [Server = server, Errors = #"JSON"[errors]]
]
else
#"JSON"[data]
in
result
@@ -0,0 +1,66 @@
(url as text) as record =>
let
ApiFetch = Extension.LoadFunction("Api.Fetch.pqm"),
Parser = Extension.LoadFunction("Parser.pqm"),
Extension.LoadFunction = (fileName as text) =>
let
binary = Extension.Contents(fileName),
asText = Text.FromBinary(binary)
in
try
Expression.Evaluate(asText, #shared)
catch (e) =>
error
[
Reason = "Extension.LoadFunction Failure",
Message.Format = "Loading '#{0}' failed - '#{1}': '#{2}'",
Message.Parameters = {fileName, e[Reason], e[Message]},
Detail = [File = fileName, Error = e]
],
// parse the URL to extract project id
parsedUrl = Parser(url),
server = parsedUrl[baseUrl],
projectId = parsedUrl[projectId],
// GraphQL query to check permissions
query = "query Project($projectId: String!) {
data:project(id: $projectId) {
data:permissions {
canLoad {
authorized
code
message
}
}
}
}",
// variables variable for api fetch (i know)
variables = [
projectId = projectId
],
result = ApiFetch(server, query, variables),
// check that the result contains the expected structure
// this will throw an error if the structure is not as expected
structureCheck = if not (Record.HasFields(result, {"data"}) and
Record.HasFields(result[data], {"data"}) and
Record.HasFields(result[data][data], {"canLoad"}) and
Record.HasFields(result[data][data][canLoad], {"authorized", "code", "message"})) then
error "Invalid response structure from permission check"
else
null,
canLoad = result[data][data][canLoad],
// return the permission result
permissionResult = [
authorized = canLoad[authorized],
code = canLoad[code],
message = canLoad[message]
]
in
permissionResult
@@ -0,0 +1,18 @@
// Function to encode userInfoData as base64-encoded JSON string
(userInfoData as record) as text =>
let
JsonText = Text.FromBinary(
Json.FromValue(userInfoData),
TextEncoding.Utf8
),
// Convert JSON text to binary
JsonBinary = Text.ToBinary(JsonText, TextEncoding.Utf8),
// Encode binary as base64
Base64Encoded = Binary.ToText(JsonBinary, BinaryEncoding.Base64),
// Return base64-encoded string
Result = Base64Encoded
in
Result
@@ -0,0 +1,135 @@
// Function to exchange powerful token for weak limited token
(powerfulToken as text, scopes as list, projectId as text, serverUrl as text, optional resourceIdString as text) as record =>
let
// Helper function to load .pqm modules dynamically
Extension.LoadFunction = (fileName as text) =>
let
binary = Extension.Contents(fileName),
asText = Text.FromBinary(binary)
in
try
Expression.Evaluate(asText, #shared)
catch (e) =>
error
[
Reason = "Extension.LoadFunction Failure",
Message.Format = "Loading '#{0}' failed - '#{1}': '#{2}'",
Message.Parameters = {fileName, e[Reason], e[Message]},
Detail = [File = fileName, Error = e]
],
// Validate inputs
ValidationError = if Text.Length(powerfulToken) = 0 then
"PowerfulToken is required"
else if List.Count(scopes) = 0 then
"Scopes are required"
else if Text.Length(projectId) = 0 then
"ProjectId is required"
else if Text.Length(serverUrl) = 0 then
"ServerUrl is required"
else
null,
// Ensure serverUrl ends with /
NormalizedServerUrl = if Text.End(serverUrl, 1) = "/" then
serverUrl
else
serverUrl & "/",
// New Share Token API mutation with variables
NewGraphQLQuery = "mutation CreateEmbedShareToken($input: CreateEmbedShareTokenInput!) {
sharingMutations {
createEmbedShareToken(input: $input) {
token
}
}
}",
NewGraphQLVariables = [
input = [
projectId = projectId,
resourceIdString = resourceIdString
]
],
// Legacy apiTokenCreate mutation with variables
TokenLifespanMs = 10 * 365 * 24 * 3600 * 1000,
TokenName = "Limited Power BI Visual Token - " & DateTime.ToText(DateTime.LocalNow(), "yyyy-MM-dd HH:mm"),
LegacyGraphQLQuery = "mutation CreateApiToken($token: ApiTokenCreateInput!) {
apiTokenCreate(token: $token)
}",
LegacyGraphQLVariables = [
token = [
name = TokenName,
scopes = scopes,
lifespan = TokenLifespanMs,
limitResources = {[
type = "project",
id = projectId
]}
]
],
// Helper: execute a GraphQL query with variables and extract token
ExecuteGraphQL = (query as text, variables as record, extractToken as function) =>
let
Response = Web.Contents(
NormalizedServerUrl & "graphql",
[
Headers = [
#"Method" = "POST",
#"Content-Type" = "application/json",
#"Authorization" = "Bearer " & powerfulToken
],
Content = Json.FromValue([
query = query,
variables = variables
]),
ManualStatusHandling = {400, 401, 403, 404, 500, 502, 503, 504},
Timeout = #duration(0, 0, 0, 10)
]
),
StatusCode = Value.Metadata(Response)[Response.Status],
JsonResponse = if StatusCode >= 200 and StatusCode < 300 then
Json.Document(Response)
else
null,
HasErrors = JsonResponse <> null and Record.HasFields(JsonResponse, {"errors"}),
Token = if JsonResponse <> null and not HasErrors then
try extractToken(JsonResponse) otherwise null
else
null,
ErrorMsg = if HasErrors then
try JsonResponse[errors]{0}[message] otherwise "GraphQL mutation failed"
else if JsonResponse = null then
"Request failed with status " & Number.ToText(StatusCode)
else
null
in
[Success = Token <> null, Token = Token, ErrorMessage = ErrorMsg],
// Try new API first, fall back to legacy
Result = if ValidationError <> null then
[Success = false, Token = null, ErrorMessage = ValidationError]
else
let
newResult = if resourceIdString <> null then
try ExecuteGraphQL(
NewGraphQLQuery,
NewGraphQLVariables,
each [data][sharingMutations][createEmbedShareToken][token]
) otherwise [Success = false, Token = null, ErrorMessage = "New API request failed"]
else
[Success = false, Token = null, ErrorMessage = null],
finalResult = if newResult[Success] then
newResult
else
try ExecuteGraphQL(
LegacyGraphQLQuery,
LegacyGraphQLVariables,
each [data][apiTokenCreate]
) otherwise [Success = false, Token = null, ErrorMessage = "Token exchange request failed"]
in
finalResult
in
Result
@@ -0,0 +1,122 @@
// function for getting model information through graphql query
(url as text) as record =>
let
// import the parser function
Parser = Extension.LoadFunction("Parser.pqm"),
// the logic for importing functions from other files
Extension.LoadFunction = (fileName as text) =>
let
binary = Extension.Contents(fileName),
asText = Text.FromBinary(binary)
in
try
Expression.Evaluate(asText, #shared)
catch (e) =>
error
[
Reason = "Extension.LoadFunction Failure",
Message.Format = "Loading '#{0}' failed - '#{1}': '#{2}'",
Message.Parameters = {fileName, e[Reason], e[Message]},
Detail = [File = fileName, Error = e]
],
// parse the url and get necessary fields
parsedUrl = Parser(url),
server = parsedUrl[baseUrl],
projectId = parsedUrl[projectId],
modelId = parsedUrl[modelId],
versionId = parsedUrl[versionId],
// get API key if available
apiKey = try Extension.CurrentCredential()[access_token] otherwise null,
// graphql query to get model info including root object id
// includes specific version if provided
query = if versionId = null then
"query ($projectId: String!, $modelId: String!) {
project(id: $projectId) {
model(id: $modelId) {
id
name
versions {
items {
id
referencedObject
sourceApplication
}
}
}
}
}"
else
"query ($projectId: String!, $modelId: String!, $versionId: String!) {
project(id: $projectId) {
model(id: $modelId) {
id
name
version(id: $versionId) {
id
referencedObject
sourceApplication
}
}
}
}",
// include versionId in variables if it exists
variables = if versionId = null then
[
projectId = projectId,
modelId = modelId
]
else
[
projectId = projectId,
modelId = modelId,
versionId = versionId
],
// make the api request
Source = Web.Contents(
Text.Combine({server, "graphql"}, "/"),
[
Headers = [
#"Method" = "POST",
#"Content-Type" = "application/json",
#"Authorization" = if apiKey = null then "" else Text.Format("Bearer #{0}", {apiKey})
],
ManualStatusHandling = {400, 401, 403},
Content = Json.FromValue([
query = query,
variables = variables
])
]
),
// parse the response
JsonResponse = Json.Document(Source),
// extract needed information, now handling both version-specific and latest version cases
result = if Record.HasFields(JsonResponse, {"errors"}) then
error JsonResponse[errors]{0}[message]
else if JsonResponse[data]?[project]?[model] = null then
error "Model not found or access denied. Please check your authentication and model ID."
else if versionId = null then
[
modelId = JsonResponse[data][project][model][id],
modelName = JsonResponse[data][project][model][name],
versionId = JsonResponse[data][project][model][versions][items]{0}[id],
rootObjectId = JsonResponse[data][project][model][versions][items]{0}[referencedObject],
sourceApplication = JsonResponse[data][project][model][versions][items]{0}[sourceApplication]
]
else
[
modelId = JsonResponse[data][project][model][id],
modelName = JsonResponse[data][project][model][name],
versionId = JsonResponse[data][project][model][version][id],
rootObjectId = JsonResponse[data][project][model][version][referencedObject],
sourceApplication = JsonResponse[data][project][model][version][sourceApplication]
]
in
result
@@ -0,0 +1,118 @@
// function for getting structured object data
(url as text) as table =>
let
// import the required functions
GetModel = Extension.LoadFunction("GetModel.pqm"),
SendToServer = Extension.LoadFunction("SendToServer.pqm"),
// the logic for importing functions from other files
Extension.LoadFunction = (fileName as text) =>
let
binary = Extension.Contents(fileName),
asText = Text.FromBinary(binary)
in
try
Expression.Evaluate(asText, #shared)
catch (e) =>
error
[
Reason = "Extension.LoadFunction Failure",
Message.Format = "Loading '#{0}' failed - '#{1}': '#{2}'",
Message.Parameters = {fileName, e[Reason], e[Message]},
Detail = [File = fileName, Error = e]
],
// get model info and server data
modelInfo = GetModel(url),
rootId = modelInfo[rootObjectId],
// Get the data from SendToServer - this is already a response from the service
JsonResponse = SendToServer(url),
// convert list to table with all columns expanded
TableFromList = Table.FromList(
JsonResponse,
Splitter.SplitByNothing(),
null,
null,
ExtraValues.Error
),
// fields to remove from data record
FieldsToRemove = {"__closure", "totalChildrenCount", "renderMaterialProxies"},
// create basic table with cleaned data records (no properties column yet)
BasicTable = Table.FromRecords(
List.Transform(
TableFromList[Column1],
each let
record = _,
fieldsToRemoveForThisRecord = List.Select(
FieldsToRemove,
each Record.HasFields(record, {_})
),
cleanedRecord = Record.RemoveFields(record, fieldsToRemoveForThisRecord)
in
[
#"Object IDs" = record[id], // Object IDs
#"Speckle Type" = record[speckle_type], // Speckle Type
#"Version Object ID" = rootId,
#"Model Info" = rootId,
#"Application ID" = Record.FieldOrDefault(record, "applicationId", null), // Application ID
data = cleanedRecord // Data
]
)
),
// function to check if a row should be excluded based on speckle type
ShouldExcludeRow = (row as record) as logical =>
let
speckleType = Record.FieldOrDefault(row[data], "speckle_type", "")
in
speckleType = "Speckle.Core.Models.DataChunk" or
Text.Contains(speckleType, "Objects.Other.RawEncoding"),
// Filtering logic here
// If model data contains any DataObject -> fetch only data objects (excluding unwanted types)
// If there are no data objects in the data -> fetch everything but exclude DataChunks and RawEncoding
// Check if model contains any DataObject
HasDataObjects = Table.RowCount(
Table.SelectRows(
BasicTable,
each Text.Contains(Record.FieldOrDefault([data], "speckle_type", ""), "DataObject")
and not ShouldExcludeRow(_)
)
) > 0,
// load the Objects.Properties function only if we have DataObjects
ObjectsProperties = if HasDataObjects then Extension.LoadFunction("Objects.Properties.pqm") else null,
// Add properties column only if model has DataObjects
FinalTable = if HasDataObjects then
Table.AddColumn(
BasicTable,
"properties",
each let
dataRecord = [data],
isDataObject = Text.Contains(Record.FieldOrDefault(dataRecord, "speckle_type", ""), "DataObject"),
hasProperties = Record.HasFields(dataRecord, {"properties"}),
extractedProperties = if hasProperties and isDataObject then
try ObjectsProperties(dataRecord) otherwise []
else
[]
in
if Record.FieldCount(extractedProperties) > 0 then extractedProperties else null
)
else
BasicTable,
// Apply the same filtering logic as before
FilteredTable = if HasDataObjects then
Table.SelectRows(
FinalTable,
each Text.Contains(Record.FieldOrDefault([data], "speckle_type", ""), "DataObject")
and not ShouldExcludeRow(_)
)
else
Table.SelectRows(FinalTable, each not ShouldExcludeRow(_))
in
FilteredTable
@@ -0,0 +1,66 @@
// function for getting the user info with graphql query
let
// import the parser function from Parser.pqm file
Parser = Extension.LoadFunction("Parser.pqm"),
// the logic for importing functions from other files
Extension.LoadFunction = (fileName as text) =>
let
binary = Extension.Contents(fileName),
asText = Text.FromBinary(binary)
in
try
Expression.Evaluate(asText, #shared)
catch (e) =>
error
[
Reason = "Extension.LoadFunction Failure",
Message.Format = "Loading '#{0}' failed - '#{1}': '#{2}'",
Message.Parameters = {fileName, e[Reason], e[Message]},
Detail = [File = fileName, Error = e]
]
in
(url as text) as record =>
let
// get base server URL using the imported function
parsedUrl = Parser(url),
server = parsedUrl[baseUrl],
apiKey = try Extension.CurrentCredential()[access_token] otherwise "",
query = "query {
activeUser {
email
name
}
serverInfo {
name
company
version
}
}",
Source = Web.Contents(
Text.Combine({server, "graphql"}, "/"),
[
Headers = [
#"Method" = "POST",
#"Content-Type" = "application/json",
#"Authorization" = if apiKey = "" then "" else Text.Format("Bearer #{0}", {apiKey})
],
ManualStatusHandling = {400},
Content = Json.FromValue([query = query])
]
),
JsonResponse = Json.Document(Source)
in
if Record.HasFields(JsonResponse, {"errors"}) then
error JsonResponse[errors]{0}[message]
else
[
UserEmail = try JsonResponse[data][activeUser][email] otherwise "",
UserName = try JsonResponse[data][activeUser][name] otherwise "",
ServerName = JsonResponse[data][serverInfo][name],
ServerCompany = JsonResponse[data][serverInfo][company],
ServerVersion = JsonResponse[data][serverInfo][version],
Token = if apiKey = "" then null else apiKey
]
@@ -0,0 +1,46 @@
() as text =>
let
// read the Speckle.pq file
specklePqContent = try
Text.FromBinary(Extension.Contents("Speckle.pq"))
otherwise
error "Could not read Speckle.pq file",
lines = Text.Split(specklePqContent, "#(lf)"),
versionLine = List.First(
List.Select(
lines,
each Text.Contains(_, "[Version = ")
),
null
),
version = if versionLine <> null then
let
// find the start and end positions of the version string
startPos = Text.PositionOf(versionLine, """") + 1,
tempText = Text.Middle(versionLine, startPos),
endPos = Text.PositionOf(tempText, """"),
versionText = Text.Middle(tempText, 0, endPos)
in
versionText
else
// fallback version if parsing fails
"3.0.0",
// validate version format
isValidVersion =
let
parts = Text.Split(version, "."),
isValid = List.Count(parts) = 3 and
List.AllTrue(List.Transform(parts, each try Number.From(_) >= 0 otherwise false))
in
isValid,
result = if isValidVersion then
version
else
error "Invalid version format found: " & version
in
result
@@ -0,0 +1,76 @@
// function for getting workspace information
(url as text) as record =>
let
ApiFetch = Extension.LoadFunction("Api.Fetch.pqm"),
Parser = Extension.LoadFunction("Parser.pqm"),
// the logic for importing functions from other files
Extension.LoadFunction = (fileName as text) =>
let
binary = Extension.Contents(fileName),
asText = Text.FromBinary(binary)
in
try
Expression.Evaluate(asText, #shared)
catch (e) =>
error
[
Reason = "Extension.LoadFunction Failure",
Message.Format = "Loading '#{0}' failed - '#{1}': '#{2}'",
Message.Parameters = {fileName, e[Reason], e[Message]},
Detail = [File = fileName, Error = e]
],
parsedUrl = Parser(url),
server = parsedUrl[baseUrl],
projectId = parsedUrl[projectId],
// query to get workspace ID from project
projectQuery = "query Project($projectId: String!) {
data:project(id: $projectId) {
workspaceId
}
}",
projectVariables = [
projectId = projectId
],
projectResult = ApiFetch(server, projectQuery, projectVariables),
workspaceId = projectResult[data][workspaceId],
// check if workspaceId is null (personal project)
workspaceInfo = if workspaceId = null then
[
workspaceId = null,
workspaceLogo = null,
workspaceName = null,
canHideBranding = false
]
else
// query workspace only if workspaceId exists
let
workspaceQuery = "query Workspace($workspaceId: String!, $featureName: WorkspaceFeatureName!) {
data:workspace(id: $workspaceId) {
logo
name
hasAccessToFeature(featureName: $featureName)
}
}",
workspaceVariables = [
workspaceId = workspaceId,
featureName = "hideSpeckleBranding"
],
workspaceResult = ApiFetch(server, workspaceQuery, workspaceVariables),
workspace = workspaceResult[data]
in
[
workspaceId = workspaceId,
workspaceLogo = workspace[logo],
workspaceName = workspace[name],
canHideBranding = workspace[hasAccessToFeature]
]
in
workspaceInfo
@@ -0,0 +1,44 @@
// Function to mark a version as received via GraphQL mutation
// Uses the powerful token
(powerfulToken as text, versionId as text, projectId as text, serverUrl as text) as logical =>
try
let
NormalizedServerUrl = if Text.End(serverUrl, 1) = "/" then
serverUrl
else
serverUrl & "/",
// Build GraphQL
GraphQLMutation = "mutation MarkVersionReceived($input: MarkReceivedVersionInput!) { versionMutations { markReceived(input: $input) } }",
Variables = [
input = [
versionId = versionId,
projectId = projectId,
sourceApplication = "powerbi-data"
]
],
// Make GraphQL request
Response = Web.Contents(
NormalizedServerUrl & "graphql",
[
Headers = [
#"Method" = "POST",
#"Content-Type" = "application/json",
#"Authorization" = "Bearer " & powerfulToken
],
Content = Json.FromValue([
query = GraphQLMutation,
variables = Variables
]),
ManualStatusHandling = {400, 401, 403, 404, 500, 502, 503, 504},
Timeout = #duration(0, 0, 0, 5)
]
),
StatusCode = Value.Metadata(Response)[Response.Status]
in
StatusCode >= 200 and StatusCode < 300
otherwise
false
@@ -0,0 +1,42 @@
// function for federating multiple tables by combining them and creating concatenated Version Object ID and Model Info fields
(tables as list, optional excludeData as logical) as table =>
let
ViewerOnly = if excludeData = null then false else excludeData,
// filter columns from each table if excludeData is true
ProcessedTables = List.Transform(
tables,
each
if ViewerOnly then
Table.SelectColumns(_, {"Version Object ID", "Model Info", "Object IDs"}, MissingField.Ignore)
else
_
),
CombinedTable = Table.Combine(ProcessedTables),
DistinctVersionObjectIDs = List.Distinct(CombinedTable[Version Object ID]),
ConcatenatedVersionObjectIDs = Text.Combine(DistinctVersionObjectIDs, ","),
DistinctModelInfo = List.Distinct(CombinedTable[Model Info]),
ConcatenatedModelInfo = Text.Combine(DistinctModelInfo, "|||"),
// Replace all Version Object ID values with the concatenated string
TableWithVersionObjectID = Table.ReplaceValue(
CombinedTable,
each [Version Object ID],
ConcatenatedVersionObjectIDs,
Replacer.ReplaceText,
{"Version Object ID"}
),
// Replace all Model Info values with the concatenated string
FederatedTable = Table.ReplaceValue(
TableWithVersionObjectID,
each [Model Info],
ConcatenatedModelInfo,
Replacer.ReplaceText,
{"Model Info"}
)
in
FederatedTable
@@ -0,0 +1,22 @@
// function for transforming a table to extract and expand Material Quantities data
(inputTable as table, optional addPrefix as logical) as table =>
let
// Default addPrefix to false if not provided
UsePrefix = if addPrefix = null then false else addPrefix,
// Add mq column using existing MaterialQuantities function with list output
AddedMQ = Table.AddColumn(inputTable, "mq", each Speckle.Objects.MaterialQuantities([data], true)),
// Expand the mq list column
ExpandMQ = Table.ExpandListColumn(AddedMQ, "mq"),
// Add MQProperties column using Properties function with error handling
AddedMQProperties = Table.AddColumn(ExpandMQ, "MQ", each try Speckle.Objects.Properties([mq]) otherwise null),
// Expand the MQProperties record using Utils.ExpandRecord
ExpandMQProperties = Speckle.Utils.ExpandRecord(AddedMQProperties, "MQ", null, UsePrefix),
// Remove the temporary mq and MQProperties columns
FinalTable = Table.RemoveColumns(ExpandMQProperties, {"mq", "MQ"}, MissingField.Ignore)
in
FinalTable
@@ -0,0 +1,163 @@
// function for mapping collection names to referenced elements in Speckle data
(inputData as table) as table =>
let
// Helper function to safely get field value
SafeFieldValue = (record as record, fieldName as text) as any =>
if Record.HasFields(record, {fieldName}) then
Record.Field(record, fieldName)
else
null,
// Helper function to safely get nested field value
SafeNestedValue = (record as record, path as list) as any =>
List.Accumulate(
path,
record,
(current, fieldName) =>
if current <> null and Value.Is(current, type record) and Record.HasFields(current, {fieldName}) then
Record.Field(current, fieldName)
else
null
),
// Step 1: Identify Collection Objects
CollectionObjects = Table.SelectRows(
inputData,
each
let
speckleType = SafeFieldValue(_, "Speckle Type")
in
speckleType <> null and Text.Contains(speckleType, "Collection")
),
// Step 2: Extract Collection Metadata
CollectionMetadata = Table.AddColumn(
CollectionObjects,
"CollectionInfo",
each
let
objectId = SafeFieldValue(_, "Object IDs"),
collectionName = SafeNestedValue(_, {"data", "name"}),
elements = SafeNestedValue(_, {"data", "elements"})
in
[
ObjectId = objectId,
CollectionName = if collectionName <> null then collectionName else "Unnamed Collection",
Elements = if elements <> null and Value.Is(elements, type list) then elements else {}
]
),
// Step 3: Build Collection Hierarchy Mapping
CollectionHierarchy = Table.AddColumn(
CollectionMetadata,
"CollectionReferences",
each
let
info = [CollectionInfo],
collectionName = info[CollectionName],
elements = info[Elements]
in
List.Transform(
elements,
(element) =>
let
referencedId = if Value.Is(element, type record) and Record.HasFields(element, {"referencedId"}) then
element[referencedId]
else
null
in
if referencedId <> null then
[
ReferencedId = referencedId,
CollectionName = collectionName,
ParentCollectionId = info[ObjectId]
]
else
null
)
),
// Step 4: Flatten Reference Mapping
FlattenedReferences = Table.SelectRows(
Table.ExpandListColumn(
Table.SelectColumns(CollectionHierarchy, {"CollectionReferences"}),
"CollectionReferences"
),
each [CollectionReferences] <> null
),
ReferenceTable = Table.ExpandRecordColumn(
FlattenedReferences,
"CollectionReferences",
{"ReferencedId", "CollectionName", "ParentCollectionId"},
{"ReferencedId", "CollectionName", "ParentCollectionId"}
),
// Step 5: Build Hierarchical Collection Paths
BuildCollectionPath = (objectId as text, visited as list) as text =>
let
// Prevent infinite loops
_ = if List.Contains(visited, objectId) then
error "Circular reference detected in collection hierarchy"
else
null,
newVisited = List.InsertRange(visited, 0, {objectId}),
// Find if this object is referenced by any collection
parentReferences = Table.SelectRows(ReferenceTable, each [ReferencedId] = objectId),
result = if Table.RowCount(parentReferences) = 0 then
// No parent collection found
""
else
let
parentRef = parentReferences{0},
parentCollectionId = parentRef[ParentCollectionId],
currentCollectionName = parentRef[CollectionName],
// Recursively get parent path
parentPath = @BuildCollectionPath(parentCollectionId, newVisited),
// Build full path
fullPath = if parentPath = "" then
currentCollectionName
else
parentPath & "::" & currentCollectionName
in
fullPath
in
result,
// Step 6: Add Collection Paths to data field
FinalData = Table.TransformColumns(
inputData,
{
"data", each
let
currentData = _,
currentRow = Table.SelectRows(inputData, each [data] = currentData){0},
objectId = SafeFieldValue(currentRow, "Object IDs"),
collectionPath = if objectId <> null then
try
BuildCollectionPath(objectId, {})
otherwise
""
else
"",
// Add CollectionPath field to the data record, set to null if empty
enhancedData = if Value.Is(currentData, type record) then
Record.AddField(
currentData,
"collectionPath",
if collectionPath = "" then null else collectionPath
)
else
currentData
in
enhancedData
}
)
in
FinalData
@@ -0,0 +1,18 @@
(objectRecord as record, optional outputAsList as nullable logical) as any =>
let
compositeStructure =
if Record.HasFields(objectRecord[properties], "Composite Structure") then
objectRecord[properties][Composite Structure]
else if Record.HasFields(objectRecord[properties], "Parameters") and
Record.HasFields(objectRecord[properties][Parameters], "Type Parameters") and
Record.HasFields(objectRecord[properties][Parameters][Type Parameters], "Structure") then
objectRecord[properties][Parameters][Type Parameters][Structure]
else
null,
result =
if outputAsList = true then
if compositeStructure <> null then Record.ToList(compositeStructure) else null
else
compositeStructure
in
result
@@ -0,0 +1,15 @@
// Helper function to extract [properties][Material Quantities] and optionally output as list
(objectRecord as record, optional outputAsList as logical) as any =>
let
// Ensure outputAsList is logical and defaults to false if not provided
OutputAsList = if outputAsList = null then false else outputAsList,
// Check if 'properties' and 'Material Quantities' exist
HasMaterialQuantities = Record.HasFields(objectRecord, {"properties"}) and Record.HasFields(Record.Field(objectRecord, "properties"), {"Material Quantities"}),
MaterialQuantities = if HasMaterialQuantities then Record.Field(Record.Field(objectRecord, "properties"), "Material Quantities") else null,
Result = if MaterialQuantities = null then null else
if OutputAsList then
Record.ToList(MaterialQuantities)
else
MaterialQuantities
in
Result
@@ -0,0 +1,257 @@
// function for extracting and flattening properties from Speckle objects
(inputRecord as any, optional filterKeys as list, optional parentPath as text, optional existingFields as list) as record =>
let
// Define excluded paths
ExcludedPaths = {
"Composite Structure",
"Material Quantities",
"Parameters.Type Parameters.Structure"
},
// Helper function to check if a path should be excluded
IsPathExcluded = (currentPath as text) as logical =>
List.AnyTrue(List.Transform(ExcludedPaths, each Text.Contains(currentPath, _))),
// Helper function to resolve naming conflicts
ResolveFieldName = (fieldName as text, parentPathParam as nullable text, existingFieldsParam as nullable list) as text =>
let
// Ensure we have valid inputs
parentPath = if parentPathParam = null then "" else parentPathParam,
existingFields = if existingFieldsParam = null then {} else existingFieldsParam,
// Try original field name first
candidateName = fieldName,
// If no conflict, return original name
finalName = if not List.Contains(existingFields, candidateName) then
candidateName
else if parentPath = "" then
fieldName // No parent path available, keep original
else
let
// Split parent path and try adding parents one by one
pathParts = Text.Split(parentPath, "."),
reversedParts = List.Reverse(pathParts), // Start with immediate parent
// Use iteration instead of recursion
ResolveWithIteration = () =>
let
// Generate all possible candidates
candidates = List.Generate(
() => [depth = 1, candidate = fieldName & "." & List.First(reversedParts)],
each [depth] <= List.Count(reversedParts),
each [
depth = [depth] + 1,
candidate = fieldName & "." & Text.Combine(List.FirstN(reversedParts, [depth]), ".")
],
each [candidate]
),
// Find first non-conflicting candidate
firstNonConflicting = List.First(
List.Select(candidates, each not List.Contains(existingFields, _)),
// If all conflict, use full path
fieldName & "." & Text.Combine(reversedParts, ".")
)
in
firstNonConflicting,
resolvedName = ResolveWithIteration()
in
resolvedName
in
finalName,
// Create the main flattening function with self-reference capability
FlattenRecordImpl = (
flattenFn as function,
inputRecord as any,
filterKeys as nullable list,
parentPathParam as nullable text,
existingFieldsParam as nullable list
) as record =>
let
// Ensure non-null values for internal use
currentParentPath = if parentPathParam = null then "" else parentPathParam,
currentExistingFields = if existingFieldsParam = null then {} else existingFieldsParam,
currentfilterKeys = filterKeys,
// Check if record has "properties" field and use it instead of the root record
recordToProcess = if inputRecord = null then
null
else if Value.Is(inputRecord, type record) and Record.HasFields(inputRecord, {"properties"}) then
Record.Field(inputRecord, "properties")
else
inputRecord,
// Helper function to check if a field should be included
ShouldIncludeField = (fieldName as text) as logical =>
if currentfilterKeys = null then true
else List.Contains(currentfilterKeys, fieldName),
// Handle different input types
result = if recordToProcess = null then
[]
else if Value.Is(recordToProcess, type record) then
let
fieldNames = Record.FieldNames(recordToProcess),
// Process each field
processedFields = List.Accumulate(
fieldNames,
[FlattenedRecord = [], ExistingFieldsList = currentExistingFields],
(state, fieldName) =>
let
fieldValue = Record.Field(recordToProcess, fieldName),
newPath = if currentParentPath = "" then fieldName else currentParentPath & "." & fieldName,
// Skip if path is excluded
shouldProcess = not IsPathExcluded(newPath),
processResult = if not shouldProcess then
state
else
let
// Check if this is a name/value record
hasNameValue = Value.Is(fieldValue, type record) and
Record.HasFields(fieldValue, {"name", "value"}),
finalResult = if hasNameValue then
let
nameField = Record.Field(fieldValue, "name"),
valueField = Record.Field(fieldValue, "value"),
// Check if this name field should be included
shouldInclude = if nameField = null then false else ShouldIncludeField(nameField),
result = if shouldInclude and nameField <> null then
let
resolvedName = ResolveFieldName(nameField, currentParentPath, state[ExistingFieldsList]),
newRecord = Record.AddField(state[FlattenedRecord], resolvedName, valueField),
newFieldsList = state[ExistingFieldsList] & {resolvedName}
in
[FlattenedRecord = newRecord, ExistingFieldsList = newFieldsList]
else
state
in
result
else if fieldValue = null then
let
shouldInclude = ShouldIncludeField(fieldName),
result = if shouldInclude then
let
resolvedName = ResolveFieldName(fieldName, currentParentPath, state[ExistingFieldsList]),
newRecord = Record.AddField(state[FlattenedRecord], resolvedName, null),
newFieldsList = state[ExistingFieldsList] & {resolvedName}
in
[FlattenedRecord = newRecord, ExistingFieldsList = newFieldsList]
else
state
in
result
else if Value.Is(fieldValue, type record) then
let
// Skip empty records
fieldCount = Record.FieldCount(fieldValue),
recursiveResult = if fieldCount = 0 then
state
else
let
// Call the function through the passed reference
// IMPORTANT: Pass the current state's existing fields list
flattened = flattenFn(flattenFn, fieldValue, currentfilterKeys, newPath, state[ExistingFieldsList]),
// Get all field names from the flattened result
flattenedFieldNames = Record.FieldNames(flattened),
// Merge the flattened record with the current state
combinedRecord = flattened & state[FlattenedRecord],
// Update the existing fields list with ALL fields from both records
allFieldNames = List.Distinct(state[ExistingFieldsList] & flattenedFieldNames)
in
[FlattenedRecord = combinedRecord, ExistingFieldsList = allFieldNames]
in
recursiveResult
else if Value.Is(fieldValue, type list) then
let
listLength = List.Count(fieldValue),
// Skip empty lists
listResult = if listLength = 0 then
state
else
List.Accumulate(
List.Positions(fieldValue),
state,
(listState, index) =>
let
listItem = fieldValue{index},
indexSuffix = Text.From(index + 1), // 1-based indexing
listFieldName = fieldName & "." & indexSuffix,
listPath = if currentParentPath = "" then listFieldName else currentParentPath & "." & listFieldName,
itemResult = if Value.Is(listItem, type record) then
let
itemFieldCount = Record.FieldCount(listItem),
itemFlattened = if itemFieldCount = 0 then
listState
else
let
// Call the function through the passed reference
flattened = flattenFn(flattenFn, listItem, currentfilterKeys, listPath, listState[ExistingFieldsList]),
// Get all field names from the flattened result
flattenedFieldNames = Record.FieldNames(flattened),
// Merge the flattened record with the current state
combinedRecord = flattened & listState[FlattenedRecord],
// Update the existing fields list with ALL fields
allFieldNames = List.Distinct(listState[ExistingFieldsList] & flattenedFieldNames)
in
[FlattenedRecord = combinedRecord, ExistingFieldsList = allFieldNames]
in
itemFlattened
else
let
shouldInclude = ShouldIncludeField(listFieldName),
result = if shouldInclude then
let
resolvedName = ResolveFieldName(listFieldName, currentParentPath, listState[ExistingFieldsList]),
newRecord = Record.AddField(listState[FlattenedRecord], resolvedName, listItem),
newFieldsList = listState[ExistingFieldsList] & {resolvedName}
in
[FlattenedRecord = newRecord, ExistingFieldsList = newFieldsList]
else
listState
in
result
in
itemResult
)
in
listResult
else
// Handle primitive values
let
shouldInclude = ShouldIncludeField(fieldName),
result = if shouldInclude then
let
resolvedName = ResolveFieldName(fieldName, currentParentPath, state[ExistingFieldsList]),
newRecord = Record.AddField(state[FlattenedRecord], resolvedName, fieldValue),
newFieldsList = state[ExistingFieldsList] & {resolvedName}
in
[FlattenedRecord = newRecord, ExistingFieldsList = newFieldsList]
else
state
in
result
in
finalResult
in
processResult
)
in
processedFields[FlattenedRecord]
else
// If input is not a record, return it as is in a record wrapper
[Value = recordToProcess]
in
result,
// Call the implementation with self-reference
result = FlattenRecordImpl(FlattenRecordImpl, inputRecord, filterKeys, parentPath, existingFields)
in
result
@@ -0,0 +1,59 @@
// function for parsing the url into base url, project id, model id and version id
(url as text) as record =>
let
urlParts = Uri.Parts(url),
baseUrl = Text.Combine({urlParts[Scheme], "://", urlParts[Host]}),
pathSegments = List.Select(Text.Split(urlParts[Path], "/"), each _ <> ""),
// extract project ID if it exists
projectId = if List.Count(pathSegments) >= 2 and pathSegments{0} = "projects"
then pathSegments{1} else null,
// extract model ID and version ID if they exist
rawModelSegment = if List.Count(pathSegments) >= 4 and pathSegments{2} = "models"
then pathSegments{3} else "",
// check if this is a federated model (contains commas)
isFederated = Text.Contains(rawModelSegment, ","),
// if federated, split by comma to get multiple model IDs
modelSegments = if isFederated
then Text.Split(rawModelSegment, ",")
else {rawModelSegment},
// process each model segment (could be modelID or modelID@versionID)
processedModels = List.Transform(
modelSegments,
each [
modelId = if Text.Contains(_, "@")
then Text.Split(_, "@"){0}
else _,
versionId = if Text.Contains(_, "@")
then Text.Split(_, "@"){1}
else null
]
),
// extract model IDs and version IDs into separate lists
modelIds = List.Transform(processedModels, each [modelId]),
versionIds = List.Transform(processedModels, each [versionId]),
// validate URL structure
isValid = projectId <> null and List.Count(modelIds) > 0 and List.First(modelIds) <> ""
in
if not isValid then
error [
Reason = "Invalid URL",
Message = "The URL must be in the format 'https://server/projects/PROJECT_ID/models/MODEL_ID' or 'https://server/projects/PROJECT_ID/models/MODEL_ID@VERSION_ID' or 'https://server/projects/PROJECT_ID/models/MODEL_ID1,MODEL_ID2'"
]
else
[
baseUrl = baseUrl,
projectId = projectId,
modelId = if isFederated then null else processedModels{0}[modelId],
versionId = if isFederated then null else processedModels{0}[versionId],
isFederated = isFederated,
federatedModels = if isFederated then processedModels else null,
resourceIdString = rawModelSegment
]
@@ -0,0 +1,173 @@
// Function for getting issues from Speckle projects, models, or versions
(url as text, optional getReplies as logical) as table =>
let
// Import required functions
Parser = Extension.LoadFunction("Parser.pqm"),
ApiFetch = Extension.LoadFunction("Api.Fetch.pqm"),
// Set default value for getReplies parameter
getRepliesValue = if getReplies = null then false else getReplies,
// Extension.LoadFunction logic for importing functions from other files
Extension.LoadFunction = (fileName as text) =>
let
binary = Extension.Contents(fileName),
asText = Text.FromBinary(binary)
in
try
Expression.Evaluate(asText, #shared)
catch (e) =>
error
[
Reason = "Extension.LoadFunction Failure",
Message.Format = "Loading '#{0}' failed - '#{1}': '#{2}'",
Message.Parameters = {fileName, e[Reason], e[Message]},
Detail = [File = fileName, Error = e]
],
// Parse the URL to get necessary components with fallback for project-only URLs
parsedUrl = try Parser(url) otherwise
// Custom parsing for project-only URLs
let
urlParts = Uri.Parts(url),
baseUrl = Text.Combine({urlParts[Scheme], "://", urlParts[Host]}),
pathSegments = List.Select(Text.Split(urlParts[Path], "/"), each _ <> ""),
projectId = if List.Count(pathSegments) >= 2 and pathSegments{0} = "projects"
then pathSegments{1} else null
in
if projectId = null then
error [
Reason = "Invalid URL",
Message = "The URL must be a valid Speckle project URL in the format 'https://server/projects/PROJECT_ID' or include models/versions"
]
else
[
baseUrl = baseUrl,
projectId = projectId,
modelId = null,
versionId = null
],
server = parsedUrl[baseUrl],
projectId = parsedUrl[projectId],
modelId = parsedUrl[modelId],
versionId = parsedUrl[versionId],
// Define the GraphQL query (single query for all scopes)
issuesQuery = "query Project($projectId: String!, $input: ProjectIssuesInput" &
(if getRepliesValue then ", $repliesInput2: IssueRepliesInput" else "") & ") {
project(id: $projectId) {
issues(input: $input) {
items {
identifier
title
rawDescription
status
priority
assignee {
user {
name
}
}
dueDate
labels {
name
}
createdAt
updatedAt
resourceIdString
viewerState
id" &
(if getRepliesValue then "
replies(input: $repliesInput2) {
items {
issueId
id
rawDescription
createdAt
author {
user {
name
}
}
}
}" else "") & "
}
}
}
}",
// Build input variable dynamically based on URL scope
inputVariable =
if versionId <> null then
// Version URL: resourceIdString = "MODEL_ID@VERSION_ID"
[
limit = 10000,
resourceIdString = modelId & "@" & versionId
]
else if modelId <> null then
// Model URL: resourceIdString = MODEL_ID
[
limit = 10000,
resourceIdString = modelId
]
else
// Project URL: no resourceIdString
[
limit = 10000
],
// Build query variables
queryVariables = if getRepliesValue then
[
projectId = projectId,
input = inputVariable,
repliesInput2 = [limit = 10000]
]
else
[
projectId = projectId,
input = inputVariable
],
// Make the API request using ApiFetch
result = ApiFetch(server, issuesQuery, queryVariables),
// Extract issues from the response
issues = result[project][issues][items],
// Transform to table structure with specified columns
issuesTable = Table.FromRecords(
List.Transform(issues, (issue) =>
let
// Extract selectedObjectApplicationIds from viewerState (already a record object)
viewerState = try issue[viewerState] otherwise null,
selectedObjectIds = try viewerState[ui][filters][selectedObjectApplicationIds] otherwise null,
objectIds = try Record.FieldNames(selectedObjectIds) otherwise null,
applicationIds = try Record.FieldValues(selectedObjectIds) otherwise null,
baseRecord = [
ID = issue[identifier],
Title = issue[title],
Description = try issue[rawDescription] otherwise null,
Status = try issue[status] otherwise null,
Priority = try issue[priority] otherwise null,
Assignee = try issue[assignee][user][name] otherwise null,
#"Due Date" = try DateTime.From(issue[dueDate]) otherwise null,
Labels = try List.Transform(issue[labels], each _[name]) otherwise {},
#"Created at" = try DateTime.From(issue[createdAt]) otherwise null,
#"Updated at" = try DateTime.From(issue[updatedAt]) otherwise null,
URL = server & "/projects/" & projectId & "/models/" & issue[resourceIdString] & "#threadId=" & issue[id],
#"Object IDs" = objectIds,
#"Application IDs" = applicationIds
],
recordWithReplies = if getRepliesValue then
baseRecord & [Replies = try issue[replies][items] otherwise null]
else
baseRecord
in
recordWithReplies
)
)
in
issuesTable
@@ -0,0 +1,100 @@
(url as text) as list =>
let
GetModel = Extension.LoadFunction("GetModel.pqm"),
Parser = Extension.LoadFunction("Parser.pqm"),
GetUser = Extension.LoadFunction("GetUser.pqm"),
ExchangeToken = Extension.LoadFunction("ExchangeToken.pqm"),
// helper function to load .pqm modules dynamically
Extension.LoadFunction = (fileName as text) =>
let
binary = Extension.Contents(fileName),
asText = Text.FromBinary(binary)
in
try
Expression.Evaluate(asText, #shared)
catch (e) =>
error
[
Reason = "Extension.LoadFunction Failure",
Message.Format = "Loading '#{0}' failed - '#{1}': '#{2}'",
Message.Parameters = {fileName, e[Reason], e[Message]},
Detail = [File = fileName, Error = e]
],
modelInfo = GetModel(url),
parsedUrl = Parser(url),
userInfo = GetUser(url),
powerfulToken = userInfo[Token],
// exchange powerful token for weak token using GraphQL
// this replaces the desktop service token exchange
tokenExchangeResult = ExchangeToken(
powerfulToken,
{"profile:read", "streams:read", "users:read"},
parsedUrl[projectId],
parsedUrl[baseUrl],
parsedUrl[resourceIdString]
),
// throw error if token exchange failed - do NOT use powerful token as fallback
tokenToUse = if tokenExchangeResult[Success] then
tokenExchangeResult[Token]
else
error [
Reason = "TokenExchangeFailed",
Message.Format = "Failed to exchange token for limited scope token: #{0}",
Message.Parameters = {tokenExchangeResult[ErrorMessage]},
Detail = [
ErrorMessage = tokenExchangeResult[ErrorMessage],
ProjectId = parsedUrl[projectId],
ServerUrl = parsedUrl[baseUrl]
]
],
// downloads data directly from server
DirectDownload = (token as text) =>
let
objectUrl = Text.Combine({
parsedUrl[baseUrl],
"/objects/",
parsedUrl[projectId],
"/",
modelInfo[rootObjectId]
}),
Response = Web.Contents(
objectUrl,
[
Headers = [
#"Authorization" = "Bearer " & token,
#"Accept" = "application/json"
],
ManualStatusHandling = {400, 401, 403, 404, 500, 502, 503, 504}
]
),
StatusCode = Value.Metadata(Response)[Response.Status],
JsonResponse = if StatusCode >= 200 and StatusCode < 300 then
Json.Document(Response)
else
error [
Reason = "DirectDownloadFailed",
Message.Format = "Failed to download model data from Speckle server (Status: #{0})",
Message.Parameters = {Text.From(StatusCode)},
Detail = [
StatusCode = StatusCode,
ObjectUrl = objectUrl,
ProjectId = parsedUrl[projectId],
RootObjectId = modelInfo[rootObjectId]
]
]
in
JsonResponse,
// download data using the token (weak if exchange succeeded, powerful otherwise)
FinalResult = DirectDownload(tokenToUse)
in
FinalResult
@@ -0,0 +1,31 @@
// Expands a record column in a table, adding new columns for each field in the record.
// If UseCombinedNames is true, columns are named as ColumnName.FieldName, otherwise just FieldName.
// If FieldNames is provided (list), only those fields are expanded.
(table as table, columnName as text, optional FieldNames as list, optional UseCombinedNames as logical) as table =>
let
useCombined = if UseCombinedNames = null then false else UseCombinedNames,
// Determine which field names to expand
allFieldNames = if FieldNames <> null then FieldNames else List.Distinct(
List.Combine(
List.Transform(
Table.Column(table, columnName),
each if _ is record then Record.FieldNames(_) else {}
)
)
),
// Add each field as a new column
addColumns = List.Accumulate(
allFieldNames,
table,
(state, field) =>
Table.AddColumn(
state,
if useCombined then columnName & "." & field else field,
(row) =>
if Record.HasFields(row, columnName) and Record.Field(row, columnName) is record and Record.HasFields(Record.Field(row, columnName), field)
then Record.Field(Record.Field(row, columnName), field)
else null
)
)
in
addColumns
+37
View File
@@ -0,0 +1,37 @@
/** @type {import("eslint").Linter.Config} */
const config = {
root: true,
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
requireConfigFile: false,
ecmaVersion: 2020,
sourceType: 'module'
},
plugins: ['@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'prettier'
],
env: {
node: true,
commonjs: true
},
ignorePatterns: [
'node_modules',
'dist',
'public',
'events.json',
'.*.{ts,js,vue,tsx,jsx}',
'generated/**/*'
],
rules: {
'no-var': 'off',
'@typescript-eslint/ban-ts-comment': 'warn'
}
}
module.exports = config
+76
View File
@@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or
advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic
address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at hello@speckle.systems. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
+50
View File
@@ -0,0 +1,50 @@
# Speckle Contribution Guidelines
## Introduction
Thank you for reading this! Speckle's a rather wide network of parts that depend on each other, either directly, indirectly or even just cosmetically.
> **Speckle** is a quite large ecosystem of moving parts. Any changes may have unintended effects, that can cause problems quickly for many people (and processes) that rely on Speckle.
This means that what might look like a simple quick change in one repo may have a big hidden cost that propagates around other parts of the project. We're all here to help each other, and this guide is meant to help you get started and promote a framework that can untangle all these dependecies through discussion!
## Bugs & Issues 🐞
### Found a new bug?
- First step is to check whether this is a new bug! We encourage you to search through the issues of the project in question **and** associated repos!
- If you come up with nothing, **open a new issue with a clear title and description**, as much relevant information as possible: system configuration, code samples & steps to reproduce the problem.
- Can't mention this often enough: tells us how to reproduce the problem! We will ignore or flag as such issues without reproduction steps.
- Try to reference & note all potentially affected projects.
### Sending a PR for Bug Fixes
You fixed something! Great! We hope you logged it first :) Make sure though that you've covered the lateral thinking needed for a bug report, as described above, also in your implementation! If there any tests, make sure they all pass. If there are none, it means they're missing - so add them!
## New Features 🎉
The golden rule is to Discuss First!
- Before embarking on adding a new feature, suggest it first as an issue with the `enhancement` label and/or title - this will allow relevant people to pitch in
- We'll now discuss your requirements and see how and if they fit within the Speckle ecosystem.
- The last step is to actually start writing code & submit a PR so we can follow along!
- All new features should, if and where possible, come with tests. We won't merge without!
> Many clients may potentially have overlapping scopes, some features might already be in dev somewhere else, or might have been postponed to the next major release due to api instability in that area. For example, adding a delete stream button in the accounts panel in rhino: this feature was planned for speckle admin, and the whole functionality of the accounts panel in rhino is to be greatly reduced!
## Cosmetic Patches ✨
Changes that are cosmetic in nature and do not add anything substantial to the stability or functionality of Speckle **will generally not be accepted**.
Why? However trivial the changes might seem, there might be subtle reasons for the original code to be as it is. Furthermore, there are a lot of potential hidden costs (that even maintainers themselves are not aware of fully!) and they eat up review time unncessarily.
> **Examples**: modifying the colour of an UI element in one client may have a big hidden cost and need propagation in several other clients that implement a similar ui element. Changing the default port or specifiying `localhost` instead of `0.0.0.0` breaks cross-vm debugging and developing.
## Wrap up
Don't worry if you get things wrong. We all do, including project owners: this document should've been here a long time ago. There's plenty of room for discussion on our community [forum](https://discourse.speckle.works).
🙌❤️💙💚💜🙌
+110
View File
@@ -0,0 +1,110 @@
---
name: Bug report
about: Help improve Speckle!
title: ''
labels: bug
assignees: ''
---
<!---
Provide a short summary in the Title above. Examples of good Issue titles:
* "Bug: Error from server when reticulating splines"
* "Bug: Revit crashes when installing connector"
-->
## Prerequisites
<!---
Please answer the following questions before submitting an issue.
-->
- [ ] I read the [contribution guidelines](https://github.com/specklesystems/speckle-server/blob/main/CONTRIBUTING.md)
- [ ] I checked the [documentation](https://speckle.guide/) and found no answer.
- [ ] I checked [existing issues](../issues?q=is%3Aissue) and found no similar issue. <!-- If you do find an existing issue, please show your support by liking it :+1: instead of creating a new issue -->
- [ ] I checked the [community forum](https://speckle.community/) for related discussions and found no answer.
- [ ] I'm reporting the issue to the correct repository (see also [speckle-server](https://github.com/specklesystems/speckle-server), [speckle-sharp](https://github.com/specklesystems/speckle-sharp), [specklepy](https://github.com/specklesystems/specklepy), [speckle-docs](https://github.com/specklesystems/speckle-docs), and [others](https://github.com/orgs/specklesystems/repositories))
## What package are you referring to?
<!---
Is it related to the server (backend) only, or does this bug relate to the frontend, viewer, objectloader or any other package?
-->
## Describe the bug
<!---
A clear and concise description of what the bug is.
-->
## To Reproduce
<!---
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
-->
## Expected behavior
<!---
A clear and concise description of what you expected to happen.
-->
## Screenshots
<!---
If applicable, add screenshots to help explain your problem.
-->
## System Info
If applicable, please fill in the below details - they help a lot!
### Desktop (please complete the following information):
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
### Smartphone (please complete the following information):
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
## Failure Logs
<!---
Please include any relevant log snippets or files here, or upload as a file.
If including inline, please use markdown code block syntax. https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks
For example:
```
your log output here
```
-->
## Additional context
<!---
Add any other context about the problem here.
-->
## Proposed Solution (if any)
<!---
Let us know what how you would solve this.
-->
#### Optional: Affected Projects
<!---
Does this issue propagate to other dependencies or dependents? If so, list them here with links!
-->
@@ -0,0 +1,65 @@
---
name: Feature request
about: Suggest an idea for Speckle!
title: ''
labels: enhancement, question
assignees: ''
---
<!---
Provide a short summary in the Title above. Examples of good Issue titles:
* "Enhancement: Connector for Minecraft"
* "Enhancement: Web viewer should support tesseracts"
-->
## Prerequisites
<!---
Please answer the following questions before submitting an issue.
-->
- [ ] I read the [contribution guidelines](https://github.com/specklesystems/speckle-server/blob/main/CONTRIBUTING.md)
- [ ] I checked the [documentation](https://speckle.guide/) and found no answer.
- [ ] I checked [existing issues](../issues?q=is%3Aissue) and found no similar issue. <!-- If you do find an existing issue, please show your support by liking it :+1: instead of creating a new issue -->
- [ ] I checked the [community forum](https://speckle.community/) for related discussions and found no answer.
- [ ] I'm requesting the feature to the correct repository (see also [speckle-server](https://github.com/specklesystems/speckle-server), [speckle-sharp](https://github.com/specklesystems/speckle-sharp), [specklepy](https://github.com/specklesystems/specklepy), [speckle-docs](https://github.com/specklesystems/speckle-docs), and [others](https://github.com/orgs/specklesystems/repositories))
## What package are you referring to?
<!---
Is it related to the server (backend) only, or does this feature request relate to the frontend, viewer, objectloader or any other package?
-->
## Is your feature request related to a problem? Please describe.
<!---
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
-->
## Describe the solution you'd like
<!---
A clear and concise description of what you want to happen.
-->
## Describe alternatives you've considered
<!---
A clear and concise description of any alternative solutions or features you've considered.
-->
## Additional context
<!---
Add any other context or screenshots about the feature request here.
Have you seen this feature implemented in any other software? Can you provide screenshots or links to video or documentation?
What works well about these existing features in other software? What doesn't work well?
-->
## Related issues or community discussions
<!---
Is this feature request related to (but sufficiently distinct from) any existing issues?
Does this feature request require other features to be available beforehand?
Has this feature been discussed in the community forum, please link here? https://speckle.community/
-->
@@ -0,0 +1,86 @@
<!---
Provide a short summary in the Title above. Examples of good PR titles:
* "Feature: adds metrics to component"
* "Fix: resolves duplication in comment thread"
* "Update: apollo v2.34.0"
-->
## Description & motivation
<!---
Describe your changes, and why you're making them. What benefit will this have to others?
Is this linked to an open Github issue, a thread in Speckle community,
or another pull request? Link it here.
If it is related to a Github issue, and resolves it, please link to the issue number, e.g.:
Fixes #85, Fixes #22, Fixes username/repo#123
Connects #123
-->
## Changes:
<!---
- Item 1
- Item 2
-->
## To-do before merge
<!---
(Optional -- remove this section if not needed)
Include any notes about things that need to happen before this PR is merged, e.g.:
- [ ] Change the base branch
- [ ] Ensure PR #56 is merged
-->
## Screenshots:
<!---
Include a screenshot the before and after. This can be a screenshot of a plugin, web frontend, or output in a terminal.
-->
## Validation of changes:
<!---
Describe what tests have been added or amended, and why these demonstrate it works and will prevent this feature being accidentally broken by future changes.
-->
## Checklist:
<!---
This checklist is mostly useful as a reminder of small things that can easily be
forgotten it is meant as a helpful tool rather than hoops to jump through.
Put an `x` in all the items that apply, make notes next to any that haven't been
addressed, and remove any items that are not relevant to this PR.
-->
- [ ] My pull request follows the guidelines in the [Contributing guide](https://github.com/specklesystems/speckle-server/blob/main/CONTRIBUTING.md)?
- [ ] My pull request does not duplicate any other open [Pull Requests](../../pulls) for the same update/change?
- [ ] My commits are related to the pull request and do not amend unrelated code or documentation.
- [ ] My code follows a similar style to existing code.
- [ ] I have added appropriate tests.
- [ ] I have updated or added relevant documentation.
+11
View File
@@ -0,0 +1,11 @@
{
"trailingComma": "none",
"tabWidth": 2,
"semi": false,
"endOfLine": "auto",
"bracketSpacing": true,
"vueIndentScriptAndStyle": false,
"htmlWhitespaceSensitivity": "ignore",
"printWidth": 100,
"singleQuote": true
}
+13
View File
@@ -0,0 +1,13 @@
{
"version": "0.1.0",
"configurations": [
{
"name": "Debugger",
"type": "chrome",
"request": "attach",
"port": 8080,
"sourceMaps": true,
"webRoot": "${cwd}/"
}
]
}
+33
View File
@@ -0,0 +1,33 @@
{
"editor.tabSize": 2,
"editor.insertSpaces": true,
"files.eol": "\n",
"files.watcherExclude": {
"**/.git/objects/**": true,
"**/node_modules/**": true,
".tmp": true
},
"files.associations": {
"*.resjson": "json"
},
"editor.formatOnSave": true,
"search.exclude": {
".tmp": true,
"typings": true
},
"json.schemas": [
{
"fileMatch": ["/pbiviz.json"],
"url": "./node_modules/powerbi-visuals-api/schema.pbiviz.json"
},
{
"fileMatch": ["/capabilities.json"],
"url": "./node_modules/powerbi-visuals-api/schema.capabilities.json"
},
{
"fileMatch": ["/dependencies.json"],
"url": "./node_modules/powerbi-visuals-api/schema.dependencies.json"
}
],
"vue.codeActions.enabled": false
}
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2020 AEC Systems
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+112
View File
@@ -0,0 +1,112 @@
<h1 align="center">
<img src="https://user-images.githubusercontent.com/2679513/131189167-18ea5fe1-c578-47f6-9785-3748178e4312.png" width="150px"/><br/>
Speckle | PowerBI Visuals
</h1>
<h3 align="center">
3D Viewer for PowerBI and more...
Expected use case is that the visual displays data pulled from Speckle via the Speckle Data Connector for PowerBI (https://github.com/specklesystems/speckle-powerbi)
</h3>
> ⚠️ This repo is still in very early stages of development, use at your own risk!
<p align="center"><b>Speckle</b> is data infrastructure for the AEC industry.</p><br/>
<p align="center"><a href="https://twitter.com/SpeckleSystems"><img src="https://img.shields.io/twitter/follow/SpeckleSystems?style=social" alt="Twitter Follow"></a> <a href="https://speckle.community"><img src="https://img.shields.io/discourse/users?server=https%3A%2F%2Fspeckle.community&amp;style=flat-square&amp;logo=discourse&amp;logoColor=white" alt="Community forum users"></a> <a href="https://speckle.systems"><img src="https://img.shields.io/badge/https://-speckle.systems-royalblue?style=flat-square" alt="website"></a> <a href="https://speckle.guide/dev/"><img src="https://img.shields.io/badge/docs-speckle.guide-orange?style=flat-square&amp;logo=read-the-docs&amp;logoColor=white" alt="docs"></a></p>
<p align="center"></p>
# About Speckle
What is Speckle? Check our ![YouTube Video Views](https://img.shields.io/youtube/views/B9humiSpHzM?label=Speckle%20in%201%20minute%20video&style=social)
### Features
- **Object-based:** say goodbye to files! Speckle is the first object based platform for the AEC industry
- **Version control:** Speckle is the Git & Hub for geometry and BIM data
- **Collaboration:** share your designs collaborate with others
- **3D Viewer:** see your CAD and BIM models online, share and embed them anywhere
- **Interoperability:** get your CAD and BIM models into other software without exporting or importing
- **Real time:** get real time updates and notifications and changes
- **GraphQL API:** get what you need anywhere you want it
- **Webhooks:** the base for a automation and next-gen pipelines
- **Built for developers:** we are building Speckle with developers in mind and got tools for every stack
- **Built for the AEC industry:** Speckle connectors are plugins for the most common software used in the industry such as Revit, Rhino, Grasshopper, AutoCAD, Civil 3D, Excel, Unreal Engine, Unity, QGIS, Blender and more!
### Try Speckle now!
Give Speckle a try in no time by:
- [![speckle](https://img.shields.io/badge/https://-app.speckle.systems-0069ff?style=flat-square&logo=hackthebox&logoColor=white)](https://app.speckle.systems) ⇒ creating an account
- [![create a droplet](https://img.shields.io/badge/Create%20a%20Droplet-0069ff?style=flat-square&logo=digitalocean&logoColor=white)](https://marketplace.digitalocean.com/apps/speckle-server?refcode=947a2b5d7dc1) ⇒ deploying an instance in 1 click
### Resources
- [![Community forum users](https://img.shields.io/badge/community-forum-green?style=for-the-badge&logo=discourse&logoColor=white)](https://speckle.community) for help, feature requests or just to hang with other speckle enthusiasts, check out our community forum!
- [![website](https://img.shields.io/badge/tutorials-speckle.systems-royalblue?style=for-the-badge&logo=youtube)](https://speckle.systems) our tutorials portal is full of resources to get you started using Speckle
- [![docs](https://img.shields.io/badge/docs-speckle.guide-orange?style=for-the-badge&logo=read-the-docs&logoColor=white)](https://speckle.guide/dev/) reference on almost any end-user and developer functionality
![Untitled](https://user-images.githubusercontent.com/2679513/132021739-15140299-624d-4410-98dc-b6ae6d9027ab.png)
# Repo structure
This repo follows the default structure of any Custom PowerBI Visual, generated by the `pbiviz` tool.
For now, it only contains a single visual -> The Speckle 3D Viewer
For more information about how a PowerBI visual is structured, you can check out the [official documentation](https://docs.microsoft.com/en-us/power-bi/developer/visuals/visual-project-structure)
### Other repos
Make sure to also check and ⭐️ these other Speckle repositories:
- [`speckle-server`](https://github.com/specklesystems/speckle-server): Server and Web packages
- [`specklepy`](https://github.com/specklesystems/specklepy): Python SDK 🐍
- [`speckle-excel`](https://github.com/specklesystems/speckle-excel): Excel connector
- [`speckle-unity`](https://github.com/specklesystems/speckle-unity): Unity 3D connector
- [`speckle-blender`](https://github.com/specklesystems/speckle-blender): Blender connector
- [`speckle-unreal`](https://github.com/specklesystems/speckle-unreal): Unreal Engine Connector
- [`speckle-qgis`](https://github.com/specklesystems/speckle-qgis): QGIS connectod
- [`speckle-powerbi`](https://github.com/specklesystems/speckle-powerbi): PowerBi connector
- and more [connectos & tooling](https://github.com/specklesystems/)!
## Developing and Debugging
There's a neat guide on setting up your environment for developing visuals [here](https://docs.microsoft.com/en-us/power-bi/developer/visuals/environment-setup)
You'll need to properly set up the certificate in order to be able to use the hot-reloading feature.
> Hot Reload will only work on PowerBI Web (**not** on Desktop).
### Local dev guide (for powerbi-visual)
1. Cd into `./src/powerbi-visual`
1. Run `npm install`
1. To ensure proper SSL cert usage
1. Ensure [mkcert](https://github.com/FiloSottile/mkcert) is installed
1. Run `npm run generate-certs`
1. If you're on Windows or WSL2, you'll need to copy over the root CA to the Windows side and install it there as a trusted root CA.
1. WSL2: Typically its in `~/.local/share/mkcert/rootCA.pem` on WSL2. From bash, `cd` to that folder and then do `explorer.exe .` to open it in Windows Explorer and then copy the pem file to someplace better accessible.
1. Windows: Typically its in `%LOCALAPPDATA%\mkcert\`.
1. Open `crtmgr` and install it into **Trusted Root Certification Authorities**. "Certificates - Current User" > "Trusted Root Certification Authorities" > "Certificates" > Right Click "All Tasks" > "Import" > "Local Machine" > "Place all certificates in the following store" > "Trusted Root Certification Authorities". You may have to set the cert filter to "All Files" to see the `.pem` file.
1. After the cert is installed you may have to restart your browser & dev server
1. Run `npm run dev`
1. PowerBI -> Home > New Report > Paste Or manually enter date > Auto-create > Create
1. In the report, click on 'Edit' to open edit mode, and add a "Developer Visual" visual
#### Source map issues
Make sure you're running the dev build (`npm run dev`) and in your browser's dev tools trigger "Clear source maps cache" and "Enable JavaScript source maps". When everything's working, you should be able to click on the "App mounted" console message's file reference link which will take you to the source-mapped source code in dev tools.
Its still a bit janky in that it maye show multiple files with the same name in the file tree,
but one of those is gonna be the real fully source mapped one.
### Contributing
Please make sure you read the [contribution guidelines](.github/CONTRIBUTING.md) for an overview of the best practices we try to follow.
### Security
For any security vulnerabilities or concerns, please contact us directly at security[at]speckle.systems.
### License
Unless otherwise described, the code in this repository is licensed under the Apache-2.0 License. Please note that some modules, extensions or code herein might be otherwise licensed. This is indicated either in the root of the containing folder under a different license file, or in the respective file's header. If you have any questions, don't hesitate to get in touch with us via [email](mailto:hello@speckle.systems).
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+263
View File
@@ -0,0 +1,263 @@
{
"dataRoles": [
{
"displayName": "Model Info",
"kind": "Measure",
"name": "rootObjectId"
},
{
"displayName": "Object IDs",
"kind": "Grouping",
"name": "objectIds"
},
{
"displayName": "Object Data (Tooltip)",
"kind": "Measure",
"name": "tooltipData"
},
{
"displayName": "Color By",
"kind": "Grouping",
"name": "colorBy"
}
],
"dataViewMappings": [
{
"matrix": {
"rows": {
"dataReductionAlgorithm": {
"top": {
"count": 150000
}
},
"select": [
{
"bind": {
"to": "colorBy"
}
},
{
"for": {
"in": "objectIds"
}
}
]
},
"values": {
"select": [
{
"for": {
"in": "rootObjectId"
}
},
{
"for": {
"in": "tooltipData"
}
}
]
}
},
"conditions": [
{
"colorBy": { "max": 1 },
"objectIds": { "max": 1 },
"rootObjectId": { "max": 1 }
}
]
}
],
"objects": {
"storedData": {
"properties": {
"speckleObjects": {
"type": { "text": true }
},
"receiveInfo": {
"type": { "text": true }
}
}
},
"workspace": {
"properties": {
"brandingHidden": {
"type": { "bool": true }
}
}
},
"viewMode": {
"properties": {
"defaultViewMode": {
"type": { "text": true }
},
"navbarHidden": {
"type": { "bool": true }
},
"edgesEnabled": {
"type": { "bool": true }
},
"edgesWeight": {
"type": { "numeric": true }
},
"edgesColor": {
"type": { "numeric": true }
}
}
},
"camera": {
"properties": {
"defaultView": {
"type": { "text": true }
},
"isOrtho": {
"type": { "bool": true }
},
"isGhost": {
"type": { "bool": true }
},
"zoomOnFilter": {
"type": { "bool": true }
}
}
},
"sectionBox": {
"properties": {
"boxData": {
"type": { "text": true }
}
}
},
"cameraPosition": {
"properties": {
"positionX": {
"type": { "text": true }
},
"positionY": {
"type": { "text": true }
},
"positionZ": {
"type": { "text": true }
},
"targetX": {
"type": { "text": true }
},
"targetY": {
"type": { "text": true }
},
"targetZ": {
"type": { "text": true }
}
}
},
"dataLoading": {
"properties": {
"internalizeData": {
"type": {
"bool": true
}
}
}
},
"color": {
"properties": {
"enabled": {
"type": {
"bool": true
}
},
"fill": {
"type": {
"fill": {
"solid": {
"color": true
}
}
}
},
"context": {
"type": {
"enumeration": [
{
"displayName": "Hidden",
"value": "hidden"
},
{
"displayName": "Ghosted",
"value": "ghosted"
}
]
}
}
}
},
"lighting": {
"properties": {
"enabled": {
"type": {
"bool": true
}
},
"intensity": {
"type": {
"numeric": true
}
},
"elevation": {
"type": {
"numeric": true
}
},
"azimuth": {
"type": {
"numeric": true
}
},
"indirect": {
"type": {
"numeric": true
}
},
"shadows": {
"type": {
"bool": true
}
},
"shadowCatcher": {
"type": {
"bool": true
}
}
}
}
},
"privileges": [
{
"essential": true,
"name": "WebAccess",
"parameters": ["https://analytics.speckle.systems", "*"]
},
{
"essential": false,
"name": "ExportContent"
},
{
"essential": true,
"name": "LocalStorage",
"parameters": []
}
],
"sorting": {
"default": {}
},
"supportsEmptyDataView": true,
"supportsHighlight": true,
"supportsKeyboardFocus": true,
"supportsLandingPage": true,
"keepAllMetadataColumns": true,
"supportsMultiVisualSelection": true,
"supportsSynchronizingFilterState": true,
"suppressDefaultTitle": true,
"tooltips": {
"supportEnhancedTooltips": true
}
}
+17174
View File
File diff suppressed because it is too large Load Diff
+90
View File
@@ -0,0 +1,90 @@
{
"name": "@specklesystems/powerbi-visual",
"description": "A 3D viewer for Speckle Object in PowerBI",
"repository": {
"type": "github",
"url": "https://github.com/specklesystems/speckle-powerbi-visuals"
},
"license": "MIT",
"scripts": {
"generate-certs": "mkcert localhost",
"build": "webpack --config webpack.config.ts",
"build:dev": "webpack --config webpack.config.dev.ts",
"dev": "webpack-dev-server --config webpack.config.dev.ts"
},
"dependencies": {
"@babel/runtime": "^7.21.5",
"@babel/runtime-corejs3": "^7.21.5",
"@headlessui/vue": "^1.7.13",
"@heroicons/vue": "^2.0.12",
"@speckle/objectloader2": "2.26.7",
"@speckle/tailwind-theme": "2.23.2",
"@speckle/ui-components": "2.23.2",
"@speckle/viewer": "2.26.5",
"color-interpolate": "^1.0.5",
"core-js": "^3.30.2",
"lodash": "^4.17.21",
"nanoevents": "^9.1.0",
"pako": "^2.1.0",
"pinia": "^2.3.0",
"postcss-loader": "^7.3.0",
"postcss-preset-env": "^8.4.1",
"powerbi-visuals-api": "^5.11.0",
"powerbi-visuals-utils-colorutils": "^6.0.5",
"powerbi-visuals-utils-dataviewutils": "^6.1.0",
"powerbi-visuals-utils-formattingmodel": "^6.0.4",
"powerbi-visuals-utils-interactivityutils": "^6.0.4",
"powerbi-visuals-utils-tooltiputils": "^6.0.4",
"regenerator-runtime": "^0.13.11",
"vue-tippy": "^6.7.1"
},
"devDependencies": {
"@babel/core": "^7.21.8",
"@babel/eslint-parser": "^7.21.8",
"@babel/preset-env": "^7.21.5",
"@tailwindcss/forms": "^0.5.3",
"@types/lodash": "^4.14.194",
"@types/node": "^20.1.7",
"@types/regenerator-runtime": "^0.13.1",
"@types/three": "^0.140.0",
"@types/webpack": "^5.28.1",
"@typescript-eslint/eslint-plugin": "^5.59.2",
"@typescript-eslint/parser": "^5.59.2",
"@vueuse/core": "^13.2.0",
"autoprefixer": "^10.4.14",
"babel-loader": "^9.1.2",
"base64-inline-loader": "^2.0.1",
"css-loader": "^6.7.3",
"eslint": "^8.40.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-vue": "^9.13.0",
"extra-watch-webpack-plugin": "^1.0.3",
"html-webpack-plugin": "^5.6.3",
"json-loader": "^0.5.7",
"mini-css-extract-plugin": "^2.7.5",
"postcss": "^8.4.23",
"postcss-import": "^15.1.0",
"powerbi-visuals-tools": "^5.6.0",
"powerbi-visuals-webpack-plugin": "^4.1.0",
"prettier": "^2.8.8",
"style-loader": "^3.3.2",
"tailwindcss": "^3.3.2",
"ts-loader": "^9.4.2",
"ts-node": "^10.9.1",
"tsconfig-paths-webpack-plugin": "^4.0.1",
"typescript": "^5.0.4",
"user-agent-data-types": "^0.3.1",
"vue": "^3.5.13",
"vue-loader": "^17.4.2",
"vue-template-compiler": "^2.7.16",
"webpack": "^5.97.1",
"webpack-bundle-analyzer": "^4.10.2",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.2"
},
"version": "3.0.0",
"engines": {
"node": "^20.17.0"
},
"packageManager": "yarn@4.9.1+sha512.f95ce356460e05be48d66401c1ae64ef84d163dd689964962c6888a9810865e39097a5e9de748876c2e0bf89b232d583c33982773e9903ae7a76257270986538"
}
+20
View File
@@ -0,0 +1,20 @@
{
"visual": {
"name": "Speckle PowerBI Viewer",
"displayName": "Speckle PowerBI Viewer",
"guid": "specklePowerBiVisual",
"visualClassName": "Visual",
"version": "3.0.0.0",
"description": "An interactive 3D viewer for Speckle Data",
"supportUrl": "https://speckle.community",
"gitHubUrl": "https://github.com/specklesystems/speckle-powerbi-visuals"
},
"apiVersion": "5.4.0",
"author": { "name": "Speckle Systems", "email": "info@speckle.systems" },
"assets": { "icon": "assets/logo.png" },
"externalJS": [],
"style": "style/visual.css",
"capabilities": "capabilities.json",
"dependencies": null,
"stringResources": []
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
'postcss-nesting': {}
}
}
+42
View File
@@ -0,0 +1,42 @@
<template>
<div
v-if="visualStore.loadingProgress"
class="absolute top-1/2 left-1/2 w-1/2 -translate-x-1/2 z-50 text-center text-sm"
>
<!-- Progress Bar -->
<LoadingBar :progress="visualStore.loadingProgress"></LoadingBar>
</div>
<div
v-if="visualStore.commonError"
class="absolute top-11 left-1/2 -translate-x-1/2 z-100 bg-white bg-opacity-70 text-black text-center text-sm px-4 py-1 rounded shadow font-medium cursor-default"
>
{{ visualStore.commonError }}
</div>
<ViewerView v-if="visualStore.isViewerReadyToLoad" />
<HomeView v-else />
</template>
<script setup lang="ts">
import HomeView from './views/HomeView.vue'
import ViewerView from './views/ViewerView.vue'
import { onMounted } from 'vue'
import { useVisualStore } from './store/visualStore'
import LoadingBar from '@src/components/loading/LoadingBar.vue'
const visualStore = useVisualStore()
onMounted(() => {
console.log('App mounted')
})
</script>
<style>
.tippy-box[data-theme~='custom'] {
font-size: 10px;
padding: 0px 0px;
border-radius: 4px;
text-align: center;
}
</style>
@@ -0,0 +1,99 @@
<template>
<div class="space-y-2">
<ViewerControlsButtonGroup>
<!-- Zoom extend -->
<ViewerControlsButtonToggle flat tooltip="Zoom extends" @click="onZoomExtentsClicked">
<ArrowsPointingOutIcon class="h-4 w-4 md:h-5 md:w-5" />
</ViewerControlsButtonToggle>
</ViewerControlsButtonGroup>
<ViewerControlsButtonGroup>
<!-- View Modes Toggle -->
<div class="relative">
<ViewerControlsButtonToggle
flat
tooltip="View modes"
:active="viewModesOpen"
@click="toggleActiveControl('viewModes')"
>
<ViewModesIcon class="h-5 w-5" />
</ViewerControlsButtonToggle>
<!-- View Modes Panel (shown when glasses icon is clicked) -->
<ViewerViewModesMenu
v-if="viewModesOpen"
@view-mode-clicked="(viewMode, options) => $emit('view-mode-clicked', viewMode, options)"
/>
</div>
<!-- Camera -->
<ViewerCameraMenu
:open="cameraOpen"
:views="views"
@update:open="(value) => toggleActiveControl(value ? 'camera' : 'none')"
@view-clicked="(view) => $emit('view-clicked', view)"
/>
<!-- Section box -->
<div class="relative">
<ViewerControlsButtonToggle
flat
tooltip="Section box"
@click="$emit('update:sectionBox')"
>
<ScissorsIcon class="h-4 w-4 md:h-5 md:w-5" />
</ViewerControlsButtonToggle>
<span
v-if="sectionBox"
class="absolute top-1 right-1 h-2 w-2 rounded-full bg-primary pointer-events-none"
/>
</div>
</ViewerControlsButtonGroup>
</div>
</template>
<script setup lang="ts">
import { ArrowsPointingOutIcon, ScissorsIcon } from '@heroicons/vue/24/solid'
import { CanonicalView, SpeckleView, ViewMode } from '@speckle/viewer'
import { computed, ref } from 'vue'
import { useVisualStore } from '@src/store/visualStore'
import ViewerControlsButtonGroup from './viewer/controls/ViewerControlsButtonGroup.vue'
import ViewerControlsButtonToggle from './viewer/controls/ViewerControlsButtonToggle.vue'
import ViewerCameraMenu from './viewer/camera/ViewerCameraMenu.vue'
import ViewerViewModesMenu from './viewer/view-modes/ViewerViewModesMenu.vue'
import ViewModesIcon from '../components/global/icon/ViewModes.vue'
import type { ViewModeOptions } from '@src/plugins/viewer'
const visualStore = useVisualStore()
const emits = defineEmits<{
(e: 'update:sectionBox', value: boolean): void
(e: 'view-clicked', view: CanonicalView | SpeckleView): void
(e: 'clear-palette'): void
(e: 'view-mode-clicked', viewMode: ViewMode, options: ViewModeOptions): void
}>()
withDefaults(defineProps<{ sectionBox: boolean; views: SpeckleView[] }>(), {
sectionBox: false
})
type ActiveControl =
| 'none'
| 'viewModes'
| 'camera'
| 'sun'
| 'projection'
| 'sectionBox'
| 'explode'
| 'settings'
const activeControl = ref<ActiveControl>('none')
const onZoomExtentsClicked = (ev: MouseEvent) => {
visualStore.viewerEmit('zoomExtends')
}
const toggleActiveControl = (control: ActiveControl) => {
activeControl.value = activeControl.value === control ? 'none' : control
}
const viewModesOpen = computed(() => activeControl.value === 'viewModes')
const cameraOpen = computed(() => activeControl.value === 'camera')
</script>
@@ -0,0 +1,323 @@
<template>
<div>
<transition name="slide-fade">
<nav
v-show="!visualStore.isNavbarHidden"
class="fixed top-0 h-9 flex items-center bg-foundation border border-outline-2 w-full transition z-20 cursor-default"
>
<div class="flex items-center transition-all justify-between w-full">
<div
v-if="visualStore.receiveInfo.workspaceName"
class="flex items-center gap-2 p-0.5 pr-1.5 hover:bg-highlight-2 rounded ml-2"
>
<WorkspaceAvatar
:name="visualStore.receiveInfo.workspaceName"
:logo="visualStore.receiveInfo.workspaceLogo"
></WorkspaceAvatar>
<div class="min-w-0 truncate flex-grow text-left text-xs">
<span>{{ visualStore.receiveInfo.workspaceName }}</span>
</div>
</div>
<div v-else>
<div class="flex items-center hover:cursor-pointer" @click="goToSpeckleWebsite">
<div class="max-[200px]:hidden block ml-2">
<img class="w-6 h-auto ml-1 mr-2 my-1" src="@assets/logo-big.png" />
</div>
<div class="font-sans font-medium">Speckle</div>
</div>
</div>
<div class="flex items-center space-x-2">
<FormButton
v-if="visualStore.latestAvailableVersion && !visualStore.isConnectorUpToDate && visualStore.isRunningInDesktop"
v-tippy="{
content: 'New connector version is available.<br>Click to download.',
allowHTML: true
}"
color="outline"
size="sm"
@click="visualStore.downloadLatestVersion"
>
Update
</FormButton>
<div class="font-thin text-xs text-gray-400">
v{{ visualStore.receiveInfo.version }}
</div>
<button
class="text-gray-400 hover:text-gray-700 transition"
title="Hide navbar"
@click="visualStore.toggleNavbar()"
>
<ChevronUpIcon class="w-4 h-4" />
</button>
</div>
</div>
</nav>
</transition>
<div
v-if="!isInteractive"
class="absolute left-1/2 -translate-x-1/2 z-20 bg-white bg-opacity-70 text-black text-center text-xs px-4 py-1 rounded shadow font-medium cursor-default transition-all duration-300"
:class="visualStore.isNavbarHidden ? 'top-1' : 'top-11'"
>
<strong>Object IDs</strong>
field is needed for interactivity with other visuals.
</div>
<div v-if="visualStore.isNavbarHidden" class="fixed top-4 right-2 z-20">
<button
class="transition opacity-50 hover:opacity-100"
title="Show navbar"
@click="visualStore.toggleNavbar()"
>
<ChevronDownIcon class="w-4 h-4 text-gray-400" />
</button>
</div>
<transition name="slide-left">
<ViewerControls
v-show="!visualStore.isNavbarHidden"
:section-box="sectionBoxEnabled"
:views="views"
class="fixed top-11 left-2 z-30"
@update:section-box="onSectionBoxToggle"
@view-clicked="(view) => viewerHandler.setView(view)"
@view-mode-clicked="(viewMode, options) => viewerHandler.setViewMode(viewMode, options)"
/>
</transition>
<div v-if="visualStore.isFilterActive" class="absolute bottom-5 left-1/2 -translate-x-1/2 z-50">
<FormButton size="sm" @click="visualStore.resetFilters(), selectionHandler.reset()">
Reset filters
</FormButton>
</div>
<div v-if="sectionBoxVisible" class="absolute bottom-5 left-1/2 -translate-x-1/2 z-50 flex gap-2">
<FormButton size="sm" color="outline" @click="onSectionBoxReset">Reset</FormButton>
<FormButton size="sm" @click="onSectionBoxDone">Done</FormButton>
</div>
<div
class="absolute z-10 flex items-center text-xs cursor-pointer"
:class="visualStore.isBrandingHidden ? 'bottom-0 right-0' : 'bottom-2 right-2'"
@click.stop="goToSpeckleWebsite"
>
<!-- TODO: fade bottom here as transition -->
<transition name="fade-bottom">
<div
v-if="!visualStore.isBrandingHidden"
class="flex items-center justify-center font-thin"
>
<div class="">Powered by</div>
<img class="w-4 h-auto mx-1" src="@assets/logo-big.png" />
<div class="font-medium">Speckle</div>
</div>
</transition>
<button
v-if="visualStore.receiveInfo && visualStore.receiveInfo.canHideBranding"
class="transition opacity-50 hover:opacity-100 ml-1"
:title="visualStore.isBrandingHidden ? '' : 'Hide branding'"
@click.stop="visualStore.toggleBranding()"
>
<ChevronUpIcon v-if="visualStore.isBrandingHidden" class="w-4 h-4 text-gray-400" />
<ChevronDownIcon v-else class="w-4 h-4" />
</button>
</div>
<div
ref="container"
class="fixed h-full w-full z-0 cursor-default"
@click="onCanvasClick"
@auxclick="onCanvasAuxClick"
/>
</div>
</template>
<script async setup lang="ts">
import FormButton from '@src/components/form/FormButton.vue'
import { computed, inject, onBeforeUnmount, onMounted, Ref, ref } from 'vue'
import { currentOS, OS } from '../utils/detectOS'
import ViewerControls from 'src/components/ViewerControls.vue'
import { SpeckleView } from '@speckle/viewer'
import { useClickDragged } from 'src/composables/useClickDragged'
import { useVisualStore } from '@src/store/visualStore'
import { ViewerHandler } from '@src/plugins/viewer'
import { selectionHandlerKey, tooltipHandlerKey } from '@src/injectionKeys'
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/vue/24/outline'
import WorkspaceAvatar from './workspace/WorkspaceAvatar.vue'
const visualStore = useVisualStore()
const { dragged } = useClickDragged()
const selectionHandler = inject(selectionHandlerKey)
const tooltipHandler = inject(tooltipHandlerKey)
let viewerHandler: ViewerHandler = null
const container = ref<HTMLElement>()
type SectionBoxState = 'inactive' | 'editing' | 'applied'
const sectionBoxState = ref<SectionBoxState>('inactive')
const sectionBoxEnabled = computed(() => sectionBoxState.value !== 'inactive')
const sectionBoxVisible = computed(() => sectionBoxState.value === 'editing')
const views: Ref<SpeckleView[]> = ref([])
const isInteractive = computed(
() => visualStore.fieldInputState.rootObjectId && visualStore.fieldInputState.objectIds
)
const goToSpeckleWebsite = () => visualStore.host.launchUrl('https://speckle.systems')
function disableSectionBox() {
sectionBoxState.value = 'inactive'
viewerHandler.toggleSectionBox(false)
visualStore.writeSectionBoxToFile(null)
visualStore.setSectionBoxData(null)
}
function onSectionBoxToggle() {
switch (sectionBoxState.value) {
case 'inactive':
sectionBoxState.value = 'editing'
viewerHandler.toggleSectionBox(true)
break
case 'editing':
onSectionBoxDone()
break
case 'applied':
sectionBoxState.value = 'editing'
viewerHandler.setSectionBoxVisible(true)
break
}
}
function onSectionBoxReset() {
disableSectionBox()
}
function onSectionBoxDone() {
sectionBoxState.value = 'applied'
viewerHandler.setSectionBoxVisible(false)
const boxData = viewerHandler.getSectionBoxData()
visualStore.setSectionBoxData(boxData)
visualStore.writeSectionBoxToFile(boxData)
}
onMounted(async () => {
console.log('Viewer Wrapper mounted')
viewerHandler = new ViewerHandler()
await viewerHandler.init(container.value)
// Set up event listener for object clicks from the FilteredSelectionExtension
viewerHandler.emitter.on('objectClicked', handleObjectClicked)
// Sync section box UI state when restored from file
viewerHandler.emitter.on('objectsLoaded', () => {
if (visualStore.sectionBoxData) {
sectionBoxState.value = 'applied'
}
})
visualStore.setViewerEmitter(viewerHandler.emit)
})
onBeforeUnmount(async () => {
await viewerHandler.dispose()
})
async function handleObjectClicked(hit: any, isMultiSelect: boolean, mouseEvent?: PointerEvent) {
// Skip if dragging occurred
if (dragged.value) return
console.log('🎯 Object clicked in ViewerWrapper:', hit, isMultiSelect)
if (hit) {
visualStore.setPostClickSkipNeeded(true)
const id = hit.object.id as string
if (isMultiSelect || !selectionHandler.isSelected(id)) {
await selectionHandler.select(id, isMultiSelect)
}
// Show tooltip if we have mouse coordinates
if (mouseEvent) {
tooltipHandler.show(hit, { x: mouseEvent.clientX, y: mouseEvent.clientY })
}
const selection = selectionHandler.getCurrentSelection()
const ids = selection.map((s) => s.id)
await viewerHandler.selectObjects(ids)
} else {
visualStore.setPostClickSkipNeeded(false)
tooltipHandler.hide()
if (!isMultiSelect) {
selectionHandler.clear()
await viewerHandler.selectObjects(null)
}
}
}
function onCanvasClick(ev: MouseEvent) {
// This click handler allows the viewer's built-in input system to handle clicks
// The viewer will emit ViewerEvent.ObjectClicked events which the SelectionExtension handles
console.log('🖱️ Canvas click detected:', ev.clientX, ev.clientY)
// Let the event propagate to the viewer's input system
// The viewer should handle the click and emit ViewerEvent.ObjectClicked
}
async function onCanvasAuxClick(ev: MouseEvent) {
if (ev.button !== 2 || dragged.value) return
// For right-clicks, we need to get the object at the click position
// Since FilteredSelectionExtension doesn't handle right-clicks, we'll ask it for current selection
const selectedObjects = viewerHandler.selection.getSelectedObjects()
const hit = selectedObjects.length > 0 ? {
guid: selectedObjects[0].id,
object: selectedObjects[0],
point: { x: 0, y: 0, z: 0 } // We don't have exact point for context menu
} : null
await selectionHandler.showContextMenu(ev, hit)
}
</script>
<style scoped>
.slide-fade-enter-active,
.slide-fade-leave-active {
transition: all 0.3s ease;
}
.slide-fade-enter-from,
.slide-fade-leave-to {
opacity: 0;
transform: translateY(-100%);
}
.slide-fade-enter-to,
.slide-fade-leave-from {
opacity: 1;
transform: translateY(0);
}
.slide-left-enter-active,
.slide-left-leave-active {
transition: all 0.3s ease;
}
.slide-left-enter-from,
.slide-left-leave-to {
opacity: 0;
transform: translateX(-20px);
}
.fade-bottom-enter-active,
.fade-bottom-leave-active {
transition: all 0.3s ease;
}
.fade-bottom-enter-from,
.fade-bottom-leave-to {
opacity: 0;
transform: translateY(10px);
}
.fade-bottom-enter-to,
.fade-bottom-leave-from {
opacity: 1;
transform: translateY(0);
}
</style>
@@ -0,0 +1,294 @@
<template>
<Component
:is="to ? linkComponent : 'button'"
:href="to"
:to="to"
:type="buttonType"
:external="external"
:class="buttonClasses"
:disabled="isDisabled"
role="button"
:style="
color !== 'subtle' && !text
? `box-shadow: -1px 1px 4px 0px #0000000a inset; box-shadow: 0px 2px 2px 0px #0000000d;`
: ''
"
@click="onClick"
>
<Component :is="finalLeftIcon" v-if="finalLeftIcon" :class="iconClasses" />
<slot v-if="!hideText">Button</slot>
<Component :is="iconRight" v-if="iconRight || !loading" :class="iconClasses" />
</Component>
</template>
<script setup lang="ts">
import { isObjectLike } from 'lodash'
import type { PropAnyComponent } from '../../helpers/common/components'
import { computed, resolveDynamicComponent } from 'vue'
import type { Nullable } from '@speckle/shared'
import type { FormButtonStyle, FormButtonSize } from '../../helpers/form/button'
const emit = defineEmits<{
/**
* Emit MouseEvent on click
*/
(e: 'click', val: MouseEvent): void
}>()
const props = defineProps<{
/**
* URL to which to navigate - can be a relative (app) path or an absolute link for an external URL
*/
to?: string
/**
* Choose from one of 3 button sizes
*/
size?: FormButtonSize
/**
* If set, will make the button take up all available space horizontally
*/
fullWidth?: boolean
/**
* Similar to "link", but without an underline and possibly in different colors
*/
text?: boolean
/**
* Will remove paddings and background. Use for links.
*/
link?: boolean
/**
* color:
* primary: the default primary blue.
* outline: foundation background and outline
* subtle: no styling
*/
color?: FormButtonStyle
/**
* Should rounded-full be added?:
*/
rounded?: boolean
/**
* Whether the target location should be forcefully treated as an external URL
* (for relative paths this will likely cause a redirect)
*/
external?: boolean
/**
* Whether to disable the button so that it can't be pressed
*/
disabled?: boolean
/**
* If set, will have type set to "submit" to enable it to submit any parent forms
*/
submit?: boolean
/**
* Add icon to the left from the text
*/
iconLeft?: Nullable<PropAnyComponent>
/**
* Add icon to the right from the text
*/
iconRight?: Nullable<PropAnyComponent>
/**
* Hide default slot (when you want to show icons only)
*/
hideText?: boolean
/**
* Customize component to be used when rendering links.
*
* The component will try to dynamically resolve NuxtLink and RouterLink and use those, if this is set to null.
*/
linkComponent?: Nullable<PropAnyComponent>
/**
* Disables the button and shows a spinning loader
*/
loading?: boolean
}>()
const NuxtLink = resolveDynamicComponent('NuxtLink')
const RouterLink = resolveDynamicComponent('RouterLink')
const linkComponent = computed(() => {
if (props.linkComponent) return props.linkComponent
if (props.external) return 'a'
if (isObjectLike(NuxtLink)) return NuxtLink
if (isObjectLike(RouterLink)) return RouterLink
return 'a'
})
const buttonType = computed(() => {
if (props.to) return undefined
if (props.submit) return 'submit'
return 'button'
})
const isDisabled = computed(() => props.disabled || props.loading)
const finalLeftIcon = computed(() => props.iconLeft)
const bgAndBorderClasses = computed(() => {
const classParts: string[] = []
const colorsBgBorder = {
subtle: [
'bg-transparent border-transparent text-foreground font-medium',
'hover:bg-primary-muted disabled:hover:bg-transparent focus-visible:border-foundation'
],
outline: [
'bg-foundation border-outline-2 text-foreground font-medium',
'hover:bg-primary-muted disabled:hover:bg-foundation focus-visible:border-foundation'
],
danger: [
'bg-danger border-danger-darker text-foundation font-medium',
'hover:bg-danger-darker disabled:hover:bg-danger focus-visible:border-foundation'
],
primary: [
'bg-primary border-outline-1 text-foreground-on-primary font-semibold',
'hover:bg-primary-focus disabled:hover:bg-primary focus-visible:border-foundation'
]
}
if (props.rounded) {
classParts.push('!rounded-full')
}
if (props.text || props.link) {
switch (props.color) {
case 'subtle':
classParts.push('text-foreground')
break
case 'outline':
classParts.push('text-foreground')
break
case 'danger':
classParts.push('text-danger')
break
case 'primary':
default:
classParts.push('text-primary')
break
}
} else {
switch (props.color) {
case 'subtle':
classParts.push(...colorsBgBorder.subtle)
break
case 'outline':
classParts.push(...colorsBgBorder.outline)
break
case 'danger':
classParts.push(...colorsBgBorder.danger)
break
case 'primary':
default:
classParts.push(...colorsBgBorder.primary)
break
}
}
return classParts.join(' ')
})
const sizeClasses = computed(() => {
switch (props.size) {
case 'sm':
return 'h-6 text-body-2xs'
case 'lg':
return 'h-10 text-body-sm'
default:
case 'base':
return 'h-8 text-body-xs'
}
})
const paddingClasses = computed(() => {
if (props.text || props.link) {
return 'p-0'
}
const hasIconLeft = !!props.iconLeft
const hasIconRight = !!props.iconRight
const hideText = props.hideText
switch (props.size) {
case 'sm':
if (hideText) return 'w-6'
if (hasIconLeft) return 'py-1 pr-2 pl-1'
if (hasIconRight) return 'py-1 pl-2 pr-1'
return 'px-2 py-1'
case 'lg':
if (hideText) return 'w-10'
if (hasIconLeft) return 'py-2 pr-6 pl-4'
if (hasIconRight) return 'py-2 pl-6 pr-4'
return 'px-6 py-2'
case 'base':
default:
if (hideText) return 'w-8'
if (hasIconLeft) return 'py-0 pr-4 pl-2'
if (hasIconRight) return 'py-0 pl-4 pr-2'
return 'px-4 py-0'
}
})
const generalClasses = computed(() => {
const baseClasses = [
'inline-flex justify-center items-center',
'text-center select-none whitespace-nowrap',
'outline outline-2 outline-transparent',
'transition duration-200 ease-in-out focus-visible:outline-outline-4'
]
const additionalClasses = []
if (!props.text && !props.link) {
additionalClasses.push('rounded-md border')
}
if (props.fullWidth) {
additionalClasses.push('w-full')
} else if (!props.hideText) {
additionalClasses.push('max-w-max')
}
if (isDisabled.value) {
additionalClasses.push('cursor-not-allowed opacity-60')
}
return [...baseClasses, ...additionalClasses].join(' ')
})
const buttonClasses = computed(() => {
return [
generalClasses.value,
sizeClasses.value,
bgAndBorderClasses.value,
paddingClasses.value
].join(' ')
})
const iconClasses = computed(() => {
const classParts: string[] = ['shrink-0']
switch (props.size) {
case 'sm':
classParts.push('h-4 w-4 p-0.5')
break
case 'lg':
classParts.push('h-6 w-6 p-1')
break
case 'base':
default:
classParts.push('h-6 w-6 p-1')
break
}
return classParts.join(' ')
})
const onClick = (e: MouseEvent) => {
if (isDisabled.value) {
e.preventDefault()
e.stopPropagation()
e.stopImmediatePropagation()
return
}
emit('click', e)
}
</script>
@@ -0,0 +1,104 @@
<template>
<div class="w-full flex flex-col gap-2">
<div class="flex items-center justify-between">
<label
:for="name"
class="block text-body-2xs text-foreground-2"
>
{{ label || name }}
</label>
<span class="text-body-2xs text-foreground-2">{{ displayValue }}</span>
</div>
<input
:id="name"
:name="name"
type="range"
:min="min"
:max="max"
:step="step"
:value="currentValue"
:disabled="disabled"
class="w-full h-1.5 outline-none slider"
:class="{
'disabled:opacity-50 disabled:cursor-not-allowed': disabled
}"
:aria-label="label"
:aria-valuemin="min"
:aria-valuemax="max"
:aria-valuenow="currentValue"
@input="handleInput"
/>
</div>
</template>
<script setup lang="ts">
import { ref, watch, computed } from 'vue'
const props = defineProps<{
min: number
max: number
step: number
name: string
label: string
disabled?: boolean
modelValue?: number
}>()
const emit = defineEmits(['update:modelValue'])
const currentValue = ref(props.modelValue ?? props.min)
// Watch for external changes to modelValue
watch(() => props.modelValue, (newVal) => {
if (newVal !== undefined && newVal !== currentValue.value) {
currentValue.value = newVal
}
})
const displayValue = computed(() => {
// Round to avoid floating point issues
return Math.round(currentValue.value * 10) / 10
})
const clampValue = (value: number): number => {
return Math.max(props.min, Math.min(props.max, value))
}
const handleInput = (event: Event) => {
const target = event.target as HTMLInputElement
const value = Number(target.value)
const clampedValue = clampValue(value)
currentValue.value = clampedValue
emit('update:modelValue', clampedValue)
}
</script>
<style scoped>
.slider {
-webkit-appearance: none;
appearance: none;
background: transparent;
}
.slider::-webkit-slider-runnable-track {
@apply h-1.5 rounded-full bg-outline-3;
}
.slider::-moz-range-track {
@apply h-1.5 rounded-full bg-outline-3;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
@apply h-2.5 w-2.5 rounded-full cursor-pointer bg-foreground-2;
margin-top: -2px;
}
.slider::-moz-range-thumb {
-webkit-appearance: none;
appearance: none;
@apply h-2.5 w-2.5 rounded-full cursor-pointer border-0 bg-foreground-2;
}
</style>
@@ -0,0 +1,48 @@
<template>
<div class="flex items-center space-x-2">
<button
:id="name"
type="button"
role="switch"
:aria-checked="modelValue"
:disabled="disabled"
class="relative inline-flex flex-shrink-0 h-[18px] w-[30px] rounded-full transition-colors ease-in-out duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-primary cursor-pointer disabled:cursor-not-allowed disabled:opacity-40"
:class="modelValue ? 'bg-primary' : 'bg-foreground-3'"
@click="toggle"
>
<span
class="pointer-events-none inline-block h-3 w-3 rounded-full mt-[3px] ml-[3px] ring-0 transition ease-in-out duration-200 bg-foreground-on-primary"
:class="modelValue ? 'translate-x-[12px]' : 'translate-x-0'"
/>
</button>
<label v-if="showLabel" :for="name" class="block label-light">
<span>{{ label || name }}</span>
</label>
</div>
</template>
<script setup lang="ts">
const props = withDefaults(
defineProps<{
modelValue?: boolean
showLabel?: boolean
name: string
label?: string
disabled?: boolean
}>(),
{
showLabel: true,
modelValue: false
}
)
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
}>()
const toggle = () => {
if (!props.disabled) {
emit('update:modelValue', !props.modelValue)
}
}
</script>
@@ -0,0 +1,18 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8 1C6.24288 1 4.81818 2.42334 4.81818 4.18004C4.81818 5.93674 6.24288 7.36008 8 7.36008C9.75712 7.36008 11.1818 5.93674 11.1818 4.18004C11.1818 2.42334 9.75712 1 8 1Z"
fill="currentColor"
/>
<path
d="M6.18182 9.17649C4.42465 9.17649 3 10.6005 3 12.3578V14.6281H13V12.3578C13 10.6005 11.5754 9.17649 9.81818 9.17649H6.18182Z"
fill="currentColor"
/>
</svg>
</template>
@@ -0,0 +1,18 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m3.75 13.5 10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75Z"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
vector-effect="non-scaling-stroke"
/>
</svg>
</template>
@@ -0,0 +1,14 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M13 11.5H15.5V13H13V15.5H11.5V13H9V11.5H11.5V9H13V11.5ZM10.5 0.75C10.9142 0.75 11.25 1.08579 11.25 1.5V2H12C13.6569 2 15 3.34315 15 5V8H13.5V6.75H1.5V12C1.5 12.8284 2.17157 13.5 3 13.5H8V15H3C1.34315 15 0 13.6569 0 12V5C8.05333e-08 3.34315 1.34315 2 3 2H4.75V1.5C4.75 1.08579 5.08579 0.75 5.5 0.75C5.91421 0.75 6.25 1.08579 6.25 1.5V2H9.75V1.5C9.75 1.08579 10.0858 0.75 10.5 0.75ZM3 3.5C2.17157 3.5 1.5 4.17157 1.5 5V5.25H13.5V5C13.5 4.17157 12.8284 3.5 12 3.5H3Z"
fill="currentColor"
/>
</svg>
</template>
@@ -0,0 +1,16 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M14.5 8C14.5 11.5899 11.5899 14.5 8 14.5C4.41015 14.5 1.5 11.5899 1.5 8C1.5 4.41015 4.41015 1.5 8 1.5C11.5899 1.5 14.5 4.41015 14.5 8ZM16 8C16 12.4183 12.4183 16 8 16C3.58172 16 0 12.4183 0 8C0 3.58172 3.58172 0 8 0C12.4183 0 16 3.58172 16 8ZM8.75 3.75C8.75 3.33579 8.41421 3 8 3C7.58579 3 7.25 3.33579 7.25 3.75V8V8.31066L7.46967 8.53033L9.72358 10.7842C10.0165 11.0771 10.4913 11.0771 10.7842 10.7842C11.0771 10.4913 11.0771 10.0165 10.7842 9.72358L8.75 7.68934V3.75Z"
fill="currentColor"
/>
</svg>
</template>
@@ -0,0 +1,16 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M12.8849 5.91851L7.25614 11.74L3.99951 8.48337L4.93388 7.549L7.24028 9.8554L11.9349 5L12.8849 5.91851Z"
fill="currentColor"
/>
</svg>
</template>
@@ -0,0 +1,14 @@
<template>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M17 2C17.7652 1.99996 18.5015 2.29233 19.0583 2.81728C19.615 3.34224 19.9501 4.06011 19.995 4.824L20 5C20.7956 5 21.5587 5.31607 22.1213 5.87868C22.6839 6.44129 23 7.20435 23 8C23.0001 9.55238 22.3984 11.0444 21.3215 12.1625C20.2446 13.2806 18.7763 13.9378 17.225 13.996L17 14H13L13.15 14.005C13.6262 14.0408 14.0738 14.2458 14.412 14.5829C14.7502 14.92 14.9567 15.3669 14.994 15.843L15 16V20C15.0002 20.5046 14.8096 20.9906 14.4665 21.3605C14.1234 21.7305 13.6532 21.9572 13.15 21.995L13 22H11C10.4954 22.0002 10.0094 21.8096 9.63945 21.4665C9.26947 21.1234 9.04284 20.6532 9.005 20.15L9 20V16C8.99984 15.4954 9.19041 15.0094 9.5335 14.6395C9.87659 14.2695 10.3468 14.0428 10.85 14.005L11 14V13C11 12.7551 11.09 12.5187 11.2527 12.3356C11.4155 12.1526 11.6397 12.0357 11.883 12.007L12 12H17C18.0609 12 19.0783 11.5786 19.8284 10.8284C20.5786 10.0783 21 9.06087 21 8C21 7.75507 20.91 7.51866 20.7473 7.33563C20.5845 7.15259 20.3603 7.03566 20.117 7.007L20 7L19.995 7.176C19.9519 7.90959 19.6411 8.60186 19.1215 9.12148C18.6019 9.6411 17.9096 9.95193 17.176 9.995L17 10H7C6.23479 10 5.49849 9.70767 4.94174 9.18272C4.38499 8.65776 4.04989 7.93989 4.005 7.176L4 7V5C3.99996 4.23479 4.29233 3.49849 4.81728 2.94174C5.34224 2.38499 6.06011 2.04989 6.824 2.005L7 2H17Z"
fill="currentColor"
/>
</svg>
</template>
@@ -0,0 +1,31 @@
<template>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5 5C5 4.46957 5.21071 3.96086 5.58579 3.58579C5.96086 3.21071 6.46957 3 7 3H17C17.5304 3 18.0391 3.21071 18.4142 3.58579C18.7893 3.96086 19 4.46957 19 5V7C19 7.53043 18.7893 8.03914 18.4142 8.41421C18.0391 8.78929 17.5304 9 17 9H7C6.46957 9 5.96086 8.78929 5.58579 8.41421C5.21071 8.03914 5 7.53043 5 7V5Z"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M19 6H20C20.5304 6 21.0391 6.21071 21.4142 6.58579C21.7893 6.96086 22 7.46957 22 8C22 9.32608 21.4732 10.5979 20.5355 11.5355C19.5979 12.4732 18.3261 13 17 13H12V15"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M10 16C10 15.7348 10.1054 15.4804 10.2929 15.2929C10.4804 15.1054 10.7348 15 11 15H13C13.2652 15 13.5196 15.1054 13.7071 15.2929C13.8946 15.4804 14 15.7348 14 16V20C14 20.2652 13.8946 20.5196 13.7071 20.7071C13.5196 20.8946 13.2652 21 13 21H11C10.7348 21 10.4804 20.8946 10.2929 20.7071C10.1054 20.5196 10 20.2652 10 20V16Z"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
@@ -0,0 +1,16 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8 14.5C8.23033 14.5 8.84266 14.2743 9.48679 12.986C9.79275 12.3741 10.0504 11.6156 10.2293 10.75H5.77067C5.94959 11.6156 6.20725 12.3741 6.51321 12.986C7.15734 14.2743 7.76967 14.5 8 14.5ZM5.55361 9.25C5.51859 8.84716 5.5 8.42956 5.5 8C5.5 7.57044 5.51859 7.15284 5.55361 6.75H10.4464C10.4814 7.15284 10.5 7.57044 10.5 8C10.5 8.42956 10.4814 8.84716 10.4464 9.25H5.55361ZM11.7574 10.75C11.5334 11.974 11.1641 13.0579 10.6914 13.9184C12.0984 13.2775 13.2369 12.1496 13.8913 10.75H11.7574ZM14.3799 9.25H11.9515C11.9834 8.84271 12 8.42523 12 8C12 7.57477 11.9834 7.15729 11.9515 6.75H14.3799C14.4587 7.15451 14.5 7.57243 14.5 8C14.5 8.42756 14.4587 8.84549 14.3799 9.25ZM4.04854 9.25H1.62008C1.54128 8.84549 1.5 8.42756 1.5 8C1.5 7.57243 1.54128 7.15451 1.62008 6.75H4.04854C4.01659 7.15729 4 7.57477 4 8C4 8.42523 4.01659 8.84271 4.04854 9.25ZM2.10868 10.75H4.2426C4.46661 11.974 4.83588 13.0579 5.30864 13.9184C3.90156 13.2775 2.7631 12.1496 2.10868 10.75ZM5.77067 5.25H10.2293C10.0504 4.38438 9.79275 3.6259 9.48679 3.01397C8.84266 1.72571 8.23033 1.5 8 1.5C7.76967 1.5 7.15734 1.72571 6.51321 3.01397C6.20725 3.6259 5.94959 4.38438 5.77067 5.25ZM11.7574 5.25H13.8913C13.2369 3.85044 12.0984 2.72251 10.6914 2.08162C11.1641 2.94207 11.5334 4.02603 11.7574 5.25ZM5.30864 2.08162C4.83588 2.94207 4.46661 4.02603 4.2426 5.25H2.10868C2.7631 3.85044 3.90156 2.72251 5.30864 2.08162ZM8 0C12.4183 0 16 3.58172 16 8C16 12.4183 12.4183 16 8 16C3.58172 16 0 12.4183 0 8C0 3.58172 3.58172 0 8 0Z"
fill="currentColor"
/>
</svg>
</template>
@@ -0,0 +1,38 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.06301 2.75L13.2511 9.93813L11.4539 11.7354C10.9854 12.2227 10.4243 12.6116 9.80367 12.8795C9.183 13.1473 8.51514 13.2887 7.83918 13.2953C7.16321 13.302 6.49271 13.1737 5.86691 12.9181C5.2411 12.6625 4.67257 12.2846 4.19456 11.8066C3.71656 11.3286 3.33869 10.76 3.08306 10.1342C2.82743 9.50843 2.69918 8.83793 2.70581 8.16196C2.71244 7.486 2.85382 6.81814 3.12167 6.19747C3.38953 5.5768 3.77848 5.01579 4.26576 4.54725L6.06301 2.75Z"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M1 15L4.0625 11.9375"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M10.625 1L7.5625 4.0625"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M15 5.375L11.9375 8.4375"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
@@ -0,0 +1,5 @@
<template>
<svg width="18" height="19" viewBox="0 0 18 19" xmlns="http://www.w3.org/2000/svg">
<path d="M4.33333 17L1 1L16 9.25806L8.5 10.5L4.33333 17Z" stroke="#2563eb" />
</svg>
</template>
@@ -0,0 +1,16 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M3 2.5C2.17157 2.5 1.5 3.17157 1.5 4V10C1.5 10.8284 2.17157 11.5 3 11.5H3.75H4.5V12.25V13.1004L6.56675 11.6378L6.76146 11.5H7H13C13.8284 11.5 14.5 10.8284 14.5 10V4C14.5 3.17157 13.8284 2.5 13 2.5H3ZM0 4C0 2.34315 1.34315 1 3 1H13C14.6569 1 16 2.34315 16 4V10C16 11.6569 14.6569 13 13 13H7.23854L4.18325 15.1622L3 15.9996V14.55V13C1.34315 13 0 11.6569 0 10V4Z"
fill="currentColor"
/>
</svg>
</template>
@@ -0,0 +1,16 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6 1C5.0335 1 4.25 1.7835 4.25 2.75V4H3C1.89543 4 1 4.89543 1 6V13C1 14.1046 1.89543 15 3 15H13C14.1046 15 15 14.1046 15 13V6C15 4.89543 14.1046 4 13 4H11.75V2.75C11.75 1.7835 10.9665 1 10 1H6ZM10.25 4V2.75C10.25 2.61193 10.1381 2.5 10 2.5H6C5.86193 2.5 5.75 2.61193 5.75 2.75V4H10.25ZM3 5.5H13C13.2761 5.5 13.5 5.72386 13.5 6V7H2.5V6C2.5 5.72386 2.72386 5.5 3 5.5ZM2.5 8.5V13C2.5 13.2761 2.72386 13.5 3 13.5H13C13.2761 13.5 13.5 13.2761 13.5 13V8.5H9V10H7V8.5H2.5Z"
fill="currentColor"
/>
</svg>
</template>
@@ -0,0 +1,31 @@
<template>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M7.8335 7.83337H7.00016C6.55814 7.83337 6.13421 8.00897 5.82165 8.32153C5.50909 8.63409 5.3335 9.05801 5.3335 9.50004V17C5.3335 17.4421 5.50909 17.866 5.82165 18.1786C6.13421 18.4911 6.55814 18.6667 7.00016 18.6667H14.5002C14.9422 18.6667 15.3661 18.4911 15.6787 18.1786C15.9912 17.866 16.1668 17.4421 16.1668 17V16.1667"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M18.9875 7.48759C19.3157 7.15938 19.5001 6.71424 19.5001 6.25009C19.5001 5.78594 19.3157 5.34079 18.9875 5.01259C18.6593 4.68438 18.2142 4.5 17.75 4.5C17.2858 4.5 16.8407 4.68438 16.5125 5.01259L9.5 12.0001V14.5001H12L18.9875 7.48759Z"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M15.3335 6.16663L17.8335 8.66663"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
@@ -0,0 +1,59 @@
<template>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M17.8438 11.1563L21.9062 7.21875V14.2187L17.9062 18.2188L17.8438 11.1563Z"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M5.19123 15.2186L2.15624 18.2188L2.09375 11.1563L6.15626 7.21875V11.7187"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M14.9062 7.71875L18.4062 3.21875H11.9062L7.90625 7.71875H14.9062Z"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M13.9062 11.7188H5.40625V20.7188H13.9062V11.7188Z"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M11.6562 8.20312V10.9687"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M16.9062 5.46875L20.1563 5.46875L20.1563 8.71875"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M14.6562 14.4531L17.0313 14.4686"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
@@ -0,0 +1,16 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M3 2.5C2.17157 2.5 1.5 3.17157 1.5 4V10C1.5 10.8284 2.17157 11.5 3 11.5H3.75H4.5V12.25V13.1004L6.56675 11.6378L6.76146 11.5H7H13C13.8284 11.5 14.5 10.8284 14.5 10V4C14.5 3.17157 13.8284 2.5 13 2.5H3ZM0 4C0 2.34315 1.34315 1 3 1H13C14.6569 1 16 2.34315 16 4V10C16 11.6569 14.6569 13 13 13H7.23854L4.18325 15.1622L3 15.9996V14.55V13C1.34315 13 0 11.6569 0 10V4Z"
fill="currentColor"
/>
</svg>
</template>
@@ -0,0 +1,14 @@
<template>
<svg
width="18"
height="16"
viewBox="0 0 18 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
stroke="currentColor"
>
<path
d="M11.4998 3.75H12.2498V3V2.16667C12.2498 1.66177 12.6582 1.25 13.1665 1.25H15.6665C16.1714 1.25 16.5832 1.65832 16.5832 2.16667V5.5C16.5832 6.0049 16.1749 6.41667 15.6665 6.41667H13.1665C12.6641 6.41667 12.2498 6.00245 12.2498 5.5V4.66667V3.91667H11.4998H9.83317H9.08317V4.66667V10.5083C9.08317 11.3725 9.79396 12.0833 10.6582 12.0833H11.4998H12.2498V11.3333V10.5C12.2498 9.9951 12.6582 9.58333 13.1665 9.58333H15.6665C16.1714 9.58333 16.5832 9.99165 16.5832 10.5V13.8333C16.5832 14.3382 16.1749 14.75 15.6665 14.75H13.1665C12.6616 14.75 12.2498 14.3417 12.2498 13.8333V13V12.25H11.4998H10.6582C9.69738 12.25 8.9165 11.4691 8.9165 10.5083V4.66667V3.91667H8.1665H6.49984H5.74984V4.66667V5.5C5.74984 6.0049 5.34152 6.41667 4.83317 6.41667H2.33317C1.82827 6.41667 1.4165 6.00835 1.4165 5.5V2.16667C1.4165 1.66421 1.83072 1.25 2.33317 1.25H4.8415C5.3464 1.25 5.75817 1.65832 5.75817 2.16667V3V3.75H6.50817H11.4998Z"
/>
</svg>
</template>
@@ -0,0 +1,24 @@
<template>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 16H16V20"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M19.458 11.042C20.318 8.67599 20.18 6.46199 18.858 5.14199C16.586 2.86799 11.673 4.09699 7.88503 7.88499C4.09703 11.673 2.86803 16.586 5.14103 18.859C7.36803 21.085 12.128 19.952 15.881 16.344"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
@@ -0,0 +1,16 @@
<template>
<svg
width="38"
height="37"
viewBox="0 0 38 37"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M30.5695 7.84167C30.4459 7.87905 30.4465 8.05327 30.5704 8.08974L30.6008 8.09869C32.0949 8.53857 33.2612 9.70436 33.6956 11.1923C33.7314 11.3148 33.9059 11.3148 33.9425 11.1925C34.386 9.71452 35.5462 8.55207 37.0303 8.10336L37.0652 8.09282C37.1885 8.05553 37.1877 7.88161 37.064 7.84544L37.0283 7.835C35.5338 7.39799 34.3679 6.23128 33.938 4.74249C33.9028 4.62035 33.7287 4.62043 33.6922 4.7422C33.249 6.21893 32.0893 7.38202 30.6065 7.83048L30.5695 7.84167ZM31.4317 23.8166L31.4317 14.0863H31.4318V10.3168H31.4317V10.3162L26.998 10.3162V10.3168H17.2469L11.255 16.849V26.5794H11.2549V30.349H11.255V30.3494H15.6888V30.349L25.4397 30.349L31.4317 23.8166ZM26.998 26.5794L26.998 14.0863H15.6888L15.6888 26.5794H26.998Z"
fill="currentColor"
/>
</svg>
</template>
@@ -0,0 +1,28 @@
<template>
<svg
width="800px"
height="800px"
viewBox="0 0 36 36"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--twemoji"
preserveAspectRatio="xMidYMid meet"
>
<path
stroke="#000"
fill="none"
stroke-width="2"
d="M36 11a2 2 0 0 0-4 0s-.011 3.285-3 3.894V12c0-6.075-4.925-11-11-11S7 5.925 7 12v3.237C1.778 16.806 0 23.231 0 27a2 2 0 0 0 4 0s.002-3.54 3.336-3.958C7.838 27.883 8.954 33 11 33h1c4 0 3 2 7 2s3-2 6-2s2.395 2 6 2a3 3 0 0 0 3-3c0-.675-2.274-4.994-3.755-9.268C35.981 21.348 36 14.58 36 11z"
></path>
<circle fill="#000" stroke-width="1" cx="13" cy="12" r="2"></circle>
<circle fill="#000" cx="23" cy="12" r="3"></circle>
<path
stroke="#000"
stroke-width="2"
fill="none"
d="M22.192 19.491c2.65 1.987 3.591 5.211 2.1 7.199c-1.491 1.988-4.849 1.988-7.5 0c-2.65-1.987-3.591-5.211-2.1-7.199c1.492-1.989 4.849-1.988 7.5 0z"
></path>
</svg>
</template>
@@ -0,0 +1,52 @@
<template>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8 13V4.5C8 4.10218 8.15804 3.72064 8.43934 3.43934C8.72064 3.15804 9.10218 3 9.5 3C9.89782 3 10.2794 3.15804 10.5607 3.43934C10.842 3.72064 11 4.10218 11 4.5V12"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M11 11.5V9.5C11 9.10218 11.158 8.72064 11.4393 8.43934C11.7206 8.15804 12.1022 8 12.5 8C12.8978 8 13.2794 8.15804 13.5607 8.43934C13.842 8.72064 14 9.10218 14 9.5V12"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M14 10.5C14 10.1022 14.158 9.72064 14.4393 9.43934C14.7206 9.15804 15.1022 9 15.5 9C15.8978 9 16.2794 9.15804 16.5607 9.43934C16.842 9.72064 17 10.1022 17 10.5V12"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M17.0002 11.5C17.0002 11.1022 17.1582 10.7206 17.4395 10.4393C17.7208 10.158 18.1024 10 18.5002 10C18.898 10 19.2795 10.158 19.5608 10.4393C19.8421 10.7206 20.0002 11.1022 20.0002 11.5V16C20.0002 17.5913 19.368 19.1174 18.2428 20.2426C17.1176 21.3679 15.5915 22 14.0002 22H12.0002H12.2082C11.2145 22.0002 10.2364 21.7535 9.36157 21.2823C8.48676 20.811 7.7427 20.1299 7.19618 19.3L7.00018 19C6.68818 18.521 5.59318 16.612 3.71418 13.272C3.52263 12.9315 3.47147 12.5298 3.57157 12.1522C3.67166 11.7745 3.91513 11.4509 4.25018 11.25C4.60706 11.0359 5.02526 10.9471 5.43834 10.9978C5.85143 11.0486 6.23572 11.2359 6.53018 11.53L8.00018 13"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M2.54102 5.59497C3.30843 5.03394 4.13302 4.55561 5.00102 4.16797"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M14 3.45703C15.32 3.81103 16.558 4.35903 17.685 5.06903"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
@@ -0,0 +1,66 @@
<template>
<svg
width="25"
height="24"
viewBox="0 0 25 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.5 13V4.5C8.5 4.10218 8.65804 3.72064 8.93934 3.43934C9.22064 3.15804 9.60218 3 10 3C10.3978 3 10.7794 3.15804 11.0607 3.43934C11.342 3.72064 11.5 4.10218 11.5 4.5V12"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M11.5 11.5V9.5C11.5 9.10218 11.658 8.72064 11.9393 8.43934C12.2206 8.15804 12.6022 8 13 8C13.3978 8 13.7794 8.15804 14.0607 8.43934C14.342 8.72064 14.5 9.10218 14.5 9.5V12"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M14.5 10.5C14.5 10.1022 14.658 9.72064 14.9393 9.43934C15.2206 9.15804 15.6022 9 16 9C16.3978 9 16.7794 9.15804 17.0607 9.43934C17.342 9.72064 17.5 10.1022 17.5 10.5V12"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M17.5002 11.5C17.5002 11.1022 17.6582 10.7206 17.9395 10.4393C18.2208 10.158 18.6024 10 19.0002 10C19.398 10 19.7795 10.158 20.0608 10.4393C20.3421 10.7206 20.5002 11.1022 20.5002 11.5V16C20.5002 17.5913 19.868 19.1174 18.7428 20.2426C17.6176 21.3679 16.0915 22 14.5002 22H12.5002H12.7082C11.7145 22.0002 10.7364 21.7535 9.86157 21.2823C8.98676 20.811 8.2427 20.1299 7.69618 19.3L7.50018 19C7.18818 18.521 6.09318 16.612 4.21418 13.272C4.02263 12.9315 3.97147 12.5298 4.07157 12.1522C4.17166 11.7745 4.41513 11.4509 4.75018 11.25C5.10706 11.0359 5.52526 10.9471 5.93834 10.9978C6.35143 11.0486 6.73572 11.2359 7.03018 11.53L8.50018 13"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M5.5 3L4.5 2"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M4.5 7H3.5"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M14.5 3L15.5 2"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M15.5 6H16.5"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
@@ -0,0 +1,73 @@
<template>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clip-path="url(#clip0_614_5022)">
<path
d="M11 14V5.5C11 5.10218 11.158 4.72064 11.4393 4.43934C11.7206 4.15804 12.1022 4 12.5 4C12.8978 4 13.2794 4.15804 13.5607 4.43934C13.842 4.72064 14 5.10218 14 5.5V13"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M14 12.5V10.5C14 10.303 14.0388 10.108 14.1142 9.92597C14.1896 9.74399 14.3001 9.57863 14.4393 9.43934C14.5786 9.30005 14.744 9.18956 14.926 9.11418C15.108 9.0388 15.303 9 15.5 9C15.697 9 15.892 9.0388 16.074 9.11418C16.256 9.18956 16.4214 9.30005 16.5607 9.43934C16.6999 9.57863 16.8104 9.74399 16.8858 9.92597C16.9612 10.108 17 10.303 17 10.5V13"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M17 11.5C17 11.1022 17.158 10.7206 17.4393 10.4393C17.7206 10.158 18.1022 10 18.5 10C18.8978 10 19.2794 10.158 19.5607 10.4393C19.842 10.7206 20 11.1022 20 11.5V13"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M20.0002 12.5C20.0002 12.1022 20.1582 11.7206 20.4395 11.4393C20.7208 11.158 21.1024 11 21.5002 11C21.898 11 22.2795 11.158 22.5608 11.4393C22.8421 11.7206 23.0002 12.1022 23.0002 12.5V17C23.0002 18.5913 22.368 20.1174 21.2428 21.2426C20.1176 22.3679 18.5915 23 17.0002 23H15.0002H15.2082C14.2145 23.0002 13.2364 22.7535 12.3616 22.2823C11.4868 21.811 10.7427 21.1299 10.1962 20.3C10.1306 20.2002 10.0653 20.1002 10.0002 20C9.68818 19.521 8.59318 17.612 6.71418 14.272C6.52263 13.9315 6.47147 13.5298 6.57157 13.1522C6.67166 12.7745 6.91513 12.4509 7.25018 12.25C7.60706 12.0359 8.02526 11.9471 8.43834 11.9978C8.85143 12.0486 9.23572 12.2359 9.53018 12.53L11.0002 14"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M0.750039 6.36034L3.75004 6.36034L3.75004 9.36034"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M3.75068 6.36101C1.75068 8.36101 -0.249322 8.89698 2.25068 11.361"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M5.24999 0.750039L5.24999 3.75004L8.24999 3.75004"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M5.24969 3.7497C7.24969 1.7497 7.78565 -0.250299 10.2497 2.2497"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</g>
<defs>
<clipPath id="clip0_614_5022">
<rect width="24" height="24" fill="white" />
</clipPath>
</defs>
</svg>
</template>
@@ -0,0 +1,90 @@
<template>
<svg
width="36"
height="36"
viewBox="0 0 36 36"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1.5 23.9655V3.00003L10.5 10.8621V31.5L1.5 23.9655Z"
stroke="#CBD5E1"
stroke-linejoin="round"
/>
<path
d="M1.5 3.00003L22.5 1.50003"
stroke="#CBD5E1"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M1.5 24L22.5 22.5"
stroke="#CBD5E1"
stroke-linecap="round"
stroke-linejoin="round"
stroke-dasharray="2 2"
/>
<path
d="M10.5 31.5L31.5 30"
stroke="#CBD5E1"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M10.5 11.25L31.5 9.75003"
stroke="#CBD5E1"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M22.5 1.50003L31.5 9.34814V30"
stroke="#CBD5E1"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M22.5 1.50003V22.5"
stroke="#CBD5E1"
stroke-linecap="round"
stroke-linejoin="round"
stroke-dasharray="2 2"
/>
<path
d="M22.5 22.5L31.5 30"
stroke="#CBD5E1"
stroke-linecap="round"
stroke-linejoin="round"
stroke-dasharray="2 2"
/>
<path
d="M6.04926 17.378L27.0493 15.878"
stroke="#3B82F6"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M4.37823 19.3902L7.10927 15.3896"
stroke="#3B82F6"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M25.6345 17.8902L28.3656 13.8896"
stroke="#3B82F6"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M4.96887 14.093L7.03114 20.663"
stroke="#3B82F6"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M26.2251 12.593L28.2874 19.163"
stroke="#3B82F6"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
@@ -0,0 +1,68 @@
<template>
<svg
width="36"
height="36"
viewBox="0 0 36 36"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3 25.4655V4.50003L12 12.3621V33L3 25.4655Z"
stroke="#CBD5E1"
stroke-linejoin="round"
/>
<path
d="M3 4.50003L24 3.00003"
stroke="#CBD5E1"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M3 25.5L24 24"
stroke="#CBD5E1"
stroke-linecap="round"
stroke-linejoin="round"
stroke-dasharray="2 2"
/>
<path
d="M12 33L33 31.5"
stroke="#CBD5E1"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M12 12.75L33 11.25"
stroke="#CBD5E1"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M24 3.00003L33 10.8481V31.5"
stroke="#CBD5E1"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M24 3.00003V24"
stroke="#CBD5E1"
stroke-linecap="round"
stroke-linejoin="round"
stroke-dasharray="2 2"
/>
<path
d="M24 24L33 31.5"
stroke="#CBD5E1"
stroke-linecap="round"
stroke-linejoin="round"
stroke-dasharray="2 2"
/>
<path
d="M3 25.5L33 10.5"
stroke="#3B82F6"
stroke-linecap="round"
stroke-linejoin="round"
/>
<circle cx="3" cy="25.5" r="2.25" fill="#3B82F6" />
<circle cx="33" cy="10.5" r="2.25" fill="#3B82F6" />
</svg>
</template>

Some files were not shown because too many files have changed in this diff Show More