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