Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9aea5ddc97 | |||
| 4d1333c302 | |||
| 9ca55f6f0e | |||
| 4698799b43 | |||
| 5d4e6ac89a | |||
| b1c09c62d9 | |||
| 2b7b74dbdd | |||
| 42f3ae8490 | |||
| 479cd9584a | |||
| ec250fe6a5 | |||
| 7d73ebf7d0 | |||
| a965065e62 | |||
| c9c9ecf5c6 | |||
| 9b5c043029 | |||
| 0eb835bed0 | |||
| 7a7ce8ff3d | |||
| 473f0890fe | |||
| a8955a435f | |||
| 80f25eb1a2 | |||
| 5716f92fbf |
+52
-18
@@ -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,15 +40,14 @@ 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
|
||||
python patch_version.py $semver
|
||||
speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\sketchup.iss
|
||||
- persist_to_workspace:
|
||||
root: ./
|
||||
@@ -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:
|
||||
@@ -91,6 +87,30 @@ jobs:
|
||||
from: '"speckle-sharp-ci-tools/Installers/"'
|
||||
to: s3://speckle-releases/installers/
|
||||
|
||||
deploy-manager2:
|
||||
docker:
|
||||
- image: mcr.microsoft.com/dotnet/sdk:6.0
|
||||
parameters:
|
||||
slug:
|
||||
type: string
|
||||
os:
|
||||
type: string
|
||||
extension:
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: ./
|
||||
- run:
|
||||
name: Install Manager Feed CLI
|
||||
command: dotnet tool install --global Speckle.Manager.Feed
|
||||
- run:
|
||||
name: Upload new version
|
||||
command: |
|
||||
TAG=$(if [ "${CIRCLE_TAG}" ]; then echo $CIRCLE_TAG; else echo "0.0.0"; fi;)
|
||||
SEMVER=$(echo "$TAG" | sed -e 's/\/[a-zA-Z-]*//')
|
||||
/root/.dotnet/tools/Speckle.Manager.Feed deploy -s << parameters.slug >> -v ${SEMVER} -u https://releases.speckle.dev/installers/<< parameters.slug >>/<< parameters.slug >>-${SEMVER}.<< parameters.extension >> -o << parameters.os >> -f speckle-sharp-ci-tools/Installers/<< parameters.slug >>/<< parameters.slug >>-${SEMVER}.<< parameters.extension >>
|
||||
|
||||
workflows:
|
||||
build-and-deploy:
|
||||
jobs:
|
||||
@@ -120,6 +140,20 @@ workflows:
|
||||
- build-connector
|
||||
filters:
|
||||
tags:
|
||||
only: /[0-9]+(\.[0-9]+)*/
|
||||
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
|
||||
branches:
|
||||
ignore: /.*/ # For testing only! /ci\/.*/
|
||||
|
||||
- deploy-manager2:
|
||||
slug: sketchup
|
||||
os: Win
|
||||
extension: exe
|
||||
requires:
|
||||
- get-ci-tools
|
||||
- build-ui
|
||||
- deploy
|
||||
filters:
|
||||
tags:
|
||||
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
|
||||
branches:
|
||||
ignore: /.*/ # For testing only! /ci\/.*/
|
||||
|
||||
@@ -24,6 +24,9 @@ speckle_connector/html
|
||||
/test/version_tmp/
|
||||
/tmp/
|
||||
|
||||
# IDE
|
||||
.idea
|
||||
|
||||
# Used by dotenv library to load environment variables.
|
||||
.env
|
||||
|
||||
|
||||
Vendored
+10
-1
@@ -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": []
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -2,15 +2,13 @@
|
||||
|
||||
source "https://rubygems.org"
|
||||
|
||||
# gem "rake", "~> 13.0"
|
||||
|
||||
gem "rubocop", "~> 1.7"
|
||||
|
||||
|
||||
group :development do
|
||||
gem "minitest"
|
||||
gem 'pry' # ruby console and debugger
|
||||
gem "sketchup-api-stubs"
|
||||
gem 'rake' # ruby make
|
||||
gem 'rubycritic', '~> 4.3', '>= 4.3.3', require: false
|
||||
gem "solargraph"
|
||||
gem "rubocop"
|
||||
gem 'skippy', '~> 0.4.1.a' # Aid with common SketchUp extension tasks.
|
||||
end
|
||||
|
||||
gem "sqlite3", "~> 1.4"
|
||||
|
||||
+112
-45
@@ -1,74 +1,141 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
ast (2.4.2)
|
||||
axiom-types (0.1.1)
|
||||
descendants_tracker (~> 0.0.4)
|
||||
ice_nine (~> 0.11.0)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
backport (1.2.0)
|
||||
benchmark (0.1.1)
|
||||
diff-lcs (1.4.4)
|
||||
e2mmap (0.1.0)
|
||||
coderay (1.1.3)
|
||||
coercible (1.0.0)
|
||||
descendants_tracker (~> 0.0.1)
|
||||
descendants_tracker (0.0.4)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
docile (1.4.0)
|
||||
equalizer (0.0.11)
|
||||
erubi (1.11.0)
|
||||
flay (2.13.0)
|
||||
erubi (~> 1.10)
|
||||
path_expander (~> 1.0)
|
||||
ruby_parser (~> 3.0)
|
||||
sexp_processor (~> 4.0)
|
||||
flog (4.6.6)
|
||||
path_expander (~> 1.0)
|
||||
ruby_parser (~> 3.1, > 3.1.0)
|
||||
sexp_processor (~> 4.8)
|
||||
git (1.11.0)
|
||||
rchardet (~> 1.8)
|
||||
ice_nine (0.11.2)
|
||||
jaro_winkler (1.5.4)
|
||||
kramdown (2.3.1)
|
||||
rexml
|
||||
kramdown-parser-gfm (1.1.0)
|
||||
kramdown (~> 2.0)
|
||||
minitest (5.14.4)
|
||||
nokogiri (1.12.5-x64-mingw32)
|
||||
kwalify (0.7.2)
|
||||
launchy (2.5.0)
|
||||
addressable (~> 2.7)
|
||||
maruku (0.7.3)
|
||||
method_source (1.0.0)
|
||||
minitest (5.16.2)
|
||||
naturally (2.2.1)
|
||||
nokogiri (1.13.8-x64-mingw32)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.12.5-x86_64-linux)
|
||||
racc (~> 1.4)
|
||||
parallel (1.20.1)
|
||||
parser (3.0.2.0)
|
||||
parallel (1.22.1)
|
||||
parser (2.7.2.0)
|
||||
ast (~> 2.4.1)
|
||||
path_expander (1.1.1)
|
||||
pry (0.14.1)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
psych (3.3.2)
|
||||
public_suffix (4.0.7)
|
||||
racc (1.6.0)
|
||||
rainbow (3.0.0)
|
||||
regexp_parser (2.1.1)
|
||||
reverse_markdown (2.0.0)
|
||||
rainbow (3.1.1)
|
||||
rake (13.0.6)
|
||||
rchardet (1.8.0)
|
||||
reek (6.0.2)
|
||||
kwalify (~> 0.7.0)
|
||||
parser (>= 2.5.0.0, < 2.8, != 2.5.1.1)
|
||||
psych (~> 3.1)
|
||||
rainbow (>= 2.0, < 4.0)
|
||||
regexp_parser (2.5.0)
|
||||
reverse_markdown (1.4.0)
|
||||
nokogiri
|
||||
rexml (3.2.5)
|
||||
rubocop (1.19.1)
|
||||
rubocop (0.93.1)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.0.0.0)
|
||||
parser (>= 2.7.1.5)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
regexp_parser (>= 1.8)
|
||||
rexml
|
||||
rubocop-ast (>= 1.9.1, < 2.0)
|
||||
rubocop-ast (>= 0.6.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 3.0)
|
||||
rubocop-ast (1.11.0)
|
||||
parser (>= 3.0.1.1)
|
||||
unicode-display_width (>= 1.4.0, < 2.0)
|
||||
rubocop-ast (1.4.1)
|
||||
parser (>= 2.7.1.5)
|
||||
ruby-progressbar (1.11.0)
|
||||
sketchup-api-stubs (0.7.7)
|
||||
solargraph (0.43.0)
|
||||
backport (~> 1.2)
|
||||
benchmark
|
||||
ruby_parser (3.19.1)
|
||||
sexp_processor (~> 4.16)
|
||||
rubycritic (4.7.0)
|
||||
flay (~> 2.8)
|
||||
flog (~> 4.4)
|
||||
launchy (>= 2.0.0)
|
||||
parser (>= 2.6.0)
|
||||
rainbow (~> 3.0)
|
||||
reek (~> 6.0, < 7.0)
|
||||
ruby_parser (~> 3.8)
|
||||
simplecov (>= 0.17.0)
|
||||
tty-which (~> 0.4.0)
|
||||
virtus (~> 1.0)
|
||||
sexp_processor (4.16.1)
|
||||
simplecov (0.21.2)
|
||||
docile (~> 1.1)
|
||||
simplecov-html (~> 0.11)
|
||||
simplecov_json_formatter (~> 0.1)
|
||||
simplecov-html (0.12.3)
|
||||
simplecov_json_formatter (0.1.4)
|
||||
sketchup-api-stubs (0.7.8)
|
||||
skippy (0.4.3.a)
|
||||
git (~> 1.3)
|
||||
naturally (~> 2.1)
|
||||
thor (~> 0.19)
|
||||
solargraph (0.38.0)
|
||||
backport (~> 1.1)
|
||||
bundler (>= 1.17.2)
|
||||
diff-lcs (~> 1.4)
|
||||
e2mmap
|
||||
jaro_winkler (~> 1.5)
|
||||
kramdown (~> 2.3)
|
||||
kramdown-parser-gfm (~> 1.1)
|
||||
parser (~> 3.0)
|
||||
reverse_markdown (>= 1.0.5, < 3)
|
||||
rubocop (>= 0.52)
|
||||
thor (~> 1.0)
|
||||
maruku (~> 0.7, >= 0.7.3)
|
||||
nokogiri (~> 1.9, >= 1.9.1)
|
||||
parser (~> 2.3)
|
||||
reverse_markdown (~> 1.0, >= 1.0.5)
|
||||
rubocop (~> 0.52)
|
||||
thor (~> 0.19, >= 0.19.4)
|
||||
tilt (~> 2.0)
|
||||
yard (~> 0.9, >= 0.9.24)
|
||||
sqlite3 (1.4.2)
|
||||
thor (1.1.0)
|
||||
tilt (2.0.10)
|
||||
unicode-display_width (2.0.0)
|
||||
yard (0.9.26)
|
||||
yard (~> 0.9)
|
||||
thor (0.20.3)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.0.11)
|
||||
tty-which (0.4.2)
|
||||
unicode-display_width (1.8.0)
|
||||
virtus (1.0.5)
|
||||
axiom-types (~> 0.1)
|
||||
coercible (~> 1.0)
|
||||
descendants_tracker (~> 0.0, >= 0.0.3)
|
||||
equalizer (~> 0.0, >= 0.0.9)
|
||||
webrick (1.7.0)
|
||||
yard (0.9.28)
|
||||
webrick (~> 1.7.0)
|
||||
|
||||
PLATFORMS
|
||||
x64-mingw32
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
minitest
|
||||
rubocop (~> 1.7)
|
||||
pry
|
||||
rake
|
||||
rubocop
|
||||
rubycritic (~> 4.3, >= 4.3.3)
|
||||
sketchup-api-stubs
|
||||
skippy (~> 0.4.1.a)
|
||||
solargraph
|
||||
sqlite3 (~> 1.4)
|
||||
|
||||
BUNDLED WITH
|
||||
2.2.26
|
||||
2.3.20
|
||||
|
||||
-15
@@ -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__)
|
||||
@@ -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
|
||||
@@ -1,8 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "speckle_connector/version"
|
||||
|
||||
module SpeckleConnector
|
||||
class Error < StandardError; end
|
||||
# Your code goes here...
|
||||
end
|
||||
@@ -1,5 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module SpeckleConnector
|
||||
VERSION = "0.1.0"
|
||||
end
|
||||
+3
-2
@@ -25,7 +25,8 @@ def patch_installer(tag):
|
||||
|
||||
with open(iss_file, "r") as file:
|
||||
lines = file.readlines()
|
||||
lines.insert(11, f'#define AppVersion "{tag}"\n')
|
||||
lines.insert(11, f'#define AppVersion "{tag.split("-")[0]}"\n')
|
||||
lines.insert(12, f'#define AppInfoVersion "{tag}"\n')
|
||||
|
||||
with open(iss_file, "w") as file:
|
||||
file.writelines(lines)
|
||||
@@ -39,7 +40,7 @@ def main():
|
||||
return
|
||||
|
||||
tag = sys.argv[1]
|
||||
if not re.match(r"[0-9]+(\.[0-9]+)*$", tag):
|
||||
if not re.match(r"([0-9]+)\.([0-9]+)\.([0-9]+)", tag):
|
||||
raise ValueError(f"Invalid tag provided: {tag}")
|
||||
|
||||
print(f"Patching version: {tag}")
|
||||
|
||||
@@ -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,18 +23,16 @@ module SpeckleSystems::SpeckleConnector
|
||||
rows.map { |row| JSON.parse(row[1]) }
|
||||
end
|
||||
|
||||
def self.get_suuid
|
||||
dir = _get_speckle_dir
|
||||
suuid_path = File.join(dir, "suuid")
|
||||
return unless File.exist?(suuid_path)
|
||||
|
||||
File.read(suuid_path)
|
||||
def self.default_account
|
||||
accts = load_accounts
|
||||
accts.select { |acc| acc["isDefault"] }[0] || accts[0]
|
||||
end
|
||||
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
@@ -6,9 +6,16 @@ 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")
|
||||
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]) }
|
||||
return if is_ignored_speckle_type(obj)
|
||||
|
||||
if obj["displayValue"].nil?
|
||||
puts(">>> Found #{obj["speckle_type"]}: #{obj["id"]}. Continuing traversal.")
|
||||
props = obj.keys.filter_map { |key| key unless key.start_with?("_") }
|
||||
props.each { |prop| traverse_commit_object(obj[prop]) }
|
||||
else
|
||||
puts(">>> Found #{obj["speckle_type"]}: #{obj["id"]} with displayValue.")
|
||||
convert_to_native(obj)
|
||||
end
|
||||
elsif obj.is_a?(Hash)
|
||||
obj.each_value { |value| traverse_commit_object(value) }
|
||||
elsif obj.is_a?(Array)
|
||||
@@ -32,20 +39,26 @@ 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)
|
||||
return display_value_to_native_component(obj, entities) unless obj["displayValue"].nil?
|
||||
|
||||
case obj["speckle_type"]
|
||||
when "Objects.Geometry.Line", "Objects.Geometry.Polyline" then edge_to_native(obj, entities)
|
||||
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.Brep" then mesh_to_native(obj["displayValue"], entities)
|
||||
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 length_to_native(length, units = @units)
|
||||
@@ -65,6 +78,15 @@ module SpeckleSystems::SpeckleConnector::ToNative
|
||||
end
|
||||
end
|
||||
|
||||
def edge_to_native_component(line, entities)
|
||||
line_id = line["applicationId"].to_s.empty? ? line["id"] : line["applicationId"]
|
||||
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,7 +95,16 @@ module SpeckleSystems::SpeckleConnector::ToNative
|
||||
Geom::Point3d.new(length_to_native(x, units), length_to_native(y, units), length_to_native(z, units))
|
||||
end
|
||||
|
||||
def point_to_native_array(x ,y ,z ,units)
|
||||
[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)
|
||||
_speckle_mesh_to_native_mesh(mesh, entities)
|
||||
end
|
||||
|
||||
def _speckle_mesh_to_native_mesh(mesh, entities)
|
||||
native_mesh = Geom::PolygonMesh.new(mesh["vertices"].count / 3)
|
||||
points = []
|
||||
mesh["vertices"].each_slice(3) do |pt|
|
||||
@@ -88,29 +119,204 @@ module SpeckleSystems::SpeckleConnector::ToNative
|
||||
native_mesh.add_polygon(indices.map { |index| points[index] })
|
||||
end
|
||||
entities.add_faces_from_mesh(native_mesh, 4, material_to_native(mesh["renderMaterial"]))
|
||||
merge_coplanar_faces(entities)
|
||||
native_mesh
|
||||
end
|
||||
|
||||
# Removes coplanar entities from the given entities.
|
||||
# @param entities [Sketchup::Entities] entities to remove edges between that make entities coplanar.
|
||||
# @note Merging coplanar faces idea originated from [CleanUp](https://github.com/thomthom/cleanup) plugin
|
||||
# which is developed by [Thomas Thomassen](https://github.com/thomthom).
|
||||
def merge_coplanar_faces(entities)
|
||||
edges = []
|
||||
faces = entities.collect { |entity| entity if entity.is_a? Sketchup::Face }.compact
|
||||
faces.each { |face| face.edges.each { |edge| edges << edge } }
|
||||
edges.compact!
|
||||
edges.each { |edge| remove_edge_have_coplanar_faces(edge, false ) }
|
||||
end
|
||||
|
||||
# Detect edges to remove by checking following controls respectively;
|
||||
# - Upcoming Sketchup entity is Sketchup::Edge or not.
|
||||
# - Whether edge has 2 face or not.
|
||||
# - Whether faces are duplicated or not.
|
||||
# - Whether edges safe to merge or not.
|
||||
# - Whether faces have same material or not.
|
||||
# - Whether UV texture map is aligned between faces or not.
|
||||
# - Finally, if faces are coplanar by correcting these checks, then removes edge from Sketchup.active_model.
|
||||
# @param edge [Sketchup::Edge] edge to check.
|
||||
# @param ignore_materials [Boolean] whether ignore materials or not.
|
||||
# Returns true if the given edge separating two coplanar faces.
|
||||
# Return false otherwise.
|
||||
def remove_edge_have_coplanar_faces(edge, ignore_materials)
|
||||
return false unless edge.valid? && edge.is_a?(Sketchup::Edge)
|
||||
return false unless edge.faces.size == 2
|
||||
|
||||
face1, face2 = edge.faces
|
||||
|
||||
return false if face_duplicate?(face1, face2)
|
||||
# Check for troublesome faces which might lead to missing geometry if merged.
|
||||
return false unless edge_safe_to_merge?(edge)
|
||||
|
||||
# Check materials match.
|
||||
unless ignore_materials
|
||||
if face1.material == face2.material && face1.back_material == face2.back_material
|
||||
# Verify UV mapping match.
|
||||
unless face1.material.nil? || face1.material.texture.nil?
|
||||
return false unless continuous_uv?(face1, face2, edge)
|
||||
end
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
# Check faces are coplanar or not.
|
||||
return false unless faces_coplanar?(face1, face2)
|
||||
edge.erase!
|
||||
true
|
||||
end
|
||||
|
||||
# Determines if two faces are overlapped.
|
||||
def face_duplicate?(face1, face2, overlapping = false)
|
||||
return false if face1 == face2
|
||||
|
||||
v1 = face1.outer_loop.vertices
|
||||
v2 = face2.outer_loop.vertices
|
||||
return true if (v1 - v2).empty? && (v2 - v1).empty?
|
||||
|
||||
if overlapping && (v2 - v1).empty?
|
||||
edges = (face2.outer_loop.edges - face1.outer_loop.edges)
|
||||
unless edges.empty?
|
||||
point = edges[0].start.position.offset(edges[0].line[1], 0.01)
|
||||
return true if face1.classify_point(point) <= 4
|
||||
end
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
# Checks the given edge for potential problems if the connected faces would
|
||||
# be merged.
|
||||
def edge_safe_to_merge?(edge)
|
||||
edge.faces.all? { |face| self.face_safe_to_merge?(face) }
|
||||
end
|
||||
|
||||
# Returns true if the two faces connected by the edge has continuous UV mapping.
|
||||
# UV's are normalized to 0.0..1.0 before comparison.
|
||||
def continuous_uv?(face1, face2, edge)
|
||||
tw = Sketchup.create_texture_writer
|
||||
uvh1 = face1.get_UVHelper(true, true, tw)
|
||||
uvh2 = face2.get_UVHelper(true, true, tw)
|
||||
p1 = edge.start.position
|
||||
p2 = edge.end.position
|
||||
self.uv_equal?(uvh1.get_front_UVQ(p1), uvh2.get_front_UVQ(p1)) &&
|
||||
self.uv_equal?(uvh1.get_front_UVQ(p2), uvh2.get_front_UVQ(p2)) &&
|
||||
self.uv_equal?(uvh1.get_back_UVQ(p1), uvh2.get_back_UVQ(p1)) &&
|
||||
self.uv_equal?(uvh1.get_back_UVQ(p2), uvh2.get_back_UVQ(p2))
|
||||
end
|
||||
|
||||
# Normalize UV's to 0.0..1.0 and compare them.
|
||||
def uv_equal?(uvq1, uvq2)
|
||||
uv1 = uvq1.to_a.map { |n| n % 1 }
|
||||
uv2 = uvq2.to_a.map { |n| n % 1 }
|
||||
uv1 == uv2
|
||||
end
|
||||
|
||||
# Validates that the given face can be merged with other faces without causing
|
||||
# problems.
|
||||
def face_safe_to_merge?(face)
|
||||
stack = face.outer_loop.edges
|
||||
edge = stack.shift
|
||||
direction = edge.line[1]
|
||||
until stack.empty?
|
||||
edge = stack.shift
|
||||
return true unless edge.line[1].parallel?(direction)
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
# Determines if two faces are coplanar.
|
||||
def faces_coplanar?(face1, face2)
|
||||
vertices = face1.vertices + face2.vertices
|
||||
plane = Geom.fit_plane_to_points(vertices)
|
||||
vertices.all? { |v| v.position.on_plane?(plane) }
|
||||
end
|
||||
|
||||
|
||||
def _hidden_edges_mesh_to_native_mesh(mesh, entities)
|
||||
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
|
||||
edge_flags = mesh["faceEdgeFlags"]
|
||||
faces = mesh["faces"]
|
||||
loops = []
|
||||
flags = []
|
||||
while faces.count.positive?
|
||||
num_pts = faces.shift
|
||||
# 0 -> 3, 1 -> 4 to preserve backwards compatibility
|
||||
num_pts += 3 if num_pts < 3
|
||||
indices = faces.shift(num_pts)
|
||||
current_edge_flags = edge_flags.shift(num_pts)
|
||||
outer_loop = indices.map { |index| points[index] }
|
||||
if current_edge_flags.include?(true)
|
||||
loops << outer_loop
|
||||
flags << current_edge_flags
|
||||
else
|
||||
native_mesh.add_polygon(outer_loop)
|
||||
end
|
||||
end
|
||||
entities.add_faces_from_mesh(native_mesh, 0, material_to_native(mesh["renderMaterial"]))
|
||||
|
||||
loops.each do |l|
|
||||
loop_flags = flags.shift
|
||||
face = entities.add_face(l)
|
||||
face.edges.each_with_index { |edge, index| edge.soft = edge.smooth = loop_flags[index] }
|
||||
end
|
||||
|
||||
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 speckle object with a display value
|
||||
def display_value_to_native_component(obj, entities)
|
||||
obj_id = obj["applicationId"].to_s.empty? ? obj["id"] : obj["applicationId"]
|
||||
definition = component_definition_to_native(obj["displayValue"], "def::#{obj_id}")
|
||||
find_and_erase_existing_instance(definition, obj_id)
|
||||
transform = obj["transform"].nil? ? Geom::Transformation.new : transform_to_native(obj["transform"])
|
||||
instance = entities.add_instance(definition, transform)
|
||||
instance.name = obj_id
|
||||
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"]})")
|
||||
puts(" entity count: #{definition.entities.count}")
|
||||
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"]
|
||||
transform = transform_to_native(
|
||||
block["transform"].is_a?(Hash) ? block["transform"]["value"] : block["transform"],
|
||||
block["units"]
|
||||
@@ -121,9 +327,12 @@ module SpeckleSystems::SpeckleConnector::ToNative
|
||||
else
|
||||
entities.add_instance(definition, transform)
|
||||
end
|
||||
# erase existing instances after creation and before rename because you can't have definitions without instances
|
||||
find_and_erase_existing_instance(definition, name, block["applicationId"])
|
||||
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
|
||||
|
||||
|
||||
@@ -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,33 @@ 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[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)
|
||||
|
||||
# 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))
|
||||
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))
|
||||
mat_groups[mat_id]["@(31250)faceEdgeFlags"].push(*mesh_edge_flags_to_array(mesh))
|
||||
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]))
|
||||
mat_groups[mat_id]["@(31250)faceEdgeFlags"].push(*face_edge_flags_to_array(face))
|
||||
end
|
||||
mat_groups[mat_id][:pt_count] += face.vertices.count
|
||||
|
||||
# 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
|
||||
|
||||
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)
|
||||
@@ -113,29 +121,37 @@ module SpeckleSystems::SpeckleConnector::ToSpeckle
|
||||
bbox: bounds_to_speckle(bounds),
|
||||
"@(31250)vertices" => [],
|
||||
"@(62500)faces" => [],
|
||||
"@(31250)faceEdgeFlags" => [],
|
||||
"@(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 = 0)
|
||||
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 an array of face indices from a sketchup polygon mesh INCLUDING negative indices for hidden meshes
|
||||
def mesh_faces_with_edges_to_array(mesh, offset)
|
||||
faces = []
|
||||
mesh.polygons.each do |poly|
|
||||
faces.push(
|
||||
poly.count, *poly.map { |index| index.positive? ? index + offset : index - offset }
|
||||
)
|
||||
end
|
||||
faces
|
||||
end
|
||||
|
||||
# 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 +163,46 @@ module SpeckleSystems::SpeckleConnector::ToSpeckle
|
||||
pts_array
|
||||
end
|
||||
|
||||
def mesh_edge_flags_to_array(mesh)
|
||||
edge_flags = []
|
||||
mesh.polygons.each do |poly|
|
||||
edge_flags.push(
|
||||
*poly.map(&:negative?)
|
||||
)
|
||||
end
|
||||
edge_flags
|
||||
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 face indices from a sketchup face
|
||||
def face_indices_with_edges_to_array(face, offset = 1)
|
||||
soft_edges = face.outer_loop.edges.map(&:soft?)
|
||||
face_array = [face.vertices.count]
|
||||
face_array.push(*face.vertices.count.times.map { |index| soft_edges[index] ? -(index + offset) : index + offset })
|
||||
face_array
|
||||
end
|
||||
|
||||
def face_edge_flags_to_array(face)
|
||||
face.outer_loop.edges.map(&:soft?)
|
||||
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 +215,15 @@ 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),
|
||||
"@(31250)faceEdgeFlags" => mesh.nil? ? face_edge_flags_to_array(face) : mesh_edge_flags_to_array(mesh),
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
+125
-13
@@ -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,13 +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})")
|
||||
@dialog.execute_script("loadAccounts(#{Accounts.load_accounts.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
|
||||
|
||||
@@ -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__)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -1,5 +1,3 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
presets: ['@vue/cli-plugin-babel/preset']
|
||||
}
|
||||
|
||||
Generated
+14142
-6738
File diff suppressed because it is too large
Load Diff
+9
-6
@@ -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",
|
||||
"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 +44,4 @@
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"vuetify-loader": "^1.7.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+30
-23
@@ -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>
|
||||
|
||||
+18
-15
@@ -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>
|
||||
@@ -95,16 +96,14 @@ import { bus } from './main'
|
||||
import userQuery from './graphql/user.gql'
|
||||
import { onLogin } from './vue-apollo'
|
||||
|
||||
global.loadAccounts = function (accounts, suuid) {
|
||||
console.log('>>> SpeckleSketchup: Loading accounts', accounts, `suuid: ${suuid}`)
|
||||
global.loadAccounts = function (accounts) {
|
||||
console.log('>>> SpeckleSketchup: Loading accounts', accounts)
|
||||
localStorage.setItem('localAccounts', JSON.stringify(accounts))
|
||||
if (suuid) {
|
||||
localStorage.setItem('suuid', suuid)
|
||||
global.setSelectedAccount(accounts.find((acct) => acct['isDefault']))
|
||||
let uuid = localStorage.getItem('uuid')
|
||||
if (uuid) {
|
||||
global.setSelectedAccount(accounts.find((acct) => acct['userInfo']['id'] == uuid))
|
||||
} else {
|
||||
global.setSelectedAccount(
|
||||
accounts.find((acct) => acct['userInfo']['id'] == localStorage.getItem('uuid'))
|
||||
)
|
||||
global.setSelectedAccount(accounts.find((acct) => acct['isDefault']))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +117,9 @@ global.setSelectedAccount = function (account) {
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {},
|
||||
components: {
|
||||
GlobalToast: () => import('@/components/GlobalToast')
|
||||
},
|
||||
props: {
|
||||
size: {
|
||||
type: Number,
|
||||
@@ -144,6 +145,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 +161,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')
|
||||
}
|
||||
|
||||
@@ -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
@@ -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',
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<v-card>hello there!</v-card>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -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
@@ -43,7 +43,7 @@ query Stream($id: String!) {
|
||||
authorName
|
||||
authorAvatar
|
||||
referencedObject
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ query Streams($cursor: String) {
|
||||
authorName
|
||||
authorAvatar
|
||||
referencedObject
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ query {
|
||||
verified
|
||||
profiles
|
||||
role
|
||||
suuid
|
||||
streams {
|
||||
totalCount
|
||||
}
|
||||
|
||||
@@ -9,6 +9,5 @@ query User($id: String!) {
|
||||
verified
|
||||
profiles
|
||||
role
|
||||
suuid
|
||||
}
|
||||
}
|
||||
|
||||
+8
-6
@@ -1,21 +1,23 @@
|
||||
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()
|
||||
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
import Vue from 'vue'
|
||||
import Vuetify from 'vuetify/lib/framework'
|
||||
|
||||
import '@/scss/styles.css'
|
||||
Vue.use(Vuetify)
|
||||
|
||||
export default new Vuetify({
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -1,7 +1,7 @@
|
||||
const path = require('path')
|
||||
|
||||
module.exports = {
|
||||
publicPath: "./",
|
||||
publicPath: './',
|
||||
outputDir: path.resolve(__dirname, '../speckle_connector', 'html'),
|
||||
transpileDependencies: ['vuetify', '@speckle/objectloader']
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user