Compare commits

...

10 Commits

Author SHA1 Message Date
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
34 changed files with 21010 additions and 4233 deletions
+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": []
},
]
}
-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
+7 -1
View File
@@ -23,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")
@@ -34,7 +39,8 @@ module SpeckleSystems::SpeckleConnector
def self._get_speckle_dir
speckle_dir =
case Sketchup.platform
when :platform_win then File.join(Dir.home, "AppData/Roaming/Speckle")
# 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
nil
@@ -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
+90 -15
View File
@@ -6,6 +6,8 @@ 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]) }
@@ -32,22 +34,60 @@ module SpeckleSystems::SpeckleConnector::ToNative
].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.Brep" then mesh_to_native(obj["displayMesh"], 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
@@ -65,6 +105,15 @@ module SpeckleSystems::SpeckleConnector::ToNative
end
end
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
nil
end
@@ -73,6 +122,7 @@ module SpeckleSystems::SpeckleConnector::ToNative
Geom::Point3d.new(length_to_native(x, units), length_to_native(y, units), length_to_native(z, units))
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(mesh["vertices"].count / 3)
points = []
@@ -92,25 +142,49 @@ module SpeckleSystems::SpeckleConnector::ToNative
native_mesh
end
def component_definition_to_native(block_def)
definition = Sketchup.active_model.definitions[block_def["name"]]
return definition if definition && (definition.name == block_def["name"] || definition.guid == block_def["applicationId"])
# 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(block_def["name"])
block_def["geometry"].each { |obj| convert_to_native(obj, definition.entities) }
puts("definition finished: #{block_def["name"]} (#{block_def["id"]})")
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"]
# 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"])
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"]
@@ -124,6 +198,7 @@ module SpeckleSystems::SpeckleConnector::ToNative
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
+54 -29
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,6 +40,7 @@ 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
{
@@ -46,7 +49,7 @@ module SpeckleSystems::SpeckleConnector::ToSpeckle
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),
"@blockDefinition" => component_definition_to_speckle(instance.definition)
@@ -56,28 +59,35 @@ module SpeckleSystems::SpeckleConnector::ToSpeckle
def group_mesh_to_speckle(component_def)
mat_groups = {}
nested_blocks = []
lines = []
component_def.entities.each do |entity|
nested_blocks.push(component_instance_to_speckle(entity)) if entity.typename == "ComponentInstance"
next unless entity.typename == "Face"
next unless %w[Edge Face].include?(entity.typename)
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 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 + nested_blocks
mat_groups.values + lines + nested_blocks
end
def transform_to_speckle(transform)
@@ -114,28 +124,25 @@ module SpeckleSystems::SpeckleConnector::ToSpeckle
"@(31250)vertices" => [],
"@(62500)faces" => [],
"@(31250)textureCoordinates" => [],
pt_count: -1,
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(
@@ -147,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|
@@ -159,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
+124 -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,6 +119,32 @@ 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})")
@@ -88,5 +153,52 @@ module SpeckleSystems::SpeckleConnector
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']
}
+8613 -3837
View File
File diff suppressed because it is too large Load Diff
+8 -5
View File
@@ -3,21 +3,24 @@
"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",
"debounce": "^1.2.1",
"mixpanel-browser": "^2.45.0",
"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 +44,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>
+12 -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>
@@ -118,7 +119,9 @@ global.setSelectedAccount = function (account) {
export default {
name: 'App',
components: {},
components: {
GlobalToast: () => import('@/components/GlobalToast')
},
props: {
size: {
type: Number,
@@ -144,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', () => {
@@ -159,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>
+182 -178
View File
@@ -1,177 +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 class="mr-1" :datetime="stream.updatedAt" />
|
<v-icon class="ml-1" small>mdi-account-key-outline</v-icon>
{{ stream.role.split(':')[1] }}
</div>
<v-toolbar-title>
<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>
<transition name="expand">
<v-card-text v-if="hover && !$apollo.loading" class="mt-0 pt-0">
<transition name="fade">
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"
class="small-text-field"
xxxclass="small-text-field"
hide-details
dense
flat
label="Commit Message"
placeholder="Write your commit message here"
></v-text-field>
</transition>
</v-card-text>
<v-progress-linear
v-if="(loadingSend || loadingReceive) && loadingStage"
height="14"
indeterminate
>
<div class="text-caption">{{ loadingStage }}</div>
</v-progress-linear>
</transition>
</v-card>
</v-hover>
/>
</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>
@@ -194,16 +165,25 @@ 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,
@@ -302,12 +282,16 @@ export default {
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`)
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) {
@@ -315,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
@@ -372,8 +365,7 @@ 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('>>> SpeckleSketchUp: Objects requested from SketchUp')
await this.sleep(2000)
@@ -382,6 +374,9 @@ export default {
if (objects.length == 0) {
this.loadingSend = false
this.loadingStage = null
this.$eventHub.$emit('notification', {
text: 'No objects selected. Nothing was sent.'
})
return
}
@@ -398,7 +393,7 @@ export default {
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)}%`
}
@@ -411,7 +406,7 @@ export default {
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)
@@ -422,7 +417,16 @@ export default {
}
})
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) {
@@ -433,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',
-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
}
}
}
}
}
+6 -6
View File
@@ -4,18 +4,18 @@ 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']
}