Compare commits

...

45 Commits

Author SHA1 Message Date
izzy lyseggen 42f3ae8490 ci: update tag parsing and ui build (#47)
* ci: update tag parsing and ui build

* chore(deps): stackoverflow and i are besties

testing out this workaround - will do a beta release

* ci: deploy filter tags

* ci: fix release label tag
2022-06-10 14:51:16 +01:00
izzy lyseggen 479cd9584a fix(ci): build on node 16 (#46)
* chore(ui): revert package lock

* chore(ui): upgrade object loader again (???)

* ci: omg i'm so dumb (node 16)
2022-06-10 09:40:30 +01:00
izzy lyseggen ec250fe6a5 fix(ui): revert package upgrades (regeneratorRuntime err) (#45)
* chore: del some old files

* chore(ui): revert to old packages

* chore(ui): updgrade speckle object loader
2022-06-10 07:24:52 +01:00
izzy lyseggen 7d73ebf7d0 feat(converter): edges and preserved faces on to_speckle (#44)
* chore: update packages

* chore: add debug 2022 vscode task

* feat(converter): ngons and edges on `to_speckle`

* feat(converter): big improvements 🥳

* feat(convert): add triangulation for faces with holes

you'll lose the true face with hole on receive, but this is the best
intermediary solution that won't break other connectors
2022-06-09 17:05:09 +01:00
izzy lyseggen a965065e62 style: formattinggggg (#39) 2022-03-29 15:49:22 +01:00
izzy lyseggen c9c9ecf5c6 chore(metrics): update oneclick reporting (#38) 2022-03-22 11:32:35 +00:00
izzy lyseggen 9b5c043029 feat(ui): saved streams & 1-click send (#37)
* feat(ui): saved streams and wip 1 click

* style(connector): remove some puts statements

* feat(accounts): default acct helper

* feat(ui): more 1 click send

need a way to wait for connector launch...

* fix(ui): waiting quick send!

* feat(ui): view in web from toast notif

note to self: the one in frontend is kinda ugly 😓 
went with outine btn for now bc it was better than that big ol dark gray btn

* fix: turn off dev

i always fkn do this i needa just use an env file gdi

* feat(ui): notify ui of oneclick send

* feat(metrics): remove old and use the new!
2022-03-08 10:15:21 +00:00
izzy lyseggen 0eb835bed0 fix(convert): add "cm" to unit strings and skip revit params (#34)
* feat(convert): skip revit params

cant do anything w them so skipping for now for efficiency

* fix(convert): add "cm" to unit strings
2022-02-23 10:11:48 +00:00
Dimitrie Stefanescu 7a7ce8ff3d feat(ui): css-ing around + notifications on actions (#33) 2022-02-14 21:05:03 +00:00
izzy lyseggen 473f0890fe fix(accounts): manually grab user dir (#30)
issue with i assume company managed computers where the "current user"
is pointing to somewhere else (maybe the admin who installed sketchup?)

to solve, I'm manually grabbing the user dir from the pwd

closes Auth error and no streams found #27
2022-01-19 12:01:23 +00:00
izzy lyseggen a8955a435f chore(deps): objectloader 2.3.0 and update others (#29)
closes 📨 Receiving fails if objects have nulls #28
2022-01-13 07:21:33 -08:00
izzylys 80f25eb1a2 fix(batching): simplify slightly
also fix a transition vs transition-group warning
2022-01-12 12:26:37 -08:00
izzylys 5716f92fbf fix(accts): sqlite gem loading 2022-01-06 09:13:45 -08:00
izzylys 63e7aaa661 feat(ui): send progress 2022-01-05 12:15:56 -08:00
izzylys 7234a4be79 feat(auth): log some info to see what's goin on 2022-01-05 12:15:31 -08:00
izzylys e1cceb9bba chore: readme typo and some clenaup 2022-01-05 07:09:53 -08:00
izzy lyseggen 30157b5ac2 feat(ui): animations were TOO FAST 4 ALA 🏃‍♀️ 2021-12-08 15:49:50 +00:00
izzy lyseggen f9b1628c18 Merge pull request #26 from specklesystems/izzy/ui-tweaks
feat(ui): cute lil animations 4 smooth commitmsgs
2021-12-08 15:33:21 +00:00
izzy lyseggen 9429a11fa7 feat(ui): cute lil animations 4 smooth commitmsgs 2021-12-08 15:31:53 +00:00
izzy lyseggen af4c6d9f71 Merge pull request #24 from specklesystems/izzy/nested-def-fix
fix(convert): `to_native` nested block fix
2021-12-03 18:04:22 +00:00
izzy lyseggen a4f23e060c fix(convert): to_native nested block fix
block definitions nested within definitions that also contain meshes
alongside those definitions were not getting picked up

fixes #23
2021-12-03 18:00:53 +00:00
izzy lyseggen d2099c98e0 Merge pull request #22 from specklesystems/izzy/ui
feat(ui): add commit messages
2021-12-03 11:40:01 +00:00
izzy lyseggen c21b51135a feat(ui): add commit messages
closes UI Improvements #4
2021-12-03 11:39:18 +00:00
izzy lyseggen c5c8bf6b6f Merge pull request #21 from specklesystems/izzy/transforms-rework
feat(converter): new transform style
2021-12-02 17:30:32 +00:00
izzy lyseggen d9a92e90ec feat(convert): receive both old and new transform blocsk 2021-12-02 17:28:38 +00:00
izzy lyseggen 79ae201646 feat(convert): new transform to speckle 2021-12-01 17:58:39 +00:00
izzy lyseggen 01b32d2558 Merge pull request #19 from specklesystems/izzy/platform-fix
fix(connector): speckle dir on macos
2021-11-24 11:34:54 +00:00
izzy lyseggen b3067aa346 fix(connector): speckle dir on macos
thanks @ruggieroguida !
addresses #18
2021-11-23 16:13:03 +00:00
izzy lyseggen d1349c5df1 Merge pull request #16 from specklesystems/izzy/kill-insertion-pt
feat(converter): depreciate insertion point
2021-11-17 15:30:03 +00:00
izzy lyseggen d44365a6d4 feat(converter): depreciate insertion point 2021-11-17 15:29:16 +00:00
izzy lyseggen c8c94184df Merge pull request #15 from specklesystems/izzy/blocks
fix(converter): receiving blocks
2021-11-16 15:57:51 +00:00
izzy lyseggen 62c436131c fix(converter): receiving blocks
WE OUT HERE BOIIIII
📦 Block Receive Failing #10
2021-11-16 15:52:52 +00:00
izzy lyseggen ac3579de46 Merge pull request #14 from specklesystems/izzy/blocks
fix(converter): revert mesh squish & fix closed polylines
2021-11-16 10:55:01 +00:00
izzy lyseggen e7a72b25d7 fix(converter): revert mesh squish 2021-11-16 10:54:00 +00:00
izzy lyseggen 6d155d987a fix(converter): only return component def if exists 2021-11-15 17:30:47 +00:00
izzy lyseggen e4a78e4c6e feat(converter): add explicit brep to native mesh 2021-11-15 17:30:27 +00:00
izzy lyseggen 35c819dab0 fix(convert): closed polylines 2021-11-15 16:55:50 +00:00
izzy lyseggen 3001a4af48 Merge pull request #13 from specklesystems/izzy/mesh-experiments
feat(converter): improved meshes!
2021-11-15 14:59:39 +00:00
izzy lyseggen eeaa69e7b4 feat(convert): mesh to native polycount cleanup 2021-11-15 14:58:51 +00:00
izzy lyseggen 5717022293 chore: pls leave me alone rubocop 2021-11-15 14:51:18 +00:00
izzy lyseggen b5c1ad1ea3 feat(convert): additional mesh improvements 2021-11-12 11:34:39 +00:00
izzy lyseggen ea56717594 fix(convert): simple line fixes 2021-11-12 11:33:58 +00:00
izzy lyseggen 2ffb4219fc feat(convert): initial mesh squish fix
🍙 Remove duplicate vertices when building meshes `to_speckle` #12
2021-11-11 17:48:56 +00:00
izzy lyseggen 08843689ec feat(ui): move read receipt to end of receive method 2021-11-10 16:22:33 +00:00
izzy lyseggen 299109505c feat(ui): Sketchup: Add Read receipts call #7 2021-11-10 16:06:54 +00:00
38 changed files with 26700 additions and 7261 deletions
+13 -17
View File
@@ -9,18 +9,15 @@ orbs:
jobs:
build-ui:
docker:
- image: 'circleci/node:14'
- image: "circleci/node:16"
steps:
- checkout
- run:
command: 'rm package-lock.json'
working_directory: 'ui'
command: "npm install"
working_directory: "ui"
- run:
command: 'npm install'
working_directory: 'ui'
- run:
command: 'npm run build'
working_directory: 'ui'
command: "npm run build"
working_directory: "ui"
- persist_to_workspace:
root: ./
paths:
@@ -43,13 +40,12 @@ jobs:
shell: powershell.exe
command:
| # If no tag, use 0.0.0.1 and don't make any YML (for testing only!)
$tag = if([string]::IsNullOrEmpty($env:CIRCLE_TAG)) { "0.0.1" } else { $env:CIRCLE_TAG }
$semver = $tag.replace("-beta","")
$version = "$($semver).$($env:CIRCLE_BUILD_NUM)"
$channel = "latest"
if($tag -like "*-beta") { $channel = "beta" }
# only create the yml if we have a tag
New-Item -Force "speckle-sharp-ci-tools/Installers/sketchup/$channel.yml" -ItemType File -Value "version: $version"
$tag = if([string]::IsNullOrEmpty($env:CIRCLE_TAG)) { "0.0.0" } else { $env:CIRCLE_TAG }
$semver = if($tag.Contains('/')) {$tag.Split("/")[1] } else { $tag }
$ver = if($semver.Contains('-')) {$semver.Split("-")[0] } else { $semver }
$channel = if($semver.Contains('-')) {$semver.Split("-")[1] } else { "latest" }
$version = "$($ver).$($env:CIRCLE_BUILD_NUM)"
New-Item -Force "speckle-sharp-ci-tools/Installers/sketchup/$channel.yml" -ItemType File -Value "version: $semver"
echo $version
python patch_version.py $version
speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\sketchup.iss
@@ -62,7 +58,7 @@ jobs:
docker:
- image: cimg/base:2021.01
steps:
- run:
- run:
name: Clone
command: git clone https://$GITHUB_TOKEN@github.com/specklesystems/speckle-sharp-ci-tools.git speckle-sharp-ci-tools
- persist_to_workspace:
@@ -120,6 +116,6 @@ workflows:
- build-connector
filters:
tags:
only: /[0-9]+(\.[0-9]+)*/
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
branches:
ignore: /.*/ # For testing only! /ci\/.*/
+3
View File
@@ -26,6 +26,9 @@ Style/DocumentationMethod:
Metrics/AbcSize:
Enabled: false
Metrics/BlockLength:
Enabled: false
Metrics/ClassLength:
Enabled: false
+10 -1
View File
@@ -12,6 +12,15 @@
"command": "&'C:/Program Files/SketchUp/SketchUp 2021/SketchUp.exe' -rdebug 'ide port=7000'",
},
"problemMatcher": []
}
},
{
"label": "Debug SketchUp 2022",
"type": "shell",
"command": "open -a '/Applications/SketchUp 2022/SketchUp.app' --args -rdebug 'ide port=7000'",
"windows": {
"command": "&'C:/Program Files/SketchUp/SketchUp 2022/SketchUp.exe' -rdebug 'ide port=7000'",
},
"problemMatcher": []
},
]
}
+3 -3
View File
@@ -77,15 +77,15 @@ This should have also have set up the package installer `gem` and interactive ru
gem -v
irb -v
Let's also install our first gem `bundle` which is a package manager that will help us with development.
Let's also install our first gem `bundler` which is a package manager that will help us with development.
gem install bundle
gem install bundler
### Editor Setup
Clone this repo and run:
bundle install
bundler install
This will install all the necessary packages for the connector.
-15
View File
@@ -1,15 +0,0 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require "bundler/setup"
require "speckle_connector"
# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.
# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start
require "irb"
IRB.start(__FILE__)
-8
View File
@@ -1,8 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -vx
bundle install
# Do any other automated setup that you need to do here
-8
View File
@@ -1,8 +0,0 @@
# frozen_string_literal: true
require_relative "speckle_connector/version"
module SpeckleConnector
class Error < StandardError; end
# Your code goes here...
end
-5
View File
@@ -1,5 +0,0 @@
# frozen_string_literal: true
module SpeckleConnector
VERSION = "0.1.0"
end
-59
View File
@@ -1,59 +0,0 @@
require "net/http"
require "json"
require "uri"
server = "https://latest.speckle.dev"
token = "1dc2e3330a56371dc9011e5bed406264c9e65dd355"
limit = 20
streams_list = "
query User {
user {
id
email
name
bio
company
avatar
verified
profiles
role
streams(limit: #{limit}) {
totalCount
cursor
items {
id
name
description
isPublic
createdAt
updatedAt
collaborators {
id
name
role
}
}
}
}
}
"
endpoint = URI("#{server}/graphql")
res =
::Net::HTTP.start(endpoint.host, endpoint.port, use_ssl: true) do |http|
req = ::Net::HTTP::Post.new(endpoint)
req["Content-Type"] = "application/json"
req["Authorization"] = "Bearer #{token}"
# The body needs to be a JSON string.
req.body = ::JSON[{ query: streams_list }]
puts(req.body)
http.request(req)
end
streams = ::JSON.parse(res.body)["data"]["user"]["streams"]["items"]
puts(streams)
+15 -12
View File
@@ -5,7 +5,6 @@ begin
rescue LoadError
# ty msp-greg! https://github.com/MSP-Greg/SUMisc/releases/tag/sqlite3-mingw-1
Gem.install(File.join(File.dirname(File.expand_path(__FILE__)), "utils/sqlite3-1.4.2.mspgreg-x64-mingw32.gem"))
else
require("sqlite3")
end
@@ -24,6 +23,11 @@ module SpeckleSystems::SpeckleConnector
rows.map { |row| JSON.parse(row[1]) }
end
def self.default_account
accts = load_accounts
accts.select { |acc| acc["isDefault"] }[0] || accts[0]
end
def self.get_suuid
dir = _get_speckle_dir
suuid_path = File.join(dir, "suuid")
@@ -33,22 +37,21 @@ module SpeckleSystems::SpeckleConnector
end
def self._get_speckle_dir
platform = RUBY_PLATFORM.downcase
speckle_dir =
if platform =~ (/mingw/) || platform =~ (/win/)
# win
File.join(Dir.home, "AppData/Roaming/Speckle")
elsif platform =~ /linux/
# linux
File.expand_path("~/.local/share/Speckle")
case Sketchup.platform
# sometimes Dir.home on windows points somewhere else bc I guess it's picking up a higher level user?
when :platform_win then File.join(Dir.pwd[%r{^((?:[^/]*/){3})}], "AppData/Roaming/Speckle")
when :platform_osx then File.join(Dir.home, ".config", "Speckle")
else
# mac
File.expand_path("~/.config/Speckle")
nil
end
return speckle_dir if Dir.exist?(speckle_dir)
raise(IOError, "No Speckle Directory exists. Please read the guide to get Speckle set up on your machine: \nhttps://speckle.guide/user/manager.html")
raise(
IOError,
"No Speckle Directory exists. Please read the guide to get Speckle set up on your machine: \nhttps://speckle.guide/user/manager.html"
)
end
end
end
@@ -3,17 +3,19 @@ require "speckle_connector/converter/to_speckle"
require "speckle_connector/converter/to_native"
module SpeckleSystems::SpeckleConnector
SKETCHUP_UNIT_STRINGS = { "m" => "m", "mm" => "mm", "ft" => "feet", "in" => "inch", "yd" => "yard" }.freeze
SKETCHUP_UNIT_STRINGS = { "m" => "m", "mm" => "mm", "ft" => "feet", "in" => "inch", "yd" => "yard", "cm" => "cm" }.freeze
public_constant :SKETCHUP_UNIT_STRINGS
class ConverterSketchup
include ToNative
include ToSpeckle
attr_accessor :units, :component_defs
attr_accessor :units, :component_defs, :registry, :entity_observer
def initialize(units = "m")
@units = units
@component_defs = {}
# @registry = Sketchup.active_model.attribute_dictionary("speckle_id_registry", true)
# @entity_observer = SpeckleEntityObserver.new
end
def convert_to_speckle(obj)
+21
View File
@@ -0,0 +1,21 @@
module SpeckleSystems::SpeckleConnector
class SpeckleEntityObserver < Sketchup::EntityObserver
attr_accessor :registry
def initialize
super()
@registry = Sketchup.active_model.attribute_dictionary("speckle_id_registry", true)
end
def onEraseEntity(entity)
app_id = entity.get_attribute("speckle", "applicationId")
return if app_id.nil?
p(app_id)
@registry.delete_key(app_id)
p(@registry)
end
end
end
+121 -42
View File
@@ -6,6 +6,9 @@ module SpeckleSystems::SpeckleConnector::ToNative
if can_convert_to_native(obj)
convert_to_native(obj, Sketchup.active_model.entities)
elsif obj.is_a?(Hash) && obj.key?("speckle_type")
return if is_ignored_speckle_type(obj)
puts(">>> Found #{obj["speckle_type"]}: #{obj["id"]}")
props = obj.keys.filter_map { |key| key unless key.start_with?("_") }
props.each { |prop| traverse_commit_object(obj[prop]) }
elsif obj.is_a?(Hash)
@@ -24,37 +27,91 @@ module SpeckleSystems::SpeckleConnector::ToNative
"Objects.Geometry.Line",
"Objects.Geometry.Polyline",
"Objects.Geometry.Mesh",
"Objects.Geometry.Brep",
"Objects.Other.BlockInstance",
"Objects.Other.BlockDefinition",
"Objects.Other.RenderMaterial"
].include?(obj["speckle_type"])
end
def convert_to_native(obj, entities = SketchUp.active_model.entities)
def is_ignored_speckle_type(obj)
["Objects.BuiltElements.Revit.Parameter"].include?(obj["speckle_type"])
end
def convert_to_native(obj, entities = Sketchup.active_model.entities)
puts(">>> Converting #{obj["speckle_type"]}: #{obj["id"]}")
case obj["speckle_type"]
when "Objects.Geometry.Line", "Objects.Geometry.Polyline" then edge_to_native(obj, entities)
when "Objects.Geometry.Line", "Objects.Geometry.Polyline" then if entities == Sketchup.active_model.entities
edge_to_native_component(obj, entities)
else
edge_to_native(obj, entities)
end
when "Objects.Other.BlockInstance" then component_instance_to_native(obj, entities)
when "Objects.Other.BlockDefinition" then component_definition_to_native(obj)
when "Objects.Geometry.Mesh" then mesh_to_native(obj, entities)
when "Objects.Geometry.Mesh" then if entities == Sketchup.active_model.entities
mesh_to_native_component(obj, entities)
else
mesh_to_native(obj, entities)
end
when "Objects.Geometry.Brep" then if entities == Sketchup.active_model.entities
mesh_to_native_component(obj["displayMesh"], entities)
else
mesh_to_native(obj["displayMesh"], entities)
end
when obj.key?["displayValue"]
parent_id = obj["applicationId"] || obj["id"]
obj["displayValue"].each do |o|
o["applicationId"] = "#{parent_id}::#{o["id"]}" if o["applicationId"].nil?
convert_to_native(o, entities)
end
else
nil
end
# rescue StandardError => e
# puts("Failed to convert #{obj["speckle_type"]} (id: #{obj["id"]})")
# puts(e)
# nil
rescue StandardError => e
puts("Failed to convert #{obj["speckle_type"]} (id: #{obj["id"]})")
puts(e)
nil
end
# def register_receive(entity, id)
# # entity.add_observer(@entity_observer)
# # entity.set_attribute("speckle", "applicationId", id)
# @registry[id] = entity.persistent_id
# end
# def get_received_entity(app_id)
# return if @registry[app_id].nil?
# end
# def received?(id)
# !@registry[id].nil?
# end
def length_to_native(length, units = @units)
length.__send__(SpeckleSystems::SpeckleConnector::SKETCHUP_UNIT_STRINGS[units])
end
def edge_to_native(line, entities)
return unless line.key?("value")
if line.key?("value")
values = line["value"]
points = values.each_slice(3).to_a.map { |pt| point_to_native(pt[0], pt[1], pt[2], line["units"]) }
points.push(points[0]) if line["closed"]
entities.add_edges(*points)
else
start_pt = point_to_native(line["start"]["x"], line["start"]["y"], line["start"]["z"], line["units"])
end_pt = point_to_native(line["end"]["x"], line["end"]["y"], line["end"]["z"], line["units"])
entities.add_edges(start_pt, end_pt)
end
end
values = line["value"]
points = values.each_slice(3).to_a.map { |pt| point_to_native(pt[0], pt[1], pt[2], line["units"]) }
entities.add_edges(*points)
def edge_to_native_component(line, entities)
line_id = line["applicationId"] || line["id"]
definition = component_definition_to_native([line], "def::#{line_id}")
find_and_erase_existing_instance(definition, line_id)
instance = entities.add_instance(definition, Geom::Transformation.new)
instance.name = line_id
instance
end
def face_to_native
@@ -65,33 +122,18 @@ module SpeckleSystems::SpeckleConnector::ToNative
Geom::Point3d.new(length_to_native(x, units), length_to_native(y, units), length_to_native(z, units))
end
def component_definition_to_native(block_def)
definition = Sketchup.active_model.definitions[block_def["name"]]
return definition if definition&.guid == block_def["applicationId"]
definition&.entities&.clear!
definition ||= Sketchup.active_model.definitions.add(block_def["name"])
block_def["geometry"].each { |obj| convert_to_native(obj, definition.entities) }
puts("definition finished: #{block_def["name"]} (#{block_def["id"]})")
puts(" entity count: #{definition.entities.count}")
definition
end
# converts a mesh to a native mesh and adds the faces to the given entities collection
def mesh_to_native(mesh, entities)
native_mesh = Geom::PolygonMesh.new
points = [] # to preserve indices - duplicate points won't be added in `point_to_native`
native_mesh = Geom::PolygonMesh.new(mesh["vertices"].count / 3)
points = []
mesh["vertices"].each_slice(3) do |pt|
points.push(point_to_native(pt[0], pt[1], pt[2], mesh["units"]))
end
faces = mesh["faces"]
while faces.count.positive?
size = faces.shift
num_pts =
case size
when 0 then 3
when 1 then 4
else size
end
num_pts = faces.shift
# 0 -> 3, 1 -> 4 to preserve backwards compatibility
num_pts += 3 if num_pts < 3
indices = faces.shift(num_pts)
native_mesh.add_polygon(indices.map { |index| points[index] })
end
@@ -100,26 +142,63 @@ module SpeckleSystems::SpeckleConnector::ToNative
native_mesh
end
# creates a component definition and instance from a mesh
def mesh_to_native_component(mesh, entities)
mesh_id = mesh["applicationId"] || mesh["id"]
definition = component_definition_to_native([mesh], "def::#{mesh_id}")
find_and_erase_existing_instance(definition, mesh_id)
instance = entities.add_instance(definition, Geom::Transformation.new)
instance.name = mesh_id
instance.material = material_to_native(mesh["renderMaterial"])
instance
end
# finds or creates a component definition from the geometry and the given name
def component_definition_to_native(geometry, name, application_id = "")
definition = Sketchup.active_model.definitions[name]
return definition if definition && (definition.name == name || definition.guid == application_id)
definition&.entities&.clear!
definition ||= Sketchup.active_model.definitions.add(name)
geometry.each { |obj| convert_to_native(obj, definition.entities) }
puts("definition finished: #{name} (#{application_id})")
puts(" entity count: #{definition.entities.count}")
definition
end
# takes a component definition and finds and erases the first instance with the matching name (and optionally the applicationId)
def find_and_erase_existing_instance(definition, name, app_id = "")
definition.instances.find { |ins| ins.name == name || ins.guid == app_id }&.erase!
end
# creates a component instance from a block
def component_instance_to_native(block, entities)
is_group = block.key?("is_sketchup_group") && block["is_sketchup_group"]
# is_group = block.key?("is_sketchup_group") && block["is_sketchup_group"]
# something about this conversion is freaking out if nested block geo is a group
# so this is set to false always until I can figure this out
is_group = false
definition = component_definition_to_native(block["blockDefinition"])
# return unless definition.entities.count.positive?
transform = transform_to_native(block["transform"], block["units"])
definition = component_definition_to_native(
block["blockDefinition"]["geometry"],
block["blockDefinition"]["name"],
block["blockDefinition"]["applicationId"]
)
name = block["name"].nil? || block["name"].empty? ? block["id"] : block["name"]
find_and_erase_existing_instance(definition, name, block["applicationId"])
transform = transform_to_native(
block["transform"].is_a?(Hash) ? block["transform"]["value"] : block["transform"],
block["units"]
)
instance =
if is_group
entities.add_group(definition.entities.to_a)
else
entities.add_instance(definition, transform)
end
puts("Failed to create instance for speckle object #{block["id"]}") if instance.nil?
if instance.nil?
p(definition.name)
p(definition.entities.to_a)
end
puts("Failed to create instance for speckle block instance #{block["id"]}") if instance.nil?
instance.transformation = transform if is_group
instance.material = material_to_native(block["renderMaterial"])
instance.name = name
instance
end
+79 -37
View File
@@ -6,6 +6,7 @@ module SpeckleSystems::SpeckleConnector::ToSpeckle
length.__send__("to_#{SpeckleSystems::SpeckleConnector::SKETCHUP_UNIT_STRINGS[@units]}")
end
# convert an edge to a speckle line
def edge_to_speckle(edge)
{
speckle_type: "Objects.Geometry.Line",
@@ -18,6 +19,7 @@ module SpeckleSystems::SpeckleConnector::ToSpeckle
}
end
# covnert a component definition to a speckle block definition
def component_definition_to_speckle(definition)
guid = definition.guid
return @component_defs[guid] if @component_defs.key?(guid)
@@ -38,55 +40,80 @@ module SpeckleSystems::SpeckleConnector::ToSpeckle
@component_defs[guid] = speckle_def
end
# convert a component instane to a speckle block instance
def component_instance_to_speckle(instance, is_group: false)
transform = instance.transformation
origin = transform.origin
{
speckle_type: "Objects.Other.BlockInstance",
applicationId: instance.guid,
is_sketchup_group: is_group,
units: @units,
bbox: bounds_to_speckle(instance.bounds),
name: instance.name,
name: instance.name == "" ? nil : instance.name,
renderMaterial: instance.material.nil? ? nil : material_to_speckle(instance.material),
transform: transform_to_speckle(transform),
insertionPoint: speckle_point(origin[0], origin[1], origin[2]),
"@blockDefinition" => component_definition_to_speckle(instance.definition)
}
end
def group_mesh_to_speckle(component_def)
mat_groups = {}
nested_blocks = []
lines = []
component_def.entities.each do |face|
next unless face.typename == "Face"
component_def.entities.each do |entity|
nested_blocks.push(component_instance_to_speckle(entity)) if entity.typename == "ComponentInstance"
next unless %w[Edge Face].include?(entity.typename)
# convert material
mat_id = face.material.nil? ? "none" : face.material.entityID
mat_groups[mat_id] = initialise_group_mesh(face, component_def.bounds) unless mat_groups.key?(mat_id)
if entity.typename == "Edge"
lines.push(edge_to_speckle(entity))
else
face = entity
# convert material
mat_id = face.material.nil? ? "none" : face.material.entityID
mat_groups[mat_id] = initialise_group_mesh(face, component_def.bounds) unless mat_groups.key?(mat_id)
if face.loops.size > 1
mesh = face.mesh
mat_groups[mat_id]["@(31250)vertices"].push(*mesh_points_to_array(mesh))
mat_groups[mat_id]["@(62500)faces"].push(*mesh_faces_to_array(mesh, mat_groups[mat_id][:pt_count] - 1))
else
mat_groups[mat_id]["@(31250)vertices"].push(*face_vertices_to_array(face))
mat_groups[mat_id]["@(62500)faces"].push(*face_indices_to_array(face, mat_groups[mat_id][:pt_count]))
end
mat_groups[mat_id][:pt_count] += face.vertices.count
# add points and texture coordinates
mesh = face.mesh(1)
mat_groups[mat_id]["@(31250)vertices"].push(*points_to_array(mesh))
mat_groups[mat_id]["@(31250)textureCoordinates"].push(*uvs_to_array(mesh))
# add faces
mat_groups[mat_id]["@(62500)faces"].push(*faces_to_array(mesh, mat_groups[mat_id][:pt_count]))
mat_groups[mat_id][:pt_count] += mesh.points.count
end
end
mat_groups.values.map { |group| group.delete(:pt_count) }
mat_groups.values
mat_groups.values + lines + nested_blocks
end
def transform_to_speckle(transform)
t_arr = transform.to_a
[
t_arr[0], t_arr[4], t_arr[8], length_to_speckle(t_arr[12]),
t_arr[1], t_arr[5], t_arr[9], length_to_speckle(t_arr[13]),
t_arr[2], t_arr[6], t_arr[10], length_to_speckle(t_arr[14]),
t_arr[3], t_arr[7], t_arr[11], t_arr[15]
]
{
speckle_type: "Objects.Other.Transform",
units: @units,
value: [
t_arr[0],
t_arr[4],
t_arr[8],
length_to_speckle(t_arr[12]),
t_arr[1],
t_arr[5],
t_arr[9],
length_to_speckle(t_arr[13]),
t_arr[2],
t_arr[6],
t_arr[10],
length_to_speckle(t_arr[14]),
t_arr[3],
t_arr[7],
t_arr[11],
t_arr[15]
]
}
end
def initialise_group_mesh(face, bounds)
@@ -97,28 +124,25 @@ module SpeckleSystems::SpeckleConnector::ToSpeckle
"@(31250)vertices" => [],
"@(62500)faces" => [],
"@(31250)textureCoordinates" => [],
pt_count: -1, # faces are 1 indexed
pt_count: 0,
renderMaterial: face.material.nil? ? nil : material_to_speckle(face.material)
}
end
def faces_to_array(mesh, offset)
# get an array of face indices from a sketchup polygon mesh
def mesh_faces_to_array(mesh, offset)
faces = []
puts(faces)
mesh.polygons.each do |poly|
faces.push(
case poly.count
when 3 then 0 # tris
when 4 then 1 # polys
else
poly.count # ngons
end,
*poly.map { |coord| coord.abs + offset }
poly.count, *poly.map { |index| index.abs + offset }
)
end
faces
end
def points_to_array(mesh)
# get a flat array of vertices from a sketchup polygon mesh
def mesh_points_to_array(mesh)
pts_array = []
mesh.points.each do |pt|
pts_array.push(
@@ -130,6 +154,25 @@ module SpeckleSystems::SpeckleConnector::ToSpeckle
pts_array
end
# get a flat array of face indices from a sketchup face
def face_indices_to_array(face, offset)
face_array = [face.vertices.count]
face_array.push(*face.vertices.count.times.map { |index| index + offset })
face_array
end
# get a flat array of vertices from a list of sketchup vertices
def face_vertices_to_array(face)
pts_array = []
face.vertices.each do |v|
pt = v.position
pts_array.push(length_to_speckle(pt[0]), length_to_speckle(pt[1]), length_to_speckle(pt[2]))
end
pts_array
end
def uvs_to_array(mesh)
uvs_array = []
mesh.uvs(true).each do |pt|
@@ -142,15 +185,14 @@ module SpeckleSystems::SpeckleConnector::ToSpeckle
end
def face_to_speckle(face)
mesh = face.mesh(1)
mesh = face.loops.count > 1 ? face.mesh : nil
{
speckle_type: "Objects.Geometry.Mesh",
units: @units,
renderMaterial: face.material.nil? ? nil : material_to_speckle(face.material),
bbox: bounds_to_speckle(face.bounds),
"@(31250)vertices" => points_to_array(mesh),
"@(62500)faces" => faces_to_array(mesh, -1),
"@(31250)textureCoordinates" => uvs_to_array(mesh)
"@(31250)vertices" => mesh.nil? ? face_vertices_to_array(face) : mesh_points_to_array(mesh),
"@(62500)faces" => mesh.nil? ? face_indices_to_array(face, 0) : mesh_faces_to_array(mesh, -1)
}
end
+126 -12
View File
@@ -7,8 +7,23 @@ require "speckle_connector/accounts"
module SpeckleSystems::SpeckleConnector
UNITS = { 0 => "in", 1 => "ft", 2 => "mm", 3 => "cm", 4 => "m", 5 => "yd" }.freeze
public_constant :UNITS
@to_send = {}
@connected = false
def self.create_dialog
def self.queue_send(stream_id, converted)
@to_send = { stream_id: stream_id, converted: converted }
send_from_queue(stream_id) if @connected
end
def self.send_from_queue(stream_id)
return unless @to_send[:stream_id] == stream_id
@dialog.execute_script("convertedFromSketchup('#{@to_send[:stream_id]}',#{@to_send[:converted].to_json})")
@dialog.execute_script("oneClickSend('#{@to_send[:stream_id]}')")
@to_send = {}
end
def self.init_dialog
options = {
dialog_title: "SpeckleSketchUp",
preferences_key: "example.htmldialog.materialinspector",
@@ -21,11 +36,11 @@ module SpeckleSystems::SpeckleConnector
dialog
end
def self.show_dialog
def self.create_dialog(show: true)
if @dialog&.visible?
@dialog.bring_to_front
else
@dialog ||= create_dialog
@dialog ||= init_dialog
@dialog.add_action_callback("send_selection") do |_action_context, stream_id|
send_selection(stream_id)
nil
@@ -37,13 +52,30 @@ module SpeckleSystems::SpeckleConnector
@dialog.add_action_callback("reload_accounts") do |_action_context|
reload_accounts
end
@dialog.add_action_callback("init_local_accounts") do |_action_context|
init_local_accounts
end
@dialog.add_action_callback("load_saved_streams") do |_action_context|
load_saved_streams
end
@dialog.add_action_callback("save_stream") do |_action_context, stream_id|
save_stream(stream_id)
end
@dialog.add_action_callback("remove_stream") do |_action_context, stream_id|
remove_stream(stream_id)
end
@dialog.add_action_callback("notify_connected") do |_action_context, stream_id|
notify_connected(stream_id)
end
# set connected to false when dialog is closed
@dialog.set_can_close do
@connected = false
!@connected
end
if DEV_MODE
puts('Launching Speckle Connector from http://localhost:8081')
puts("Launching Speckle Connector from http://localhost:8081")
@dialog.set_url("http://localhost:8081")
else
html_file = File.join(File.dirname(File.expand_path(__FILE__)), "html", "index.html")
@@ -51,16 +83,23 @@ module SpeckleSystems::SpeckleConnector
@dialog.set_file(html_file)
end
@dialog.show
@dialog
@dialog.show if show
end
@dialog
end
def self.notify_connected(stream_id)
@connected = true
send_from_queue(stream_id)
end
def self.convert_to_speckle(to_convert)
converter = ConverterSketchup.new(UNITS[Sketchup.active_model.options["UnitsOptions"]["LengthUnit"]])
to_convert.map { |entity| converter.convert_to_speckle(entity) }
end
def self.send_selection(stream_id)
model = Sketchup.active_model
converter = ConverterSketchup.new(UNITS[model.options["UnitsOptions"]["LengthUnit"]])
converted = model.selection.map { |entity| converter.convert_to_speckle(entity) }
converted = convert_to_speckle(Sketchup.active_model.selection)
puts("converted #{converted.count} objects for stream #{stream_id}")
# puts(converted.to_json)
@dialog.execute_script("convertedFromSketchup('#{stream_id}',#{converted.to_json})")
@@ -80,11 +119,86 @@ module SpeckleSystems::SpeckleConnector
@dialog.execute_script("sketchupOperationFailed('#{stream_id}')")
end
def self.one_click_send
acct = Accounts.default_account
return puts("No local account found. Please refer to speckle.guide for more information.") if acct.nil?
create_dialog
to_convert = Sketchup.active_model.selection.count > 0 ? Sketchup.active_model.selection : Sketchup.active_model.entities
if first_saved_stream.nil?
create_stream(to_convert)
else
queue_send(first_saved_stream, convert_to_speckle(to_convert))
end
rescue StandardError => e
puts(e)
@dialog.execute_script("sketchupOperationFailed('#{@to_send[:stream_id]}')")
end
def self.first_saved_stream
saved_streams = Sketchup.active_model.attribute_dictionary("speckle", true)["streams"] or []
saved_streams.nil? || saved_streams.empty? ? nil : saved_streams[0]
end
def self.load_saved_streams
saved_streams = Sketchup.active_model.attribute_dictionary("speckle", true)["streams"] or []
@dialog.execute_script("setSavedStreams(#{saved_streams})")
end
def self.init_local_accounts
puts("Initialisation of Speckle accounts requested by plugin")
@dialog.execute_script("loadAccounts(#{Accounts.load_accounts.to_json}, #{Accounts.get_suuid.to_json})")
end
def self.reload_accounts
puts("Reload of Speckle accounts requested by plugin")
@dialog.execute_script("loadAccounts(#{Accounts.load_accounts.to_json})")
load_saved_streams
end
def self.save_stream(stream_id)
speckle_dict = Sketchup.active_model.attribute_dictionary("speckle", true)
saved = speckle_dict["streams"] || []
saved = saved.empty? ? [stream_id] : saved.unshift(stream_id)
speckle_dict["streams"] = saved
load_saved_streams
end
def self.remove_stream(stream_id)
speckle_dict = Sketchup.active_model.attribute_dictionary("speckle", true)
saved = speckle_dict["streams"] || []
saved -= [stream_id]
speckle_dict["streams"] = saved
load_saved_streams
end
def self.create_stream(to_convert)
acct = Accounts.default_account
return if acct.nil?
path = Sketchup.active_model.path
stream_name = path ? File.basename(path, ".*") : "Untitled SketchUp Model"
query = "mutation streamCreate($stream: StreamCreateInput!) {streamCreate(stream: $stream)}"
vars = { stream: { name: stream_name, description: "Stream created from SketchUp" } }
request = Sketchup::Http::Request.new("#{acct["serverInfo"]["url"]}/graphql", Sketchup::Http::POST)
request.headers = { "Authorization" => "Bearer #{acct["token"]}", "Content-Type" => "application/json" }
request.body = { query: query, variables: vars }.to_json
request.start do |_req, res|
res_data = JSON.parse(res.body)["data"]
raise(StandardError) unless res_data
stream_id = res_data["streamCreate"]
save_stream(stream_id)
queue_send(stream_id, convert_to_speckle(to_convert))
# send_selection(stream_id)
end
load_saved_streams
rescue StandardError => e
puts(e)
puts("Could not create a new stream")
end
end
+13 -3
View File
@@ -1,13 +1,13 @@
# frozen_string_literal: true
require "sketchup"
require "speckle_connector/dialog.rb"
require "speckle_connector/debug.rb"
require "speckle_connector/dialog"
require "speckle_connector/debug"
module SpeckleSystems
module SpeckleConnector
unless file_loaded?(__FILE__)
cmd_cube = UI::Command.new("Dialog") { show_dialog }
cmd_cube = UI::Command.new("Dialog") { create_dialog }
cmd_cube.tooltip = "Launch Connector"
cmd_cube.status_bar_text = "Opens the Speckle Connector window"
cmd_cube.small_icon = "icons/s2logo.png"
@@ -16,8 +16,18 @@ module SpeckleSystems
menu = UI.menu("Tools")
menu.add_item(cmd_cube)
cmd_send = UI::Command.new("Send") { one_click_send }
cmd_send.tooltip = "Send to Speckle"
cmd_send.status_bar_text = "Send to Speckle"
cmd_send.small_icon = "icons/Sender.png"
cmd_send.large_icon = "icons/Sender.png"
menu = UI.menu("Tools")
menu.add_item(cmd_send)
toolbar = UI::Toolbar.new("Speckle")
toolbar.add_item(cmd_cube)
toolbar.add_item(cmd_send)
toolbar.restore
file_loaded(__FILE__)
+19
View File
@@ -0,0 +1,19 @@
/* eslint-env node */
/** @type {import("eslint").Linter.Config} */
const config = {
env: {
browser: true,
es2021: true,
commonjs: false
},
ignorePatterns: ['nginx'],
extends: ['plugin:vue/recommended', 'eslint:recommended', 'prettier', 'prettier/vue'],
parserOptions: {
sourceType: 'module'
},
plugins: ['vue'],
rules: { 'no-console': 1 }
}
module.exports = config
-47
View File
@@ -1,47 +0,0 @@
{
"env": {
"browser": true,
"es2021": true
},
"ignorePatterns": ["main-frontend.js"],
"extends": [
"plugin:vue/recommended",
"prettier",
"prettier/vue",
"plugin:prettier/recommended",
"eslint:recommended"
],
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": ["vue", "prettier"],
"rules": {
"arrow-spacing": [
2,
{
"before": true,
"after": true
}
],
//"array-bracket-spacing": [2, "always"],
"block-spacing": [2, "always"],
"camelcase": [
1,
{
"properties": "always"
}
],
// "space-in-parens": [2, "always"],
"keyword-spacing": 2,
"semi": "off",
"indent": ["error", 2, { "SwitchCase": 1 }],
"space-unary-ops": [
2,
{
"words": true,
"nonwords": false
}
]
}
}
+5
View File
@@ -1,24 +1,29 @@
# ui
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
+1 -3
View File
@@ -1,5 +1,3 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
presets: ['@vue/cli-plugin-babel/preset']
}
+14143 -6738
View File
File diff suppressed because it is too large Load Diff
+10 -6
View File
@@ -3,21 +3,25 @@
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve --port 8081",
"dev": "vue-cli-service serve --port 8081",
"build": "vue-cli-service build",
"watch-build": "vue-cli-service build --watch --mode production",
"lint": "vue-cli-service lint"
"lint": "eslint . --ext .js,.ts,.vue",
"prettier:check": "prettier --check .",
"prettier:fix": "prettier --write ."
},
"dependencies": {
"@speckle/objectloader": "^2.1.1",
"@speckle/objectloader": "^2.6.0",
"aws-sdk": "^2.981.0",
"core-js": "^3.6.5",
"core-js": "^3.22.8",
"debounce": "^1.2.1",
"mixpanel-browser": "^2.45.0",
"regenerator-runtime": "^0.13.9",
"register-service-worker": "^1.7.1",
"sqlite3": "^5.0.2",
"v-tooltip": "^2.1.3",
"vue": "^2.6.11",
"vue-apollo": "^3.0.0-beta.11",
"vue-matomo": "^4.1.0",
"vue-router": "^3.2.0",
"vue-timeago": "^5.1.3",
"vuetify": "^2.4.0"
@@ -41,4 +45,4 @@
"vue-template-compiler": "^2.6.11",
"vuetify-loader": "^1.7.0"
}
}
}
+30 -23
View File
@@ -1,26 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<title><%= htmlWebpackPlugin.options.title %></title>
<link
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500&display=swap"
rel="stylesheet"
/>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css"
/>
</head>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
<body>
<noscript>
<strong>
We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without
JavaScript enabled. Please enable it to continue.
</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
+13 -7
View File
@@ -19,7 +19,7 @@
dense
flat
solo
></v-text-field>
/>
<v-spacer />
<v-btn icon small class="mx-1" @click="switchTheme">
<v-icon>mdi-theme-light-dark</v-icon>
@@ -59,7 +59,7 @@
</div>
</v-card-text>
<v-card-text v-if="accounts">
<v-divider class="my-3"></v-divider>
<v-divider class="my-3" />
<div v-for="account in accounts" :key="account.id">
<v-btn
@@ -85,6 +85,7 @@
<v-container fluid>
<router-view :stream-search-query="streamSearchQuery" />
</v-container>
<global-toast />
</v-main>
</v-app>
</template>
@@ -96,6 +97,7 @@ import userQuery from './graphql/user.gql'
import { onLogin } from './vue-apollo'
global.loadAccounts = function (accounts, suuid) {
console.log('>>> SpeckleSketchup: Loading accounts', accounts, `suuid: ${suuid}`)
localStorage.setItem('localAccounts', JSON.stringify(accounts))
if (suuid) {
localStorage.setItem('suuid', suuid)
@@ -117,7 +119,9 @@ global.setSelectedAccount = function (account) {
export default {
name: 'App',
components: {},
components: {
GlobalToast: () => import('@/components/GlobalToast')
},
props: {
size: {
type: Number,
@@ -143,6 +147,7 @@ export default {
mounted() {
bus.$on('selected-account-reloaded', async () => {
await onLogin(this.$apollo.provider.defaultClient)
this.$refreshMixpanelIds()
this.refresh()
})
bus.$on('streams-loaded', () => {
@@ -158,18 +163,19 @@ export default {
switchTheme() {
this.$vuetify.theme.dark = !this.$vuetify.theme.dark
localStorage.setItem('theme', this.$vuetify.theme.dark ? 'dark' : 'light')
this.$mixpanel.track('Connector Action', { name: 'Toggle Theme' })
},
switchAccount(account) {
this.$matomo && this.$matomo.setCustomUrl(`http://connectors/SketchUp/account/switch`)
this.$matomo && this.$matomo.trackPageView(`account/switch`)
this.$mixpanel.track('Connector Action', { name: 'Account Select' })
global.setSelectedAccount(account)
},
requestRefresh() {
sketchup.reload_accounts()
sketchup.load_saved_streams()
this.refresh()
},
refresh() {
this.$matomo && this.$matomo.setCustomUrl(`http://connectors/SketchUp/stream/list`)
this.$matomo && this.$matomo.trackPageView(`stream/list`)
this.$mixpanel.track('Connector Action', { name: 'Refresh' })
this.$apollo.queries.user.refetch()
bus.$emit('refresh-streams')
}
+48
View File
@@ -0,0 +1,48 @@
<template>
<v-snackbar v-model="snack" app bottom color="primary">
{{ text }}
<template #action="{}">
<v-btn v-if="actionName" small outlined @click="openUrl(url)" @click:append="snack = false">
{{ actionName }}
</v-btn>
<v-btn small icon @click="snack = false">
<v-icon small>mdi-close</v-icon>
</v-btn>
</template>
</v-snackbar>
</template>
<script>
export default {
data() {
return {
snack: false,
text: null,
actionName: null,
url: null
}
},
watch: {
snack(newVal) {
if (!newVal) {
this.text = null
this.actionName = null
this.url = null
}
}
},
mounted() {
this.$eventHub.$on('notification', (args) => {
this.snack = true
this.text = args.text
this.actionName = args.action ? args.action.name : null
this.url = args.action ? args.action.url : null
})
},
methods: {
openUrl(link) {
this.$mixpanel.track('Connector Action', { name: 'Open In Web' })
window.open(link)
}
}
}
</script>
+251 -179
View File
@@ -1,164 +1,148 @@
<template>
<v-hover v-slot="{ hover }">
<v-card color="" class="mt-5 mb-5" style="transition: all 0.2s ease-in-out">
<v-row>
<v-col v-if="$apollo.loading">
<v-row>
<v-col><v-skeleton-loader type="article" /></v-col>
</v-row>
</v-col>
<v-col v-else>
<v-toolbar class="transparent elevation-0" dense>
<v-toolbar-title>{{ stream.name }}</v-toolbar-title>
<v-spacer />
</v-toolbar>
<v-card-text class="transparent elevation-0 mt-0 pt-0" dense>
<div class="text-caption">
Updated
<timeago :datetime="stream.updatedAt" />
</div>
<v-toolbar-title>
<v-chip v-if="stream.role" small class="mr-1">
<v-icon small left>mdi-account-key-outline</v-icon>
{{ stream.role.split(':')[1] }}
</v-chip>
<v-menu offset-y>
<template #activator="{ on, attrs }">
<v-chip v-if="stream.branches" small v-bind="attrs" class="mr-1" v-on="on">
<v-icon small class="mr-1 float-left">mdi-source-branch</v-icon>
{{ branchName }}
</v-chip>
</template>
<v-list dense>
<v-list-item
v-for="(branch, index) in stream.branches.items"
:key="index"
link
@click="switchBranch(branch.name)"
>
<v-list-item-title class="text-caption font-weight-regular">
<v-icon v-if="branch.name == branchName" small class="mr-1 float-left">
mdi-check
</v-icon>
<v-icon v-else small class="mr-1 float-left">mdi-source-branch</v-icon>
{{ branch.name }} ({{ branch.commits.totalCount }})
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
<v-menu offset-y>
<template #activator="{ on, attrs }">
<v-chip v-if="stream.commits" small v-bind="attrs" v-on="on">
<v-icon small class="mr-1 float-left">mdi-source-commit</v-icon>
{{ selectedBranch.commits.items.length ? commitId : 'no commits' }}
</v-chip>
</template>
<v-list dense>
<v-list-item
v-for="(commit, index) in selectedBranch.commits.items"
:key="index"
link
@click="switchCommit(commit.id)"
>
<v-list-item-title class="text-caption font-weight-regular">
<v-icon
v-if="(commitId == 'latest' && index == 0) || commit.id == commitId"
small
class="mr-1 float-left"
>
mdi-check
</v-icon>
<v-icon v-else small class="mr-1 float-left">mdi-source-commit</v-icon>
{{ commit.id }} |
<span class="font-weight-regular">{{ commit.message }} |</span>
<span class="font-weight-light ml-1">
<timeago :datetime="commit.createdAt" />
</span>
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-toolbar-title>
</v-card-text>
</v-col>
<v-col v-if="hover && !$apollo.loading" align="end" justify="center">
<v-tooltip bottom>
<template #activator="{ on, attrs }">
<v-btn icon class="mr-4 btn-fix" v-bind="attrs" v-on="on" @click="openInWeb">
<v-icon>mdi-open-in-new</v-icon>
</v-btn>
</template>
<span>Open in web</span>
</v-tooltip>
<v-tooltip bottom>
<template #activator="{ on, attrs }">
<v-btn
fab
:loading="loadingSend"
class="mr-4 elevation-1 btn-fix"
hint="Send"
v-bind="attrs"
v-on="on"
@click="send"
<v-card
v-if="stream"
:class="`mb-3 rounded-lg grey ${$vuetify.theme.dark ? 'darken-4' : 'lighten-4'}`"
@mouseenter="hover = true"
@mouseleave="hover = false"
>
<v-toolbar flat height="70">
<v-toolbar-title class="ml-0" style="position: relative; left: -10px">
<!-- Uncomment when pinning is in place and add style="position: relative; left: -10px" to the element above :) -->
<v-btn
v-tooltip="'Pin this stream - it will be saved to this file.'"
icon
x-small
@click="toggleSavedStream"
>
<v-icon v-if="saved" x-small>mdi-pin</v-icon>
<v-icon v-else x-small>mdi-pin-outline</v-icon>
</v-btn>
{{ stream.name }}
</v-toolbar-title>
<v-spacer />
<v-slide-x-transition>
<div v-show="hover" style="white-space: nowrap">
<v-btn v-tooltip="'View online'" icon small class="mr-3" @click="openInWeb">
<v-icon small>mdi-open-in-new</v-icon>
</v-btn>
<v-btn
v-tooltip="'Send'"
icon
class="mr-3 elevation-2"
:loading="loadingSend"
@click="send"
>
<!-- <v-icon>mdi-upload</v-icon> -->
<v-img v-if="$vuetify.theme.dark" src="@/assets/SenderWhite.png" max-width="30" />
<v-img v-else src="@/assets/Sender.png" max-width="30" />
</v-btn>
<v-btn
v-tooltip="'Receive'"
icon
class="elevation-2"
:loading="loadingReceive"
@click="receive"
>
<!-- <v-icon>mdi-download</v-icon> -->
<v-img v-if="$vuetify.theme.dark" src="@/assets/ReceiverWhite.png" max-width="30" />
<v-img v-else src="@/assets/Receiver.png" max-width="30" />
</v-btn>
</div>
</v-slide-x-transition>
</v-toolbar>
<v-card-text class="caption pt-1 text-truncate" style="white-space: nowrap">
Updated
<timeago class="mr-1" :datetime="stream.updatedAt" />
|
<v-icon class="ml-1" small>mdi-account-key-outline</v-icon>
{{ stream.role.split(':')[1] }}
</v-card-text>
<v-card-text class="d-flex align-center pb-5 mb-5 -mt-2" style="height: 50px">
<v-menu offset-y>
<template #activator="{ on, attrs }">
<v-chip v-if="stream.branches" small v-bind="attrs" class="mr-1" v-on="on">
<v-icon small class="mr-1 float-left">mdi-source-branch</v-icon>
{{ branchName }}
</v-chip>
</template>
<v-list dense>
<v-list-item
v-for="(branch, index) in stream.branches.items"
:key="index"
link
@click="switchBranch(branch.name)"
>
<v-list-item-title class="text-caption font-weight-regular">
<v-icon v-if="branch.name == branchName" small class="mr-1 float-left">
mdi-check
</v-icon>
<v-icon v-else small class="mr-1 float-left">mdi-source-branch</v-icon>
{{ branch.name }} ({{ branch.commits.totalCount }})
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
<v-menu offset-y>
<template #activator="{ on, attrs }">
<v-chip v-if="stream.commits" small v-bind="attrs" v-on="on">
<v-icon small class="mr-1 float-left">mdi-source-commit</v-icon>
{{ selectedBranch.commits.items.length ? commitId : 'no commits' }}
</v-chip>
</template>
<v-list dense>
<v-list-item
v-for="(commit, index) in selectedBranch.commits.items"
:key="index"
link
@click="switchCommit(commit.id)"
>
<v-list-item-title class="text-caption font-weight-regular">
<v-icon
v-if="(commitId == 'latest' && index == 0) || commit.id == commitId"
small
class="mr-1 float-left"
>
<v-img
v-if="$vuetify.theme.dark"
src="@/assets/SenderWhite.png"
max-width="40"
style="display: inline-block"
/>
<v-img
v-else
src="@/assets/Sender.png"
max-width="40"
style="display: inline-block"
/>
</v-btn>
</template>
<span>Send</span>
</v-tooltip>
<v-tooltip bottom>
<template #activator="{ on, attrs }">
<v-btn
fab
:loading="loadingReceive"
class="mr-4 elevation-1 btn-fix"
hint="Receive"
v-bind="attrs"
v-on="on"
@click="receive"
>
<v-img
v-if="$vuetify.theme.dark"
src="@/assets/ReceiverWhite.png"
max-width="40"
style="display: inline-block"
/>
<v-img
v-else
src="@/assets/Receiver.png"
max-width="40"
style="display: inline-block"
/>
</v-btn>
</template>
<span>Receive</span>
</v-tooltip>
</v-col>
</v-row>
<v-progress-linear
v-if="(loadingSend || loadingReceive) && loadingStage"
height="14"
indeterminate
>
<div class="text-caption">{{ loadingStage }}</div>
</v-progress-linear>
</v-card>
</v-hover>
mdi-check
</v-icon>
<v-icon v-else small class="mr-1 float-left">mdi-source-commit</v-icon>
{{ commit.id }} |
<span class="font-weight-regular">{{ commit.message }} |</span>
<span class="font-weight-light ml-1">
<timeago :datetime="commit.createdAt" />
</span>
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
<div class="flex-grow-1 px-4">
<v-slide-y-transition>
<div v-show="hover">
<v-text-field
v-model="commitMessage"
xxxclass="small-text-field"
hide-details
dense
flat
placeholder="Write your commit message here"
/>
</div>
</v-slide-y-transition>
</div>
</v-card-text>
<v-progress-linear
v-if="(loadingSend || loadingReceive) && loadingStage"
key="progress-bar"
height="14"
indeterminate
>
<div class="text-caption">
{{ loadingStage }}
</div>
</v-progress-linear>
</v-card>
<v-card v-else class="my-2">
<v-skeleton-loader type="article" />
</v-card>
</template>
<script>
@@ -181,21 +165,31 @@ global.sketchupOperationFailed = function (streamId) {
bus.$emit(`sketchup-fail-${streamId}`)
}
global.oneClickSend = function (streamId) {
bus.$emit(`one-click-send-${streamId}`)
}
export default {
name: 'StreamCard',
props: {
streamId: {
type: String,
default: null
},
saved: {
type: Boolean,
default: false
}
},
data() {
return {
hover: false,
loadingSend: false,
loadingReceive: false,
loadingStage: null,
branchName: 'main',
commitId: 'latest'
commitId: 'latest',
commitMessage: null
}
},
apollo: {
@@ -278,22 +272,26 @@ export default {
},
mounted() {
bus.$on(`sketchup-objects-${this.streamId}`, async (objects) => {
console.log('received objects from sketchup', objects)
console.log('>>> SpeckleSketchUp: Received objects from sketchup')
await this.createCommit(objects)
})
bus.$on(`sketchup-received-${this.streamId}`, () => {
console.log('finished receiving in sketchup', this.streamId)
console.log('>>> SpeckleSketchUp: Finished receiving in sketchup', this.streamId)
this.loadingReceive = false
this.loadingStage = null
})
bus.$on(`sketchup-fail-${this.streamId}`, () => {
this.$matomo && this.$matomo.setCustomUrl(`http://connectors/SketchUp/stream/fail`)
this.$matomo && this.$matomo.trackPageView(`stream/fail`)
console.log('sketchup operation failed', this.streamId)
this.$mixpanel.track('Connector Action', { name: 'Stream Fail' })
console.log('>>> SpeckleSketchUp: operation failed', this.streamId)
this.loadingReceive = this.loadingSend = false
this.loadingStage = null
})
bus.$on(`one-click-send-${this.streamId}`, () => {
this.$mixpanel.track('Send', { method: 'OneClick' })
})
if (this.saved) sketchup.notify_connected(this.streamId)
},
methods: {
sleep(ms) {
@@ -301,21 +299,30 @@ export default {
},
openInWeb() {
window.open(`${localStorage.getItem('serverUrl')}/streams/${this.streamId}`)
this.$matomo && this.$matomo.setCustomUrl(`http://connectors/SketchUp/stream/open-in-web`)
this.$matomo && this.$matomo.trackPageView(`stream/open-in-web`)
this.$mixpanel.track('Connector Action', { name: 'Open In Web' })
},
switchBranch(branchName) {
this.$mixpanel.track('Connector Action', { name: 'Branch Switch' })
this.branchName = branchName
this.commitId = 'latest'
},
switchCommit(commitId) {
this.$mixpanel.track('Connector Action', { name: 'Commit Switch' })
this.commitId = commitId
},
toggleSavedStream() {
if (this.saved) {
sketchup.remove_stream(this.streamId)
this.$mixpanel.track('Connector Action', { name: 'Stream Remove' })
} else {
sketchup.save_stream(this.streamId)
this.$mixpanel.track('Connector Action', { name: 'Stream Save' })
}
},
async receive() {
this.loadingStage = 'requesting'
this.loadingReceive = true
this.$matomo && this.$matomo.setCustomUrl(`http://connectors/SketchUp/receive`)
this.$matomo && this.$matomo.trackPageView(`receive`)
this.$mixpanel.track('Receive')
const refId = this.selectedCommit?.referencedObject
if (!refId) {
this.loadingReceive = false
@@ -332,7 +339,24 @@ export default {
let rootObj = await loader.getAndConstructObject(this.updateLoadingStage)
console.log(rootObj)
sketchup.receive_objects(rootObj, this.streamId)
await this.$apollo.mutate({
mutation: gql`
mutation commitReceive($input: CommitReceivedInput!) {
commitReceive(input: $input)
}
`,
variables: {
input: {
sourceApplication: 'sketchup',
streamId: this.streamId,
commitId: this.selectedCommit.id
}
}
})
this.loadingStage = 'converting'
},
updateLoadingStage({ stage }) {
@@ -341,16 +365,18 @@ export default {
async send() {
this.loadingStage = 'converting'
this.loadingSend = true
this.$matomo && this.$matomo.setCustomUrl(`http://connectors/SketchUp/send`)
this.$matomo && this.$matomo.trackPageView(`send`)
this.$mixpanel.track('Send')
sketchup.send_selection(this.streamId)
console.log('request for data sent to sketchup')
console.log('>>> SpeckleSketchUp: Objects requested from SketchUp')
await this.sleep(2000)
},
async createCommit(objects) {
if (objects.length == 0) {
this.loadingSend = false
this.loadingStage = null
this.$eventHub.$emit('notification', {
text: 'No objects selected. Nothing was sent.'
})
return
}
@@ -362,20 +388,25 @@ export default {
this.loadingStage = 'uploading'
this.loadingSend = true
let batches = s.batchObjects()
const totBatches = batches.length
console.log(`>>> SpeckleSketchUp: ${totBatches} batches ready for sending`)
let batchesSent = 0
for (const batch of batches) {
let res = await this.sendBatch(batch)
if (res.status !== 201) throw `Upload request failed: ${res}`
if (res.status !== 201) throw `Upload request failed: ${res.status}`
batchesSent++
this.loadingStage = `uploading: ${Math.round((batchesSent / totBatches) * 100)}%`
}
let commit = {
streamId: this.streamId,
branchName: this.branchName,
objectId: hash,
message: 'sent from sketchup',
message: this.commitMessage ?? 'sent from sketchup',
sourceApplication: 'sketchup',
totalChildrenCount: s.objects[hash].totalChildrenCount
}
await this.$apollo.mutate({
let res = await this.$apollo.mutate({
mutation: gql`
mutation CommitCreate($commit: CommitCreateInput!) {
commitCreate(commit: $commit)
@@ -385,8 +416,17 @@ export default {
commit: commit
}
})
console.log('sent to stream: ' + this.streamId, commit)
console.log('>>> SpeckleSketchUp: Sent to stream: ' + this.streamId, commit)
this.$eventHub.$emit('notification', {
text: 'Model selection sent!',
action: {
name: 'View in Web',
url: `${localStorage.getItem('serverUrl')}/streams/${this.streamId}/commits/${
res.data.commitCreate
}`
}
})
this.$apollo.queries.stream.refetch()
this.loadingSend = false
this.loadingStage = null
} catch (err) {
@@ -397,7 +437,7 @@ export default {
},
async sendBatch(batch) {
let formData = new FormData()
formData.append(`batch-1`, new Blob([JSON.stringify(batch)], { type: 'application/json' }))
formData.append(`batch-1`, new Blob([batch], { type: 'application/json' }))
let token = localStorage.getItem('SpeckleSketchup.AuthToken')
let res = await fetch(`${localStorage.getItem('serverUrl')}/objects/${this.streamId}`, {
method: 'POST',
@@ -410,7 +450,39 @@ export default {
}
</script>
<style>
<style scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.2s ease-in;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.expand-enter-active {
transition: all 0.2s ease;
max-height: 1200px;
overflow: hidden;
}
.expand-leave-active {
transition: all 0.3s ease;
max-height: 1200px;
overflow: hidden;
}
.expand-enter,
.expand-leave-to {
max-height: 0;
opacity: 0;
}
.v-text-field >>> input {
font-size: 0.9em;
}
.v-text-field >>> label {
font-size: 0.9em;
}
.btn-fix:focus::before {
opacity: 0 !important;
}
-11
View File
@@ -1,11 +0,0 @@
<template>
<v-container>
<v-card>hello there!</v-card>
</v-container>
</template>
<script>
export default {}
</script>
<style></style>
+1 -1
View File
@@ -14,7 +14,7 @@
<v-img v-else :src="`https://robohash.org/` + id + `.png?size=40x40`" />
</v-avatar>
<v-avatar v-else class="ma-1" :size="size" v-bind="attrs" v-on="on">
<v-img contain src="/logo.svg"></v-img>
<v-img contain src="/logo.svg" />
</v-avatar>
</template>
<v-card v-if="userById" style="width: 200px" :to="isSelf ? '/profile' : '/profile/' + id">
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -43,7 +43,7 @@ query Stream($id: String!) {
authorName
authorAvatar
referencedObject
}
}
}
}
}
+1 -1
View File
@@ -46,7 +46,7 @@ query Streams($cursor: String) {
authorName
authorAvatar
referencedObject
}
}
}
}
}
+9 -6
View File
@@ -1,21 +1,24 @@
import 'core-js/stable'
import 'regenerator-runtime/runtime'
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import vuetify from './plugins/vuetify'
import { createProvider } from './vue-apollo'
Vue.prototype.$eventHub = new Vue()
Vue.config.productionTip = false
import VueTimeago from 'vue-timeago'
Vue.use(VueTimeago, { locale: 'en' })
import VueMatomo from 'vue-matomo'
import VueTooltip from 'v-tooltip'
Vue.use(VueTooltip)
Vue.use(VueMatomo, {
host: 'https://speckle.matomo.cloud',
siteId: 2,
userId: localStorage.getItem('suuid')
})
import SpeckleMetrics from './plugins/speckle-metrics'
Vue.use(SpeckleMetrics, { token: 'acd87c5a50b56df91a795e999812a3a4' })
export const bus = new Vue()
+42
View File
@@ -0,0 +1,42 @@
let mixpanel = require('mixpanel-browser')
import crypto from 'crypto'
const SpeckleMetrics = {
install(Vue, { token, config }) {
config = config || {
// eslint-disable-next-line camelcase
api_host: 'https://analytics.speckle.systems'
}
Vue.prototype.$mixpanel = mixpanel
Vue.prototype.$mixpanel.init(token, config)
Vue.prototype.$mixpanel.register({ hostApp: 'sketchup', type: 'action' })
Vue.prototype.$refreshMixpanelIds = function () {
let distinctId =
'@' +
crypto
.createHash('md5')
.update(
JSON.parse(localStorage.getItem('selectedAccount'))['userInfo']['email'].toLowerCase()
)
.digest('hex')
.toUpperCase()
let serverId = crypto
.createHash('md5')
.update(localStorage.getItem('serverUrl').toLowerCase())
.digest('hex')
.toUpperCase()
Vue.prototype.$mixpanel.register({
// eslint-disable-next-line camelcase
distinct_id: distinctId,
// eslint-disable-next-line camelcase
server_id: serverId
})
}
}
}
export default SpeckleMetrics
+1 -1
View File
@@ -1,6 +1,6 @@
import Vue from 'vue'
import Vuetify from 'vuetify/lib/framework'
import '@/scss/styles.css'
Vue.use(Vuetify)
export default new Vuetify({
+121
View File
@@ -0,0 +1,121 @@
.background-light {
background: #8e9eab;
background: -webkit-linear-gradient(to top right, #eeeeee, #c8e8ff) !important;
background: linear-gradient(to top right, #ffffff, #c8e8ff) !important;
}
.background-dark {
background: #141e30;
background: -webkit-linear-gradient(to top left, #243b55, #141e30) !important;
background: linear-gradient(to top left, #243b55, #141e30) !important;
}
/* TOOLTIPs */
.tooltip {
display: block !important;
z-index: 10000;
font-family: 'Roboto', sans-serif !important;
font-size: 0.75rem !important;
}
.tooltip .tooltip-inner {
background: rgba(0, 0, 0, 1);
color: white;
border-radius: 16px;
padding: 5px 10px 4px;
}
.tooltip .tooltip-arrow {
width: 0;
height: 0;
border-style: solid;
position: absolute;
margin: 5px;
border-color: rgba(0, 0, 0, 0.5);
z-index: 1;
}
.tooltip[x-placement^='top'] {
margin-bottom: 5px;
}
.tooltip[x-placement^='top'] .tooltip-arrow {
border-width: 5px 5px 0 5px;
border-left-color: transparent !important;
border-right-color: transparent !important;
border-bottom-color: transparent !important;
bottom: -5px;
left: calc(50% - 5px);
margin-top: 0;
margin-bottom: 0;
}
.tooltip[x-placement^='bottom'] {
margin-top: 5px;
}
.tooltip[x-placement^='bottom'] .tooltip-arrow {
border-width: 0 5px 5px 5px;
border-left-color: transparent !important;
border-right-color: transparent !important;
border-top-color: transparent !important;
top: -5px;
left: calc(50% - 5px);
margin-top: 0;
margin-bottom: 0;
}
.tooltip[x-placement^='right'] {
margin-left: 5px;
}
.tooltip[x-placement^='right'] .tooltip-arrow {
border-width: 5px 5px 5px 0;
border-left-color: transparent !important;
border-top-color: transparent !important;
border-bottom-color: transparent !important;
left: -5px;
top: calc(50% - 5px);
margin-left: 0;
margin-right: 0;
}
.tooltip[x-placement^='left'] {
margin-right: 5px;
}
.tooltip[x-placement^='left'] .tooltip-arrow {
border-width: 5px 0 5px 5px;
border-top-color: transparent !important;
border-right-color: transparent !important;
border-bottom-color: transparent !important;
right: -5px;
top: calc(50% - 5px);
margin-left: 0;
margin-right: 0;
}
.tooltip.popover .popover-inner {
background: #f9f9f9;
color: black;
padding: 24px;
border-radius: 5px;
box-shadow: 0 5px 30px rgba(black, 0.1);
}
.tooltip.popover .popover-arrow {
border-color: #f9f9f9;
}
.tooltip[aria-hidden='true'] {
visibility: hidden;
opacity: 0;
transition: opacity 0.15s, visibility 0.15s;
}
.tooltip[aria-hidden='false'] {
visibility: visible;
opacity: 1;
transition: opacity 0.15s;
}
+5 -5
View File
@@ -170,21 +170,21 @@ export class BaseObjectSerializer {
batchObjects(maxBatchSizeMb = 1) {
const maxSize = maxBatchSizeMb * 1000 * 1000
let batches = []
let batch = []
let batch = '['
let batchSize = 0
let objects = Object.values(this.objects)
objects.forEach((obj) => {
let objString = JSON.stringify(obj)
if (batchSize + objString.length < maxSize) {
batch.push(obj)
batch += objString + ','
batchSize += objString.length
} else {
batches.push(batch)
batch = [obj]
batches.push(batch.slice(0, -1) + ']')
batch = '[' + objString + ','
batchSize = objString.length
}
})
batches.push(batch)
batches.push(batch.slice(0, -1) + ']')
return batches
}
+55 -7
View File
@@ -1,17 +1,22 @@
<template>
<v-container>
<div>
<v-row>
<v-col v-if="$apollo.loading && !streams">
<v-row>
<v-col>
<v-skeleton-loader type="card-heading, list-item-three-line"></v-skeleton-loader>
<v-skeleton-loader type="card-heading, list-item-three-line" />
</v-col>
</v-row>
</v-col>
</v-row>
<div v-if="!streamsFound" class="text-subtitle-1 text-center mt-8">No streams found... 👀</div>
<div v-if="streams">
<div v-for="stream in streams.items" :key="stream.id">
<div v-if="savedStreams" class="mt-5">
<div v-for="streamId in savedStreams" :key="streamId">
<stream-card :stream-id="streamId" :saved="true" />
</div>
</div>
<div v-if="allStreamsList" class="mt-5">
<div v-for="stream in allStreamsList" :key="stream.id">
<stream-card :stream-id="stream.id" />
</div>
<div class="actions text-center">
@@ -26,13 +31,19 @@
</v-btn>
</div>
</div>
</v-container>
</div>
</template>
<script>
/*global sketchup*/
import gql from 'graphql-tag'
import { bus } from '../main'
global.setSavedStreams = function (streamIds) {
localStorage.setItem('savedStreams', JSON.stringify(streamIds))
bus.$emit('set-saved-streams', streamIds)
}
const streamLimit = 5
export default {
name: 'Streams',
@@ -44,18 +55,33 @@ export default {
},
data() {
return {
showMoreEnabled: true
showMoreEnabled: true,
savedStreams: []
}
},
computed: {
streamsFound() {
return this.streams && this.streams?.items?.length != 0
return (this.streams && this.streams?.items?.length != 0) || this.savedStreams?.length !== 0
},
isSavedStream(streamId) {
return this.savedStreams?.includes(streamId)
},
allStreamsList() {
if (this.$apollo.loading) return
return this.streams?.items.filter((stream) => !this.savedStreams?.includes(stream.id))
}
},
mounted() {
bus.$on('refresh-streams', () => {
this.$apollo.queries.streams.refetch()
})
bus.$on('set-saved-streams', (streamIds) => {
this.savedStreams = streamIds
})
sketchup.load_saved_streams()
console.log('LAUNCHED')
this.$mixpanel.track('Connector Action', { name: 'Launched' })
},
apollo: {
streams: {
@@ -85,6 +111,28 @@ export default {
this.showMoreEnabled = data.streams?.items.length < data.streams.totalCount
return data.streams
}
},
$subscribe: {
userStreamAdded: {
query: gql`
subscription {
userStreamAdded
}
`,
result() {
this.$apollo.queries.stream.refetch()
}
},
userStreamRemoved: {
query: gql`
subscription {
userStreamRemoved
}
`,
result() {
this.$apollo.queries.stream.refetch()
}
}
}
},
methods: {
+1 -1
View File
@@ -1,7 +1,7 @@
const path = require('path')
module.exports = {
publicPath: "./",
publicPath: './',
outputDir: path.resolve(__dirname, '../speckle_connector', 'html'),
transpileDependencies: ['vuetify', '@speckle/objectloader']
}