Merge branch 'main' into dim/frontend-fiddle

This commit is contained in:
Dimitrie Stefanescu
2021-09-01 17:09:03 +01:00
16 changed files with 509 additions and 248 deletions
+78 -87
View File
@@ -1,91 +1,82 @@
{
"window.openFilesInNewWindow": "off",
"explorer.confirmDelete": false,
// "[vue]": {
// "editor.formatOnSave": true,
// },
"files.associations": {
"*.vue": "vue"
"window.openFilesInNewWindow": "off",
"explorer.confirmDelete": false,
// "[vue]": {
// "editor.formatOnSave": true,
// },
"files.associations": {
"*.vue": "vue"
},
"editor.formatOnPaste": true,
"editor.multiCursorModifier": "ctrlCmd",
"editor.snippetSuggestions": "top",
"eslint.format.enable": true,
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"vetur.format.defaultFormatterOptions": {
"js-beautify-html": {
"wrap_attributes": "force-expand-multiline"
},
"editor.formatOnPaste": true,
"editor.multiCursorModifier": "ctrlCmd",
"editor.snippetSuggestions": "top",
"workbench.colorTheme": "Monokai",
"eslint.format.enable": true,
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"vetur.format.defaultFormatterOptions": {
"js-beautify-html": {
"wrap_attributes": "force-expand-multiline"
},
"prettyhtml": {
"printWidth": 100,
"singleQuote": false,
"wrapAttributes": false,
"sortAttributes": false
}
},
"cSpell.enableFiletypes": [
"vue-html",
"vue-postcss"
],
"[vue]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"cSpell.userWords": [
"Matomo",
"SUPPRESSMSGBOXES",
"matomo"
],
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
"prettyhtml": {
"printWidth": 100,
"singleQuote": false,
"wrapAttributes": false,
"sortAttributes": false
}
// "[javascript]": {
// "editor.defaultFormatter": "dbaeumer.vscode-eslint"
// },
// "[jsonc]": {
// "editor.defaultFormatter": "esbenp.prettier-vscode"
// }
// "eslint.codeAction.showDocumentation": {
// "enable": true
// },
// "eslint.validate": ["vue"],
// "[js]": {
// "editor.defaultFormatter": "dbaeumer.vscode-eslint"
// },
// "vetur.format.defaultFormatterOptions": {
// "prettier": {
// // Prettier option here
// "semi": false,
// "trailingComma": "es5",
// "tabWidth": 2,
// "bracketSpacing": true,
// "vueIndentScriptAndStyle": true,
// "eslintIntegration": true
// }
// },
// "[javascript]": {
// "editor.defaultFormatter": "dbaeumer.vscode-eslint"
// },
// "[vue]": {
// //"editor.defaultFormatter": "octref.vetur"
// "editor.defaultFormatter": "dbaeumer.vscode-eslint"
// },
// "cSpell.userWords": [
// "vetur",
// "vuetify"
// ],
// "[jsonc]": {
// "editor.defaultFormatter": "esbenp.prettier-vscode"
// }
},
"cSpell.enableFiletypes": ["vue-html", "vue-postcss"],
"[vue]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"cSpell.userWords": ["Matomo", "SUPPRESSMSGBOXES", "matomo"],
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
// "[javascript]": {
// "editor.defaultFormatter": "dbaeumer.vscode-eslint"
// },
// "[jsonc]": {
// "editor.defaultFormatter": "esbenp.prettier-vscode"
// }
// "eslint.codeAction.showDocumentation": {
// "enable": true
// },
// "eslint.validate": ["vue"],
// "[js]": {
// "editor.defaultFormatter": "dbaeumer.vscode-eslint"
// },
// "vetur.format.defaultFormatterOptions": {
// "prettier": {
// // Prettier option here
// "semi": false,
// "trailingComma": "es5",
// "tabWidth": 2,
// "bracketSpacing": true,
// "vueIndentScriptAndStyle": true,
// "eslintIntegration": true
// }
// },
// "[javascript]": {
// "editor.defaultFormatter": "dbaeumer.vscode-eslint"
// },
// "[vue]": {
// //"editor.defaultFormatter": "octref.vetur"
// "editor.defaultFormatter": "dbaeumer.vscode-eslint"
// },
// "cSpell.userWords": [
// "vetur",
// "vuetify"
// ],
// "[jsonc]": {
// "editor.defaultFormatter": "esbenp.prettier-vscode"
// }
}
+5
View File
@@ -8,6 +8,11 @@ server {
try_files $uri $uri/ /app.html;
}
location /embed {
default_type text/html;
alias /usr/share/nginx/html/embedApp.html;
}
location ~ ^/streams/.* {
default_type text/html;
content_by_lua_block {
+130
View File
@@ -0,0 +1,130 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- <base href="/appname/"> -->
<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 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">
<style type="text/css">
body {
background-color: #333333;
color: #0A66FF;
}
@media screen and (prefers-color-scheme: light) {
body {
background-color: white;
color: #0A66FF;
}
}
.tada {
-webkit-animation-name: tada;
animation-name: tada;
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
animation-iteration-count: infinite;
}
@-webkit-keyframes tada {
0% {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
10%,
20% {
-webkit-transform: scale3d(.8, .8, .8) rotate3d(0, 0, 1, -3deg);
transform: scale3d(.8, .8, .8) rotate3d(0, 0, 1, -3deg);
}
30%,
50%,
70%,
90% {
-webkit-transform: scale3d(1.4, 1.4, 1.4) rotate3d(0, 0, 1, 3deg);
transform: scale3d(1.4, 1.4, 1.4) rotate3d(0, 0, 1, 3deg);
}
40%,
60%,
80% {
-webkit-transform: scale3d(1.4, 1.4, 1.4) rotate3d(0, 0, 1, -3deg);
transform: scale3d(1.4, 1.4, 1.4) rotate3d(0, 0, 1, -3deg);
}
100% {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
@keyframes tada {
0% {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
10%,
20% {
-webkit-transform: scale3d(.8, .8, .8) rotate3d(0, 0, 1, -3deg);
transform: scale3d(.8, .8, .8) rotate3d(0, 0, 1, -3deg);
}
30%,
50%,
70%,
90% {
-webkit-transform: scale3d(1.4, 1.4, 1.4) rotate3d(0, 0, 1, 3deg);
transform: scale3d(1.4, 1.4, 1.4) rotate3d(0, 0, 1, 3deg);
}
40%,
60%,
80% {
-webkit-transform: scale3d(1.4, 1.4, 1.4) rotate3d(0, 0, 1, -3deg);
transform: scale3d(1.4, 1.4, 1.4) rotate3d(0, 0, 1, -3deg);
}
100% {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
</style>
</head>
<body>
<noscript>
<strong>We're sorry but Speckle doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app">
<div style='
width: 100%;
height: 300px;
font-family: sans-serif !important;
position: absolute;
top:0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
text-align: center;
font-weight: 400;
font-size: 10px;
'>
<img src="<%= BASE_URL %>logo.svg" style="max-width: 50px" class="tada">
</div>
<!-- built files will be auto injected -->
</body>
</html>
@@ -352,9 +352,15 @@ export default {
async getPreviewImage(angle) {
angle = angle || 0
let previewUrl = this.objectUrl.replace('streams', 'preview') + '/' + angle
let token = undefined
try {
token = localStorage.getItem('AuthToken')
}catch (e) {
console.warn("Sanboxed mode, only public streams will fetch properly.")
}
const res = await fetch(previewUrl, {
headers: localStorage.getItem('AuthToken')
? { Authorization: `Bearer ${localStorage.getItem('AuthToken')}` }
headers: token
? { Authorization: `Bearer ${token}` }
: {}
})
const blob = await res.blob()
+8
View File
@@ -0,0 +1,8 @@
<template lang="html">
<router-view />
</template>
<script>
export default {
components: {}
}
</script>
+11
View File
@@ -0,0 +1,11 @@
import Vue from 'vue'
import App from './EmbedApp.vue'
import vuetify from './embedVuetify'
import router from './embedRouter'
Vue.config.productionTip = false
new Vue({
router,
vuetify,
render: (h) => h(App)
}).$mount('#app')
@@ -0,0 +1,22 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '*',
meta: {
title: 'Embed View | Speckle'
},
component: () => import('@/views/EmbedViewer.vue')
}
]
const router = new VueRouter({
mode: 'history',
// base: process.env.BASE_URL,
routes
})
export default router
@@ -0,0 +1,39 @@
import '@mdi/font/css/materialdesignicons.css'
import Vue from 'vue'
import Vuetify from 'vuetify/lib'
Vue.use(Vuetify)
let darkMediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
export default new Vuetify({
icons: {
iconfont: 'mdi'
},
theme: {
options: { customProperties: true },
dark: darkMediaQuery ? 'dark' : null,
themes: {
light: {
primary: '#047EFB', //blue
secondary: '#7BBCFF', //light blue
accent: '#FCF25E', //yellow
error: '#FF5555', //red
warning: '#FF9100', //orange
info: '#313BCF', //dark blue
success: '#4caf50',
background: '#eeeeee',
text: '#FFFFFF'
},
dark: {
primary: '#047EFB', //blue
secondary: '#7BBCFF', //light blue
accent: '#FCF25E', //yellow
error: '#FF5555', //red
warning: '#FF9100', //orange
info: '#313BCF', //dark blue
success: '#4caf50',
background: '#3a3b3c'
}
}
}
})
@@ -0,0 +1,44 @@
import gql from "graphql-tag";
export const serverInfoQuery = `
query ServerInfo {
serverInfo {
name
}
}
`
export const streamCommitQuery = `
query Stream($id: String!, $commit: String!) {
stream(id: $id) {
id
name
description
isPublic
commit(id: $commit) {
referencedObject
}
}
}
`
export const branchLastCommitQuery = `
query Stream($id: String!, $branch: String!) {
stream(id: $id) {
id
name
description
isPublic
branch(name: $branch) {
commits(limit: 1) {
totalCount
items {
referencedObject
}
}
}
}
}
`
@@ -0,0 +1,30 @@
import {
branchLastCommitQuery,
serverInfoQuery,
streamCommitQuery
} from "./speckleQueries.js";
export let SERVER_URL = window.location.origin;
// Unauthorised fetch, without token to prevent use of localStorage or exposing elsewhere.
export async function speckleFetch(query, variables) {
try {
var res = await fetch(`${SERVER_URL}/graphql`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
query: query,
variables: variables
})
});
return await res.json();
} catch (err) {
console.error("API call failed", err);
}
}
export const getServerInfo = () => speckleFetch(serverInfoQuery);
export const getLatestBranchCommit = (id, branch) => speckleFetch(branchLastCommitQuery, { id, branch });
export const getCommit = (id, commit) => speckleFetch(streamCommitQuery, { id, commit });
-5
View File
@@ -239,11 +239,6 @@ const routes = [
},
component: () => import('@/views/GettingStartedView.vue')
},
{
path: '/embed',
name: 'Embeded Viewer',
component: () => import('@/views/EmbedViewer.vue')
},
{
path: '*',
name: 'notfound',
+45 -90
View File
@@ -1,4 +1,4 @@
<template lang="html">
<template>
<v-app class="no-scrollbar">
<speckle-loading v-if="!stream || error" :error="error" style="z-index: 101" />
<div v-if="!error" class="no-scrollbar embed-view">
@@ -87,9 +87,10 @@
</template>
<script>
import gql from 'graphql-tag'
import Renderer from '../components/Renderer.vue'
import SpeckleLoading from '../components/SpeckleLoading.vue'
import { getCommit, getLatestBranchCommit, getServerInfo } from "@/embed/speckleUtils";
export default {
name: 'EmbedViewer',
components: { Renderer, SpeckleLoading },
@@ -98,6 +99,45 @@ export default {
return str.length > n ? str.substr(0, n - 3) + '...' : str
}
},
async beforeMount() {
try {
var serverInfoResponse = await getServerInfo()
this.serverInfo = serverInfoResponse.data.serverInfo
} catch (e) {
this.error = e.message
return
}
if(this.displayType === 'commit'){
try {
var res = await getCommit(this.input.stream, this.input.commit)
var data = res.data
var latestCommit = data.stream.commit
if (this.input.object === undefined) this.objectId = latestCommit.referencedObject
this.specificCommit = data.stream
} catch (e) {
this.error = e.message
return
}
} else {
try {
var res = await getLatestBranchCommit(this.input.stream, this.input.branch)
var data = res.data
console.log(data)
var latestCommit = data.stream.branch.commits.items[0] || data.stream.branch.commit
if (!latestCommit) {
this.error = 'No commit for this branch'
this.lastCommit = data.stream
return
}
if (this.input.object == undefined) this.objectId = latestCommit.referencedObject
else this.objectId = this.input.object
this.lastCommit = data.stream
} catch (e) {
this.error = e.message
return
}
}
},
data() {
return {
hideDetails: false,
@@ -108,95 +148,10 @@ export default {
object: this.$route.query.object,
branch: this.$route.query.branch || 'main',
commit: this.$route.query.commit
}
}
},
apollo: {
lastCommit: {
query: gql`
query Stream($id: String!, $branch: String!) {
stream(id: $id) {
id
name
description
isPublic
branch(name: $branch) {
commits(limit: 1) {
totalCount
items {
referencedObject
}
}
}
}
}
`,
variables() {
return {
id: this.input.stream,
branch: this.input.branch
}
},
error(err) {
this.error = err.message
},
update(data) {
var latestCommit = data.stream.branch.commits.items[0] || data.stream.branch.commit
if (!latestCommit) {
this.error = 'No commit for this branch'
return data.stream
}
if (this.input.object == undefined) this.objectId = latestCommit.referencedObject
else this.objectId = this.input.object
return data.stream
},
skip() {
return this.displayType === 'commit'
}
},
specificCommit: {
query: gql`
query Stream($id: String!, $commit: String!) {
stream(id: $id) {
id
name
description
isPublic
commit(id: $commit) {
referencedObject
}
}
}
`,
variables() {
return {
id: this.input.stream,
commit: this.input.commit
}
},
error(err) {
this.error = err.message
},
update(data) {
var latestCommit = data.stream.commit
if (this.input.object === undefined) this.objectId = latestCommit.referencedObject
return data.stream
},
skip() {
return this.displayType !== 'commit'
}
},
serverInfo: {
query: gql`
query ServerInfo {
serverInfo {
name
}
}
`,
error(err) {
this.error = err.message
}
lastCommit: null,
specificCommit: null,
serverInfo: null
}
},
computed: {
+6
View File
@@ -6,6 +6,12 @@ module.exports = {
title: 'Speckle',
template: 'public/app.html',
filename: 'app.html'
},
embedApp: {
entry: 'src/embed/embedApp.js',
title: 'Speckle Embed Viewer',
template: 'public/embedApp.html',
filename: 'embedApp.html'
}
},
devServer: {
+30 -25
View File
@@ -1,5 +1,5 @@
/**
* Simple client that streams object info from a Speckle Server.
* Simple client that streams object info from a Speckle Server.
* TODO: Object construction progress reporting is weird.
*/
@@ -9,7 +9,7 @@ export default class ObjectLoader {
/**
* Creates a new object loader instance.
* @param {*} param0
* @param {*} param0
*/
constructor( { serverUrl, streamId, token, objectId, options = { fullyTraverseArrays: false, excludeProps: [ ] } } ) {
this.INTERVAL_MS = 20
@@ -18,13 +18,18 @@ export default class ObjectLoader {
this.serverUrl = serverUrl || window.location.origin
this.streamId = streamId
this.objectId = objectId
this.token = token || localStorage.getItem( 'AuthToken' )
console.log('Object loader constructor called!!!')
try {
this.token = token || localStorage.getItem( 'AuthToken' )
} catch (error) {
// Accessing localStorage may throw when executing on sandboxed document, ignore.
}
this.headers = {
'Accept': 'text/plain'
}
if( token ) {
if( this.token ) {
this.headers['Authorization'] = `Bearer ${this.token}`
}
@@ -45,25 +50,25 @@ export default class ObjectLoader {
/**
* Use this method to receive and construct the object. It will return the full, de-referenced and de-chunked original object.
* @param {*} onProgress
* @returns
* @param {*} onProgress
* @returns
*/
async getAndConstructObject( onProgress ) {
;( await this.downloadObjectsInBuffer( onProgress ) ) // Fire and forget; PS: semicolon of doom
let rootObject = await this.getObject( this.objectId )
return this.traverseAndConstruct( rootObject, onProgress )
}
/**
* Internal function used to download all the objects in a local buffer.
* @param {*} onProgress
* @param {*} onProgress
*/
async downloadObjectsInBuffer( onProgress ) {
let first = true
let downloadNum = 0
for await ( let obj of this.getObjectIterator() ) {
if( first ) {
this.totalChildrenCount = obj.totalChildrenCount
@@ -78,9 +83,9 @@ export default class ObjectLoader {
/**
* Internal function used to recursively traverse an object and populate its references and dechunk any arrays.
* @param {*} obj
* @param {*} onProgress
* @returns
* @param {*} obj
* @param {*} onProgress
* @returns
*/
async traverseAndConstruct( obj, onProgress ) {
if( !obj ) return
@@ -91,20 +96,20 @@ export default class ObjectLoader {
let arr = []
for ( let element of obj ) {
if ( typeof element !== 'object' && ! this.options.fullyTraverseArrays ) return obj
// Dereference element if needed
let deRef = element.referencedId ? await this.getObject( element.referencedId ) : element
if( element.referencedId && onProgress ) onProgress( { stage: 'construction', current: ++this.traversedReferencesCount > this.totalChildrenCount ? this.totalChildrenCount : this.traversedReferencesCount, total: this.totalChildrenCount } )
if( element.referencedId && onProgress ) onProgress( { stage: 'construction', current: ++this.traversedReferencesCount > this.totalChildrenCount ? this.totalChildrenCount : this.traversedReferencesCount, total: this.totalChildrenCount } )
// Push the traversed object in the array
arr.push( await this.traverseAndConstruct( deRef, onProgress ) )
}
// De-chunk
if( arr[0]?.speckle_type?.toLowerCase().includes('datachunk') ) {
if( arr[0]?.speckle_type?.toLowerCase().includes('datachunk') ) {
return arr.reduce( ( prev, curr ) => prev.concat( curr.data ), [] )
}
return arr
}
@@ -113,11 +118,11 @@ export default class ObjectLoader {
for( let ignoredProp of this.options.excludeProps ) {
delete obj[ ignoredProp ]
}
// 2) Iterate through obj
for( let prop in obj ) {
for( let prop in obj ) {
if( typeof obj[prop] !== 'object' ) continue // leave alone primitive props
if( obj[prop].referencedId ) {
obj[prop] = await this.getObject( obj[prop].referencedId )
if( onProgress ) onProgress( { stage: 'construction', current: ++this.traversedReferencesCount > this.totalChildrenCount ? this.totalChildrenCount : this.traversedReferencesCount, total: this.totalChildrenCount } )
@@ -131,8 +136,8 @@ export default class ObjectLoader {
/**
* Internal function. Returns a promise that is resolved when the object id is loaded into the internal buffer.
* @param {*} id
* @returns
* @param {*} id
* @returns
*/
async getObject( id ){
if ( this.buffer[id] ) return this.buffer[id]
@@ -198,7 +203,7 @@ export default class ObjectLoader {
let result = re.exec( chunk )
if ( !result ) {
if ( readerDone ) break
let remainder = chunk.substr( startIndex )
let remainder = chunk.substr( startIndex )
;( { value: chunk, done: readerDone } = await reader.read() ) // PS: semicolon of doom
chunk = remainder + ( chunk ? decoder.decode( chunk ) : '' )
startIndex = re.lastIndex = 0
@@ -10,7 +10,12 @@ export default class ViewerObjectLoader {
constructor( parent, objectUrl, authToken ) {
this.viewer = parent
this.token = authToken || localStorage.getItem( 'AuthToken' )
this.token = null
try {
this.token = authToken || localStorage.getItem( 'AuthToken' )
} catch ( error ) {
// Accessing localStorage may throw when executing on sandboxed document, ignore.
}
if ( !this.token ) {
console.warn( 'Viewer: no auth token present. Requests to non-public stream objects will fail.' )
@@ -32,7 +37,7 @@ export default class ViewerObjectLoader {
serverUrl: this.serverUrl,
token: this.token,
streamId: this.streamId,
objectId: this.objectId,
objectId: this.objectId
} )
this.converter = new Converter( this.loader )
+46 -37
View File
@@ -1,59 +1,72 @@
# Speckle Web
<h1 align="center">
<img src="https://user-images.githubusercontent.com/2679513/131189167-18ea5fe1-c578-47f6-9785-3748178e4312.png" width="150px"/><br/>
Speckle
</h1><br/>
<p align="center"><b>Speckle</b> is data infrastructure for the AEC industry.</p><br/>
[![Twitter Follow](https://img.shields.io/twitter/follow/SpeckleSystems?style=social)](https://twitter.com/SpeckleSystems) [![Community forum users](https://img.shields.io/discourse/users?server=https%3A%2F%2Fspeckle.community&style=flat-square&logo=discourse&logoColor=white)](https://speckle.community) [![website](https://img.shields.io/badge/https://-speckle.systems-royalblue?style=flat-square)](https://speckle.systems) [![docs](https://img.shields.io/badge/docs-speckle.guide-orange?style=flat-square&logo=read-the-docs&logoColor=white)](https://speckle.guide/dev/)
#### Status
[![Speckle-Next](https://circleci.com/gh/specklesystems/speckle-server.svg?style=svg&circle-token=76eabd350ea243575cbb258b746ed3f471f7ac29)](https://github.com/Speckle-Next/SpeckleServer/) [![codecov](https://codecov.io/gh/specklesystems/speckle-server/branch/master/graph/badge.svg)](https://codecov.io/gh/specklesystems/speckle-server)
<p align="center"><a href="https://twitter.com/SpeckleSystems"><img src="https://img.shields.io/twitter/follow/SpeckleSystems?style=social" alt="Twitter Follow"></a> <a href="https://speckle.community"><img src="https://img.shields.io/discourse/users?server=https%3A%2F%2Fspeckle.community&amp;style=flat-square&amp;logo=discourse&amp;logoColor=white" alt="Community forum users"></a> <a href="https://speckle.systems"><img src="https://img.shields.io/badge/https://-speckle.systems-royalblue?style=flat-square" alt="website"></a> <a href="https://speckle.guide/dev/"><img src="https://img.shields.io/badge/docs-speckle.guide-orange?style=flat-square&amp;logo=read-the-docs&amp;logoColor=white" alt="docs"></a></p>
<p align="center"><a href="https://github.com/Speckle-Next/SpeckleServer/"><img src="https://circleci.com/gh/specklesystems/speckle-server.svg?style=svg&amp;circle-token=76eabd350ea243575cbb258b746ed3f471f7ac29" alt="Speckle-Next"></a> <a href="https://codecov.io/gh/specklesystems/speckle-server"><img src="https://codecov.io/gh/specklesystems/speckle-server/branch/master/graph/badge.svg" alt="codecov"></a></p>
## Introduction
This monorepo is the home of the Speckle 2.0 web packages. If you're looking for the desktop connectors, you'll find them [here](https://github.com/specklesystems/speckle-sharp).
What is Speckle? Check our ![YouTube Video Views](https://img.shields.io/youtube/views/B9humiSpHzM?label=Speckle%20in%201%20minute%20video&style=social)
Specifically, this monorepo contains:
### Features
### ➡️ [Server](packages/server), the Speckle Server
- **Object-based:** say goodbye to files! Speckle is the first object based platform for the AEC industry
- **Version control:** Speckle is the Git & Hub for geometry and BIM data
- **Collaboration:** share your designs collaborate with others
- **3D Viewer:** see your CAD and BIM models online, share and embed them anywhere
- **Interoperability:** get your CAD and BIM models into other software without exporting or importing
- **Real time:** get real time updates and notifications and changes
- **GraphQL API:** get what you need anywhere you want it
- **Webhooks:** the base for a automation and next-gen pipelines
- **Built for developers:** we are building Speckle with developers in mind and got tools for every stack
- **Built for the AEC industry:** Speckle connectors are plugins for the most common software used in the industry such as Revit, Rhino, Grasshopper, AutoCAD, Civil 3D, Excel, Unreal Engine, Unity, QGIS, Blender and more!
The server is a nodejs app. Core external dependencies are a Redis and Postgresql db. Deploy one in a minute on Digital Ocean using the button below!
### Try Speckle now!
[![do-btn-blue-re](https://user-images.githubusercontent.com/7696515/120513666-69be0800-c3c4-11eb-9d50-7b9811b8e0f1.png)](https://marketplace.digitalocean.com/apps/speckle-server?refcode=947a2b5d7dc1)
Give Speckle a try in no time by:
### ➡️ [Frontend](packages/frontend), the Speckle Frontend
- using our general availability instance at ⇒ [![speckle XYZ](https://img.shields.io/badge/https://-speckle.xyz-0069ff?style=flat-square&logo=hackthebox&logoColor=white)](https://speckle.xyz)
- deploying an instance in 1 click ⇒ [![create a droplet](https://img.shields.io/badge/Create%20a%20Droplet-0069ff?style=flat-square&logo=digitalocean&logoColor=white)](https://marketplace.digitalocean.com/apps/speckle-server?refcode=947a2b5d7dc1)
The frontend is a static Vue app.
### Resources
### ➡️ [Viewer](packages/viewer), the Speckle Viewer
[![Community forum users](https://img.shields.io/badge/community-forum-green?style=for-the-badge&logo=discourse&logoColor=white)](https://speckle.community) [![website](https://img.shields.io/badge/tutorials-speckle.systems-royalblue?style=for-the-badge&logo=youtube)](https://speckle.systems) [![docs](https://img.shields.io/badge/docs-speckle.guide-orange?style=for-the-badge&logo=read-the-docs&logoColor=white)](https://speckle.guide/dev/)
[![npm version](https://badge.fury.io/js/%40speckle%2Fviewer.svg)](https://www.npmjs.com/package/@speckle/viewer)
The viewer is a [threejs](https://threejs.org/) extension that allows you to display data from Speckle.
### ➡️ [Object Loader](packages/objectloader), a JS helper module
## Speckle Server | Repo structure
[![npm version](https://badge.fury.io/js/%40speckle%2Fobjectloader.svg)](https://www.npmjs.com/package/@speckle/objectloader)
This monorepo is the home of the Speckle v2 web packages:
A small utility class that helps you stream an object and all its sub-components from the Speckle Server API.
- [`packages/server`](https://github.com/specklesystems/speckle-server/blob/main/packages/server): the Server, a nodejs app. Core external dependencies are a Redis and Postgresql db.
- [`packages/frontend`](https://github.com/specklesystems/speckle-server/blob/main/packages/frontend): the Frontend, a static Vue app.
- [`packages/viewer`](https://github.com/specklesystems/speckle-server/blob/main/packages/viewer): a threejs extension that allows you to display 3D data [![npm version](https://camo.githubusercontent.com/dc69232cc57b77de6554e752dd6dfc60ca0ecdfbe91bdfcbf7c7531a511ec200/68747470733a2f2f62616467652e667572792e696f2f6a732f253430737065636b6c652532467669657765722e737667)](https://www.npmjs.com/package/@speckle/viewer)
- [`packages/objectloader`](https://github.com/specklesystems/speckle-server/blob/main/packages/objectloader): a small js utility class that helps you stream an object and all its sub-components from the Speckle Server API. [![npm version](https://camo.githubusercontent.com/4d4f1e38ce50aaf11b4a3ad8e01ce3eaaa561dc5fd08febbae556f52f1d41097/68747470733a2f2f62616467652e667572792e696f2f6a732f253430737065636b6c652532466f626a6563746c6f616465722e737667)](https://www.npmjs.com/package/@speckle/objectloader)
- [`packages/preview-service`](https://github.com/specklesystems/speckle-server/blob/main/packages/preview-service): generates object previews for Speckle Objects headlessly. This package is meant to be called on by the server.
- [`webhook-service`](https://github.com/specklesystems/speckle-server/tree/main/packages/webhook-service): the Webhook service
### ➡️ [Preview Service](packages/preview-service), for headlessly generating images for 3d objects
### Other repos
Generates object previews for Speckle Objects. This package is meant to be called on by the server.
Make sure to also check and ⭐️ these other Speckle repositories:
## Documentation
- [`speckle-sharp`](https://github.com/specklesystems/speckle-sharp) .NET tooling, connectors and interoperability
- [`specklepy`](https://github.com/specklesystems/specklepy) Python SDK 🐍
- and more [connectos & tooling](https://github.com/specklesystems/)!
Comprehensive developer and user documentation can be found in our:
#### 📚 [Speckle Docs website](https://speckle.guide/dev/)
## Usage
To start using Speckle, it's not necessary to deploy it yourself. The easiest way is to register a free account on speckle.xyz, our general availability offering. Check [https://speckle.systems/getstarted/](https://speckle.systems/getstarted/) for more information.
## Developing and Debugging
If you want to deploy the Server, we have a detailed [guide on how to do so](https://speckle.guide/dev/server-setup.html). To get started developing locally, you can read the [run in development mode](https://speckle.guide/dev/server-setup.html#run-in-development-mode) chapter of our deployment guide.
Have you checked our [dev docs](https://speckle.guide/dev/)?
## Contributing
We have a detailed section on [deploying a Speckle server](https://speckle.guide/dev/server-setup.html). To get started developing locally, you can see the [run in development mode](https://speckle.guide/dev/server-setup.html#run-in-development-mode) chapter.
Please make sure you read the [contribution guidelines](CONTRIBUTING.md) for an overview of the best practices we try to follow.
### Contributing
Please make sure you read the [contribution guidelines](https://github.com/specklesystems/speckle-server/blob/main/CONTRIBUTING.md) for an overview of the best practices we try to follow.
When pushing commits to this repo, please follow the following guidelines:
@@ -61,14 +74,10 @@ When pushing commits to this repo, please follow the following guidelines:
- When ready to commit, `git cz` & follow the prompts.
- Please use either `server` or `frontend` as the scope of your commit.
## Community
### Security
The Speckle Community hangs out on [the forum](https://speckle.community), do join and introduce yourself & feel free to ask us questions!
For any security vulnerabilities or concerns, please contact us directly at security[at]speckle.systems.
## Security
For any security vulnerabilities or concerns, please contact us directly at security[at]speckle.systems.
## License
### License
Unless otherwise described, the code in this repository is licensed under the Apache-2.0 License. Please note that some modules, extensions or code herein might be otherwise licensed. This is indicated either in the root of the containing folder under a different license file, or in the respective file's header. If you have any questions, don't hesitate to get in touch with us via [email](mailto:hello@speckle.systems).