chore: re-formatted everything with prettier

This commit is contained in:
Fabians
2022-03-29 16:53:49 +03:00
parent 4e97360e48
commit abe6d5e086
307 changed files with 12690 additions and 8378 deletions
+10 -8
View File
@@ -4,7 +4,6 @@ about: Help improve Speckle!
title: ''
labels: bug
assignees: ''
---
**What package are you referring to?**
@@ -15,6 +14,7 @@ A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
@@ -30,15 +30,17 @@ If applicable, add screenshots to help explain your problem.
If applicable, please fill in the below details - they help a lot!
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
@@ -4,7 +4,6 @@ about: Suggest an idea for Speckle!
title: ''
labels: enhancement, question
assignees: ''
---
**What package are you referring to?**
-1
View File
@@ -75,4 +75,3 @@ jobs:
}
}
}' -f project=$PROJECT_ID -f status=$STATUS_FIELD_ID -f id=$ITEM_ID -f value=${{ env.DONE_ID }}
+11
View File
@@ -0,0 +1,11 @@
node_modules
build
dist
coverage
.nyc_output
packages/server/reports*
package-lock.json
yarn.lock
# Prettier doesn't understand the syntax inside the Yaml files, because of the brackets
utils/helm/speckle-server/templates
+1 -3
View File
@@ -4,10 +4,8 @@
"semi": false,
"endOfLine": "auto",
"bracketSpacing": true,
"eslintIntegration": true,
"jsxBracketSameLine": true,
"vueIndentScriptAndStyle": false,
"htmlWhitespaceSensitivity": "ignore",
"printWidth": 100,
"printWidth": 88,
"singleQuote": true
}
+29 -30
View File
@@ -1,6 +1,5 @@
version: "2"
version: '2'
services:
speckle-frontend:
build:
context: .
@@ -8,7 +7,7 @@ services:
image: speckle/speckle-frontend:local
restart: always
ports:
- "0.0.0.0:80:80"
- '0.0.0.0:80:80'
speckle-server:
build:
@@ -18,26 +17,26 @@ services:
restart: always
environment:
# TODO: Change this to the URL of the speckle server, as accessed from the network
CANONICAL_URL: "http://localhost"
CANONICAL_URL: 'http://localhost'
# TODO: Change this to a unique secret for this server
SESSION_SECRET: "TODO:Replace"
SESSION_SECRET: 'TODO:Replace'
STRATEGY_LOCAL: "true"
DEBUG: "speckle:*"
STRATEGY_LOCAL: 'true'
DEBUG: 'speckle:*'
POSTGRES_URL: "postgres"
POSTGRES_USER: "speckle"
POSTGRES_PASSWORD: "speckle"
POSTGRES_DB: "speckle"
POSTGRES_URL: 'postgres'
POSTGRES_USER: 'speckle'
POSTGRES_PASSWORD: 'speckle'
POSTGRES_DB: 'speckle'
REDIS_URL: "redis://redis"
REDIS_URL: 'redis://redis'
S3_ENDPOINT: "http://minio:9000"
S3_ACCESS_KEY: "minioadmin"
S3_SECRET_KEY: "minioadmin"
S3_BUCKET: "speckle-server"
S3_CREATE_BUCKET: "true"
S3_ENDPOINT: 'http://minio:9000'
S3_ACCESS_KEY: 'minioadmin'
S3_SECRET_KEY: 'minioadmin'
S3_BUCKET: 'speckle-server'
S3_CREATE_BUCKET: 'true'
preview-service:
build:
@@ -45,11 +44,11 @@ services:
dockerfile: packages/preview-service/Dockerfile
image: speckle/speckle-preview-service:local
restart: always
mem_limit: "3000m"
memswap_limit: "3000m"
mem_limit: '3000m'
memswap_limit: '3000m'
environment:
DEBUG: "preview-service:*"
PG_CONNECTION_STRING: "postgres://speckle:speckle@postgres/speckle"
DEBUG: 'preview-service:*'
PG_CONNECTION_STRING: 'postgres://speckle:speckle@postgres/speckle'
webhook-service:
build:
@@ -58,8 +57,8 @@ services:
image: speckle/speckle-webhook-service:local
restart: always
environment:
DEBUG: "webhook-service:*"
PG_CONNECTION_STRING: "postgres://speckle:speckle@postgres/speckle"
DEBUG: 'webhook-service:*'
PG_CONNECTION_STRING: 'postgres://speckle:speckle@postgres/speckle'
fileimport-service:
build:
@@ -68,12 +67,12 @@ services:
image: speckle/speckle-fileimport-service:local
restart: always
environment:
DEBUG: "fileimport-service:*"
PG_CONNECTION_STRING: "postgres://speckle:speckle@postgres/speckle"
DEBUG: 'fileimport-service:*'
PG_CONNECTION_STRING: 'postgres://speckle:speckle@postgres/speckle'
S3_ENDPOINT: "http://minio:9000"
S3_ACCESS_KEY: "minioadmin"
S3_SECRET_KEY: "minioadmin"
S3_BUCKET: "speckle-server"
S3_ENDPOINT: 'http://minio:9000'
S3_ACCESS_KEY: 'minioadmin'
S3_SECRET_KEY: 'minioadmin'
S3_BUCKET: 'speckle-server'
SPECKLE_SERVER_URL: "http://speckle-server:3000"
SPECKLE_SERVER_URL: 'http://speckle-server:3000'
+1 -3
View File
@@ -1,6 +1,4 @@
{
"packages": [
"packages/*"
],
"packages": ["packages/*"],
"version": "independent"
}
+2
View File
@@ -3,6 +3,8 @@
"private": true,
"scripts": {
"lint": "eslint . --ext .js,.ts,.vue",
"prettier:check": "prettier --check .",
"prettier:fix": "prettier --write .",
"docker:deps:up": "docker-compose -f ./docker-compose-deps.yml up -d",
"docker:deps:down": "docker-compose -f ./docker-compose-deps.yml down",
"dev": "lerna run dev --parallel",
+74 -65
View File
@@ -1,49 +1,52 @@
'use strict'
const crypto = require( 'crypto' )
const crs = require( 'crypto-random-string' )
const bcrypt = require( 'bcrypt' )
const crypto = require('crypto')
const crs = require('crypto-random-string')
const bcrypt = require('bcrypt')
const knex = require( '../knex' )
const Streams = ( ) => knex( 'streams' )
const Branches = ( ) => knex( 'branches' )
const Objects = ( ) => knex( 'objects' )
const Closures = ( ) => knex( 'object_children_closure' )
const ApiTokens = ( ) => knex( 'api_tokens' )
const TokenScopes = ( ) => knex( 'token_scopes' )
const knex = require('../knex')
const Streams = () => knex('streams')
const Branches = () => knex('branches')
const Objects = () => knex('objects')
const Closures = () => knex('object_children_closure')
const ApiTokens = () => knex('api_tokens')
const TokenScopes = () => knex('token_scopes')
module.exports = class ServerAPI {
constructor( { streamId } ) {
constructor({ streamId }) {
this.streamId = streamId
this.isSending = false
this.buffer = []
}
async saveObject( obj ) {
if( !obj ) throw new Error( 'Null object' )
async saveObject(obj) {
if (!obj) throw new Error('Null object')
if( !obj.id ) {
obj.id = crypto.createHash( 'md5' ).update( JSON.stringify( obj ) ).digest( 'hex' )
if (!obj.id) {
obj.id = crypto.createHash('md5').update(JSON.stringify(obj)).digest('hex')
}
await this.createObject( this.streamId, obj )
await this.createObject(this.streamId, obj)
return obj.id
}
async createObject( streamId, object ) {
let insertionObject = this.prepInsertionObject( streamId, object )
async createObject(streamId, object) {
let insertionObject = this.prepInsertionObject(streamId, object)
let closures = [ ]
let closures = []
let totalChildrenCountByDepth = {}
if ( object.__closure !== null ) {
for ( const prop in object.__closure ) {
closures.push( { streamId: streamId, parent: insertionObject.id, child: prop, minDepth: object.__closure[ prop ] } )
if (object.__closure !== null) {
for (const prop in object.__closure) {
closures.push({
streamId: streamId,
parent: insertionObject.id,
child: prop,
minDepth: object.__closure[prop]
})
if ( totalChildrenCountByDepth[ object.__closure[ prop ].toString( ) ] )
totalChildrenCountByDepth[ object.__closure[ prop ].toString( ) ]++
else
totalChildrenCountByDepth[ object.__closure[ prop ].toString( ) ] = 1
if (totalChildrenCountByDepth[object.__closure[prop].toString()])
totalChildrenCountByDepth[object.__closure[prop].toString()]++
else totalChildrenCountByDepth[object.__closure[prop].toString()] = 1
}
}
@@ -51,30 +54,34 @@ module.exports = class ServerAPI {
delete insertionObject.__closure
insertionObject.totalChildrenCount = closures.length
insertionObject.totalChildrenCountByDepth = JSON.stringify( totalChildrenCountByDepth )
insertionObject.totalChildrenCountByDepth = JSON.stringify(
totalChildrenCountByDepth
)
let q1 = Objects( ).insert( insertionObject ).toString( ) + ' on conflict do nothing'
await knex.raw( q1 )
let q1 = Objects().insert(insertionObject).toString() + ' on conflict do nothing'
await knex.raw(q1)
if ( closures.length > 0 ) {
let q2 = `${ Closures().insert( closures ).toString() } on conflict do nothing`
await knex.raw( q2 )
if (closures.length > 0) {
let q2 = `${Closures().insert(closures).toString()} on conflict do nothing`
await knex.raw(q2)
}
return insertionObject.id
}
prepInsertionObject( streamId, obj ) {
prepInsertionObject(streamId, obj) {
const MAX_OBJECT_SIZE = 10 * 1024 * 1024
if ( obj.hash )
obj.id = obj.hash
if (obj.hash) obj.id = obj.hash
else
obj.id = obj.id || crypto.createHash( 'md5' ).update( JSON.stringify( obj ) ).digest( 'hex' ) // generate a hash if none is present
obj.id =
obj.id || crypto.createHash('md5').update(JSON.stringify(obj)).digest('hex') // generate a hash if none is present
let stringifiedObj = JSON.stringify( obj )
if ( stringifiedObj.length > MAX_OBJECT_SIZE ) {
throw new Error( `Object too large (${stringifiedObj.length} > ${MAX_OBJECT_SIZE})` )
let stringifiedObj = JSON.stringify(obj)
if (stringifiedObj.length > MAX_OBJECT_SIZE) {
throw new Error(
`Object too large (${stringifiedObj.length} > ${MAX_OBJECT_SIZE})`
)
}
return {
@@ -85,41 +92,44 @@ module.exports = class ServerAPI {
}
}
async getBranchByNameAndStreamId( { streamId, name } ) {
let query = Branches( ).select( '*' ).where( { streamId: streamId } ).andWhere( knex.raw( 'LOWER(name) = ?', [ name ] ) ).first( )
async getBranchByNameAndStreamId({ streamId, name }) {
let query = Branches()
.select('*')
.where({ streamId: streamId })
.andWhere(knex.raw('LOWER(name) = ?', [name]))
.first()
return await query
}
async createBranch( { name, description, streamId, authorId } ) {
async createBranch({ name, description, streamId, authorId }) {
let branch = {}
branch.id = crs( { length: 10 } )
branch.id = crs({ length: 10 })
branch.streamId = streamId
branch.authorId = authorId
branch.name = name.toLowerCase( )
branch.name = name.toLowerCase()
branch.description = description
await Branches( ).returning( 'id' ).insert( branch )
await Branches().returning('id').insert(branch)
// update stream updated at
await Streams().where( { id: streamId } ).update( { updatedAt: knex.fn.now() } )
await Streams().where({ id: streamId }).update({ updatedAt: knex.fn.now() })
return branch.id
}
async createBareToken( ) {
let tokenId = crs( { length: 10 } )
let tokenString = crs( { length: 32 } )
let tokenHash = await bcrypt.hash( tokenString, 10 )
let lastChars = tokenString.slice( tokenString.length - 6, tokenString.length )
async createBareToken() {
let tokenId = crs({ length: 10 })
let tokenString = crs({ length: 32 })
let tokenHash = await bcrypt.hash(tokenString, 10)
let lastChars = tokenString.slice(tokenString.length - 6, tokenString.length)
return { tokenId, tokenString, tokenHash, lastChars }
}
async createToken( { userId, name, scopes, lifespan } ) {
let { tokenId, tokenString, tokenHash, lastChars } = await this.createBareToken( )
async createToken({ userId, name, scopes, lifespan }) {
let { tokenId, tokenString, tokenHash, lastChars } = await this.createBareToken()
if ( scopes.length === 0 ) throw new Error( 'No scopes provided' )
if (scopes.length === 0) throw new Error('No scopes provided')
let token = {
id: tokenId,
@@ -129,21 +139,20 @@ module.exports = class ServerAPI {
name: name,
lifespan: lifespan
}
let tokenScopes = scopes.map( scope => ( { tokenId: tokenId, scopeName: scope } ) )
let tokenScopes = scopes.map((scope) => ({ tokenId: tokenId, scopeName: scope }))
await ApiTokens( ).insert( token )
await TokenScopes( ).insert( tokenScopes )
await ApiTokens().insert(token)
await TokenScopes().insert(tokenScopes)
return { id: tokenId, token: tokenId + tokenString }
}
async revokeTokenById( tokenId ) {
let delCount = await ApiTokens( ).where( { id: tokenId.slice( 0, 10 ) } ).del( )
async revokeTokenById(tokenId) {
let delCount = await ApiTokens()
.where({ id: tokenId.slice(0, 10) })
.del()
if ( delCount === 0 )
throw new Error( 'Token revokation failed' )
if (delCount === 0) throw new Error('Token revokation failed')
return true
}
}
+11 -11
View File
@@ -1,17 +1,17 @@
const fs = require( 'fs' )
const fs = require('fs')
const TMP_RESULTS_PATH = '/tmp/import_result.json'
const { parseAndCreateCommit } = require( './index' )
const { parseAndCreateCommit } = require('./index')
async function main() {
let cmdArgs = process.argv.slice( 2 )
let cmdArgs = process.argv.slice(2)
let [ filePath, userId, streamId, branchName, commitMessage ] = cmdArgs
let [filePath, userId, streamId, branchName, commitMessage] = cmdArgs
console.log( 'ARGV: ', filePath, userId, streamId, branchName, commitMessage )
console.log('ARGV: ', filePath, userId, streamId, branchName, commitMessage)
const data = fs.readFileSync( filePath )
const data = fs.readFileSync(filePath)
let ifcInput = {
data,
@@ -19,7 +19,7 @@ async function main() {
userId: userId,
message: commitMessage || 'Imported file'
}
if ( branchName ) ifcInput.branchName = branchName
if (branchName) ifcInput.branchName = branchName
let output = {
success: false,
@@ -27,21 +27,21 @@ async function main() {
}
try {
let commitId = await parseAndCreateCommit( ifcInput )
let commitId = await parseAndCreateCommit(ifcInput)
output = {
success: true,
commitId
}
} catch ( err ) {
} catch (err) {
output = {
success: false,
error: err.toString()
}
}
fs.writeFileSync( TMP_RESULTS_PATH, JSON.stringify( output ) )
fs.writeFileSync(TMP_RESULTS_PATH, JSON.stringify(output))
process.exit( 0 )
process.exit(0)
}
main()
+4 -1
View File
@@ -23,7 +23,10 @@ async function parseAndCreateCommit({
totalChildrenCount: tCount
}
let branch = await serverApi.getBranchByNameAndStreamId({ streamId: streamId, name: branchName })
let branch = await serverApi.getBranchByNameAndStreamId({
streamId: streamId,
name: branchName
})
if (!branch) {
await serverApi.createBranch({
+221 -108
View File
@@ -1,21 +1,23 @@
const WebIFC = require( 'web-ifc/web-ifc-api-node' )
const ServerAPI = require( './api.js' )
const WebIFC = require('web-ifc/web-ifc-api-node')
const ServerAPI = require('./api.js')
module.exports = class IFCParser {
constructor( { serverApi } ) {
constructor({ serverApi }) {
this.api = new WebIFC.IfcAPI()
this.serverApi = serverApi || new ServerAPI()
}
async parse( data ) {
if ( this.api.wasmModule === undefined ) await this.api.Init()
async parse(data) {
if (this.api.wasmModule === undefined) await this.api.Init()
this.modelId = this.api.OpenModel( data, { COORDINATE_TO_ORIGIN: true, USE_FAST_BOOLS: true } )
this.modelId = this.api.OpenModel(data, {
COORDINATE_TO_ORIGIN: true,
USE_FAST_BOOLS: true
})
this.projectId = this.api.GetLineIDsWithType( this.modelId, WebIFC.IFCPROJECT ).get( 0 )
this.projectId = this.api.GetLineIDsWithType(this.modelId, WebIFC.IFCPROJECT).get(0)
this.project = this.api.GetLine( this.modelId, this.projectId, true )
this.project = this.api.GetLine(this.modelId, this.projectId, true)
this.project.__closure = {}
this.cache = {}
@@ -25,51 +27,56 @@ module.exports = class IFCParser {
// as reference objects in this.productGeo
this.productGeo = {}
await this.createGeometries()
console.log( `Geometries created: ${Object.keys( this.productGeo ).length} meshes.` )
console.log(`Geometries created: ${Object.keys(this.productGeo).length} meshes.`)
// Lastly, traverse the ifc project object and parse it into something friendly; as well as
// replace all its geometries with actual references to speckle meshes from the productGeo map
await this.traverse( this.project, true, 0 )
await this.traverse(this.project, true, 0)
let id = await this.serverApi.saveObject( this.project )
return { id, tCount: Object.keys( this.project.__closure ).length }
let id = await this.serverApi.saveObject(this.project)
return { id, tCount: Object.keys(this.project.__closure).length }
}
async createGeometries() {
this.rawGeo = this.api.LoadAllGeometry( this.modelId )
this.rawGeo = this.api.LoadAllGeometry(this.modelId)
for( let i = 0; i < this.rawGeo.size(); i++ ) {
const mesh = this.rawGeo.get( i )
for (let i = 0; i < this.rawGeo.size(); i++) {
const mesh = this.rawGeo.get(i)
const prodId = mesh.expressID
this.productGeo[prodId ] = []
this.productGeo[prodId] = []
for( let j = 0; j < mesh.geometries.size(); j++ ) {
let placedGeom = mesh.geometries.get( j )
let geom = this.api.GetGeometry( this.modelId, placedGeom.geometryExpressID )
for (let j = 0; j < mesh.geometries.size(); j++) {
let placedGeom = mesh.geometries.get(j)
let geom = this.api.GetGeometry(this.modelId, placedGeom.geometryExpressID)
let matrix = placedGeom.flatTransformation
let raw = {
color: geom.color, // NOTE: material: x, y, z = rgb, w = opacity
vertices: this.api.GetVertexArray( geom.GetVertexData(), geom.GetVertexDataSize() ),
indices: this.api.GetIndexArray( geom.GetIndexData(), geom.GetIndexDataSize() )
vertices: this.api.GetVertexArray(
geom.GetVertexData(),
geom.GetVertexDataSize()
),
indices: this.api.GetIndexArray(geom.GetIndexData(), geom.GetIndexDataSize())
}
const { vertices } = this.extractVertexData( raw.vertices )
const { vertices } = this.extractVertexData(raw.vertices)
for( let k = 0; k < vertices.length; k += 3 ) {
let x = vertices[k], y = vertices[k + 1], z = vertices[k + 2]
for (let k = 0; k < vertices.length; k += 3) {
let x = vertices[k],
y = vertices[k + 1],
z = vertices[k + 2]
vertices[k] = matrix[0] * x + matrix[4] * y + matrix[8] * z + matrix[12]
vertices[k + 1] = ( matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14] ) * -1
vertices[k + 1] =
(matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14]) * -1
vertices[k + 2] = matrix[1] * x + matrix[5] * y + matrix[9] * z + matrix[13]
}
// Since all faces are triangles, we must add a `0` before each group of 3.
let spcklFaces = [ ]
for ( let i = 0; i < raw.indices.length; i++ ) {
if( i % 3 === 0 )
spcklFaces.push( 0 )
spcklFaces.push( raw.indices[i] )
let spcklFaces = []
for (let i = 0; i < raw.indices.length; i++) {
if (i % 3 === 0) spcklFaces.push(0)
spcklFaces.push(raw.indices[i])
}
// Create a propper Speckle Mesh
@@ -79,40 +86,61 @@ module.exports = class IFCParser {
volume: 0,
area: 0,
faces: spcklFaces,
vertices: Array.from( vertices ),
renderMaterial: placedGeom.color ? this.colorToMaterial( placedGeom.color ) : null
vertices: Array.from(vertices),
renderMaterial: placedGeom.color
? this.colorToMaterial(placedGeom.color)
: null
}
let id = await this.serverApi.saveObject( spcklMesh )
let id = await this.serverApi.saveObject(spcklMesh)
let ref = { speckle_type: 'reference', referencedId: id }
this.productGeo[prodId].push( ref )
this.productGeo[prodId].push(ref)
}
}
}
async traverse( element, recursive = true, depth = 0, specialTypes = [ { type: 'IfcProject', key: 'Name' }, { type: 'IfcBuilding', key: 'Name' }, { type: 'IfcSite', key: 'Name' } ] ) {
async traverse(
element,
recursive = true,
depth = 0,
specialTypes = [
{ type: 'IfcProject', key: 'Name' },
{ type: 'IfcBuilding', key: 'Name' },
{ type: 'IfcSite', key: 'Name' }
]
) {
// Fast exit if null/undefined
if ( !element ) return
if (!element) return
// If array, traverse all items in it.
if( Array.isArray( element ) ) {
return await Promise.all( element.map( async el => await this.traverse( el,recursive, depth + 1 , specialTypes ) ) )
if (Array.isArray(element)) {
return await Promise.all(
element.map(
async (el) => await this.traverse(el, recursive, depth + 1, specialTypes)
)
)
}
// If it has no expressID, its either a simple type or a { type, value } object.
if( !element.expressID ) {
return await Promise.resolve( element.value !== null && element.value !== undefined ? element.value : element )
if (!element.expressID) {
return await Promise.resolve(
element.value !== null && element.value !== undefined ? element.value : element
)
}
if( this.cache[element.expressID.toString()] ) return this.cache[element.expressID.toString()]
if (this.cache[element.expressID.toString()])
return this.cache[element.expressID.toString()]
// If you got here -> It's an IFC Element: create base object, upload and return ref.
// console.log( `Traversing element ${element.expressID}; Recurse: ${recursive}; Stack ${depth}` )
// Traverse all key/value pairs first.
for( let key of Object.keys( element ) ) {
element[key] = await this.traverse( element[key], recursive, depth + 1, specialTypes )
for (let key of Object.keys(element)) {
element[key] = await this.traverse(
element[key],
recursive,
depth + 1,
specialTypes
)
}
// Assign speckle_type and empty closure table.
@@ -120,46 +148,126 @@ module.exports = class IFCParser {
element.__closure = {}
// Find spatial children and assign to element
const spatialChildrenIds = this.getAllRelatedItemsOfType( element.expressID, WebIFC.IFCRELAGGREGATES, 'RelatingObject', 'RelatedObjects' )
if( spatialChildrenIds.length > 0 ) element.rawSpatialChildren = spatialChildrenIds.map( ( childId ) => this.api.GetLine( this.modelId, childId, true ) )
const spatialChildrenIds = this.getAllRelatedItemsOfType(
element.expressID,
WebIFC.IFCRELAGGREGATES,
'RelatingObject',
'RelatedObjects'
)
if (spatialChildrenIds.length > 0)
element.rawSpatialChildren = spatialChildrenIds.map((childId) =>
this.api.GetLine(this.modelId, childId, true)
)
// Find children and populate element
const childrenIds = this.getAllRelatedItemsOfType( element.expressID, WebIFC.IFCRELCONTAINEDINSPATIALSTRUCTURE, 'RelatingStructure', 'RelatedElements' )
if( childrenIds.length > 0 ) element.rawChildren = childrenIds.map( ( childId ) => this.api.GetLine( this.modelId, childId, true ) )
const childrenIds = this.getAllRelatedItemsOfType(
element.expressID,
WebIFC.IFCRELCONTAINEDINSPATIALSTRUCTURE,
'RelatingStructure',
'RelatedElements'
)
if (childrenIds.length > 0)
element.rawChildren = childrenIds.map((childId) =>
this.api.GetLine(this.modelId, childId, true)
)
// Find related property sets
const psetsIds = this.getAllRelatedItemsOfType( element.expressID, WebIFC.IFCRELDEFINESBYPROPERTIES, 'RelatingPropertyDefinition', 'RelatedObjects' )
if( psetsIds.length > 0 ) element.rawPsets = psetsIds.map( ( childId ) => this.api.GetLine( this.modelId, childId, true ) )
const psetsIds = this.getAllRelatedItemsOfType(
element.expressID,
WebIFC.IFCRELDEFINESBYPROPERTIES,
'RelatingPropertyDefinition',
'RelatedObjects'
)
if (psetsIds.length > 0)
element.rawPsets = psetsIds.map((childId) =>
this.api.GetLine(this.modelId, childId, true)
)
// Find related type properties
const typePropsId = this.getAllRelatedItemsOfType( element.expressID, WebIFC.IFCRELDEFINESBYTYPE, 'RelatingType', 'RelatedObjects' )
if( typePropsId.length > 0 ) element.rawTypeProps = typePropsId.map( ( childId ) => this.api.GetLine( this.modelId, childId, true ) )
const typePropsId = this.getAllRelatedItemsOfType(
element.expressID,
WebIFC.IFCRELDEFINESBYTYPE,
'RelatingType',
'RelatedObjects'
)
if (typePropsId.length > 0)
element.rawTypeProps = typePropsId.map((childId) =>
this.api.GetLine(this.modelId, childId, true)
)
// Lookup geometry in generated geometries object
if( this.productGeo[element.expressID] ) {
if (this.productGeo[element.expressID]) {
element['@displayValue'] = this.productGeo[element.expressID]
this.productGeo[element.expressID].forEach( ref => {
this.productGeo[element.expressID].forEach((ref) => {
this.project.__closure[ref.referencedId.toString()] = depth
element.__closure[ref.referencedId.toString()] = 1
} )
})
}
const isSpecial = specialTypes.find( t => t.type === element.speckle_type )
const isSpecial = specialTypes.find((t) => t.type === element.speckle_type)
// Recurse all children
if ( recursive ) {
await this.processSubElements( element, 'rawSpatialChildren', 'spatialChildren', isSpecial, recursive, depth, specialTypes )
await this.processSubElements( element, 'rawChildren', 'children', isSpecial, recursive, depth, specialTypes )
await this.processSubElements( element, 'rawPsets', 'propertySets', false, recursive, depth, specialTypes )
await this.processSubElements( element, 'rawTypeProps', 'typeProps', false, recursive, depth, specialTypes )
if (recursive) {
await this.processSubElements(
element,
'rawSpatialChildren',
'spatialChildren',
isSpecial,
recursive,
depth,
specialTypes
)
await this.processSubElements(
element,
'rawChildren',
'children',
isSpecial,
recursive,
depth,
specialTypes
)
await this.processSubElements(
element,
'rawPsets',
'propertySets',
false,
recursive,
depth,
specialTypes
)
await this.processSubElements(
element,
'rawTypeProps',
'typeProps',
false,
recursive,
depth,
specialTypes
)
if( element.children || element.spatialChildren || element.propertySets || element.typeProps ) {
console.log( `${element.constructor.name} ${element.GlobalId}:\n\tchildren count: ${ element.children ? element.children.length : '0'};\n\tspatial children count: ${element.spatialChildren ? element.spatialChildren.length : '0'};\n\tproperty sets count: ${element.propertySets ? element.propertySets.length : 0};\n\ttype properties: ${element.typeProps ? element.typeProps.length : 0}` )
if (
element.children ||
element.spatialChildren ||
element.propertySets ||
element.typeProps
) {
console.log(
`${element.constructor.name} ${element.GlobalId}:\n\tchildren count: ${
element.children ? element.children.length : '0'
};\n\tspatial children count: ${
element.spatialChildren ? element.spatialChildren.length : '0'
};\n\tproperty sets count: ${
element.propertySets ? element.propertySets.length : 0
};\n\ttype properties: ${element.typeProps ? element.typeProps.length : 0}`
)
}
}
}
if( this.productGeo[element.expressID] || element.spatialChildren || element.children ) {
let id = await this.serverApi.saveObject( element )
if (
this.productGeo[element.expressID] ||
element.spatialChildren ||
element.children
) {
let id = await this.serverApi.saveObject(element)
let ref = { speckle_type: 'reference', referencedId: id }
this.cache[element.expressID.toString()] = ref
this.closureCache[element.expressID.toString()] = element.__closure
@@ -171,34 +279,38 @@ module.exports = class IFCParser {
}
}
async processSubElements( element, key, newKey, isSpecial, recursive, depth, specialTypes ) {
if ( element[key] ) {
if ( !isSpecial )
element[newKey] = []
async processSubElements(
element,
key,
newKey,
isSpecial,
recursive,
depth,
specialTypes
) {
if (element[key]) {
if (!isSpecial) element[newKey] = []
let childCount = {}
for ( let child of element[key] ) {
let res = await this.traverse( child, recursive, depth + 1, specialTypes )
if ( res.referencedId ) {
if ( isSpecial ) {
for (let child of element[key]) {
let res = await this.traverse(child, recursive, depth + 1, specialTypes)
if (res.referencedId) {
if (isSpecial) {
let name = child[isSpecial.key]
if ( !name || name.length === 0 )
name = 'Undefined'
if ( !childCount[name] )
childCount[name] = 0
if ( childCount[name] > 0 )
name += '-' + childCount[name]++
if (!name || name.length === 0) name = 'Undefined'
if (!childCount[name]) childCount[name] = 0
if (childCount[name] > 0) name += '-' + childCount[name]++
element[name] = res
}
else
element[newKey].push( res )
} else element[newKey].push(res)
this.project.__closure[res.referencedId.toString()] = depth
element.__closure[res.referencedId.toString()] = 1
// adds to parent (this element) the child's closure tree.
if ( this.closureCache[child.expressID.toString()] ) {
for ( let key of Object.keys( this.closureCache[child.expressID.toString()] ) ) {
element.__closure[key] = this.closureCache[child.expressID.toString()][key] + 1
if (this.closureCache[child.expressID.toString()]) {
for (let key of Object.keys(
this.closureCache[child.expressID.toString()]
)) {
element.__closure[key] =
this.closureCache[child.expressID.toString()][key] + 1
}
}
}
@@ -208,45 +320,46 @@ module.exports = class IFCParser {
}
// (c) https://github.com/agviegas/web-ifc-three
extractVertexData( vertexData ) {
extractVertexData(vertexData) {
const vertices = []
const normals = []
let isNormalData = false
for ( let i = 0; i < vertexData.length; i++ ) {
isNormalData ? normals.push( vertexData[i] ) : vertices.push( vertexData[i] )
if ( ( i + 1 ) % 3 === 0 ) isNormalData = !isNormalData
for (let i = 0; i < vertexData.length; i++) {
isNormalData ? normals.push(vertexData[i]) : vertices.push(vertexData[i])
if ((i + 1) % 3 === 0) isNormalData = !isNormalData
}
return { vertices, normals }
}
// (c) https://github.com/agviegas/web-ifc-three/blob/907e08b5673d5e1c18261a4fceade7189d6b2db7/src/IFC/PropertyManager.ts#L110
getAllRelatedItemsOfType( elementID, type, relation, relatedProperty ) {
const lines = this.api.GetLineIDsWithType( this.modelId, type )
getAllRelatedItemsOfType(elementID, type, relation, relatedProperty) {
const lines = this.api.GetLineIDsWithType(this.modelId, type)
const IDs = []
for ( let i = 0; i < lines.size(); i++ ) {
const relID = lines.get( i )
const rel = this.api.GetLine( this.modelId, relID )
for (let i = 0; i < lines.size(); i++) {
const relID = lines.get(i)
const rel = this.api.GetLine(this.modelId, relID)
const relatedItems = rel[relation]
let foundElement = false
if ( Array.isArray( relatedItems ) ) {
const values = relatedItems.map( ( item ) => item.value )
foundElement = values.includes( elementID )
} else foundElement = ( relatedItems.value === elementID )
if (Array.isArray(relatedItems)) {
const values = relatedItems.map((item) => item.value)
foundElement = values.includes(elementID)
} else foundElement = relatedItems.value === elementID
if ( foundElement ) {
if (foundElement) {
const element = rel[relatedProperty]
if ( !Array.isArray( element ) ) IDs.push( element.value )
else element.forEach( ( ele ) => IDs.push( ele.value ) )
if (!Array.isArray(element)) IDs.push(element.value)
else element.forEach((ele) => IDs.push(ele.value))
}
}
return IDs
}
colorToMaterial( color ) {
let intColor = ( color.w << 24 ) + ( ( color.x * 255 ) << 16 ) + ( ( color.y * 255 ) << 8 ) + ( ( color.z * 255 ) )
colorToMaterial(color) {
let intColor =
(color.w << 24) + ((color.x * 255) << 16) + ((color.y * 255) << 8) + color.z * 255
return {
diffuse: intColor,
+4 -3
View File
@@ -1,8 +1,9 @@
'use strict'
module.exports = require( 'knex' )( {
module.exports = require('knex')({
client: 'pg',
connection: process.env.PG_CONNECTION_STRING || 'postgres://speckle:speckle@localhost/speckle',
connection:
process.env.PG_CONNECTION_STRING || 'postgres://speckle:speckle@localhost/speckle',
pool: { min: 1, max: 1 }
// migrations are in managed in the server package
} )
})
+82 -74
View File
@@ -1,12 +1,12 @@
'use strict'
const knex = require( '../knex' )
const knex = require('../knex')
const { getFileStream } = require( './filesApi' )
const fs = require( 'fs' )
const { spawn } = require( 'child_process' )
const { getFileStream } = require('./filesApi')
const fs = require('fs')
const { spawn } = require('child_process')
const ServerAPI = require( '../ifc/api' )
const ServerAPI = require('../ifc/api')
const HEALTHCHECK_FILE_PATH = '/tmp/last_successful_query'
@@ -16,7 +16,7 @@ const TMP_RESULTS_PATH = '/tmp/import_result.json'
let shouldExit = false
async function startTask() {
let { rows } = await knex.raw( `
let { rows } = await knex.raw(`
UPDATE file_uploads
SET
"convertedStatus" = 1,
@@ -29,37 +29,45 @@ async function startTask() {
) as task
WHERE file_uploads."id" = task."id"
RETURNING file_uploads."id"
` )
`)
return rows[0]
}
async function doTask( task ) {
async function doTask(task) {
let tempUserToken = null
let serverApi = null
try {
console.log( 'Doing task ', task )
let { rows } = await knex.raw( `
console.log('Doing task ', task)
let { rows } = await knex.raw(
`
SELECT
id as "fileId", "streamId", "branchName", "userId", "fileName", "fileType"
FROM file_uploads
WHERE id = ?
LIMIT 1
`, [ task.id ] )
`,
[task.id]
)
let info = rows[0]
if ( !info ) {
throw new Error( 'Internal error: DB inconsistent' )
if (!info) {
throw new Error('Internal error: DB inconsistent')
}
let upstreamFileStream = await getFileStream( { fileId: info.fileId } )
let diskFileStream = fs.createWriteStream( TMP_FILE_PATH )
let upstreamFileStream = await getFileStream({ fileId: info.fileId })
let diskFileStream = fs.createWriteStream(TMP_FILE_PATH)
upstreamFileStream.pipe( diskFileStream )
upstreamFileStream.pipe(diskFileStream)
await new Promise( fulfill => diskFileStream.on( 'finish' , fulfill ) )
await new Promise((fulfill) => diskFileStream.on('finish', fulfill))
serverApi = new ServerAPI( { streamId: info.streamId } )
let { token } = await serverApi.createToken( { userId: info.userId, name: 'temp upload token', scopes: [ 'streams:write', 'streams:read' ], lifespan: 1000000 } )
serverApi = new ServerAPI({ streamId: info.streamId })
let { token } = await serverApi.createToken({
userId: info.userId,
name: 'temp upload token',
scopes: ['streams:write', 'streams:read'],
lifespan: 1000000
})
tempUserToken = token
await runProcessWithTimeout(
@@ -78,14 +86,14 @@ async function doTask( task ) {
10 * 60 * 1000
)
let output = JSON.parse( fs.readFileSync( TMP_RESULTS_PATH ) )
let output = JSON.parse(fs.readFileSync(TMP_RESULTS_PATH))
if ( !output.success )
throw new Error( output.error )
if (!output.success) throw new Error(output.error)
let commitId = output.commitId
await knex.raw( `
await knex.raw(
`
UPDATE file_uploads
SET
"convertedStatus" = 2,
@@ -93,103 +101,103 @@ async function doTask( task ) {
"convertedMessage" = 'File converted successfully',
"convertedCommitId" = ?
WHERE "id" = ?
`, [ commitId, task.id ] )
} catch ( err ) {
console.log( 'Error: ', err )
await knex.raw( `
`,
[commitId, task.id]
)
} catch (err) {
console.log('Error: ', err)
await knex.raw(
`
UPDATE file_uploads
SET
"convertedStatus" = 3,
"convertedLastUpdate" = NOW(),
"convertedMessage" = ?
WHERE "id" = ?
`, [ err.toString(), task.id ] )
`,
[err.toString(), task.id]
)
}
if ( fs.existsSync( TMP_FILE_PATH ) ) fs.unlinkSync( TMP_FILE_PATH )
if ( fs.existsSync( TMP_RESULTS_PATH ) ) fs.unlinkSync( TMP_RESULTS_PATH )
if (fs.existsSync(TMP_FILE_PATH)) fs.unlinkSync(TMP_FILE_PATH)
if (fs.existsSync(TMP_RESULTS_PATH)) fs.unlinkSync(TMP_RESULTS_PATH)
if ( tempUserToken ) {
await serverApi.revokeTokenById( tempUserToken )
if (tempUserToken) {
await serverApi.revokeTokenById(tempUserToken)
}
}
function runProcessWithTimeout( cmd, cmdArgs, extraEnv, timeoutMs ) {
function runProcessWithTimeout(cmd, cmdArgs, extraEnv, timeoutMs) {
return new Promise((resolve, reject) => {
console.log(`Starting process: ${cmd} ${cmdArgs}`)
const childProc = spawn(cmd, cmdArgs, { env: { ...process.env, ...extraEnv } })
return new Promise( ( resolve, reject ) => {
console.log( `Starting process: ${cmd} ${cmdArgs}` )
const childProc = spawn( cmd, cmdArgs, { env: { ...process.env, ...extraEnv } } )
childProc.stdout.on('data', (data) => {
console.log('Parser: ', data.toString())
})
childProc.stdout.on( 'data', ( data ) => {
console.log( 'Parser: ', data.toString() )
} )
childProc.stderr.on( 'data', ( data ) => {
console.error( 'Parser: ', data.toString() )
} )
childProc.stderr.on('data', (data) => {
console.error('Parser: ', data.toString())
})
let timedOut = false
let timeout = setTimeout( () => {
console.log( 'Process timeout. Killing process...' )
let timeout = setTimeout(() => {
console.log('Process timeout. Killing process...')
timedOut = true
childProc.kill( 9 )
reject( `Timeout: Process took longer than ${timeoutMs} ms to execute` )
}, timeoutMs )
childProc.kill(9)
reject(`Timeout: Process took longer than ${timeoutMs} ms to execute`)
}, timeoutMs)
childProc.on( 'close', ( code ) => {
console.log( `Process exited with code ${code}` )
childProc.on('close', (code) => {
console.log(`Process exited with code ${code}`)
if ( timedOut ) return // ignore `close` calls after killing (the promise was already rejected)
if (timedOut) return // ignore `close` calls after killing (the promise was already rejected)
clearTimeout( timeout )
clearTimeout(timeout)
if ( code === 0 ) {
if (code === 0) {
resolve()
} else {
reject( `Parser exited with code ${code}` )
reject(`Parser exited with code ${code}`)
}
} )
} )
})
})
}
async function tick() {
if ( shouldExit ) {
process.exit( 0 )
if (shouldExit) {
process.exit(0)
}
try {
let task = await startTask()
fs.writeFile( HEALTHCHECK_FILE_PATH, '' + Date.now(), () => {} )
fs.writeFile(HEALTHCHECK_FILE_PATH, '' + Date.now(), () => {})
if ( !task ) {
setTimeout( tick, 1000 )
if (!task) {
setTimeout(tick, 1000)
return
}
await doTask( task )
await doTask(task)
// Check for another task very soon
setTimeout( tick, 10 )
} catch ( err ) {
console.log( 'Error executing task: ', err )
setTimeout( tick, 5000 )
setTimeout(tick, 10)
} catch (err) {
console.log('Error executing task: ', err)
setTimeout(tick, 5000)
}
}
async function main() {
console.log( 'Starting FileUploads Service...' )
console.log('Starting FileUploads Service...')
process.on( 'SIGTERM', () => {
process.on('SIGTERM', () => {
shouldExit = true
console.log( 'Shutting down...' )
} )
console.log('Shutting down...')
})
tick()
}
+9
View File
@@ -18,6 +18,15 @@ const config = {
parserOptions: {
sourceType: 'module'
},
overrides: [
{
files: './*.{js, ts}',
env: {
node: true,
commonjs: true
}
}
],
plugins: ['vue'],
rules: {
'no-console': 1
+1
View File
@@ -37,6 +37,7 @@ npm run serve
```
### Packaging for production
If you plan to package the frontend to use in a production setting, see our [Server deployment instructions](https://speckle.guide/dev/server-setup.html) (chapter `Run your speckle-server fork`)
## Community
+4 -1
View File
@@ -2,7 +2,10 @@ const path = require('path')
// Load .env files
const { loadEnv } = require('vue-cli-plugin-apollo/utils/load-env')
const env = loadEnv([path.resolve(__dirname, '.env'), path.resolve(__dirname, '.env.local')])
const env = loadEnv([
path.resolve(__dirname, '.env'),
path.resolve(__dirname, '.env.local')
])
module.exports = {
client: {
+1 -3
View File
@@ -1,6 +1,4 @@
module.exports = {
presets: ['@vue/cli-plugin-babel/preset'],
exclude: [
/(Speckle.js\.). /
]
exclude: [/(Speckle.js\.). /]
}
+57 -31
View File
@@ -2,29 +2,38 @@
<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,maximum-scale=1.0, user-scalable=0">
<meta property="og:title" content="Speckle">
<meta property="og:description" content="">
<meta property="og:image" content="https://speckle.xyz/og_image.png">
<meta property="og:url" content="<%= BASE_URL %>">
<meta name="twitter:card" content="summary_large_image">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width,initial-scale=1.0,maximum-scale=1.0, user-scalable=0"
/>
<meta property="og:title" content="Speckle" />
<meta property="og:description" content="" />
<meta property="og:image" content="https://speckle.xyz/og_image.png" />
<meta property="og:url" content="<%= BASE_URL %>" />
<meta name="twitter:card" content="summary_large_image" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<title><%= htmlWebpackPlugin.options.title %></title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500&display=swap"
rel="stylesheet"
/>
<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;
color: #0a66ff;
}
@media screen and (prefers-color-scheme: light) {
body {
background-color: white !important;
color: #0A66FF;
color: #0a66ff;
}
}
@@ -38,7 +47,7 @@
animation-iteration-count: infinite;
}
.hover-tada:hover{
.hover-tada:hover {
-webkit-animation-name: tada;
animation-name: tada;
-webkit-animation-duration: 1s;
@@ -53,15 +62,21 @@
-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);
10%,
20% {
-webkit-transform: scale3d(0.8, 0.8, 0.8) rotate3d(0, 0, 1, -3deg);
transform: scale3d(0.8, 0.8, 0.8) rotate3d(0, 0, 1, -3deg);
}
30%, 50%, 70%, 90% {
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% {
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);
}
@@ -75,15 +90,21 @@
-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);
10%,
20% {
-webkit-transform: scale3d(0.8, 0.8, 0.8) rotate3d(0, 0, 1, -3deg);
transform: scale3d(0.8, 0.8, 0.8) rotate3d(0, 0, 1, -3deg);
}
30%, 50%, 70%, 90% {
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% {
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);
}
@@ -96,15 +117,19 @@
</head>
<body>
<noscript>
<strong>We're sorry but Speckle doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
<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='
<div
style="
width: 100%;
height: 300px;
font-family: sans-serif !important;
position: absolute;
top:0;
top: 0;
bottom: 0;
left: 0;
right: 0;
@@ -112,9 +137,10 @@
text-align: center;
font-weight: 400;
font-size: 10px;
'
"
>
<img src="<%= BASE_URL %>logo.svg" style="max-width: 50px" class="tada">
<img src="<%= BASE_URL %>logo.svg" style="max-width: 50px" class="tada" />
</div>
</div>
<!-- built files will be auto injected -->
</body>
+34 -26
View File
@@ -1,27 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<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">
<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;
color: #0a66ff;
}
@media screen and (prefers-color-scheme: light) {
body {
background-color: white;
color: #0A66FF;
color: #0a66ff;
}
}
@@ -43,8 +46,8 @@
10%,
20% {
-webkit-transform: scale3d(.8, .8, .8) rotate3d(0, 0, 1, -3deg);
transform: scale3d(.8, .8, .8) rotate3d(0, 0, 1, -3deg);
-webkit-transform: scale3d(0.8, 0.8, 0.8) rotate3d(0, 0, 1, -3deg);
transform: scale3d(0.8, 0.8, 0.8) rotate3d(0, 0, 1, -3deg);
}
30%,
@@ -76,8 +79,8 @@
10%,
20% {
-webkit-transform: scale3d(.8, .8, .8) rotate3d(0, 0, 1, -3deg);
transform: scale3d(.8, .8, .8) rotate3d(0, 0, 1, -3deg);
-webkit-transform: scale3d(0.8, 0.8, 0.8) rotate3d(0, 0, 1, -3deg);
transform: scale3d(0.8, 0.8, 0.8) rotate3d(0, 0, 1, -3deg);
}
30%,
@@ -101,19 +104,23 @@
}
}
</style>
</head>
</head>
<body>
<body>
<noscript>
<strong>We're sorry but Speckle doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
<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='
<div
style="
width: 100%;
height: 300px;
font-family: sans-serif !important;
position: absolute;
top:0;
top: 0;
bottom: 0;
left: 0;
right: 0;
@@ -121,10 +128,11 @@
text-align: center;
font-weight: 400;
font-size: 10px;
'>
<img src="<%= BASE_URL %>logo.svg" style="max-width: 50px" class="tada">
"
>
<img src="<%= BASE_URL %>logo.svg" style="max-width: 50px" class="tada" />
</div>
</div>
<!-- built files will be auto injected -->
</body>
</body>
</html>
+4 -1
View File
@@ -6,7 +6,10 @@ export default {
components: {},
mounted() {
let mixpanelId = this.$mixpanelId()
this.$mixpanel.register({ server_id: this.$mixpanelServerId(), hostApp: 'web-embed' })
this.$mixpanel.register({
server_id: this.$mixpanelServerId(),
hostApp: 'web-embed'
})
if (mixpanelId !== null) {
this.$mixpanel.identify(mixpanelId)
}
+17 -6
View File
@@ -1,12 +1,18 @@
<template>
<v-app :class="`no-scrollbar ${$vuetify.theme.dark ? 'background-dark' : 'background-light'}`">
<v-app
:class="`no-scrollbar ${
$vuetify.theme.dark ? 'background-dark' : 'background-light'
}`"
>
<!-- <speckle-loading v-if="!stream || error" :error="error" style="z-index: 101000" /> -->
<div v-if="!error" style="z-index: 1000">
<div class="top-left bottom-left ma-2 d-flex">
<span class="caption d-inline-flex align-center">
<img src="@/assets/logo.svg" height="20" />
<span style="margin-top: 2px" class="primary--text">
<a href="https://speckle.xyz" target="_blank" class="text-decoration-none">Speckle</a>
<a href="https://speckle.xyz" target="_blank" class="text-decoration-none">
Speckle
</a>
</span>
</span>
</div>
@@ -90,7 +96,9 @@ export default {
},
computed: {
isSmall() {
return this.$vuetify.breakpoint.name == 'xs' || this.$vuetify.breakpoint.name == 'sm'
return (
this.$vuetify.breakpoint.name == 'xs' || this.$vuetify.breakpoint.name == 'sm'
)
},
displayType() {
if (!this.input.stream) {
@@ -147,7 +155,8 @@ export default {
let res = await getCommit(this.input.stream, this.input.commit)
let data = res.data
let latestCommit = data.stream.commit
if (this.input.object === undefined) this.objectId = latestCommit.referencedObject
if (this.input.object === undefined)
this.objectId = latestCommit.referencedObject
this.specificCommit = data.stream
} catch (e) {
this.error = e.message
@@ -157,13 +166,15 @@ export default {
try {
let res = await getLatestBranchCommit(this.input.stream, this.input.branch)
let data = res.data
let latestCommit = data.stream.branch.commits.items[0] || data.stream.branch.commit
let 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
if (this.input.object == undefined)
this.objectId = latestCommit.referencedObject
else this.objectId = this.input.object
this.lastCommit = data.stream
} catch (e) {
+5 -1
View File
@@ -1,4 +1,8 @@
import { branchLastCommitQuery, serverInfoQuery, streamCommitQuery } from './speckleQueries.js'
import {
branchLastCommitQuery,
serverInfoQuery,
streamCommitQuery
} from './speckleQueries.js'
export let SERVER_URL = window.location.origin
@@ -1,4 +1,4 @@
query Object($streamId: String!, $id: String!){
query Object($streamId: String!, $id: String!) {
stream(id: $streamId) {
id
object(id: $id) {
@@ -1,4 +1,4 @@
query Object($streamId: String!, $id: String!){
query Object($streamId: String!, $id: String!) {
stream(id: $streamId) {
id
name
@@ -4,7 +4,7 @@ query StreamCommits($id: String!) {
role
commits {
totalCount
items{
items {
id
authorId
authorName
+4 -1
View File
@@ -5,7 +5,10 @@ Vue.prototype.$eventHub = new Vue()
import App from '@/main/App.vue'
import { createProvider } from '@/vue-apollo'
import { checkAccessCodeAndGetTokens, prefetchUserAndSetSuuid } from '@/plugins/authHelpers'
import {
checkAccessCodeAndGetTokens,
prefetchUserAndSetSuuid
} from '@/plugins/authHelpers'
import router from '@/main/router/index'
import vuetify from '@/plugins/vuetify'
@@ -36,11 +36,16 @@
class="mr-3"
:user-id="activityItem.info.targetUser"
:color="
lastActivity.actionType === 'stream_permissions_add' ? 'success' : 'error'
lastActivity.actionType === 'stream_permissions_add'
? 'success'
: 'error'
"
></user-pill>
<span v-if="$vuetify.breakpoint.smAndUp" class="mr-3 body-2 font-italic">
<span
v-if="$vuetify.breakpoint.smAndUp"
class="mr-3 body-2 font-italic"
>
{{
lastActivity.actionType === 'stream_permissions_add'
? 'user added as'
@@ -85,7 +90,9 @@
<v-icon color="primary" small>mdi-folder</v-icon>
{{ stream.name }}
</router-link>
<span class="ml-3 body-2 font-italic">{{ lastActivityBrief.actionText }}</span>
<span class="ml-3 body-2 font-italic">
{{ lastActivityBrief.actionText }}
</span>
<v-spacer />
@@ -168,10 +175,14 @@
>
<v-card-text class="pa-5 body-1">
<v-chip :to="url" :color="lastActivityBrief.color">
<v-icon small class="mr-2 float-left" light>{{ lastActivityBrief.icon }}</v-icon>
<v-icon small class="mr-2 float-left" light>
{{ lastActivityBrief.icon }}
</v-icon>
{{ branchName }}
</v-chip>
<span class="ml-3 body-2 font-italic">{{ lastActivityBrief.actionText }}</span>
<span class="ml-3 body-2 font-italic">
{{ lastActivityBrief.actionText }}
</span>
<div class="mt-3">
<div
v-for="activityItem in activityGroup"
@@ -215,7 +226,9 @@
</v-chip>
<span v-if="lastActivity.actionType === 'commit_create'">
<span class="mx-3 body-2 font-italic">from</span>
<source-app-avatar :application-name="commit.sourceApplication" />
<source-app-avatar
:application-name="commit.sourceApplication"
/>
</span>
<span v-if="lastActivity.actionType === 'commit_receive'">
<span class="mx-3 body-2 font-italic">in</span>
@@ -438,13 +451,17 @@ export default {
case 'stream_permissions_add':
return {
captionText: `added ${
this.activityGroup.length === 1 ? 'a user' : this.activityGroup.length + ' users'
this.activityGroup.length === 1
? 'a user'
: this.activityGroup.length + ' users'
} to`
}
case 'stream_permissions_remove':
return {
captionText: `removed ${
this.activityGroup.length === 1 ? 'a user' : this.activityGroup.length + ' users'
this.activityGroup.length === 1
? 'a user'
: this.activityGroup.length + ' users'
} from`
}
case 'branch_create':
@@ -7,7 +7,12 @@
<v-icon x-small color="primary" class="mr-1">{{ icons[value.name] }}</v-icon>
{{ capitalize(value.name.split('History')[0]) }} history
</p>
<apex-chart class="primary--text" type="bar" :options="options" :series="[value]" />
<apex-chart
class="primary--text"
type="bar"
:options="options"
:series="[value]"
/>
</v-col>
</v-row>
</section-card>
@@ -16,13 +16,17 @@
Updated
<b><timeago :datetime="stream.updatedAt"></timeago></b>
<br />
<span class="grey--text">({{ new Date(stream.updatedAt).toLocaleString() }})</span>
<span class="grey--text">
({{ new Date(stream.updatedAt).toLocaleString() }})
</span>
</v-col>
<v-col cols="2" class="caption text-truncate">
Created
<b><timeago :datetime="stream.createdAt"></timeago></b>
<br />
<span class="grey--text">({{ new Date(stream.createdAt).toLocaleString() }})</span>
<span class="grey--text">
({{ new Date(stream.createdAt).toLocaleString() }})
</span>
</v-col>
<v-col v-tooltip="'Stream total size'" class="caption font-weight-bold">
{{ `${(stream.size ? stream.size / 1048576 : 0.0).toFixed(2)} MB` }}
@@ -37,7 +41,13 @@
<collaborators-display :stream="stream" :link-to-collabs="false" />
</v-col>
<v-col cols="1" class="text-right">
<v-btn v-tooltip="'Delete stream'" small icon color="error" @click="$emit('delete', stream)">
<v-btn
v-tooltip="'Delete stream'"
small
icon
color="error"
@click="$emit('delete', stream)"
>
<v-icon small>mdi-delete-outline</v-icon>
</v-btn>
</v-col>
@@ -3,12 +3,20 @@
<v-col class="text-truncate">
<user-avatar :id="selfUser.id" :size="30" class="mr-2"></user-avatar>
<router-link class="text-decoration-none space-grotesk mx-1" :to="`/profile/${selfUser.id}`">
<router-link
class="text-decoration-none space-grotesk mx-1"
:to="`/profile/${selfUser.id}`"
>
{{ selfUser.name }}
</router-link>
</v-col>
<v-col cols="3" class="caption text-truncate">
<v-icon v-if="selfUser.verified" v-tooltip="'Verfied email'" small class="mr-2 primary--text">
<v-icon
v-if="selfUser.verified"
v-tooltip="'Verfied email'"
small
class="mr-2 primary--text"
>
mdi-shield-check
</v-icon>
<v-icon v-else v-tooltip="'Email not verified'" small class="mr-2 warning--text">
@@ -45,7 +53,13 @@
></v-select>
<!-- </v-col>
<v-col cols="1" class="text-right"> -->
<v-btn v-tooltip="'Delete user'" small icon color="error" @click="$emit('delete', selfUser)">
<v-btn
v-tooltip="'Delete user'"
small
icon
color="error"
@click="$emit('delete', selfUser)"
>
<v-icon small>mdi-delete-outline</v-icon>
</v-btn>
</v-col>
@@ -75,7 +75,9 @@ export default {
},
methods: {
getLatestVersion() {
return fetch('https://api.github.com/repos/specklesystems/speckle-server/releases/latest')
return fetch(
'https://api.github.com/repos/specklesystems/speckle-server/releases/latest'
)
.then(async (res) => {
let x = await res.json()
return x.tag_name
@@ -3,18 +3,26 @@
<v-card-text class="text-h3 text-sm-h4 text-md-h3 primary--text">
<span class="primary--text">
<b>
<a class="text-decoration-none" href="https://speckle.systems" target="_blank">Speckle</a>
<a
class="text-decoration-none"
href="https://speckle.systems"
target="_blank"
>
Speckle
</a>
</b>
</span>
<span class="font-weight-light">, empowering your design and construction data.</span>
<span class="font-weight-light">
, empowering your design and construction data.
</span>
</v-card-text>
<div class="">
<v-card-text class="text-h6 font-weight-regular">
Speckle helps leading AEC companies freely exchange data between software silos and automate
design and delivery processes:
Speckle helps leading AEC companies freely exchange data between software silos
and automate design and delivery processes:
<span class="primary--text text--disabled">
join 100s of designers, architects, engineers and developers building the digital future
of AEC.
join 100s of designers, architects, engineers and developers building the
digital future of AEC.
</span>
</v-card-text>
</div>
@@ -7,7 +7,12 @@
</v-card-title>
<v-card-text class="pb-5">
<template v-for="s in strategies">
<v-col :key="s.name" cols="12" class="text-center py-1 my-0" @click="trackSignIn(s.name)">
<v-col
:key="s.name"
cols="12"
class="text-center py-1 my-0"
@click="trackSignIn(s.name)"
>
<v-btn
dark
block
@@ -1,6 +1,10 @@
<template>
<div v-if="user" style="display: inline-block" class="text-center">
<user-avatar-icon :size="size" :avatar="user.avatar" :seed="user.id"></user-avatar-icon>
<user-avatar-icon
:size="size"
:avatar="user.avatar"
:seed="user.id"
></user-avatar-icon>
<p class="text-h6 mt-4">
{{ user.name }}
<br />
@@ -23,14 +23,24 @@
<!-- <br /> -->
<span v-if="commentDetails.replies.totalCount > 0">
<!-- eslint-disable-next-line prettier/prettier -->
Last reply <timeago :datetime="commentDetails.updatedAt" /> <!--, on {{ new Date(commentDetails.updatedAt).toLocaleString() }} -->
Last reply
<timeago :datetime="commentDetails.updatedAt" />
<!--, on {{ new Date(commentDetails.updatedAt).toLocaleString() }} -->
<br />
</span>
<span class="grey--text">
Created on {{ new Date(commentDetails.createdAt).toLocaleString() }}
</span>
<br>
<v-btn v-if="canArchiveThread" @click="showArchiveDialog=true" class="ml-n2 red--text rounded-lg elevation-0" x-small plain>Archive</v-btn>
<br />
<v-btn
v-if="canArchiveThread"
@click="showArchiveDialog = true"
class="ml-n2 red--text rounded-lg elevation-0"
x-small
plain
>
Archive
</v-btn>
<v-dialog v-model="showArchiveDialog" max-width="500">
<v-card>
<v-toolbar color="error" dark flat>
@@ -39,7 +49,9 @@
</v-app-bar-nav-icon>
<v-toolbar-title>Archive Comment Thread</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn icon @click="showArchiveDialog = false"><v-icon>mdi-close</v-icon></v-btn>
<v-btn icon @click="showArchiveDialog = false">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-toolbar>
<v-card-text class="mt-4">
This comment thread will be archived. Are you sure?
@@ -51,7 +63,15 @@
</v-card-actions>
</v-card>
</v-dialog>
<v-btn v-if="isUnread" @click="markAsRead" class="ml-n2 rounded-lg elevation-0" x-small plain>Mark as read</v-btn>
<v-btn
v-if="isUnread"
@click="markAsRead"
class="ml-n2 rounded-lg elevation-0"
x-small
plain
>
Mark as read
</v-btn>
</div>
</div>
<div class="body-2 px-4 flex-shrink-0">
@@ -103,12 +123,17 @@ export default {
},
props: {
comment: { type: Object, default: () => null },
stream: { type: Object, default: () => { return { role: null } } }
stream: {
type: Object,
default: () => {
return { role: null }
}
}
},
apollo: {
commentDetails: {
query: gql`
query($streamId: String!, $id: String!) {
query ($streamId: String!, $id: String!) {
comment(streamId: $streamId, id: $id) {
id
text
@@ -146,7 +171,7 @@ export default {
$subscribe: {
commentThreadActivity: {
query: gql`
subscription($streamId: String!, $commentId: String!) {
subscription ($streamId: String!, $commentId: String!) {
commentThreadActivity(streamId: $streamId, commentId: $commentId)
}
`,
@@ -160,7 +185,7 @@ export default {
return !this.$loggedIn()
},
result({ data }) {
if(!data || !data.commentThreadActivity) return
if (!data || !data.commentThreadActivity) return
if (data.commentThreadActivity.eventType === 'reply-added') {
this.commentDetails.replies.totalCount++
this.commentDetails.updatedAt = Date.now()
@@ -181,9 +206,13 @@ export default {
},
computed: {
canArchiveThread() {
if(!this.comment || !this.stream ) return false
if(!this.stream.role) return false
if(this.comment.authorId === this.$userId() || this.stream.role ==='stream:owner') return true
if (!this.comment || !this.stream) return false
if (!this.stream.role) return false
if (
this.comment.authorId === this.$userId() ||
this.stream.role === 'stream:owner'
)
return true
},
link() {
if (!this.commentDetails) return
@@ -197,10 +226,14 @@ export default {
},
isUnread() {
if (!this.commentDetails) return
return new Date(this.commentDetails.updatedAt) - new Date(this.commentDetails.viewedAt) > 0
return (
new Date(this.commentDetails.updatedAt) -
new Date(this.commentDetails.viewedAt) >
0
)
}
},
methods:{
methods: {
async markAsRead() {
this.commentDetails.viewedAt = Date.now()
await this.$apollo.mutate({
@@ -209,7 +242,10 @@ export default {
commentView(streamId: $streamId, commentId: $commentId)
}
`,
variables: { streamId: this.$route.params.streamId, commentId: this.comment.id }
variables: {
streamId: this.$route.params.streamId,
commentId: this.comment.id
}
})
},
async archiveComment() {
@@ -1,10 +1,17 @@
<template>
<div
class="no-mouse pa-2"
:style="`${$vuetify.breakpoint.xs ? 'width: 90vw;' : 'width: 300px;'} xxx-background: rgba(0.5, 0.5, 0.5, 0.5)`"
:style="`${
$vuetify.breakpoint.xs ? 'width: 90vw;' : 'width: 300px;'
} xxx-background: rgba(0.5, 0.5, 0.5, 0.5)`"
>
<div v-if="$vuetify.breakpoint.xs" class="text-right mb-5 mouse">
<v-btn icon small class="background ml-2 elevation-10" @click="minimise = !minimise">
<v-btn
icon
small
class="background ml-2 elevation-10"
@click="minimise = !minimise"
>
<v-icon v-if="!minimise" small>mdi-minus</v-icon>
<v-icon v-else small>mdi-plus</v-icon>
</v-btn>
@@ -18,17 +25,27 @@
</v-btn>
</div>
<div v-show="!minimise" style="width: 100%" class="mouse">
<div v-if="!isComplete" class="warning rounded-xl py-2 caption mb-2 text-center" dense>
<div
v-if="!isComplete"
class="warning rounded-xl py-2 caption mb-2 text-center"
dense
>
<v-icon x-small>mdi-alert-circle-outline</v-icon>
This comment is targeting other resources.
<v-btn x-small @click="addMissingResources()">View in full context</v-btn>
</div>
<div class="px-2" v-show="$apollo.loading">
<v-progress-linear indeterminate/>
<v-progress-linear indeterminate />
</div>
<template v-for="(reply, index) in thread">
<div v-if="showTime(index)" :key="index + 'date'" class="d-flex justify-center mouse">
<div class="d-inline px-2 py-0 caption text-center mb-2 rounded-lg background grey--text">
<div
v-if="showTime(index)"
:key="index + 'date'"
class="d-flex justify-center mouse"
>
<div
class="d-inline px-2 py-0 caption text-center mb-2 rounded-lg background grey--text"
>
{{ new Date(reply.createdAt).toLocaleString() }}
<timeago :datetime="reply.createdAt" class="font-italic ma-1"></timeago>
</div>
@@ -52,11 +69,14 @@
</template>
<div v-if="$loggedIn()" class="px-0 mb-4">
<v-slide-y-transition>
<div class="px-4 py-2 caption mb-2 background rounded-xl" v-show="whoIsTyping.length > 0">
{{typingStatusText}}
<div
class="px-4 py-2 caption mb-2 background rounded-xl"
v-show="whoIsTyping.length > 0"
>
{{ typingStatusText }}
</div>
</v-slide-y-transition>
<div >
<div>
<v-textarea
:disabled="loadingReply"
v-model="replyText"
@@ -73,7 +93,7 @@
></v-textarea>
</div>
<div class="px-2" v-show="loadingReply">
<v-progress-linear indeterminate/>
<v-progress-linear indeterminate />
</div>
<div class="text-right" ref="replyinput">
<v-btn
@@ -108,7 +128,9 @@
</v-app-bar-nav-icon>
<v-toolbar-title>Archive Comment Thread</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn icon @click="showArchiveDialog = false"><v-icon>mdi-close</v-icon></v-btn>
<v-btn icon @click="showArchiveDialog = false">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-toolbar>
<v-card-text class="mt-4">
This comment thread will be archived. Are you sure?
@@ -122,7 +144,13 @@
</v-dialog>
</div>
<div v-else>
<v-btn block depressed color="primary" class="rounded-xl" @click="$loginAndSetRedirect()">
<v-btn
block
depressed
color="primary"
class="rounded-xl"
@click="$loginAndSetRedirect()"
>
<v-icon small class="mr-1">mdi-account</v-icon>
Sign in to reply
</v-btn>
@@ -144,7 +172,7 @@ export default {
apollo: {
user: {
query: gql`
query{
query {
user {
name
id
@@ -154,8 +182,8 @@ export default {
},
stream: {
query: gql`
query($streamId: String!) {
stream(id: $streamId){
query ($streamId: String!) {
stream(id: $streamId) {
id
role
}
@@ -167,7 +195,7 @@ export default {
},
replyQuery: {
query: gql`
query($streamId: String!, $id: String!) {
query ($streamId: String!, $id: String!) {
comment(streamId: $streamId, id: $id) {
id
viewedAt
@@ -193,7 +221,7 @@ export default {
}
},
result({ data }) {
if(!data) return
if (!data) return
data.comment.replies.items.forEach((item) => {
if (this.localReplies.findIndex((c) => c.id === item.id) === -1)
this.localReplies.push(item)
@@ -205,7 +233,7 @@ export default {
$subscribe: {
commentThreadActivity: {
query: gql`
subscription($streamId: String!, $commentId: String!) {
subscription ($streamId: String!, $commentId: String!) {
commentThreadActivity(streamId: $streamId, commentId: $commentId)
}
`,
@@ -219,7 +247,7 @@ export default {
return !this.$loggedIn()
},
result({ data }) {
if(!data || !data.commentThreadActivity) return
if (!data || !data.commentThreadActivity) return
if (data.commentThreadActivity.eventType === 'reply-added') {
if (!this.comment.expanded) return this.$emit('bounce', this.comment.id)
else {
@@ -228,26 +256,26 @@ export default {
}, 100)
}
this.localReplies.push({ ...data.commentThreadActivity })
this.$refs.replyinput.scrollIntoView({behaviour: 'smooth', block: 'end' })
this.$refs.replyinput.scrollIntoView({ behaviour: 'smooth', block: 'end' })
return
}
if (data.commentThreadActivity.eventType === 'comment-archived') {
this.$emit('deleted', this.comment)
}
if(data.commentThreadActivity.eventType === 'reply-typing-status') {
if (data.commentThreadActivity.eventType === 'reply-typing-status') {
let state = data.commentThreadActivity.data
if(state.userId === this.$userId()) return
let existingUser = this.whoIsTyping.find( u => u.userId === state.userId)
if(state.isTyping && existingUser) {
if (state.userId === this.$userId()) return
let existingUser = this.whoIsTyping.find((u) => u.userId === state.userId)
if (state.isTyping && existingUser) {
existingUser.lastSeenAt = Date.now()
return
}
if(!state.isTyping) {
let indx = this.whoIsTyping.findIndex( u => u.userId === state.userId)
if(indx!==-1) this.whoIsTyping.splice(indx, 1)
if (!state.isTyping) {
let indx = this.whoIsTyping.findIndex((u) => u.userId === state.userId)
if (indx !== -1) this.whoIsTyping.splice(indx, 1)
return
}
if(state.isTyping && !existingUser) {
if (state.isTyping && !existingUser) {
state.lastSeenAt = Date.now()
this.whoIsTyping.push(state)
}
@@ -269,9 +297,13 @@ export default {
},
computed: {
canArchiveThread() {
if(!this.comment || !this.stream ) return false
if(!this.stream.role) return false
if(this.comment.authorId === this.$userId() || this.stream.role ==='stream:owner') return true
if (!this.comment || !this.stream) return false
if (!this.stream.role) return false
if (
this.comment.authorId === this.$userId() ||
this.stream.role === 'stream:owner'
)
return true
},
thread() {
let sorted = [...this.localReplies].sort(
@@ -302,13 +334,13 @@ export default {
return route
},
typingStatusText() {
if(this.whoIsTyping.length === 0) return null
if(this.whoIsTyping.length > 1) {
return `${this.whoIsTyping.map(u=>u.userName).join(', ')} are typing...`
if (this.whoIsTyping.length === 0) return null
if (this.whoIsTyping.length > 1) {
return `${this.whoIsTyping.map((u) => u.userName).join(', ')} are typing...`
} else {
return `${this.whoIsTyping[0].userName} is typing...`
}
},
}
},
watch: {
'comment.expanded': {
@@ -320,7 +352,10 @@ export default {
commentView(streamId: $streamId, commentId: $commentId)
}
`,
variables: { streamId: this.$route.params.streamId, commentId: this.comment.id }
variables: {
streamId: this.$route.params.streamId,
commentId: this.comment.id
}
})
// eslint-disable-next-line vue/no-mutating-props
@@ -332,33 +367,42 @@ export default {
}
}
},
mounted(){
mounted() {
window.addEventListener('beforeunload', async (e) => {
await this.sendTypingUpdate( false )
await this.sendTypingUpdate(false)
})
setInterval(()=>{
setInterval(() => {
let now = Date.now()
for(let i = this.whoIsTyping.length-1; i >= 0; i--) {
if(Math.abs(now - this.whoIsTyping[i].lastSeenAt) > 10000) this.whoIsTyping.splice(i, 1)
for (let i = this.whoIsTyping.length - 1; i >= 0; i--) {
if (Math.abs(now - this.whoIsTyping[i].lastSeenAt) > 10000)
this.whoIsTyping.splice(i, 1)
}
}, 5000)
},
methods: {
debTypingUpdate: debounce( async function() {
if(!this.$loggedIn()) return
debTypingUpdate: debounce(
async function () {
if (!this.$loggedIn()) return
await this.sendTypingUpdate(this.isTyping)
this.isTyping = !this.isTyping
}, 7000, { leading: true }),
},
7000,
{ leading: true }
),
async sendTypingUpdate( state = true) {
if(!this.$loggedIn()) return
async sendTypingUpdate(state = true) {
if (!this.$loggedIn()) return
await this.$apollo.mutate({
mutation: gql`
mutation typingUpdate($sId: String!, $cId: String!, $d: JSONObject ) {
userCommentThreadActivityBroadcast(streamId: $sId, commentId: $cId, data: $d)
mutation typingUpdate($sId: String!, $cId: String!, $d: JSONObject) {
userCommentThreadActivityBroadcast(
streamId: $sId
commentId: $cId
data: $d
)
}
`,
variables:{
variables: {
sId: this.$route.params.streamId,
cId: this.comment.id,
d: {
@@ -428,7 +472,7 @@ export default {
`,
variables: { input: replyInput }
})
await this.sendTypingUpdate( false )
await this.sendTypingUpdate(false)
this.$mixpanel.track('Comment Action', { type: 'action', name: 'reply' })
} catch (e) {
this.$eventHub.$emit('notification', {
@@ -2,7 +2,9 @@
<div>
<v-list dense nav class="mt-4 py-0 mb-3">
<v-list-item
:class="`px-2 list-overlay-${$vuetify.theme.dark ? 'dark' : 'light'} elevation-2`"
:class="`px-2 list-overlay-${
$vuetify.theme.dark ? 'dark' : 'light'
} elevation-2`"
style="position: sticky; top: 82px"
@click="expand = !expand"
>
@@ -29,7 +31,8 @@
:key="comment.id + '-card-sidebar'"
no-gutters
:class="`${isUnread(comment) ? 'border' : ''} my-2 property-row rounded-lg ${
$store.state.selectedComment && $store.state.selectedComment.id === comment.id
$store.state.selectedComment &&
$store.state.selectedComment.id === comment.id
? 'elevation-5 selected'
: ''
}`"
@@ -40,7 +43,9 @@
</v-col>
<v-col
cols="8"
:class="`pl-2 body-2 text-truncate px-1 ${$vuetify.theme.dark ? 'grey--text' : ''}`"
:class="`pl-2 body-2 text-truncate px-1 ${
$vuetify.theme.dark ? 'grey--text' : ''
}`"
style="line-height: 30px"
>
{{ comment.text }}
@@ -70,7 +75,14 @@
<timeago :datetime="comment.updatedAt" />
</v-col>
</v-row>
<v-btn small block class="rounded-xl" :to="`/streams/${$route.params.streamId}/comments`">all stream comments</v-btn>
<v-btn
small
block
class="rounded-xl"
:to="`/streams/${$route.params.streamId}/comments`"
>
all stream comments
</v-btn>
</div>
</v-scroll-y-transition>
</div>
@@ -33,7 +33,7 @@
</div>
</v-card-text>
</div>
<v-divider v-if="showStreamAndBranch"/>
<v-divider v-if="showStreamAndBranch" />
<div v-if="showStreamAndBranch" class="d-flex align-center caption px-5 py-2">
<div class="text-truncate mr-2">
<router-link
@@ -58,8 +58,17 @@
<commit-received-receipts :stream-id="streamId" :commit-id="commit.id" shadow />
</div>
<div style="position: absolute; top: 10px; left: 12px">
<v-chip v-if="commit.commentCount !== 0" small class="caption primary" dark v-tooltip="`${commit.commentCount} comment${commit.commentCount === 1 ? '' : 's'}`">
<v-icon x-small class="mr-1">mdi-comment-outline</v-icon> {{ commit.commentCount }}
<v-chip
v-if="commit.commentCount !== 0"
small
class="caption primary"
dark
v-tooltip="
`${commit.commentCount} comment${commit.commentCount === 1 ? '' : 's'}`
"
>
<v-icon x-small class="mr-1">mdi-comment-outline</v-icon>
{{ commit.commentCount }}
</v-chip>
<source-app-avatar :application-name="commit.sourceApplication" />
</div>
@@ -70,7 +79,8 @@
export default {
components: {
PreviewImage: () => import('@/main/components/common/PreviewImage'),
CommitReceivedReceipts: () => import('@/main/components/common/CommitReceivedReceipts'),
CommitReceivedReceipts: () =>
import('@/main/components/common/CommitReceivedReceipts'),
SourceAppAvatar: () => import('@/main/components/common/SourceAppAvatar')
},
props: {
@@ -78,7 +88,7 @@ export default {
previewHeight: { type: Number, default: () => 180 },
showStreamAndBranch: { type: Boolean, default: true }
},
computed:{
computed: {
streamId() {
return this.commit.streamId ?? this.$route.params.streamId
}
@@ -47,7 +47,9 @@
<v-app-bar-nav-icon><v-icon>mdi-download</v-icon></v-app-bar-nav-icon>
<v-toolbar-title>All Received Receipts</v-toolbar-title>
<v-spacer />
<v-btn icon @click="showAllActivityDialog = false"><v-icon>mdi-close</v-icon></v-btn>
<v-btn icon @click="showAllActivityDialog = false">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-toolbar>
<v-list>
<v-list-item v-for="(act, i) in activity.items" :key="i">
@@ -63,7 +65,10 @@
</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action>
<source-app-avatar class="mt-3 mb-3" :application-name="act.info.sourceApplication" />
<source-app-avatar
class="mt-3 mb-3"
:application-name="act.info.sourceApplication"
/>
</v-list-item-action>
</v-list-item>
</v-list>
@@ -131,7 +136,8 @@ export default {
},
computed: {
receivedUsersUnique() {
if (!(this.activity && this.activity.items && this.activity.items.length > 0)) return []
if (!(this.activity && this.activity.items && this.activity.items.length > 0))
return []
let set = new Set()
this.activity.items.forEach((item) => set.add(item.userId))
return Array.from(set)
@@ -1,10 +1,21 @@
<template>
<v-container>
<v-row justify="center" style="margin-top: 50px" dense>
<v-col cols="12" lg="6" md="6" xl="6" class="d-flex flex-column justify-center align-center">
<v-col
cols="12"
lg="6"
md="6"
xl="6"
class="d-flex flex-column justify-center align-center"
>
<v-card flat tile color="transparent" class="pa-0">
<div class="d-flex flex-column justify-space-between align-center mb-10">
<v-img v-if="!errorType" contain max-height="200" src="@/assets/emptybox.png"></v-img>
<v-img
v-if="!errorType"
contain
max-height="200"
src="@/assets/emptybox.png"
></v-img>
<v-img
v-else-if="errorType == 'access'"
contain
@@ -29,7 +40,9 @@
class="primary mb-4 no-overlay"
dark
:to="`${
$route.params.streamId && errorType !== '404' && errorType !== 'access'
$route.params.streamId &&
errorType !== '404' &&
errorType !== 'access'
? '/streams/' + $route.params.streamId
: '/'
}`"
@@ -39,7 +52,9 @@
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Home</v-list-item-title>
<v-list-item-subtitle class="caption">Go to the homepage</v-list-item-subtitle>
<v-list-item-subtitle class="caption">
Go to the homepage
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</v-list>
@@ -1,9 +1,18 @@
<template>
<v-container>
<v-row justify="center" style="margin-top: 50px" dense>
<v-col cols="12" lg="6" md="6" xl="6" class="d-flex flex-column justify-center align-center">
<v-col
cols="12"
lg="6"
md="6"
xl="6"
class="d-flex flex-column justify-center align-center"
>
<v-card flat tile color="transparent" class="pa-0">
<div v-if="showImage" class="d-flex flex-column justify-space-between align-center mb-10">
<div
v-if="showImage"
class="d-flex flex-column justify-space-between align-center mb-10"
>
<v-img contain max-height="200" src="@/assets/emptybox.png"></v-img>
</div>
<div class="text-center mb-2 space-grotesk">
@@ -25,8 +34,8 @@
<v-list-item-content>
<v-list-item-title>Install Connectors</v-list-item-title>
<p class="caption pb-0 mb-0">
Download Speckle Manager to install connectors for your design applications
and start sending data in no time!
Download Speckle Manager to install connectors for your design
applications and start sending data in no time!
</p>
</v-list-item-content>
</v-list-item>
@@ -42,7 +51,8 @@
<v-list-item-content>
<v-list-item-title>Authenticate</v-list-item-title>
<p class="caption pb-0 mb-0">
Link up your Speckle account with the desktop connectors you have installed.
Link up your Speckle account with the desktop connectors you have
installed.
</p>
</v-list-item-content>
</v-list-item>
@@ -32,7 +32,8 @@
<v-list-item-content>
<v-list-item-title>
<div class="text-subtitle-1 text-truncate">
Nothing found. Please search again (your query has to be longer than 3 charact)
Nothing found. Please search again (your query has to be longer than 3
charact)
</div>
</v-list-item-title>
</v-list-item-content>
@@ -1,6 +1,12 @@
<template>
<v-card :class="`elevation-${elevation} rounded-lg overflow-hidden ${funky ? 'funky' : ''}`">
<v-toolbar v-show="hasHeaderSlot || hasActionsSlot || expandable" flat :dense="dense">
<v-card
:class="`elevation-${elevation} rounded-lg overflow-hidden ${funky ? 'funky' : ''}`"
>
<v-toolbar
v-show="hasHeaderSlot || hasActionsSlot || expandable"
flat
:dense="dense"
>
<v-toolbar-title class="text-subtitle-1">
<slot name="header"></slot>
</v-toolbar-title>
@@ -1,6 +1,10 @@
<template>
<v-hover v-slot="{ hover }">
<v-card class="rounded-lg" :elevation="hover ? 10 : 1" style="transition: all 0.2s ease-in-out">
<v-card
class="rounded-lg"
:elevation="hover ? 10 : 1"
style="transition: all 0.2s ease-in-out"
>
<router-link :to="`/streams/${stream.id}`">
<preview-image
:url="`/preview/${stream.id}`"
@@ -35,8 +39,18 @@
</div>
</v-card-text>
<div style="position: absolute; top: 10px; left: 12px">
<v-chip :to="`/streams/${stream.id}/comments`" v-if="stream.commentCount !== 0" small class="caption primary" dark v-tooltip="`${stream.commentCount} comment${stream.commentCount === 1 ? '' : 's'}`">
<v-icon x-small class="mr-1">mdi-comment-outline</v-icon> {{ stream.commentCount }}
<v-chip
:to="`/streams/${stream.id}/comments`"
v-if="stream.commentCount !== 0"
small
class="caption primary"
dark
v-tooltip="
`${stream.commentCount} comment${stream.commentCount === 1 ? '' : 's'}`
"
>
<v-icon x-small class="mr-1">mdi-comment-outline</v-icon>
{{ stream.commentCount }}
</v-chip>
</div>
<v-divider />
@@ -54,7 +68,9 @@
>
<v-icon
small
:class="`mr-1 ${stream.role.split(':')[1] === 'owner' ? 'primary--text' : ''}`"
:class="`mr-1 ${
stream.role.split(':')[1] === 'owner' ? 'primary--text' : ''
}`"
>
mdi-account-key-outline
</v-icon>
@@ -69,7 +85,8 @@ export default {
components: {
PreviewImage: () => import('@/main/components/common/PreviewImage.vue'),
CollaboratorsDisplay: () => import('@/main/components/stream/CollaboratorsDisplay'),
StreamFavoriteBtn: () => import('@/main/components/stream/favorites/StreamFavoriteBtn.vue')
StreamFavoriteBtn: () =>
import('@/main/components/stream/favorites/StreamFavoriteBtn.vue')
},
props: {
stream: { type: Object, default: () => null },
@@ -46,7 +46,9 @@
</div>
</div>
<div class="mt-2">
<v-btn x-small block :to="isSelf ? '/profile' : '/profile/' + id">View profile</v-btn>
<v-btn x-small block :to="isSelf ? '/profile' : '/profile/' + id">
View profile
</v-btn>
</div>
</v-card-text>
</v-card>
@@ -1,6 +1,10 @@
<template>
<v-navigation-drawer app right fixed class="transparent overflow-auto" floating>
<v-card rounded="lg" style="overflow: hidden" class="transparent elevation-0 pl-1 pr-3 pb-4">
<v-card
rounded="lg"
style="overflow: hidden"
class="transparent elevation-0 pl-1 pr-3 pb-4"
>
<v-toolbar class="mt-3" rounded="lg" dense>
<v-toolbar-title class="body-2 font-weight-bold">
<a
@@ -12,7 +16,10 @@
</a>
</v-toolbar-title>
<v-spacer />
<v-app-bar-nav-icon href="https://github.com/specklesystems/speckle-server" target="_blank">
<v-app-bar-nav-icon
href="https://github.com/specklesystems/speckle-server"
target="_blank"
>
<v-icon small class="yellow--text">mdi-star</v-icon>
</v-app-bar-nav-icon>
</v-toolbar>
@@ -56,7 +63,11 @@
</div>
<v-toolbar class="my-4" rounded="lg" dense flat>
<v-toolbar-title class="body-2">
<a href="https://speckle.systems/tutorials" target="_blank" class="text-decoration-none">
<a
href="https://speckle.systems/tutorials"
target="_blank"
class="text-decoration-none"
>
More Tutorials
</a>
</v-toolbar-title>
@@ -68,12 +79,20 @@
<v-card-text class="caption">
<p class="mb-0">
At
<a href="https://speckle.systems" target="_blank" class="text-decoration-none">
<a
href="https://speckle.systems"
target="_blank"
class="text-decoration-none"
>
Speckle
</a>
we're working tirelessly to bring you the best open source data platform for AEC. Tell
us what you think on our
<a href="https://speckle.community" target="_blank" class="text-decoration-none">
we're working tirelessly to bring you the best open source data platform for
AEC. Tell us what you think on our
<a
href="https://speckle.community"
target="_blank"
class="text-decoration-none"
>
forum
</a>
, and don't forget to give us a on
@@ -36,11 +36,16 @@
class="mr-3"
:user-id="activityItem.info.targetUser"
:color="
lastActivity.actionType === 'stream_permissions_add' ? 'success' : 'error'
lastActivity.actionType === 'stream_permissions_add'
? 'success'
: 'error'
"
></user-pill>
<span v-if="$vuetify.breakpoint.smAndUp" class="mr-3 body-2 font-italic">
<span
v-if="$vuetify.breakpoint.smAndUp"
class="mr-3 body-2 font-italic"
>
{{
lastActivity.actionType === 'stream_permissions_add'
? 'user added as'
@@ -85,7 +90,9 @@
<v-icon color="primary" small>mdi-folder</v-icon>
{{ stream.name }}
</router-link>
<span class="ml-3 body-2 font-italic">{{ lastActivityBrief.actionText }}</span>
<span class="ml-3 body-2 font-italic">
{{ lastActivityBrief.actionText }}
</span>
<v-spacer />
@@ -168,10 +175,14 @@
>
<v-card-text class="pa-5 body-1">
<v-chip :to="url" :color="lastActivityBrief.color">
<v-icon small class="mr-2 float-left" light>{{ lastActivityBrief.icon }}</v-icon>
<v-icon small class="mr-2 float-left" light>
{{ lastActivityBrief.icon }}
</v-icon>
{{ branchName }}
</v-chip>
<span class="ml-3 body-2 font-italic">{{ lastActivityBrief.actionText }}</span>
<span class="ml-3 body-2 font-italic">
{{ lastActivityBrief.actionText }}
</span>
<div class="mt-3">
<div
v-for="activityItem in activityGroup"
@@ -215,7 +226,9 @@
</v-chip>
<span v-if="lastActivity.actionType === 'commit_create'">
<span class="mx-3 body-2 font-italic">from</span>
<source-app-avatar :application-name="commit.sourceApplication" />
<source-app-avatar
:application-name="commit.sourceApplication"
/>
</span>
<span v-if="lastActivity.actionType === 'commit_receive'">
<span class="mx-3 body-2 font-italic">in</span>
@@ -299,7 +312,7 @@ export default {
},
user: {
query: gql`
query($id: String) {
query ($id: String) {
user(id: $id) {
name
avatar
@@ -316,7 +329,7 @@ export default {
stream: {
query: gql`
query($id: String!) {
query ($id: String!) {
stream(id: $id) {
id
name
@@ -339,7 +352,7 @@ export default {
},
branch: {
query: gql`
query($id: String!, $branchName: String!) {
query ($id: String!, $branchName: String!) {
stream(id: $id) {
id
branch(name: $branchName) {
@@ -361,7 +374,7 @@ export default {
},
commit: {
query: gql`
query($id: String!, $commitId: String!) {
query ($id: String!, $commitId: String!) {
stream(id: $id) {
id
commit(id: $commitId) {
@@ -438,13 +451,17 @@ export default {
case 'stream_permissions_add':
return {
captionText: `added ${
this.activityGroup.length === 1 ? 'a user' : this.activityGroup.length + ' users'
this.activityGroup.length === 1
? 'a user'
: this.activityGroup.length + ' users'
} to`
}
case 'stream_permissions_remove':
return {
captionText: `removed ${
this.activityGroup.length === 1 ? 'a user' : this.activityGroup.length + ' users'
this.activityGroup.length === 1
? 'a user'
: this.activityGroup.length + ' users'
} from`
}
case 'branch_create':
@@ -36,11 +36,16 @@
class="mr-3"
:user-id="activityItem.info.targetUser"
:color="
lastActivity.actionType === 'stream_permissions_add' ? 'success' : 'error'
lastActivity.actionType === 'stream_permissions_add'
? 'success'
: 'error'
"
></user-pill>
<span v-if="$vuetify.breakpoint.smAndUp" class="mr-3 body-2 font-italic">
<span
v-if="$vuetify.breakpoint.smAndUp"
class="mr-3 body-2 font-italic"
>
{{
lastActivity.actionType === 'stream_permissions_add'
? 'user added as'
@@ -85,7 +90,9 @@
<v-icon color="primary" small>mdi-folder</v-icon>
{{ stream.name }}
</router-link>
<span class="ml-3 caption font-italic">{{ lastActivityBrief.actionText }}</span>
<span class="ml-3 caption font-italic">
{{ lastActivityBrief.actionText }}
</span>
<v-spacer />
@@ -168,10 +175,14 @@
>
<v-card-text class="xxxpa-5 body-1">
<v-chip :to="url" :color="lastActivityBrief.color">
<v-icon small class="mr-2 float-left" light>{{ lastActivityBrief.icon }}</v-icon>
<v-icon small class="mr-2 float-left" light>
{{ lastActivityBrief.icon }}
</v-icon>
{{ branchName }}
</v-chip>
<span class="ml-3 body-2 font-italic">{{ lastActivityBrief.actionText }}</span>
<span class="ml-3 body-2 font-italic">
{{ lastActivityBrief.actionText }}
</span>
<div class="mt-3">
<div
v-for="activityItem in activityGroup"
@@ -215,7 +226,9 @@
</v-chip>
<span v-if="lastActivity.actionType === 'commit_create'">
<span class="mx-3 body-2 font-italic">from</span>
<source-app-avatar :application-name="commit.sourceApplication" />
<source-app-avatar
:application-name="commit.sourceApplication"
/>
</span>
<span v-if="lastActivity.actionType === 'commit_receive'">
<span class="mx-3 body-2 font-italic">in</span>
@@ -299,7 +312,7 @@ export default {
},
user: {
query: gql`
query($id: String) {
query ($id: String) {
user(id: $id) {
name
avatar
@@ -316,7 +329,7 @@ export default {
stream: {
query: gql`
query($id: String!) {
query ($id: String!) {
stream(id: $id) {
id
name
@@ -339,7 +352,7 @@ export default {
},
branch: {
query: gql`
query($id: String!, $branchName: String!) {
query ($id: String!, $branchName: String!) {
stream(id: $id) {
id
branch(name: $branchName) {
@@ -361,7 +374,7 @@ export default {
},
commit: {
query: gql`
query($id: String!, $commitId: String!) {
query ($id: String!, $commitId: String!) {
stream(id: $id) {
id
commit(id: $commitId) {
@@ -438,13 +451,17 @@ export default {
case 'stream_permissions_add':
return {
captionText: `added ${
this.activityGroup.length === 1 ? 'a user' : this.activityGroup.length + ' users'
this.activityGroup.length === 1
? 'a user'
: this.activityGroup.length + ' users'
} to`
}
case 'stream_permissions_remove':
return {
captionText: `removed ${
this.activityGroup.length === 1 ? 'a user' : this.activityGroup.length + ' users'
this.activityGroup.length === 1
? 'a user'
: this.activityGroup.length + ' users'
} from`
}
case 'branch_create':
@@ -40,7 +40,8 @@
<no-data-placeholder v-if="quickUser">
<h2>Welcome {{ quickUser.name.split(' ')[0] }}!</h2>
<p class="caption">
Once you create a stream and start sending some data, your activity will show up here.
Once you create a stream and start sending some data, your activity will show
up here.
</p>
<template #actions>
@@ -100,7 +101,7 @@ export default {
},
timeline: {
query: gql`
query($cursor: DateTime) {
query ($cursor: DateTime) {
user {
id
timeline(cursor: $cursor) {
@@ -173,7 +174,11 @@ export default {
if (curr.actionType === test.actionType && curr.streamId === test.streamId) {
if (curr.actionType.includes('stream_permissions')) {
//skip multiple stream_permission actions on the same user, just pick the last!
if (prev[prev.length - 1].some((x) => x.info.targetUser === curr.info.targetUser))
if (
prev[prev.length - 1].some(
(x) => x.info.targetUser === curr.info.targetUser
)
)
action = 'skip'
else action = 'combine'
} //stream, branch, commit
@@ -208,7 +213,10 @@ export default {
if (newItems.length === 0) $state.complete()
else $state.loaded()
fetchMoreResult.user.timeline.items = [...previousResult.user.timeline.items, ...newItems]
fetchMoreResult.user.timeline.items = [
...previousResult.user.timeline.items,
...newItems
]
return fetchMoreResult
}
@@ -14,8 +14,16 @@
<v-window v-model="onboarding" class="pb-3">
<v-window-item>
<v-card class="transparent elevation-0 text-center" color="transparent" align="start">
<v-img class="align-start mb-3" src="@/assets/onboarding-1.png" :aspect-ratio="16 / 9">
<v-card
class="transparent elevation-0 text-center"
color="transparent"
align="start"
>
<v-img
class="align-start mb-3"
src="@/assets/onboarding-1.png"
:aspect-ratio="16 / 9"
>
<template #sources>
<source srcset="@/assets/onboarding-1.webp" />
</template>
@@ -32,8 +40,8 @@
<b>plugins</b>
- our speckle
<b>connectors</b>
- to help you extract your 3D objects and all their properties from your
desktop application.
- to help you extract your 3D objects and all their properties
from your desktop application.
</p>
</v-col>
</v-row>
@@ -43,8 +51,16 @@
</v-window-item>
<v-window-item>
<v-card class="transparent elevation-0 text-center" color="transparent" align="start">
<v-img class="align-start mb-3" src="@/assets/onboarding-2.png" :aspect-ratio="16 / 9">
<v-card
class="transparent elevation-0 text-center"
color="transparent"
align="start"
>
<v-img
class="align-start mb-3"
src="@/assets/onboarding-2.png"
:aspect-ratio="16 / 9"
>
<template #sources>
<source srcset="@/assets/onboarding-2.webp" />
</template>
@@ -57,10 +73,11 @@
<v-row>
<v-col cols="6" class="mt-5 mb-12">
<p>
Every time you send your 3D objects out of your application, they are captured
as a
Every time you send your 3D objects out of your application, they
are captured as a
<b>"commit"</b>
that includes a description, the sender, timestamp, and source application.
that includes a description, the sender, timestamp, and source
application.
</p>
</v-col>
</v-row>
@@ -80,8 +97,16 @@
</v-window-item>
<v-window-item>
<v-card class="transparent elevation-0 text-center" color="transparent" align="start">
<v-img class="align-start mb-3" src="@/assets/onboarding-3.png" :aspect-ratio="16 / 9">
<v-card
class="transparent elevation-0 text-center"
color="transparent"
align="start"
>
<v-img
class="align-start mb-3"
src="@/assets/onboarding-3.png"
:aspect-ratio="16 / 9"
>
<template #sources>
<source srcset="@/assets/onboarding-3.webp" />
</template>
@@ -111,7 +136,8 @@
<p>
Create
<b>additional</b>
branches in your stream if you want to store parallel collections of data.
branches in your stream if you want to store parallel collections
of data.
</p>
<p>Use branches to keep your stream organization tidy!</p>
</v-col>
@@ -122,8 +148,16 @@
</v-window-item>
<v-window-item>
<v-card class="transparent elevation-0 text-center" color="transparent" align="start">
<v-img class="align-start mb-3" src="@/assets/onboarding-4.png" :aspect-ratio="16 / 9">
<v-card
class="transparent elevation-0 text-center"
color="transparent"
align="start"
>
<v-img
class="align-start mb-3"
src="@/assets/onboarding-4.png"
:aspect-ratio="16 / 9"
>
<template #sources>
<source srcset="@/assets/onboarding-4.webp" />
</template>
@@ -143,8 +177,8 @@
<p>
Speckle Web gives you access to all your information and
<b>activity,</b>
with a data viewer that lets you filter and customize your objects by their
properties.
with a data viewer that lets you filter and customize your objects
by their properties.
</p>
</v-col>
</v-row>
@@ -154,8 +188,16 @@
</v-window-item>
<v-window-item>
<v-card class="transparent elevation-0 text-center" color="transparent" align="start">
<v-img class="align-start mb-3" src="@/assets/onboarding-5.png" :aspect-ratio="16 / 9">
<v-card
class="transparent elevation-0 text-center"
color="transparent"
align="start"
>
<v-img
class="align-start mb-3"
src="@/assets/onboarding-5.png"
:aspect-ratio="16 / 9"
>
<template #sources>
<source srcset="@/assets/onboarding-5.webp" />
</template>
@@ -168,8 +210,8 @@
<v-row>
<v-col cols="5" class="mt-5 mb-12">
<p>
Get your original 3D data back into your application with our connectors -
just select the commits you want to receive!
Get your original 3D data back into your application with our
connectors - just select the commits you want to receive!
</p>
</v-col>
</v-row>
@@ -186,7 +228,9 @@
<v-window-item>
<v-card class="transparent elevation-0 text-center" color="transparent">
<v-card-title class="display-1 justify-center my-5">🏃 Get Started!</v-card-title>
<v-card-title class="display-1 justify-center my-5">
🏃 Get Started!
</v-card-title>
<v-card-subtitle class="subtitle-1 justify-center mb-5">
Time to make the most of
<b>your</b>
@@ -194,10 +238,12 @@
</v-card-subtitle>
<v-card-text class="body-1 text--primary">
<p>
You can now start creating your own workflows for automation, interoperaility or
collaboration using Speckle!
You can now start creating your own workflows for automation,
interoperaility or collaboration using Speckle!
</p>
<p>
We have put together a series of tutorials that you might find useful:
</p>
<p>We have put together a series of tutorials that you might find useful:</p>
</v-card-text>
<v-container fluid>
<v-row dense>
@@ -228,7 +274,7 @@
</v-window-item>
</v-window>
<v-card-actions class="justify-space-between pb-7">
<v-btn text @click="prev" :disabled="onboarding == 0" rounded class="pr-4">
<v-btn text :disabled="onboarding == 0" rounded class="pr-4" @click="prev">
<v-icon>mdi-chevron-left</v-icon>
BACK
</v-btn>
@@ -239,7 +285,7 @@
</v-btn>
</v-item>
</v-item-group>
<v-btn color="primary" @click="next" rounded elevation="10" class="pl-4">
<v-btn color="primary" rounded elevation="10" class="pl-4" @click="next">
{{ onboarding == length - 1 ? 'FINISH' : 'NEXT' }}
<v-icon>mdi-chevron-right</v-icon>
</v-btn>
@@ -1,7 +1,11 @@
<template>
<v-row>
<v-col cols="12">
<v-timeline v-if="stream && groupedActivity && groupedActivity.length !== 0" align-top dense>
<v-timeline
v-if="stream && groupedActivity && groupedActivity.length !== 0"
align-top
dense
>
<list-item-activity
v-for="activity in groupedActivity"
:key="activity.time"
@@ -10,7 +14,9 @@
class="my-1"
></list-item-activity>
<infinite-loading
v-if="stream.activity && stream.activity.items.length < stream.activity.totalCount"
v-if="
stream.activity && stream.activity.items.length < stream.activity.totalCount
"
@infinite="infiniteHandler"
>
<div slot="no-more">This is all your activity!</div>
@@ -96,11 +102,18 @@ export default {
if (curr.actionType === test.actionType && curr.streamId === test.streamId) {
if (curr.actionType.includes('stream_permissions')) {
//skip multiple stream_permission actions on the same user, just pick the last!
if (prev[prev.length - 1].some((x) => x.info.targetUser === curr.info.targetUser))
if (
prev[prev.length - 1].some(
(x) => x.info.targetUser === curr.info.targetUser
)
)
action = 'skip'
else action = 'combine'
} //stream, branch, commit
else if (curr.actionType.includes('_update') || curr.actionType === 'commit_create')
else if (
curr.actionType.includes('_update') ||
curr.actionType === 'commit_create'
)
action = 'combine'
}
if (action === 'combine') {
@@ -33,7 +33,10 @@
</v-btn>
</span>
<span v-else-if="!linkToCollabs && collaborators.length > 5">
<span v-tooltip="`${collaborators.length - 4} more collaborators`" class="caption">
<span
v-tooltip="`${collaborators.length - 4} more collaborators`"
class="caption"
>
+{{ collaborators.length - 4 }}
</span>
</span>
@@ -42,7 +42,10 @@
{{ commit.branchName }}
</v-chip>
</span>
<source-app-avatar v-if="showSourceApp" :application-name="commit.sourceApplication" />
<source-app-avatar
v-if="showSourceApp"
:application-name="commit.sourceApplication"
/>
</div>
</div>
</div>
@@ -54,7 +57,8 @@ export default {
components: {
UserAvatar: () => import('@/main/components/common/UserAvatar'),
SourceAppAvatar: () => import('@/main/components/common/SourceAppAvatar'),
CommitReceivedReceipts: () => import('@/main/components/common/CommitReceivedReceipts')
CommitReceivedReceipts: () =>
import('@/main/components/common/CommitReceivedReceipts')
},
props: {
commit: {
@@ -150,7 +154,8 @@ export default {
}/branches/${encodeURIComponent(this.commit.branchName)}`
},
receivedUsersUnique() {
if (!(this.activity && this.activity.items && this.activity.items.length > 0)) return []
if (!(this.activity && this.activity.items && this.activity.items.length > 0))
return []
let set = new Set()
this.activity.items.forEach((item) => set.add(item.userId))
return Array.from(set)
@@ -14,7 +14,9 @@
<!-- <v-btn x-small color="primary">change</v-btn> -->
<v-menu offset-y>
<template #activator="{ on, attrs }">
<v-btn x-small color="" dark v-bind="attrs" :disabled="disabled" v-on="on">Change</v-btn>
<v-btn x-small color="" dark v-bind="attrs" :disabled="disabled" v-on="on">
Change
</v-btn>
</template>
<v-list dense>
<v-list-item
@@ -25,7 +27,9 @@
$emit('update-user-role', userSelf)
"
>
<v-list-item-action><v-icon small>mdi-chevron-right</v-icon></v-list-item-action>
<v-list-item-action>
<v-icon small>mdi-chevron-right</v-icon>
</v-list-item-action>
<v-list-item-title>{{ item.name }}</v-list-item-title>
</v-list-item>
<v-list-item @click="$emit('remove-user', userSelf)">
@@ -1,7 +1,9 @@
<template>
<no-data-placeholder>
<h2>Nothing favorited yet!</h2>
<p class="caption">Once you mark any streams as favorite, they're going to appear here.</p>
<p class="caption">
Once you mark any streams as favorite, they're going to appear here.
</p>
<template #actions>
<v-list rounded class="transparent">
<v-list-item link class="primary mb-4" to="/streams">
@@ -8,10 +8,22 @@
</template>
<template slot="actions">
<v-spacer />
<v-btn v-tooltip="'Clear all globals'" color="error" icon class="mr-2" @click="clearGlobals">
<v-btn
v-tooltip="'Clear all globals'"
color="error"
icon
class="mr-2"
@click="clearGlobals"
>
<v-icon>mdi-close</v-icon>
</v-btn>
<v-btn v-tooltip="'Undo any changes'" color="primary" icon class="mr-2" @click="resetGlobals">
<v-btn
v-tooltip="'Undo any changes'"
color="primary"
icon
class="mr-2"
@click="resetGlobals"
>
<v-icon>mdi-undo</v-icon>
</v-btn>
<v-btn
@@ -49,7 +61,12 @@
<v-progress-linear indeterminate></v-progress-linear>
</template>
<v-card-title>Save Globals</v-card-title>
<v-form ref="form" v-model="saveValid" lazy-validation @submit.prevent="saveGlobals">
<v-form
ref="form"
v-model="saveValid"
lazy-validation
@submit.prevent="saveGlobals"
>
<v-card-text>
<v-text-field
v-model="saveMessage"
@@ -66,7 +83,9 @@
</v-alert>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="primary" text :disabled="!saveValid" type="submit">Save</v-btn>
<v-btn color="primary" text :disabled="!saveValid" type="submit">
Save
</v-btn>
</v-card-actions>
</v-form>
</v-card>
@@ -146,7 +165,9 @@ export default {
},
saveValid: false,
saveLoading: false,
nameRules: [(v) => (v && v.length >= 3) || 'Message must be at least 3 characters'],
nameRules: [
(v) => (v && v.length >= 3) || 'Message must be at least 3 characters'
],
saveMessage: null,
saveError: null
}
@@ -298,7 +319,9 @@ export default {
: this.nestedGlobals(this.sample)
},
clearGlobals() {
this.globalsArray = this.nestedGlobals({ placeholder: 'something cool goes here...' })
this.globalsArray = this.nestedGlobals({
placeholder: 'something cool goes here...'
})
},
addProp(kwargs) {
let globals = this.getNestedGlobals(kwargs.path)
@@ -24,7 +24,11 @@
dense
rounded
/>
<v-text-field v-model="entry.value" class="entry-value mr-5" hint="property value" />
<v-text-field
v-model="entry.value"
class="entry-value mr-5"
hint="property value"
/>
<v-btn
v-if="true"
v-tooltip="'Transform this field into a nested object'"
@@ -54,10 +58,16 @@
v-if="!editTitle"
@mouseenter="mouseOver = true"
@mouseleave="mouseOver = false"
@click="editTitle=true"
@click="editTitle = true"
>
{{ entry.key }}
<v-btn v-show="mouseOver" icon small color="primary" @click="editTitle = true">
<v-btn
v-show="mouseOver"
icon
small
color="primary"
@click="editTitle = true"
>
<v-icon small>mdi-pencil</v-icon>
</v-btn>
</v-toolbar-title>
@@ -68,9 +78,9 @@
:rules="rules.keys(index, entries)"
:error-messages="entry.valid === true ? null : entry.valid"
append-icon="mdi-check"
@click:append="editTitle=false"
@keyup.enter="editTitle=false"
style="width: 300px; margin-top:14px;"
@click:append="editTitle = false"
@keyup.enter="editTitle = false"
style="width: 300px; margin-top: 14px"
></v-text-field>
</v-toolbar-title>
<v-spacer></v-spacer>
@@ -175,13 +185,15 @@ export default {
(v) => {
let filtered = entries.filter((_, i) => i != index)
let result =
filtered.findIndex((e) => e.key === v) === -1 || 'Each property name must be unique'
filtered.findIndex((e) => e.key === v) === -1 ||
'Each property name must be unique'
if (entries[index].valid === true) entries[index].valid = result
return result
},
(v) => {
const re = /[./]/
let result = !re.test(v) || 'The name cannot contain invalid characters: "." or "/"'
let result =
!re.test(v) || 'The name cannot contain invalid characters: "." or "/"'
if (entries[index].valid === true) entries[index].valid = result
return result
}
@@ -1,7 +1,12 @@
<template>
<v-card class="my-4 pa-1 elevation-0" :loading="$apollo.loading">
<div v-if="!$apollo.loading && file" class="d-flex align-center">
<v-btn v-tooltip="`Download the original file`" icon small @click="downloadOriginalFile()">
<v-btn
v-tooltip="`Download the original file`"
icon
small
@click="downloadOriginalFile()"
>
<v-icon small>mdi-download</v-icon>
</v-btn>
@@ -12,13 +17,21 @@
<template v-if="file.convertedStatus === 0">
<v-btn text small disabled>
<span class="mr-2">Queued</span>
<v-progress-circular indeterminate :size="20" :width="2"></v-progress-circular>
<v-progress-circular
indeterminate
:size="20"
:width="2"
></v-progress-circular>
</v-btn>
</template>
<template v-if="file.convertedStatus === 1">
<v-btn text small>
<span class="mr-2">Converting</span>
<v-progress-circular indeterminate :size="20" :width="2"></v-progress-circular>
<v-progress-circular
indeterminate
:size="20"
:width="2"
></v-progress-circular>
</v-btn>
</template>
<template v-if="file.convertedStatus === 2">
@@ -11,7 +11,12 @@
<v-spacer />
<v-menu offset-y>
<template #activator="{ attrs, on }">
<v-btn v-tooltip="`Change the branch to upload to`" text v-bind="attrs" v-on="on">
<v-btn
v-tooltip="`Change the branch to upload to`"
text
v-bind="attrs"
v-on="on"
>
<v-icon small>mdi-source-branch</v-icon>
<span class="caption">{{ selectedBranch }}</span>
</v-btn>
@@ -55,7 +60,10 @@ export default {
this.selectedBranch ? this.selectedBranch : 'main'
}`
)
request.setRequestHeader('Authorization', `Bearer ${localStorage.getItem('AuthToken')}`)
request.setRequestHeader(
'Authorization',
`Bearer ${localStorage.getItem('AuthToken')}`
)
request.upload.addEventListener(
'progress',
@@ -1,7 +1,12 @@
<template>
<v-card>
<v-card-title class="primary white--text">Edit App</v-card-title>
<v-form v-show="!appUpdateResult" ref="form" v-model="valid" @submit.prevent="editApp">
<v-form
v-show="!appUpdateResult"
ref="form"
v-model="valid"
@submit.prevent="editApp"
>
<v-card-text>
<v-text-field
v-model="name"
@@ -53,8 +58,8 @@
></v-textarea>
<v-alert type="info" class="mt-5">
<b>Note:</b>
After editing an app, all users will need to authorise it again (existing tokens will be
invalidated).
After editing an app, all users will need to authorise it again (existing
tokens will be invalidated).
</v-alert>
<v-card-actions>
<v-spacer></v-spacer>
@@ -69,7 +74,9 @@
<p>
<b>Note:</b>
To authenticate users inside your app, direct them to
<code style="word-break: break-all">{{ rootUrl }}/authn/verify/{appId}/{challenge}</code>
<code style="word-break: break-all">
{{ rootUrl }}/authn/verify/{appId}/{challenge}
</code>
, where
<code>challenge</code>
is an OAuth2 plain code challenge.
@@ -2,7 +2,12 @@
<v-card>
<v-card-title class="primary white--text">Create a New App</v-card-title>
<v-form v-show="!appCreateResult" ref="form" v-model="valid" @submit.prevent="createApp">
<v-form
v-show="!appCreateResult"
ref="form"
v-model="valid"
@submit.prevent="createApp"
>
<v-card-text>
<v-text-field
v-model="name"
@@ -65,7 +70,9 @@
<p>
<b>Note:</b>
To authenticate users inside your app, direct them to
<code style="word-break: break-all">{{ rootUrl }}/authn/verify/{appId}/{challenge}</code>
<code style="word-break: break-all">
{{ rootUrl }}/authn/verify/{appId}/{challenge}
</code>
, where
<code>challenge</code>
is an OAuth2 plain code challenge.
@@ -30,7 +30,15 @@
>
Verification email sent, please check you inbox.
</v-alert>
<v-alert v-if="errors" type="error" height="44" dismissible rounded="lg" elevation="8" dense>
<v-alert
v-if="errors"
type="error"
height="44"
dismissible
rounded="lg"
elevation="8"
dense
>
Email verification failed.{{ errorMessage ? ` Reason: ${errorMessage}` : '' }}
</v-alert>
</transition>
@@ -81,7 +81,7 @@ export default {
apollo: {
appScopes: {
query: gql`
query($id: String!) {
query ($id: String!) {
app(id: $id) {
id
name
@@ -1,7 +1,12 @@
<template>
<v-card class="pa-4">
<v-card-title>Create a New Personal Access Token</v-card-title>
<v-form v-show="!fullTokenResult" ref="form" v-model="valid" @submit.prevent="createToken">
<v-form
v-show="!fullTokenResult"
ref="form"
v-model="valid"
@submit.prevent="createToken"
>
<v-card-text>
<v-text-field
v-model="name"
@@ -44,8 +49,8 @@
</div>
<v-alert type="info">
<b>Note:</b>
This is the first and last time you will be able to see the full token. Please copy paste it
somewhere safe now.
This is the first and last time you will be able to see the full token. Please
copy paste it somewhere safe now.
</v-alert>
<v-btn block color="primary" @click="clearAndClose">Close</v-btn>
</v-card-text>
@@ -7,11 +7,12 @@
</template>
<v-card rounded="lg">
<v-card-text>
Personal Access Tokens can be used to access the Speckle API on this server; they function
like ordinary OAuth access tokens. Use them in your scripts or apps!
Personal Access Tokens can be used to access the Speckle API on this server;
they function like ordinary OAuth access tokens. Use them in your scripts or
apps!
<b>
Treat them like a password: do not post them anywhere where they could be accessed by
others (e.g., public repos).
Treat them like a password: do not post them anywhere where they could be
accessed by others (e.g., public repos).
</b>
</v-card-text>
<v-card-text v-if="$apollo.loading">Loading...</v-card-text>
@@ -6,8 +6,8 @@
<v-btn small color="primary" @click="appDialog = true">new app</v-btn>
</template>
<v-card-text>
Register and manage third-party Speckle Apps that, once authorised by a user on this server,
can act on their behalf.
Register and manage third-party Speckle Apps that, once authorised by a user on
this server, can act on their behalf.
</v-card-text>
<v-card-text v-if="$apollo.loading">Loading...</v-card-text>
<v-card-text v-if="apps && apps.length !== 0">
@@ -1,8 +1,8 @@
<template>
<v-card class="elevation-0 mt-3 mb-5 transparent">
<v-card-text class="">
Here you can review the apps that you have granted access to. If something looks suspcious,
revoke the access!
Here you can review the apps that you have granted access to. If something looks
suspcious, revoke the access!
<v-btn
v-if="!hasManager"
plain
@@ -52,12 +52,14 @@
<v-card>
<v-card-title>Revoke Access</v-card-title>
<v-card-text>
Revoking access to your app will log you out of it on all devices. Are you sure you want
to proceed?
Revoking access to your app will log you out of it on all devices. Are you
sure you want to proceed?
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn plain small color="error" @click="revokeAccess()">Revoke access</v-btn>
<v-btn plain small color="error" @click="revokeAccess()">
Revoke access
</v-btn>
<v-btn plain small>Cancel</v-btn>
</v-card-actions>
</v-card>
@@ -93,7 +95,8 @@ export default {
}
}
`,
update: (data) => data.user.authorizedApps.filter((app) => app.id !== 'spklwebapp')
update: (data) =>
data.user.authorizedApps.filter((app) => app.id !== 'spklwebapp')
}
},
computed: {
@@ -8,12 +8,14 @@
<v-toolbar flat class="error--text" dense>
<v-toolbar-title>Delete Account</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn icon @click="showDelete = !showDelete"><v-icon>mdi-chevron-down</v-icon></v-btn>
<v-btn icon @click="showDelete = !showDelete">
<v-icon>mdi-chevron-down</v-icon>
</v-btn>
</v-toolbar>
<div v-show="showDelete">
<v-card-text>
This action cannot be undone. We will delete all streams where you are the sole owner,
and any associated data.
This action cannot be undone. We will delete all streams where you are the
sole owner, and any associated data.
</v-card-text>
<v-card-actions>
<v-btn block @click="deleteUser">Delete account</v-btn>
@@ -9,10 +9,20 @@
&nbsp;
<b>{{ user.name }}</b>
<span v-if="isSelf">!</span>
<v-icon v-if="user.verified" v-tooltip="'Verfied email'" small class="ml-3 primary--text">
<v-icon
v-if="user.verified"
v-tooltip="'Verfied email'"
small
class="ml-3 primary--text"
>
mdi-shield-check
</v-icon>
<v-icon v-else v-tooltip="'Email not verified'" small class="mr-2 warning--text">
<v-icon
v-else
v-tooltip="'Email not verified'"
small
class="mr-2 warning--text"
>
mdi-shield-alert
</v-icon>
</template>
@@ -46,7 +56,9 @@
<v-icon color="red darken-3">mdi-heart</v-icon>
</span>
</template>
<span>Total amount of favorites for all streams owned by this user</span>
<span>
Total amount of favorites for all streams owned by this user
</span>
</v-tooltip>
</p>
<span v-if="isSelf" class="caption">
@@ -29,7 +29,9 @@
<v-icon
class="primary--text"
large
:style="`opacity: ${user.hidden ? '0.2' : 1}; position: relative; right: -60%; font-size: 4.2em`"
:style="`opacity: ${
user.hidden ? '0.2' : 1
}; position: relative; right: -60%; font-size: 4.2em`"
>
mdi-menu-right
</v-icon>
@@ -38,10 +40,14 @@
v-for="sessionUser in users"
:ref="`user-bubble-${sessionUser.uuid}`"
:key="sessionUser.uuid"
:class="`${sessionUser.name === 'Anonymous Viewer' ? 'background' : '' } absolute-pos rounded-pill user-bubble elevation-5`"
:style="`opacity: ${sessionUser.hidden ? '0.2' : 1}; border: 4px solid #047EFB;`"
:class="`${
sessionUser.name === 'Anonymous Viewer' ? 'background' : ''
} absolute-pos rounded-pill user-bubble elevation-5`"
:style="`opacity: ${
sessionUser.hidden ? '0.2' : 1
}; border: 4px solid #047EFB;`"
>
<div @click="setUserPow(sessionUser)" >
<div @click="setUserPow(sessionUser)">
<user-avatar
v-if="sessionUser.name !== 'Anonymous Viewer'"
:id="sessionUser.id"
@@ -50,7 +56,13 @@
:size="30"
:margin="false"
></user-avatar>
<v-avatar color="background" :size="30" v-else v-tooltip="sessionUser.name" style="cursor: pointer;">
<v-avatar
color="background"
:size="30"
v-else
v-tooltip="sessionUser.name"
style="cursor: pointer"
>
👀
</v-avatar>
<text-dots-typing v-if="sessionUser.status === 'writing'" />
@@ -101,7 +113,7 @@ export default {
$subscribe: {
userViewerActivity: {
query: gql`
subscription($streamId: String!, $resourceId: String!) {
subscription ($streamId: String!, $resourceId: String!) {
userViewerActivity(streamId: $streamId, resourceId: $resourceId)
}
`,
@@ -114,18 +126,25 @@ export default {
skip() {
return !this.$route.params.resourceId || !this.$loggedIn()
},
result( res ) {
result(res) {
let data = res.data
// Note: swap user id checks for .userId (vs. uuid) if wanting to not allow same user two diff browsers
// it's easier to test like this though :)
if(!data.userViewerActivity) return
if (data.userViewerActivity.status && data.userViewerActivity.status === 'disconnect') {
this.users = this.users.filter((u) => u.uuid !== data.userViewerActivity.uuid)
if (!data.userViewerActivity) return
if (
data.userViewerActivity.status &&
data.userViewerActivity.status === 'disconnect'
) {
this.users = this.users.filter(
(u) => u.uuid !== data.userViewerActivity.uuid
)
this.updateBubbles(true)
return
}
if (data.userViewerActivity.uuid === this.uuid) return
let indx = this.users.findIndex((u) => u.uuid === data.userViewerActivity.uuid)
let indx = this.users.findIndex(
(u) => u.uuid === data.userViewerActivity.uuid
)
if (indx !== -1) {
let user = this.users[indx]
user.hidden = false
@@ -284,7 +303,11 @@ export default {
$resourceId: String!
$data: JSONObject
) {
userViewerActivityBroadcast(streamId: $streamId, resourceId: $resourceId, data: $data)
userViewerActivityBroadcast(
streamId: $streamId
resourceId: $resourceId
data: $data
)
}
`,
variables: {
@@ -303,7 +326,11 @@ export default {
$resourceId: String!
$data: JSONObject
) {
userViewerActivityBroadcast(streamId: $streamId, resourceId: $resourceId, data: $data)
userViewerActivityBroadcast(
streamId: $streamId
resourceId: $resourceId
data: $data
)
}
`,
variables: {
@@ -402,7 +429,10 @@ export default {
uTargetEl.style.transform = `translate(-50%, -50%) translate(${targetLoc.x}px,${targetLoc.y}px)`
uTargetEl.style.opacity = user.clipped ? '0' : '1'
const angle = Math.atan2(targetLoc.y - 16 - newTarget.y, targetLoc.x - 16 - newTarget.x)
const angle = Math.atan2(
targetLoc.y - 16 - newTarget.y,
targetLoc.x - 16 - newTarget.x
)
uArrowEl.style.transform = `translate(${newTarget.x}px,${newTarget.y}px) rotate(${angle}rad)`
uArrowEl.style.opacity = user.clipped ? '0' : '1'
}
@@ -15,7 +15,11 @@
</v-btn>
</template>
<v-list dense nav>
<v-list-item v-for="(item, index) in items" :key="index" @click="setView(item.title)">
<v-list-item
v-for="(item, index) in items"
:key="index"
@click="setView(item.title)"
>
<v-list-item-title>
<v-icon small>mdi-camera-control</v-icon>
{{ item.title }}
@@ -33,11 +33,15 @@
:class="`mouse elevation-5 ${!expand ? 'primary' : 'background'} mr-2`"
@click="toggleExpand()"
>
<v-icon v-if="!expand" dark >mdi-plus</v-icon>
<v-icon v-if="!expand" dark>mdi-plus</v-icon>
<v-icon v-else dark x-small>mdi-close</v-icon>
</v-btn>
<v-slide-x-transition>
<div v-if="expand && !$vuetify.breakpoint.xs" style="width: 100%" class="d-flex">
<div
v-if="expand && !$vuetify.breakpoint.xs"
style="width: 100%"
class="d-flex"
>
<v-textarea
v-if="$loggedIn()"
v-model="commentText"
@@ -160,7 +164,10 @@ export default {
},
mounted() {
window.__viewer.on('select', debounce(this.handleSelect, 10))
window.__viewer.cameraHandler.controls.addEventListener('update', this.updateCommentBubble)
window.__viewer.cameraHandler.controls.addEventListener(
'update',
this.updateCommentBubble
)
// this.$refs.commentTextArea.calculateInputHeight()
document.addEventListener(
'keyup',
@@ -258,8 +265,16 @@ export default {
if (!this.$refs.commentButton) return
this.visible = true
let projectedLocation = new THREE.Vector3(info.location.x, info.location.y, info.location.z)
this.location = new THREE.Vector3(info.location.x, info.location.y, info.location.z)
let projectedLocation = new THREE.Vector3(
info.location.x,
info.location.y,
info.location.z
)
this.location = new THREE.Vector3(
info.location.x,
info.location.y,
info.location.z
)
let cam = window.__viewer.cameraHandler.camera
cam.updateProjectionMatrix()
@@ -267,8 +282,10 @@ export default {
let collapsedSize = this.$refs.commentButton.clientWidth
collapsedSize = 36
const mappedLocation = new THREE.Vector3(
(projectedLocation.x * 0.5 + 0.5) * this.$refs.parent.clientWidth - collapsedSize / 2,
(projectedLocation.y * -0.5 + 0.5) * this.$refs.parent.clientHeight - collapsedSize / 1,
(projectedLocation.x * 0.5 + 0.5) * this.$refs.parent.clientWidth -
collapsedSize / 2,
(projectedLocation.y * -0.5 + 0.5) * this.$refs.parent.clientHeight -
collapsedSize / 1,
0
)
this.$refs.commentButton.style.transform = ''
@@ -287,8 +304,10 @@ export default {
let collapsedSize = this.$refs.commentButton.clientWidth
collapsedSize = 36
const mappedLocation = new THREE.Vector3(
(projectedLocation.x * 0.5 + 0.5) * this.$refs.parent.clientWidth - collapsedSize / 2,
(projectedLocation.y * -0.5 + 0.5) * this.$refs.parent.clientHeight - collapsedSize / 1,
(projectedLocation.x * 0.5 + 0.5) * this.$refs.parent.clientWidth -
collapsedSize / 2,
(projectedLocation.y * -0.5 + 0.5) * this.$refs.parent.clientHeight -
collapsedSize / 1,
0
)
this.$refs.commentButton.style.transform = ''
@@ -4,12 +4,24 @@
-->
<div
ref="parent"
style="width: 100%; height: 100vh; position: absolute; pointer-events: none; overflow: hidden"
style="
width: 100%;
height: 100vh;
position: absolute;
pointer-events: none;
overflow: hidden;
"
class="d-flex align-center justify-center no-mouse"
>
<div
v-show="showComments && !$store.state.addingComment"
style="width: 100%; height: 100vh; position: absolute; pointer-events: none; overflow: hidden"
style="
width: 100%;
height: 100vh;
position: absolute;
pointer-events: none;
overflow: hidden;
"
class="no-mouse"
>
<!-- Comment bubbles -->
@@ -18,8 +30,13 @@
:key="comment.id"
:ref="`comment-${comment.id}`"
:class="`absolute-pos rounded-xl no-mouse`"
:style="`transition: opacity 0.2s ease; z-index:${comment.expanded ? '20' : '10'}; ${
hasExpandedComment && !comment.expanded && !comment.hovered && !comment.bouncing
:style="`transition: opacity 0.2s ease; z-index:${
comment.expanded ? '20' : '10'
}; ${
hasExpandedComment &&
!comment.expanded &&
!comment.hovered &&
!comment.bouncing
? 'opacity: 0.1;'
: 'opacity: 1;'
}`"
@@ -38,7 +55,9 @@
? 'dark white--text primary'
: 'background'
}`"
@click="comment.expanded ? collapseComment(comment) : expandComment(comment)"
@click="
comment.expanded ? collapseComment(comment) : expandComment(comment)
"
>
<v-icon v-if="!comment.expanded" x-small class="">mdi-comment</v-icon>
<v-icon v-if="comment.expanded" x-small class="">mdi-close</v-icon>
@@ -49,7 +68,10 @@
style="position: absolute; left: 30px; width: max-content"
class="rounded-xl primary white--text px-2 ml-1 caption"
>
<timeago :datetime="comment.updatedAt" class="font-italic mr-2"></timeago>
<timeago
:datetime="comment.updatedAt"
class="font-italic mr-2"
></timeago>
<v-icon x-small class="white--text">mdi-comment-outline</v-icon>
{{ comment.replies.totalCount + 1 }}
<v-icon v-if="comment.data.filters" x-small class="white--text">
@@ -124,12 +146,13 @@ import gql from 'graphql-tag'
export default {
components: {
CommentThreadViewer: () => import('@/main/components/comments/CommentThreadViewer'),
CommentsViewerNavbar: () => import('@/main/components/comments/CommentsViewerNavbar')
CommentsViewerNavbar: () =>
import('@/main/components/comments/CommentsViewerNavbar')
},
apollo: {
comments: {
query: gql`
query($streamId: String!, $resources: [ResourceIdentifierInput]!) {
query ($streamId: String!, $resources: [ResourceIdentifierInput]!) {
comments(streamId: $streamId, resources: $resources, limit: 1000) {
totalCount
cursor
@@ -142,7 +165,7 @@ export default {
viewedAt
archived
data
resources{
resources {
resourceId
resourceType
}
@@ -176,33 +199,42 @@ export default {
}
},
result({ data }) {
if(!data) return
if (!data) return
for (let c of data.comments.items) {
c.expanded = false
c.hovered = false
c.bouncing = false
if (this.localComments.findIndex((lc) => c.id === lc.id) === -1 && !c.archived) {
if (
this.localComments.findIndex((lc) => c.id === lc.id) === -1 &&
!c.archived
) {
this.localComments.push({ ...c })
}
}
return data
},
subscribeToMore:{
subscribeToMore: {
document: gql`
subscription($streamId: String!, $resourceIds: [String]) {
subscription ($streamId: String!, $resourceIds: [String]) {
commentActivity(streamId: $streamId, resourceIds: $resourceIds)
}
`,
variables() {
let resIds = [this.$route.params.resourceId]
if(this.$route.query.overlay) resIds = [...resIds, ...this.$route.query.overlay.split(',')]
if (this.$route.query.overlay)
resIds = [...resIds, ...this.$route.query.overlay.split(',')]
return {
streamId: this.$route.params.streamId,
resourceIds: resIds
}
},
updateQuery(prevResult, {subscriptionData}) {
if(!subscriptionData || !subscriptionData.data || !subscriptionData.data.commentActivity) return
updateQuery(prevResult, { subscriptionData }) {
if (
!subscriptionData ||
!subscriptionData.data ||
!subscriptionData.data.commentActivity
)
return
let newComment = subscriptionData.data.commentActivity
newComment.expanded = false
@@ -214,12 +246,11 @@ export default {
newComment.archived = false
if(subscriptionData.data.commentActivity.eventType === 'comment-added') {
if(prevResult.comments.items.find( c => c.id === newComment.id)) {
if (subscriptionData.data.commentActivity.eventType === 'comment-added') {
if (prevResult.comments.items.find((c) => c.id === newComment.id)) {
return
}
if(!newComment.archived)
this.localComments.push(newComment)
if (!newComment.archived) this.localComments.push(newComment)
setTimeout(() => {
this.updateCommentBubbles()
@@ -239,7 +270,7 @@ export default {
},
computed: {
activeComments() {
return this.localComments.filter(c => !c.archived)
return this.localComments.filter((c) => !c.archived)
},
hasExpandedComment() {
return this.localComments.filter((c) => c.expanded).length !== 0
@@ -282,7 +313,7 @@ export default {
window.__viewer.cameraHandler.controls.addEventListener('update', () =>
this.updateCommentBubbles()
)
setTimeout(()=>{
setTimeout(() => {
this.updateCommentBubbles()
}, 1000)
},
@@ -350,7 +381,7 @@ export default {
},
async handleDeletion(comment) {
this.collapseComment(comment)
let comm = this.localComments.find(c => c.id === comment.id)
let comm = this.localComments.find((c) => c.id === comment.id)
comm.archived = true
this.updateCommentBubbles()
},
@@ -387,14 +418,16 @@ export default {
const paddingYBottom = 90
if (tX < -300)
if (!comment.preventAutoClose && !this.$vuetify.breakpoint.xs) comment.expanded = false // collapse if too far out leftwise
if (!comment.preventAutoClose && !this.$vuetify.breakpoint.xs)
comment.expanded = false // collapse if too far out leftwise
if (tX < paddingX) {
tX = paddingX
}
if (tX > this.$refs.parent.clientWidth - (paddingX + 50)) {
tX = this.$refs.parent.clientWidth - (paddingX + 50)
if (!comment.preventAutoClose && !this.$vuetify.breakpoint.xs) comment.expanded = false // collapse if too far down right
if (!comment.preventAutoClose && !this.$vuetify.breakpoint.xs)
comment.expanded = false // collapse if too far down right
}
if (tY < 0 && !comment.preventAutoClose && !this.$vuetify.breakpoint.xs)
comment.expanded = false // collapse if too far out topwise
@@ -26,13 +26,20 @@
>
<v-icon x-small>mdi-close</v-icon>
</v-btn>
<v-btn v-tooltip="'Toggle visibility'" small icon @click.stop="toggleVisibility()">
<v-btn
v-tooltip="'Toggle visibility'"
small
icon
@click.stop="toggleVisibility()"
>
<v-icon class="grey--text" style="font-size: 12px">
{{ visible ? 'mdi-eye' : 'mdi-eye-off' }}
</v-icon>
</v-btn>
<v-btn v-tooltip="'Isolate objects'" small icon @click.stop="isolate()">
<v-icon x-small :class="`${isolated ? 'primary--text' : ''}`">mdi-filter</v-icon>
<v-icon x-small :class="`${isolated ? 'primary--text' : ''}`">
mdi-filter
</v-icon>
</v-btn>
<v-btn small icon @click.stop="expanded = !expanded">
<v-icon x-small>{{ expanded ? 'mdi-minus' : 'mdi-plus' }}</v-icon>
@@ -81,25 +88,45 @@ export default {
},
isolated() {
return (
this.$store.state.isolateValues.indexOf(this.resource.data.commit.referencedObject) !== -1
this.$store.state.isolateValues.indexOf(
this.resource.data.commit.referencedObject
) !== -1
)
},
visible() {
return this.$store.state.hideValues.indexOf(this.resource.data.commit.referencedObject) === -1
return (
this.$store.state.hideValues.indexOf(
this.resource.data.commit.referencedObject
) === -1
)
}
},
methods: {
isolate() {
let id = this.resource.data.commit.referencedObject
if (this.isolated)
this.$store.commit('unisolateObjects', { filterKey: '__parents', filterValues: [id] })
else this.$store.commit('isolateObjects', { filterKey: '__parents', filterValues: [id] })
this.$store.commit('unisolateObjects', {
filterKey: '__parents',
filterValues: [id]
})
else
this.$store.commit('isolateObjects', {
filterKey: '__parents',
filterValues: [id]
})
},
toggleVisibility() {
let id = this.resource.data.commit.referencedObject
if (this.visible)
this.$store.commit('hideObjects', { filterKey: '__parents', filterValues: [id] })
else this.$store.commit('showObjects', { filterKey: '__parents', filterValues: [id] })
this.$store.commit('hideObjects', {
filterKey: '__parents',
filterValues: [id]
})
else
this.$store.commit('showObjects', {
filterKey: '__parents',
filterValues: [id]
})
}
}
}
@@ -8,7 +8,9 @@
icon
@click.stop="toggleColors()"
>
<v-icon small :class="`${colorBy ? 'primary--text' : ''}`">mdi-palette</v-icon>
<v-icon small :class="`${colorBy ? 'primary--text' : ''}`">
mdi-palette
</v-icon>
</v-btn>
</v-list-item-action>
</portal>
@@ -20,7 +22,9 @@
>
<v-col
cols="1"
:class="`text-center text-truncate px-1 ${$vuetify.theme.dark ? 'grey--text' : ''}`"
:class="`text-center text-truncate px-1 ${
$vuetify.theme.dark ? 'grey--text' : ''
}`"
style="line-height: 24px; font-size: 9px"
>
{{ type.count }}
@@ -35,13 +39,17 @@
</v-col>
<v-col
cols="4"
:class="`caption text-truncate text-right px-1 ${$vuetify.theme.dark ? 'grey--text' : ''}`"
:class="`caption text-truncate text-right px-1 ${
$vuetify.theme.dark ? 'grey--text' : ''
}`"
style="line-height: 24px"
>
<div
v-if="colorBy"
class="d-inline-block rounded mr-3 mt-1 elevation-3"
:style="`width: 8px; height: 8px; background:${$store.state.colorLegend[type.fullName]};`"
:style="`width: 8px; height: 8px; background:${
$store.state.colorLegend[type.fullName]
};`"
></div>
<v-btn
v-tooltip="'Toggle visibility'"
@@ -8,23 +8,33 @@
icon
@click.stop="colorBy = !colorBy"
>
<v-icon small :class="`${colorBy ? 'primary--text' : ''}`">mdi-palette</v-icon>
<v-icon small :class="`${colorBy ? 'primary--text' : ''}`">
mdi-palette
</v-icon>
</v-btn>
</v-list-item-action>
</portal>
<v-row no-gutters class="my-1 property-row rounded-lg">
<v-col
cols="1"
:class="`text-center text-truncate px-1 ${$vuetify.theme.dark ? 'grey--text' : ''}`"
:class="`text-center text-truncate px-1 ${
$vuetify.theme.dark ? 'grey--text' : ''
}`"
style="line-height: 24px; font-size: 9px"
>
<v-icon small style="font-size: 12px" :class="`${$vuetify.theme.dark ? 'grey--text' : ''}`">
<v-icon
small
style="font-size: 12px"
:class="`${$vuetify.theme.dark ? 'grey--text' : ''}`"
>
mdi-information-outline
</v-icon>
</v-col>
<v-col
cols="11"
:class="`caption text-truncatexxx px-1 ${$vuetify.theme.dark ? 'grey--text' : ''}`"
:class="`caption text-truncatexxx px-1 ${
$vuetify.theme.dark ? 'grey--text' : ''
}`"
style="line-height: 24px"
>
{{ filter.data.objectCount }} elements; min:
@@ -34,7 +44,9 @@
<v-col
v-if="filter.data.maxValue === filter.data.minValue"
cols="12"
:class="`caption text-truncatexxx px-1 ${$vuetify.theme.dark ? 'grey--text' : ''}`"
:class="`caption text-truncatexxx px-1 ${
$vuetify.theme.dark ? 'grey--text' : ''
}`"
style="line-height: 24px"
>
Invalid values (min value equals to max value).
@@ -144,6 +156,10 @@ export default {
}
.super-slider .v-slider__track-fill {
background: linear-gradient(to left, #fc466b, #3f5efb) !important;
background: -webkit-linear-gradient(to left, #fc466b, #3f5efb); /* Chrome 10-25, Safari 5.1-6 */
background: -webkit-linear-gradient(
to left,
#fc466b,
#3f5efb
); /* Chrome 10-25, Safari 5.1-6 */
}
</style>
@@ -1,7 +1,15 @@
<template>
<v-row no-gutters class="my-1 property-row rounded-lg" @click="$emit('active-toggle', filter)">
<v-row
no-gutters
class="my-1 property-row rounded-lg"
@click="$emit('active-toggle', filter)"
>
<v-col cols="1" class="text-center" style="line-height: 30px">
<v-icon small style="font-size: 12px" :class="`${$vuetify.theme.dark ? 'grey--text' : ''}`">
<v-icon
small
style="font-size: 12px"
:class="`${$vuetify.theme.dark ? 'grey--text' : ''}`"
>
{{ filter.data.type === 'number' ? 'mdi-numeric' : 'mdi-format-text' }}
</v-icon>
</v-col>
@@ -43,7 +43,10 @@
<filter-category-active :filter="activeFilter" />
</div>
<div v-if="activeFilter && activeFilter.data.type === 'number'">
<filter-numeric-active :filter="activeFilter" :prevent-first-set="preventFirstSet" />
<filter-numeric-active
:filter="activeFilter"
:prevent-first-set="preventFirstSet"
/>
</div>
<div v-show="activeFilter === null">
<div class="">
@@ -67,9 +70,14 @@
/>
</div>
</div>
<v-subheader>{{ filterSearch ? 'Matching' : 'Other' }} filters:</v-subheader>
<v-subheader>
{{ filterSearch ? 'Matching' : 'Other' }} filters:
</v-subheader>
<div v-for="filter in matchingFilters" :key="filter.targetKey">
<filter-row-select :filter="filter" @active-toggle="(e) => (activeFilter = e)" />
<filter-row-select
:filter="filter"
@active-toggle="(e) => (activeFilter = e)"
/>
</div>
</div>
</div>
@@ -22,13 +22,20 @@
>
<v-icon x-small>mdi-close</v-icon>
</v-btn>
<v-btn v-tooltip="'Toggle visibility'" small icon @click.stop="toggleVisibility()">
<v-btn
v-tooltip="'Toggle visibility'"
small
icon
@click.stop="toggleVisibility()"
>
<v-icon class="grey--text" style="font-size: 12px">
{{ visible ? 'mdi-eye' : 'mdi-eye-off' }}
</v-icon>
</v-btn>
<v-btn v-tooltip="'Isolate objects'" small icon @click.stop="isolate()">
<v-icon x-small :class="`${isolated ? 'primary--text' : ''}`">mdi-filter</v-icon>
<v-icon x-small :class="`${isolated ? 'primary--text' : ''}`">
mdi-filter
</v-icon>
</v-btn>
<v-btn small icon @click.stop="expanded = !expanded">
<v-icon x-small>{{ expanded ? 'mdi-minus' : 'mdi-plus' }}</v-icon>
@@ -60,7 +67,9 @@ export default {
},
computed: {
isolated() {
return this.$store.state.isolateValues.indexOf(this.resource.data.object.id) !== -1
return (
this.$store.state.isolateValues.indexOf(this.resource.data.object.id) !== -1
)
},
visible() {
return this.$store.state.hideValues.indexOf(this.resource.data.object.id) === -1
@@ -70,14 +79,28 @@ export default {
isolate() {
let id = this.resource.data.object.id
if (this.isolated)
this.$store.commit('unisolateObjects', { filterKey: '__parents', filterValues: [id] })
else this.$store.commit('isolateObjects', { filterKey: '__parents', filterValues: [id] })
this.$store.commit('unisolateObjects', {
filterKey: '__parents',
filterValues: [id]
})
else
this.$store.commit('isolateObjects', {
filterKey: '__parents',
filterValues: [id]
})
},
toggleVisibility() {
let id = this.resource.data.object.id
if (this.visible)
this.$store.commit('hideObjects', { filterKey: '__parents', filterValues: [id] })
else this.$store.commit('showObjects', { filterKey: '__parents', filterValues: [id] })
this.$store.commit('hideObjects', {
filterKey: '__parents',
filterValues: [id]
})
else
this.$store.commit('showObjects', {
filterKey: '__parents',
filterValues: [id]
})
}
}
}
@@ -98,7 +98,9 @@ export default {
for (let key of Object.keys(this.realObject)) {
if (this.ignoredProps.indexOf(key) !== -1) continue
let value = this.realObject[key]
let type = Array.isArray(this.realObject[key]) ? 'array' : typeof this.realObject[key]
let type = Array.isArray(this.realObject[key])
? 'array'
: typeof this.realObject[key]
let extras = []
if (value?.referencedId) extras.push('open', 'visibility')
if (
@@ -1,17 +1,29 @@
<template>
<v-row
no-gutters
:class="`my-1 py-1 property-row rounded-lg ${$vuetify.theme.dark ? 'black-bg' : 'white-bg'} ${
prop.type === 'object' || prop.type === 'array' ? (expanded ? 'border-blue' : 'border') : ''
:class="`my-1 py-1 property-row rounded-lg ${
$vuetify.theme.dark ? 'black-bg' : 'white-bg'
} ${
prop.type === 'object' || prop.type === 'array'
? expanded
? 'border-blue'
: 'border'
: ''
} ${
prop.type === 'object' || prop.type === 'array'
? 'hover-cursor property-row-hover'
: 'normal-cursor'
}`"
@click.stop="prop.type === 'object' || prop.type === 'array' ? (expanded = !expanded) : null"
@click.stop="
prop.type === 'object' || prop.type === 'array' ? (expanded = !expanded) : null
"
>
<v-col cols="1" class="text-center">
<v-icon small style="font-size: 12px" :class="`${$vuetify.theme.dark ? 'grey--text' : ''}`">
<v-icon
small
style="font-size: 12px"
:class="`${$vuetify.theme.dark ? 'grey--text' : ''}`"
>
{{ icon }}
</v-icon>
</v-col>
@@ -79,18 +91,32 @@
class="mr-1"
@click.stop="toggleFilter()"
>
<v-icon :class="`${isolated ? 'primary--text' : 'grey--text'}`" style="font-size: 12px">
<v-icon
:class="`${isolated ? 'primary--text' : 'grey--text'}`"
style="font-size: 12px"
>
{{ !isolated ? 'mdi-filter' : 'mdi-filter' }}
</v-icon>
</v-btn>
<v-btn v-tooltip="'Expand/collapse property'" x-small icon @click.stop="expanded = !expanded">
<v-icon :class="`${expanded ? 'grey--text' : 'primary--text'}`" style="font-size: 12px">
<v-btn
v-tooltip="'Expand/collapse property'"
x-small
icon
@click.stop="expanded = !expanded"
>
<v-icon
:class="`${expanded ? 'grey--text' : 'primary--text'}`"
style="font-size: 12px"
>
{{ expanded ? 'mdi-minus' : 'mdi-plus' }}
</v-icon>
</v-btn>
</v-col>
<v-scroll-y-transition>
<v-col v-if="expanded && (prop.type === 'object' || prop.type === 'array')" cols="12">
<v-col
v-if="expanded && (prop.type === 'object' || prop.type === 'array')"
cols="12"
>
<object-properties :obj="prop.value" :stream-id="streamId" />
</v-col>
</v-scroll-y-transition>
@@ -129,7 +155,9 @@ export default {
}
if (this.prop.type === 'array') {
let ids = this.prop.value.map((o) => o.referencedId)
let targetIds = this.$store.state.hideValues.filter((val) => ids.indexOf(val) !== -1)
let targetIds = this.$store.state.hideValues.filter(
(val) => ids.indexOf(val) !== -1
)
if (targetIds.length === 0) return true
else return false // return "partial" or "full", depending on state
}
@@ -137,11 +165,15 @@ export default {
},
isolated() {
if (this.prop.type === 'object') {
return this.$store.state.isolateValues.indexOf(this.prop.value.referencedId) !== -1
return (
this.$store.state.isolateValues.indexOf(this.prop.value.referencedId) !== -1
)
}
if (this.prop.type === 'array') {
let ids = this.prop.value.map((o) => o.referencedId)
let targetIds = this.$store.state.isolateValues.filter((val) => ids.indexOf(val) !== -1)
let targetIds = this.$store.state.isolateValues.filter(
(val) => ids.indexOf(val) !== -1
)
if (targetIds.length === 0) return false
else return true // return "partial" or "full", depending on state
}
@@ -158,8 +190,15 @@ export default {
}
if (this.visible)
this.$store.commit('hideObjects', { filterKey: '__parents', filterValues: targetIds })
else this.$store.commit('showObjects', { filterKey: '__parents', filterValues: targetIds })
this.$store.commit('hideObjects', {
filterKey: '__parents',
filterValues: targetIds
})
else
this.$store.commit('showObjects', {
filterKey: '__parents',
filterValues: targetIds
})
},
toggleFilter() {
let targetIds
@@ -169,8 +208,15 @@ export default {
}
if (this.isolated)
this.$store.commit('unisolateObjects', { filterKey: '__parents', filterValues: targetIds })
else this.$store.commit('isolateObjects', { filterKey: '__parents', filterValues: targetIds })
this.$store.commit('unisolateObjects', {
filterKey: '__parents',
filterValues: targetIds
})
else
this.$store.commit('isolateObjects', {
filterKey: '__parents',
filterValues: targetIds
})
}
}
}
@@ -27,7 +27,9 @@
icon
@click.stop="isolateSelection()"
>
<v-icon x-small :class="`${!isolated ? 'primary--text' : ''}`">mdi-filter</v-icon>
<v-icon x-small :class="`${!isolated ? 'primary--text' : ''}`">
mdi-filter
</v-icon>
</v-btn>
<v-btn
v-show="$vuetify.breakpoint.xs"
@@ -40,11 +42,21 @@
</v-btn>
</div>
<div v-for="prop in props" :key="prop.value.id" style="width: 99%">
<v-card class="transparent elevation-3 rounded-lg mb-3" style="pointer-events: auto">
<object-properties-row :prop="prop" :stream-id="streamId" :ref-id="prop.refId" />
<v-card
class="transparent elevation-3 rounded-lg mb-3"
style="pointer-events: auto"
>
<object-properties-row
:prop="prop"
:stream-id="streamId"
:ref-id="prop.refId"
/>
</v-card>
</div>
<div v-show="props.length === 1 && !$vuetify.breakpoint.xs" class="caption grey--text">
<div
v-show="props.length === 1 && !$vuetify.breakpoint.xs"
class="caption grey--text"
>
Hint: hold shift to select multiple objects.
</div>
</perfect-scrollbar>
@@ -98,12 +110,21 @@ export default {
isolateSelection() {
let ids = this.objects.map((o) => o.id)
if (!this.isolated)
this.$store.commit('unisolateObjects', { filterKey: '__parents', filterValues: ids })
else this.$store.commit('isolateObjects', { filterKey: '__parents', filterValues: ids })
this.$store.commit('unisolateObjects', {
filterKey: '__parents',
filterValues: ids
})
else
this.$store.commit('isolateObjects', {
filterKey: '__parents',
filterValues: ids
})
},
getSelectionUrl() {
if (this.objects.length < 2) return ''
let url = `/streams/${this.streamId}/objects/${this.objects[0].id}?overlay=${this.objects
let url = `/streams/${this.streamId}/objects/${
this.objects[0].id
}?overlay=${this.objects
.slice(1)
.map((o) => o.id)
.join(',')}`
@@ -50,7 +50,11 @@
/> -->
<div v-show="removedResources.length !== 0" class="px-3 caption pb-5">
Removed resources:
<span v-for="(res, index) in removedResources" :key="index" v-tooltip="'Click to re-add'">
<span
v-for="(res, index) in removedResources"
:key="index"
v-tooltip="'Click to re-add'"
>
<a
@click="
$emit('add-resource', res.id)
@@ -62,10 +66,16 @@
>
<span v-if="res.type === 'object'">Object</span>
<!-- eslint-disable-next-line prettier/prettier -->
<span v-else><v-icon x-small>mdi-source-commit</v-icon>{{ res.id }}</span>
<span v-else>
<v-icon x-small>mdi-source-commit</v-icon>
{{ res.id }}
</span>
</a>
<!-- eslint-disable-next-line prettier/prettier -->
<span v-if="removedResources.length > 1 && index < removedResources.length - 1">,&nbsp;
<span
v-if="removedResources.length > 1 && index < removedResources.length - 1"
>
,&nbsp;
</span>
</span>
</div>
@@ -28,7 +28,14 @@
<v-icon small>mdi-perspective-less</v-icon>
</v-btn> -->
<canonical-views :small="small" />
<v-btn v-tooltip="'Zoom extents'" :small="small" rounded icon class="mr-2" @click="zoomEx()">
<v-btn
v-tooltip="'Zoom extents'"
:small="small"
rounded
icon
class="mr-2"
@click="zoomEx()"
>
<v-icon small>mdi-arrow-expand</v-icon>
</v-btn>
<v-btn
@@ -42,7 +49,11 @@
<v-icon small>mdi-scissors-cutting</v-icon>
</v-btn>
<!-- Other components teleport extra controls in here -->
<portal-target name="viewercontrols" class="d-flex align-center" multiple></portal-target>
<portal-target
name="viewercontrols"
class="d-flex align-center"
multiple
></portal-target>
</v-card>
</div>
</template>
@@ -14,7 +14,11 @@
:url="`/preview/${streamId}/commits/${commit.id}`"
></preview-image>
<div style="position: absolute; top: 10px; right: 20px">
<commit-received-receipts :stream-id="streamId" :commit-id="commit.id" shadow />
<commit-received-receipts
:stream-id="streamId"
:commit-id="commit.id"
shadow
/>
</div>
<div style="position: absolute; top: 10px; left: 12px">
<source-app-avatar :application-name="commit.sourceApplication" />
@@ -48,14 +52,15 @@ export default {
InfiniteLoading: () => import('vue-infinite-loading'),
ListItemCommit: () => import('@/main/components/stream/ListItemCommit'),
PreviewImage: () => import('@/main/components/common/PreviewImage'),
CommitReceivedReceipts: () => import('@/main/components/common/CommitReceivedReceipts'),
CommitReceivedReceipts: () =>
import('@/main/components/common/CommitReceivedReceipts'),
SourceAppAvatar: () => import('@/main/components/common/SourceAppAvatar')
},
props: ['streamId'],
apollo: {
stream: {
query: gql`
query($streamId: String!, $cursor: String) {
query ($streamId: String!, $cursor: String) {
stream(id: $streamId) {
id
commits(cursor: $cursor, limit: 2) {
@@ -101,7 +106,8 @@ export default {
let allItems = [...previousResult.stream.commits.items]
for (const commit of newItems) {
if (allItems.findIndex((c) => c.id === commit.id) === -1) allItems.push(commit)
if (allItems.findIndex((c) => c.id === commit.id) === -1)
allItems.push(commit)
}
return {
@@ -1,14 +1,24 @@
<template>
<div>
<v-row v-if="commits.length != 0" dense>
<v-col v-for="commit in commits" :key="commit.id + 'card'" cols="12" sm="6" md="4">
<v-col
v-for="commit in commits"
:key="commit.id + 'card'"
cols="12"
sm="6"
md="4"
>
<v-card @click.stop="$emit('add-resource', commit.id)">
<preview-image
:height="180"
:url="`/preview/${streamId}/commits/${commit.id}`"
></preview-image>
<div style="position: absolute; top: 10px; right: 20px">
<commit-received-receipts :stream-id="streamId" :commit-id="commit.id" shadow />
<commit-received-receipts
:stream-id="streamId"
:commit-id="commit.id"
shadow
/>
</div>
<div style="position: absolute; top: 10px; left: 12px">
<source-app-avatar :application-name="commit.sourceApplication" />
@@ -43,7 +53,8 @@ export default {
InfiniteLoading: () => import('vue-infinite-loading'),
ListItemCommit: () => import('@/main/components/stream/ListItemCommit'),
PreviewImage: () => import('@/main/components/common/PreviewImage'),
CommitReceivedReceipts: () => import('@/main/components/common/CommitReceivedReceipts'),
CommitReceivedReceipts: () =>
import('@/main/components/common/CommitReceivedReceipts'),
SourceAppAvatar: () => import('@/main/components/common/SourceAppAvatar')
},
props: ['streamId', 'branchName'],
@@ -20,10 +20,14 @@
required
></v-text-field>
<p class="caption">
Tip: you can create nested branches by using "/" as a separator in their names. E.g.,
"mep/stage-1" or "arch/sketch-design".
Tip: you can create nested branches by using "/" as a separator in their
names. E.g., "mep/stage-1" or "arch/sketch-design".
</p>
<v-textarea v-model="editableBranch.description" rows="2" label="Description"></v-textarea>
<v-textarea
v-model="editableBranch.description"
rows="2"
label="Description"
></v-textarea>
</v-card-text>
</v-form>
<v-card-actions>
@@ -43,7 +47,9 @@
</v-app-bar-nav-icon>
<v-toolbar-title>Delete Branch</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn icon @click="showDeleteDialog = false"><v-icon>mdi-close</v-icon></v-btn>
<v-btn icon @click="showDeleteDialog = false">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-toolbar>
<v-card-text class="mt-4">
You cannot undo this action. The branch
@@ -90,8 +96,12 @@ export default {
nameRules: [
(v) => !!v || 'Name is required.',
(v) =>
!(v.startsWith('#') || v.endsWith('#') || v.startsWith('/') || v.endsWith('/')) ||
'Branch names cannot start or end with "#" or "/"',
!(
v.startsWith('#') ||
v.endsWith('#') ||
v.startsWith('/') ||
v.endsWith('/')
) || 'Branch names cannot start or end with "#" or "/"',
(v) =>
(v && this.allBranchNames.findIndex((e) => e === v) === -1) ||
'A branch with this name already exists',
@@ -200,11 +210,18 @@ export default {
text: 'Branch updated',
action: {
name: 'View',
to: `/streams/` + this.$route.params.streamId + `/branches/` + this.editableBranch.name
to:
`/streams/` +
this.$route.params.streamId +
`/branches/` +
this.editableBranch.name
}
})
this.$router.push(
`/streams/` + this.$route.params.streamId + `/branches/` + this.editableBranch.name
`/streams/` +
this.$route.params.streamId +
`/branches/` +
this.editableBranch.name
)
this.$emit('close')
}
@@ -48,11 +48,13 @@
</v-app-bar-nav-icon>
<v-toolbar-title>Delete Commit</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn icon @click="showDeleteDialog = false"><v-icon>mdi-close</v-icon></v-btn>
<v-btn icon @click="showDeleteDialog = false">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-toolbar>
<v-card-text class="mt-4">
You cannot undo this action. This will permanently delete this commit. To confirm, type in
its its id (
You cannot undo this action. This will permanently delete this commit. To
confirm, type in its its id (
<code>{{ commit.id }}</code>
) below:
<v-text-field
@@ -109,7 +111,11 @@ export default {
}
`,
variables: {
myCommit: { streamId: this.stream.id, id: this.commit.id, message: this.commit.message }
myCommit: {
streamId: this.stream.id,
id: this.commit.id,
message: this.commit.message
}
}
})
} catch (err) {
@@ -143,7 +149,9 @@ export default {
text: err.message
})
}
this.$router.push(`/streams/` + this.$route.params.streamId + `/branches/` + commitBranch)
this.$router.push(
`/streams/` + this.$route.params.streamId + `/branches/` + commitBranch
)
this.loading = false
this.showDeleteDialog = false
}
@@ -22,8 +22,8 @@
autofocus
></v-text-field>
<p class="caption">
Tip: you can create nested branches by using "/" as a separator in their names. E.g.,
"mep/stage-1" or "arch/sketch-design".
Tip: you can create nested branches by using "/" as a separator in their
names. E.g., "mep/stage-1" or "arch/sketch-design".
</p>
<v-textarea v-model="description" rows="2" label="Description"></v-textarea>
</v-card-text>
@@ -50,7 +50,8 @@ export default {
nameRules: [
(v) => !!v || 'Branches need a name too!',
(v) =>
!(v.startsWith('#') || v.startsWith('/')) || 'Branch names cannot start with "#" or "/"',
!(v.startsWith('#') || v.startsWith('/')) ||
'Branch names cannot start with "#" or "/"',
(v) =>
(v && this.reservedBranchNames.findIndex((e) => e === v) === -1) ||
'This is a reserved branch name',
@@ -8,7 +8,13 @@
<v-spacer></v-spacer>
<v-btn icon @click="$emit('close')"><v-icon>mdi-close</v-icon></v-btn>
</v-toolbar>
<v-form ref="form" v-model="valid" lazy-validation class="px-2" @submit.prevent="createStream">
<v-form
ref="form"
v-model="valid"
lazy-validation
class="px-2"
@submit.prevent="createStream"
>
<v-card-text>
<v-text-field
v-model="name"
@@ -17,7 +23,12 @@
autofocus
label="Stream Name (optional)"
/>
<v-textarea v-model="description" rows="1" row-height="15" label="Description (optional)" />
<v-textarea
v-model="description"
rows="1"
row-height="15"
label="Description (optional)"
/>
<v-switch
v-model="isPublic"
v-tooltip="
@@ -46,7 +57,11 @@
<v-list-item-subtitle>Try a different search query.</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-list-item v-for="item in userSearch.items" :key="item.id" @click="addCollab(item)">
<v-list-item
v-for="item in userSearch.items"
:key="item.id"
@click="addCollab(item)"
>
<v-list-item-avatar>
<user-avatar
:id="item.id"
@@ -218,7 +233,8 @@ export default {
}
}
this.$emit('created')
if (this.redirect) this.$router.push({ path: `/streams/${res.data.streamCreate}` })
if (this.redirect)
this.$router.push({ path: `/streams/${res.data.streamCreate}` })
} catch (e) {
this.$eventHub.$emit('notification', {
text: e.message
@@ -8,7 +8,12 @@
<v-spacer></v-spacer>
<v-btn icon @click="$emit('close')"><v-icon>mdi-close</v-icon></v-btn>
</v-toolbar>
<v-alert v-model="showError" dismissible type="error" :class="`${success ? 'mb-0' : ''}`">
<v-alert
v-model="showError"
dismissible
type="error"
:class="`${success ? 'mb-0' : ''}`"
>
{{ error }}
</v-alert>
<v-alert v-model="success" timeout="3000" dismissible type="success">
@@ -16,11 +21,15 @@
</v-alert>
<v-form ref="form" v-model="valid" class="px-2" @submit.prevent="sendInvite">
<v-card-text class="pb-0 mb-0">
Speckle will send a server invite link to the email below. You can also add a personal
message if you want to.
Speckle will send a server invite link to the email below. You can also add a
personal message if you want to.
</v-card-text>
<v-card-text class="pt-0 mt-0">
<v-text-field v-model="email" :rules="validation.emailRules" label="email"></v-text-field>
<v-text-field
v-model="email"
:rules="validation.emailRules"
label="email"
></v-text-field>
<v-text-field
v-model="message"
:rules="validation.messageRules"
@@ -85,7 +94,7 @@ export default {
try {
await this.$apollo.mutate({
mutation: gql`
mutation($input: ServerInviteCreateInput!) {
mutation ($input: ServerInviteCreateInput!) {
serverInviteCreate(input: $input)
}
`,
@@ -34,7 +34,10 @@
@focus="copyToClipboard"
></v-text-field>
<v-text-field
v-if="$route.params.resourceId && $resourceType($route.params.resourceId) === 'commit'"
v-if="
$route.params.resourceId &&
$resourceType($route.params.resourceId) === 'commit'
"
ref="commitUrl"
dark
filled
@@ -73,7 +76,8 @@
<div v-if="stream.isPublic">
<v-card-text>
<div class="caption mx-1 pb-2">
Copy the code below to embed an iframe of this model in your webpage or document.
Copy the code below to embed an iframe of this model in your webpage or
document.
</div>
<v-text-field
dense
@@ -86,7 +90,10 @@
</v-card-text>
</div>
</v-sheet>
<v-sheet v-if="stream" :class="`${!$vuetify.theme.dark ? 'grey lighten-4' : 'grey darken-4'}`">
<v-sheet
v-if="stream"
:class="`${!$vuetify.theme.dark ? 'grey lighten-4' : 'grey darken-4'}`"
>
<v-toolbar v-if="stream.role === 'stream:owner'" class="transparent" rounded flat>
<v-app-bar-nav-icon style="pointer-events: none">
<v-icon>{{ stream.isPublic ? 'mdi-lock-open' : 'mdi-lock' }}</v-icon>
@@ -104,7 +111,8 @@
></v-switch>
</v-toolbar>
<v-card-text v-if="stream.isPublic" class="pt-2">
This stream is public. This means that anyone with the link can view and read data from it.
This stream is public. This means that anyone with the link can view and read
data from it.
</v-card-text>
<v-card-text v-if="!stream.isPublic" class="pt-2 pb-2">
This stream is private. This means that only collaborators can access it.
@@ -115,7 +123,9 @@
v-tooltip="
`${
stream.role !== 'stream:owner'
? 'You do not have the right access level (' + stream.role + ') to add collaborators.'
? 'You do not have the right access level (' +
stream.role +
') to add collaborators.'
: ''
}`
"
@@ -127,7 +137,10 @@
<v-toolbar-title>
Collaborators
<user-avatar
v-for="collab in stream.collaborators.slice(0, stream.collaborators.length > 5 ? 4 : 5)"
v-for="collab in stream.collaborators.slice(
0,
stream.collaborators.length > 5 ? 4 : 5
)"
:id="collab.id"
:key="collab.id"
:size="20"
@@ -11,7 +11,12 @@
<v-btn icon @click="showDialog = false"><v-icon>mdi-close</v-icon></v-btn>
</v-toolbar>
<v-alert v-model="showError" dismissible type="error" :class="`${success ? 'mb-0' : ''}`">
<v-alert
v-model="showError"
dismissible
type="error"
:class="`${success ? 'mb-0' : ''}`"
>
{{ error }}
</v-alert>
<v-alert v-model="success" dismissible type="success">
@@ -19,8 +24,8 @@
</v-alert>
<v-form ref="form" v-model="valid" class="px-2" @submit.prevent="sendInvite">
<v-card-text class="pb-0 mb-0">
We will send an invite to the email below - once they accept, they will also gain access
to this stream!
We will send an invite to the email below - once they accept, they will also
gain access to this stream!
</v-card-text>
<v-card-text class="pt-0 mt-0">
<v-text-field
@@ -117,7 +122,7 @@ export default {
try {
await this.$apollo.mutate({
mutation: gql`
mutation($input: StreamInviteCreateInput!) {
mutation ($input: StreamInviteCreateInput!) {
streamInviteCreate(input: $input)
}
`,
@@ -15,8 +15,8 @@
<v-row>
<v-col cols="12" class="pb-0">
<p>
To protect against accidental deletion, please enter the email address associated
with this account:
To protect against accidental deletion, please enter the email address
associated with this account:
</p>
</v-col>
</v-row>
@@ -38,7 +38,9 @@
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text @click="cancel">Cancel</v-btn>
<v-btn color="error" :disabled="!valid" type="submit">Delete my account</v-btn>
<v-btn color="error" :disabled="!valid" type="submit">
Delete my account
</v-btn>
</v-card-actions>
</v-form>
</v-card>
@@ -28,7 +28,11 @@
</v-row>
<v-row>
<v-col cols="12" class="pt-0 pb-0">
<v-text-field v-model="user.company" filled label="Company"></v-text-field>
<v-text-field
v-model="user.company"
filled
label="Company"
></v-text-field>
</v-col>
</v-row>
<v-row>
@@ -64,7 +68,9 @@ export default {
(v) => !!v || 'Name is required',
(v) => (v && v.length <= 60) || 'Name must be less than 60 characters.'
],
bioRules: [(v) => !v || (v && v.length <= 500) || 'Bio must be less than 500 characters.'],
bioRules: [
(v) => !v || (v && v.length <= 500) || 'Bio must be less than 500 characters.'
],
valid: true
}
},
+8 -1
View File
@@ -2,7 +2,14 @@
<v-app :class="`${$vuetify.theme.dark ? 'background-dark' : 'background-light'}`">
<v-container fill-height fluid>
<v-row align="center" justify="center">
<v-col v-if="showBlurb" cols="12" md="6" lg="6" xl="4" class="hidden-sm-and-down">
<v-col
v-if="showBlurb"
cols="12"
md="6"
lg="6"
xl="4"
class="hidden-sm-and-down"
>
<blurb :server-info="serverInfo" />
</v-col>
<v-col cols="11" sm="8" md="6" lg="4" xl="3">
+18 -5
View File
@@ -12,7 +12,10 @@
<div v-show="$route.meta.resizableNavbar" class="nav-resizer"></div>
<main-nav :expanded="drawer" @hide-drawer="drawer = false" />
<template #append>
<div :xxxstyle="`${$isMobile() ? 'padding-bottom: 58px' : ''}`" class="elevation-10">
<div
:xxxstyle="`${$isMobile() ? 'padding-bottom: 58px' : ''}`"
class="elevation-10"
>
<main-nav-bottom />
</div>
</template>
@@ -70,7 +73,8 @@ export default {
SearchBar: () => import('@/main/components/common/SearchBar'),
GlobalToast: () => import('@/main/components/common/GlobalToast'),
GlobalLoading: () => import('@/main/components/common/GlobalLoading'),
EmailVerificationBanner: () => import('@/main/components/user/EmailVerificationBanner')
EmailVerificationBanner: () =>
import('@/main/components/user/EmailVerificationBanner')
},
apollo: {
serverInfo: {
@@ -129,7 +133,10 @@ export default {
let mixpanelId = this.$mixpanelId()
if (mixpanelId !== null) {
this.$mixpanel.identify(mixpanelId)
this.$mixpanel.people.set('Theme Web', this.$vuetify.theme.dark ? 'dark' : 'light')
this.$mixpanel.people.set(
'Theme Web',
this.$vuetify.theme.dark ? 'dark' : 'light'
)
this.$mixpanel.people.set('Identified', true)
}
this.$mixpanel.track('Visit Web App')
@@ -137,8 +144,14 @@ export default {
methods: {
switchTheme() {
this.$vuetify.theme.dark = !this.$vuetify.theme.dark
localStorage.setItem('darkModeEnabled', this.$vuetify.theme.dark ? 'dark' : 'light')
this.$mixpanel.people.set('Theme Web', this.$vuetify.theme.dark ? 'dark' : 'light')
localStorage.setItem(
'darkModeEnabled',
this.$vuetify.theme.dark ? 'dark' : 'light'
)
this.$mixpanel.people.set(
'Theme Web',
this.$vuetify.theme.dark ? 'dark' : 'light'
)
},
setNavResizeEvents() {
const minSize = this.borderSize
@@ -1,6 +1,10 @@
<template>
<div>
<main-logo :shadow="shadowSpeckle" :expanded="expanded" @hide-drawer="$emit('hide-drawer')" />
<main-logo
:shadow="shadowSpeckle"
:expanded="expanded"
@hide-drawer="$emit('hide-drawer')"
/>
<portal-target name="nav">
<!-- Main Actions -->
@@ -40,7 +44,9 @@
<template slot="activator">
<v-list-item-content>
<v-list-item-title>Streams</v-list-item-title>
<v-list-item-subtitle class="caption">All your streams</v-list-item-subtitle>
<v-list-item-subtitle class="caption">
All your streams
</v-list-item-subtitle>
</v-list-item-content>
</template>
@@ -62,7 +68,9 @@
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Commits</v-list-item-title>
<v-list-item-subtitle class="caption">Your latest commits</v-list-item-subtitle>
<v-list-item-subtitle class="caption">
Your latest commits
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<portal-target name="subnav-commits" />
@@ -72,7 +80,9 @@
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Admin</v-list-item-title>
<v-list-item-subtitle class="caption">Server Management</v-list-item-subtitle>
<v-list-item-subtitle class="caption">
Server Management
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<portal-target name="subnav-admin" />
@@ -89,7 +99,9 @@
<v-list-item-content>
<v-list-item-title>Profile</v-list-item-title>
<v-list-item-subtitle class="caption">Settings & Security</v-list-item-subtitle>
<v-list-item-subtitle class="caption">
Settings & Security
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<portal-target name="subnav-profile" />
@@ -98,11 +110,19 @@
<!-- Dialogs -->
<v-dialog v-model="newStreamDialog" max-width="500" :fullscreen="$vuetify.breakpoint.xsOnly">
<v-dialog
v-model="newStreamDialog"
max-width="500"
:fullscreen="$vuetify.breakpoint.xsOnly"
>
<new-stream @created="newStreamDialog = false" @close="newStreamDialog = false" />
</v-dialog>
<v-dialog v-model="inviteUsersDialog" max-width="500" :fullscreen="$vuetify.breakpoint.xsOnly">
<v-dialog
v-model="inviteUsersDialog"
max-width="500"
:fullscreen="$vuetify.breakpoint.xsOnly"
>
<server-invites @close="inviteUsersDialog = false" />
</v-dialog>
</div>
@@ -117,7 +137,10 @@ export default {
ServerInvites: () => import('@/main/dialogs/ServerInvites'),
UserAvatarIcon: () => import('@/main/components/common/UserAvatarIcon')
},
props: { expanded: { type: Boolean, default: false }, drawer: { type: Boolean, default: true } },
props: {
expanded: { type: Boolean, default: false },
drawer: { type: Boolean, default: true }
},
apollo: {
user: {
query: MainUserDataQuery
@@ -131,7 +154,9 @@ export default {
}
},
mounted() {
let navContent = [...document.getElementsByClassName('v-navigation-drawer__content')][0]
let navContent = [
...document.getElementsByClassName('v-navigation-drawer__content')
][0]
navContent.addEventListener('scroll', () => {
if (navContent.scrollTop > 50) this.shadowSpeckle = true
else this.shadowSpeckle = false
@@ -141,8 +166,14 @@ export default {
methods: {
switchTheme() {
this.$vuetify.theme.dark = !this.$vuetify.theme.dark
localStorage.setItem('darkModeEnabled', this.$vuetify.theme.dark ? 'dark' : 'light')
this.$mixpanel.people.set('Theme Web', this.$vuetify.theme.dark ? 'dark' : 'light')
localStorage.setItem(
'darkModeEnabled',
this.$vuetify.theme.dark ? 'dark' : 'light'
)
this.$mixpanel.people.set(
'Theme Web',
this.$vuetify.theme.dark ? 'dark' : 'light'
)
}
}
}
@@ -19,7 +19,11 @@
<v-col>
<v-btn x-small block depressed @click="switchTheme()">
<v-icon x-small class="mr-1">
{{ $vuetify.theme.dark ? 'mdi-white-balance-sunny' : 'mdi-weather-night' }}
{{
$vuetify.theme.dark
? 'mdi-white-balance-sunny'
: 'mdi-weather-night'
}}
</v-icon>
<!-- {{ $vuetify.theme.dark ? 'mdi-white-balance-sunny' : 'mdi-weather-night' }} -->
</v-btn>
@@ -64,8 +68,14 @@ export default {
},
switchTheme() {
this.$vuetify.theme.dark = !this.$vuetify.theme.dark
localStorage.setItem('darkModeEnabled', this.$vuetify.theme.dark ? 'dark' : 'light')
this.$mixpanel.people.set('Theme Web', this.$vuetify.theme.dark ? 'dark' : 'light')
localStorage.setItem(
'darkModeEnabled',
this.$vuetify.theme.dark ? 'dark' : 'light'
)
this.$mixpanel.people.set(
'Theme Web',
this.$vuetify.theme.dark ? 'dark' : 'light'
)
}
}
}

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