adds existing app
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
not ie <= 8
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw*
|
||||
@@ -0,0 +1,29 @@
|
||||
# speckle-ui
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Run your tests
|
||||
```
|
||||
npm run test
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/app'
|
||||
]
|
||||
}
|
||||
Generated
+10923
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "speckle-ui",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build"
|
||||
},
|
||||
"dependencies": {
|
||||
"material-design-icons-iconfont": "^3.0.3",
|
||||
"roboto-fontface": "*",
|
||||
"vue": "^2.6.6",
|
||||
"vue-router": "^3.0.1",
|
||||
"vuetify": "^1.5.5",
|
||||
"vuex": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^3.5.0",
|
||||
"@vue/cli-service": "^3.5.0",
|
||||
"axios": "^0.18.0",
|
||||
"node-sass": "^4.9.0",
|
||||
"sass-loader": "^7.1.0",
|
||||
"sockette": "^2.0.5",
|
||||
"vue-cli-plugin-vuetify": "^0.5.0",
|
||||
"vue-template-compiler": "^2.5.21",
|
||||
"vue-timeago": "^5.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title>speckle-ui</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but speckle-ui doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<v-toolbar app>
|
||||
<v-toolbar-title class="headline text-uppercase">
|
||||
<span @click='showDev()'>Speckle </span>
|
||||
<span class="font-weight-light">{{$store.state.hostAppName}}</span>
|
||||
</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="secondary" dark absolute bottom right fab :ripple="false" @click.native='showAddNewReceiver=true'>
|
||||
<v-icon>cloud_download</v-icon>
|
||||
</v-btn>
|
||||
<v-btn color="primary" absolute bottom right fab :ripple="false" @click.native='showAddNewSender=true' style="margin-right:60px">
|
||||
<v-icon>cloud_upload</v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
<v-dialog v-model="showAddNewReceiver" scrollable fullscreen>
|
||||
<NewClient :is-visible='showAddNewReceiver' @close='showAddNewReceiver=false'>
|
||||
</NewClient>
|
||||
</v-dialog>
|
||||
<v-dialog v-model="showAddNewSender" scrollable fullscreen>
|
||||
<NewClientSender :is-visible='showAddNewSender' @close='showAddNewSender=false'>
|
||||
</NewClientSender>
|
||||
</v-dialog>
|
||||
<v-content>
|
||||
<v-container grid-list-md pa-0 mt-4>
|
||||
<v-layout row wrap>
|
||||
<v-flex xs12 md6 pa-3 xxxv-if='receivers.length>0'>
|
||||
<span class='headline text-uppercase secondary--text'>Receivers</span>
|
||||
<v-divider class='my-4 secondary'></v-divider>
|
||||
<span class="" v-if="receivers.length===0">There are no receiver clients in this file.</span>
|
||||
<v-container grid-list-xl>
|
||||
<v-layout row wrap>
|
||||
<client-receiver v-for='client in receivers' :key='client.streamId + ":" + client.AccountId' :client='client '>
|
||||
</client-receiver>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
</v-flex>
|
||||
<v-flex xs12 md6 pa-3>
|
||||
<span class='headline text-uppercase primary--text'>Senders</span>
|
||||
<v-divider class='my-4 primary'></v-divider>
|
||||
<v-container grid-list-xl>
|
||||
<v-layout row wrap>
|
||||
<client-sender v-for='client in senders' :key='client.streamId + ":" + client.AccountId' :client='client'>{{client}}</client-sender>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
</v-content>
|
||||
</v-app>
|
||||
</template>
|
||||
<script>
|
||||
import HelloWorld from './components/HelloWorld'
|
||||
import NewClient from './components/NewClient.vue'
|
||||
import NewClientSender from './components/NewClientSender.vue'
|
||||
import ClientReceiver from './components/ClientReceiver.vue'
|
||||
import ClientSender from './components/ClientSender.vue'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
HelloWorld,
|
||||
NewClient,
|
||||
NewClientSender,
|
||||
ClientReceiver,
|
||||
ClientSender
|
||||
},
|
||||
computed: {
|
||||
receivers( ) {
|
||||
return this.$store.state.clients.filter( cl => cl.type === 'receiver' )
|
||||
},
|
||||
senders( ) {
|
||||
return this.$store.state.clients.filter( cl => cl.type === 'sender' )
|
||||
}
|
||||
},
|
||||
data( ) {
|
||||
return {
|
||||
showAddNewReceiver: false,
|
||||
showAddNewSender: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showDev( ) {
|
||||
console.log( 'showing dev' )
|
||||
UiBindings.showDev( )
|
||||
}
|
||||
},
|
||||
mounted( ) {
|
||||
console.log( 'app mounted!' )
|
||||
|
||||
this.$store.dispatch( 'getAccounts' )
|
||||
this.$store.dispatch( 'getApplicationHostName' )
|
||||
this.$store.dispatch( 'getExistingClients' )
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.5 100"><defs><style>.cls-1{fill:#1697f6;}.cls-2{fill:#7bc6ff;}.cls-3{fill:#1867c0;}.cls-4{fill:#aeddff;}</style></defs><title>Artboard 46</title><polyline class="cls-1" points="43.75 0 23.31 0 43.75 48.32"/><polygon class="cls-2" points="43.75 62.5 43.75 100 0 14.58 22.92 14.58 43.75 62.5"/><polyline class="cls-3" points="43.75 0 64.19 0 43.75 48.32"/><polygon class="cls-4" points="64.58 14.58 87.5 14.58 43.75 100 43.75 62.5 64.58 14.58"/></svg>
|
||||
|
After Width: | Height: | Size: 539 B |
@@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<v-flex xs12>
|
||||
<v-hover>
|
||||
<v-card
|
||||
:class="`elevation-${client.expired ? '15' : '1'} ${client.expired ? 'expired' : ''}`"
|
||||
slot-scope="{ hover }"
|
||||
>
|
||||
<v-toolbar color="secondary text-truncate elevation-0" dark>
|
||||
<v-icon color="white">cloud_download</v-icon>
|
||||
<v-toolbar-title class="text-truncate font-weight-light">{{client.name}}</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
icon
|
||||
:href="`${client.account.RestApi.replace('api','#')}streams/${client.streamId}`"
|
||||
target="_blank"
|
||||
>
|
||||
<v-icon>open_in_new</v-icon>
|
||||
</v-btn>
|
||||
<v-btn :flat="!client.expired" @click.native="bakeReceiver()">
|
||||
Pull
|
||||
<v-icon small right>cloud_download</v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
<v-card-text class="caption">
|
||||
<span>
|
||||
<v-icon small>developer_board</v-icon>
|
||||
{{account.ServerName}}
|
||||
</span>
|
||||
<span class="caption">
|
||||
<v-icon small>vpn_key</v-icon>StreamId:
|
||||
<span style="user-select:all;">
|
||||
<b>{{client.streamId}}</b>
|
||||
</span>
|
||||
</span>
|
||||
<span class="caption">
|
||||
<v-icon small>hourglass_full</v-icon>Last update:
|
||||
<timeago :datetime="client.updatedAt" :auto-update="60"></timeago>
|
||||
</span>
|
||||
<v-progress-linear
|
||||
v-show="client.loading"
|
||||
:active="client.loading"
|
||||
:indeterminate="client.isLoadingIndeterminate"
|
||||
height="2"
|
||||
v-model="client.loadingProgress"
|
||||
color="primary darken-1"
|
||||
></v-progress-linear>
|
||||
<br>
|
||||
<span class="caption text--lighten-3">{{client.loadingBlurb}}</span>
|
||||
<span class="caption grey--text">Total objects: {{client.objects.length}}</span>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn small icon @click.native="selectObjects">
|
||||
<v-icon small>gps_fixed</v-icon>
|
||||
</v-btn>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn small flat outline icon color="error" @click.native="deleteClient">
|
||||
<v-icon small>delete</v-icon>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
<v-alert
|
||||
v-model="client.expired"
|
||||
dismissible
|
||||
color="grey darken-2"
|
||||
v-if="client.message && client.message!== ''"
|
||||
>{{client.message}}</v-alert>
|
||||
<v-alert
|
||||
v-model="client.errors"
|
||||
dismissible
|
||||
type="warning"
|
||||
xxxcolor="grey darken-2"
|
||||
v-if="client.errors && client.errors!== ''"
|
||||
|
||||
><div v-html="client.errors"></div></v-alert>
|
||||
</v-card>
|
||||
</v-hover>
|
||||
</v-flex>
|
||||
</template>
|
||||
<script>
|
||||
import Sockette from "sockette";
|
||||
|
||||
export default {
|
||||
name: "Client",
|
||||
props: {
|
||||
client: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
account() {
|
||||
return this.$store.state.accounts.find(
|
||||
ac => ac.AccountId === this.client.AccountId
|
||||
);
|
||||
},
|
||||
updatedAt() {
|
||||
return new Date(this.client.updatedAt).toLocaleDateString();
|
||||
}
|
||||
},
|
||||
data: () => ({}),
|
||||
methods: {
|
||||
bakeReceiver() {
|
||||
this.$store.dispatch("bakeReceiver", this.client);
|
||||
},
|
||||
deleteClient() {
|
||||
this.$store.dispatch("removeReceiverClient", this.client);
|
||||
this.sockette.close();
|
||||
},
|
||||
selectObjects() {
|
||||
UiBindings.selectClientObjects(JSON.stringify(this.client));
|
||||
},
|
||||
wsOpen(e) {
|
||||
this.sockette.json({
|
||||
eventName: "join",
|
||||
resourceType: "stream",
|
||||
resourceId: this.client.streamId
|
||||
});
|
||||
},
|
||||
wsMessage(e) {
|
||||
console.log(e.data);
|
||||
if (e.data === "ping") {
|
||||
this.sockette.send("alive");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
let message = JSON.parse(e.data);
|
||||
switch (message.args.eventType) {
|
||||
case "update-global":
|
||||
this.$store.dispatch("updateClient", {
|
||||
client: this.client,
|
||||
expire: true
|
||||
});
|
||||
break;
|
||||
case "update-meta":
|
||||
this.$store.dispatch("updateClient", {
|
||||
client: this.client,
|
||||
expire: false
|
||||
});
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
`Could not parse/interpret ${e.data} for ${this.client.streamId}`
|
||||
);
|
||||
console.log(e.data);
|
||||
}
|
||||
},
|
||||
wsError(e) {
|
||||
console.log(e);
|
||||
},
|
||||
wsReconnect(e) {
|
||||
console.log(e);
|
||||
},
|
||||
wsClose(e) {
|
||||
console.log(e);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log("client mounted!");
|
||||
console.log(this.client);
|
||||
let wsUrl = this.account.RestApi.replace("http", "ws");
|
||||
this.sockette = new Sockette(
|
||||
`${wsUrl}?client_id=${this.client.clientId}&access_token=${
|
||||
this.account.Token
|
||||
}`,
|
||||
{
|
||||
timeout: 5e3,
|
||||
maxAttempts: 100,
|
||||
onopen: this.wsOpen,
|
||||
onmessage: this.wsMessage,
|
||||
onerror: this.wsError,
|
||||
onreconnect: this.wsReconnect,
|
||||
onclose: this.wsClose
|
||||
}
|
||||
);
|
||||
},
|
||||
beforeDestroy() {
|
||||
console.log("bye bye...");
|
||||
this.sockette.close();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped lang='scss'>
|
||||
</style>
|
||||
@@ -0,0 +1,216 @@
|
||||
<template>
|
||||
<v-flex xs12>
|
||||
<v-hover>
|
||||
<v-card
|
||||
:class="`elevation-${client.expired ? '15' : '1'} ${client.expired ? 'expired' : ''}`"
|
||||
slot-scope="{ hover }"
|
||||
>
|
||||
<v-toolbar color="primary xxxdarken-1 text-truncate elevation-0" dark>
|
||||
<v-icon color="white">cloud_upload</v-icon>
|
||||
<v-toolbar-title class="text-truncate font-weight-light">{{client.name}}</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
icon
|
||||
:href="`${client.account.RestApi.replace('api','#')}streams/${client.streamId}`"
|
||||
target="_blank"
|
||||
>
|
||||
<v-icon>open_in_new</v-icon>
|
||||
</v-btn>
|
||||
<v-btn :flat="!client.expired" @click.native="startUpload()">
|
||||
Push
|
||||
<v-icon small right>cloud_upload</v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
<v-card-text class="caption">
|
||||
<span>
|
||||
<v-icon small>developer_board</v-icon>
|
||||
{{account.ServerName}}
|
||||
</span>
|
||||
<span class="caption">
|
||||
<v-icon small>vpn_key</v-icon>StreamId:
|
||||
<span style="user-select:all;">
|
||||
<b>{{client.streamId}}</b>
|
||||
</span>
|
||||
</span>
|
||||
<span class="caption">
|
||||
<v-icon small>hourglass_full</v-icon>Last update:
|
||||
<timeago :datetime="client.updatedAt" :auto-update="60"></timeago>
|
||||
</span>
|
||||
<v-progress-linear
|
||||
:active="client.loading"
|
||||
:indeterminate="client.isLoadingIndeterminate"
|
||||
height="2"
|
||||
v-model="client.loadingProgress"
|
||||
color="primary darken-1"
|
||||
></v-progress-linear>
|
||||
<span class="caption text--lighten-3">{{client.loadingBlurb}}</span>
|
||||
<span class="caption grey--text">Total objects: {{client.objects.length}}</span>
|
||||
</v-card-text>
|
||||
<!-- <v-card-text class="caption text--lighten-3">{{client.message}}</v-card-text> -->
|
||||
<v-card-actions>
|
||||
<!-- <v-btn @click.native="startUpload()">PUSH</v-btn> -->
|
||||
<v-btn
|
||||
small
|
||||
round
|
||||
:disabled="$store.state.selectionCount===0"
|
||||
@click.native="addSelection()"
|
||||
>
|
||||
add
|
||||
<v-icon right>add</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
small
|
||||
round
|
||||
:disabled="$store.state.selectionCount===0"
|
||||
@click.native="removeSelection()"
|
||||
>
|
||||
remove
|
||||
<v-icon right>remove</v-icon>
|
||||
</v-btn>
|
||||
<span
|
||||
class="caption grey--text"
|
||||
>{{$store.state.selectionCount}} selected objects</span>
|
||||
<v-btn small icon @click.native="selectObjects">
|
||||
<v-icon small>gps_fixed</v-icon>
|
||||
</v-btn>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn small flat outline icon color="error" @click.native="deleteClient">
|
||||
<v-icon small>delete</v-icon>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
<v-alert
|
||||
v-model="client.expired"
|
||||
dismissible
|
||||
color="grey darken-2"
|
||||
v-if="client.message && client.message!== ''"
|
||||
>{{client.message}}</v-alert>
|
||||
<v-alert
|
||||
v-model="client.errors"
|
||||
dismissible
|
||||
type="warning"
|
||||
xxxcolor="grey darken-2"
|
||||
v-if="client.errors && client.errors!== ''"
|
||||
>{{client.errors}}</v-alert>
|
||||
</v-card>
|
||||
</v-hover>
|
||||
</v-flex>
|
||||
</template>
|
||||
<script>
|
||||
import Sockette from "sockette";
|
||||
|
||||
export default {
|
||||
name: "SenderClient",
|
||||
props: {
|
||||
client: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
account() {
|
||||
return this.$store.state.accounts.find(
|
||||
ac => ac.AccountId === this.client.AccountId
|
||||
);
|
||||
},
|
||||
updatedAt() {
|
||||
return new Date(this.client.updatedAt).toLocaleDateString();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
"client.loading"(val, oldVal) {
|
||||
if (!val && this.sendStarted) this.broadcastSendEnd();
|
||||
},
|
||||
client: {
|
||||
handler(val, oldVal) {
|
||||
console.log(val);
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
sendStarted: false
|
||||
}),
|
||||
methods: {
|
||||
startUpload() {
|
||||
this.sendStarted = true;
|
||||
this.$store.dispatch("cloneStream", this.client);
|
||||
this.client.updatedAt = new Date().toISOString();
|
||||
this.client.message = "";
|
||||
this.client.expired = false;
|
||||
UiBindings.updateSender(JSON.stringify(this.client));
|
||||
},
|
||||
deleteClient() {
|
||||
this.$store.dispatch("removeReceiverClient", this.client);
|
||||
this.sockette.close();
|
||||
},
|
||||
broadcastSendEnd() {
|
||||
this.sendStarted = false;
|
||||
this.sockette.json({
|
||||
eventName: "broadcast",
|
||||
resourceType: "stream",
|
||||
resourceId: this.client.streamId,
|
||||
args: {
|
||||
eventType: "update-global"
|
||||
}
|
||||
});
|
||||
},
|
||||
addSelection() {
|
||||
UiBindings.addSelectionToSender(JSON.stringify(this.client));
|
||||
},
|
||||
removeSelection() {
|
||||
UiBindings.removeSelectionFromSender(JSON.stringify(this.client));
|
||||
},
|
||||
selectObjects() {
|
||||
UiBindings.selectClientObjects(JSON.stringify(this.client));
|
||||
},
|
||||
wsOpen(e) {
|
||||
this.sockette.json({
|
||||
eventName: "join",
|
||||
resourceType: "stream",
|
||||
resourceId: this.client.streamId
|
||||
});
|
||||
},
|
||||
wsMessage(e) {
|
||||
console.log(e.data);
|
||||
if (e.data === "ping") {
|
||||
this.sockette.send("alive");
|
||||
return;
|
||||
}
|
||||
},
|
||||
wsError(e) {
|
||||
console.log(e);
|
||||
},
|
||||
wsReconnect(e) {
|
||||
console.log(e);
|
||||
},
|
||||
wsClose(e) {
|
||||
console.log(e);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let wsUrl = this.account.RestApi.replace("http", "ws");
|
||||
this.sockette = new Sockette(
|
||||
`${wsUrl}?client_id=${this.client.clientId}&access_token=${
|
||||
this.account.Token
|
||||
}`,
|
||||
{
|
||||
timeout: 5e3,
|
||||
maxAttempts: 100,
|
||||
onopen: this.wsOpen,
|
||||
onmessage: this.wsMessage,
|
||||
onerror: this.wsError,
|
||||
onreconnect: this.wsReconnect,
|
||||
onclose: this.wsClose
|
||||
}
|
||||
);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.sockette.close();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped lang='scss'>
|
||||
.expired {
|
||||
// border-left: 12px solid red;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<v-layout text-xs-center wrap>
|
||||
<v-flex xs12>
|
||||
Hello World.
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data: ( ) => ( {
|
||||
|
||||
} )
|
||||
}
|
||||
</script>
|
||||
<style scoped lang='scss'>
|
||||
</style>
|
||||
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<v-card>
|
||||
<v-toolbar card dark color="secondary">
|
||||
<v-btn icon dark @click="$emit('close')">
|
||||
<v-icon>close</v-icon>
|
||||
</v-btn>
|
||||
<v-toolbar-title class="text-truncate font-weight-light">Add a new receiver</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<v-card-text class="pa-5">
|
||||
<v-layout row wrap align-center>
|
||||
<v-flex xs12>
|
||||
<p class="headline font-weight-light">Account</p>
|
||||
<p class="caption">We first need to know which speckle server the data is coming from.</p>
|
||||
<v-overflow-btn
|
||||
:items="$store.state.accounts"
|
||||
label="Account"
|
||||
editable
|
||||
solo
|
||||
v-model="selectedAccount"
|
||||
item-text="fullName"
|
||||
return-object
|
||||
></v-overflow-btn>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
<v-layout row wrap align-center v-if="selectedAccount && selectedAccount.streams.length > 0">
|
||||
<v-flex xs12>
|
||||
<p class="headline font-weight-light">Stream</p>
|
||||
<p
|
||||
class="caption"
|
||||
>If the stream you're looking for doesn't show up here, try refreshing the list and make sure it's shared with you!</p>
|
||||
<v-overflow-btn
|
||||
append-icon="refresh"
|
||||
@click:append="refreshStreamsAtAccount()"
|
||||
:items="selectedAccount.streams"
|
||||
:label="'Streams from ' + selectedAccount.fullName"
|
||||
editable
|
||||
v-model="selectedStream"
|
||||
item-text="fullName"
|
||||
return-object
|
||||
></v-overflow-btn>
|
||||
<!-- <v-select :items="selectedAccount.streams" item-text="name"></v-select> -->
|
||||
</v-flex>
|
||||
<v-flex xs12 v-if="selectedStream" class="caption">
|
||||
Last updated:
|
||||
<timeago :datetime="selectedStream.updatedAt" :auto-update="60"></timeago>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
<v-layout row wrap align-center>
|
||||
<v-flex
|
||||
xs12
|
||||
class="text-xs-left"
|
||||
v-if="selectedAccount && selectedAccount.streams.length===0"
|
||||
>Seems like you don't have any streams to receive. Get someone to share some with you, or, even better, create one!</v-flex>
|
||||
<v-flex
|
||||
xs12
|
||||
class="text-xs-left"
|
||||
v-if="!selectedAccount || !selectedAccount.validated"
|
||||
>Could not access that server (is it online?) or no server selected.</v-flex>
|
||||
</v-layout>
|
||||
<v-layout row wrap align-center>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
block
|
||||
:ripple="false"
|
||||
:disabled="selectedStream===null"
|
||||
@click.native="addReceiver()"
|
||||
>Create Receiver</v-btn>
|
||||
</v-layout>
|
||||
<v-alert
|
||||
value="true"
|
||||
type="info"
|
||||
>If the stream contains objects that cannot be converted to Revit, they will not be visible in any way.</v-alert>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: "NewClient",
|
||||
props: {
|
||||
isVisible: { type: Boolean, default: false }
|
||||
},
|
||||
watch: {
|
||||
selectedAccount(val) {
|
||||
// todo: get streams for the account
|
||||
},
|
||||
isVisible(val) {
|
||||
if (val) {
|
||||
this.selectedAccount = this.$store.state.accounts.find(
|
||||
ac => ac.IsDefault === true
|
||||
);
|
||||
this.selectedStream = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
selectedAccount: null,
|
||||
selectedStream: null
|
||||
}),
|
||||
methods: {
|
||||
refreshStreamsAtAccount() {
|
||||
this.$store.dispatch("getAccountStreams", this.selectedAccount);
|
||||
},
|
||||
async addReceiver() {
|
||||
let res = await this.$store.dispatch("addReceiverClient", {
|
||||
account: this.selectedAccount,
|
||||
stream: this.selectedStream
|
||||
});
|
||||
this.$emit("close");
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped lang='scss'>
|
||||
</style>
|
||||
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<v-card>
|
||||
<v-toolbar card dark color="primary">
|
||||
<v-btn icon dark @click="$emit('close')">
|
||||
<v-icon>close</v-icon>
|
||||
</v-btn>
|
||||
<v-toolbar-title class="text-truncate font-weight-light">Add a new sender</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<v-card-text class='pa-5'>
|
||||
<v-layout row wrap align-center >
|
||||
<v-flex xs12>
|
||||
<p class='headline font-weight-light'>Account</p>
|
||||
<p class='caption'>We first need to know which speckle server the data is going to go to.</p>
|
||||
<v-overflow-btn :items="$store.state.accounts" label="Account" editable solo v-model='selectedAccount' item-text='fullName' return-object></v-overflow-btn>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
<v-layout row wrap align-center v-if='selectedAccount'>
|
||||
<v-flex xs12>
|
||||
<p class='headline font-weight-light'>Stream Name</p>
|
||||
<p class='caption'>Something meaningful would do, like 'walls-final-final-2'.</p>
|
||||
<v-text-field label="walls-final-final-2" single-line solo v-model='newStreamName'></v-text-field>
|
||||
</v-flex>
|
||||
<v-flex xs12>
|
||||
<p class='headline font-weight-light'>Objects</p>
|
||||
<p class='caption'>Will add current object selection to this stream ({{$store.state.selectionCount}}). If no objects are selected in the host application, you will be able to add some later.</p>
|
||||
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
<v-layout row wrap align-center>
|
||||
<v-flex xs12 class='text-xs-left' v-if='!selectedAccount || !selectedAccount.validated'>
|
||||
Could not access that server (is it online?) or no server selected.
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
<br>
|
||||
<v-layout row wrap align-center>
|
||||
<!-- <v-spacer></v-spacer> -->
|
||||
<v-btn block color="primary" :ripple="false" :disabled='!validated' @click.native='addSender()'>
|
||||
Create Sender
|
||||
</v-btn>
|
||||
</v-layout>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'NewClient',
|
||||
props: {
|
||||
isVisible: { type: Boolean, default: false }
|
||||
},
|
||||
watch: {
|
||||
isVisible( val ) {
|
||||
if ( val ) {
|
||||
this.selectedAccount = this.$store.state.accounts.find( ac => ac.IsDefault === true )
|
||||
this.selectedStream = null
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
validated( ) {
|
||||
if ( this.newStreamName !== null )
|
||||
return true
|
||||
return false
|
||||
}
|
||||
},
|
||||
data: ( ) => ( {
|
||||
selectedAccount: null,
|
||||
newStreamName: null,
|
||||
selectedObjects: [ ]
|
||||
} ),
|
||||
methods: {
|
||||
async refreshSelection( ) {
|
||||
let res = await UiBindings.getObjectSelection( )
|
||||
if ( res ) {
|
||||
this.selectedObjects = JSON.parse( res )
|
||||
} else
|
||||
this.selectedObjects = [ ]
|
||||
},
|
||||
refreshStreamsAtAccount( ) {
|
||||
this.$store.dispatch( 'getAccountStreams', this.selectedAccount )
|
||||
},
|
||||
async addSender( ) {
|
||||
let res = await this.$store.dispatch( 'addSenderClient', { account: this.selectedAccount, streamName: this.newStreamName, objects: this.selectedObjects } )
|
||||
this.$emit( "close" )
|
||||
}
|
||||
},
|
||||
mounted( ) {
|
||||
this.refreshSelection( )
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped lang='scss'>
|
||||
</style>
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import Vue from 'vue'
|
||||
import './plugins/vuetify'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
import 'roboto-fontface/css/roboto/roboto-fontface.css'
|
||||
import 'material-design-icons-iconfont/dist/material-design-icons.css'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
import VueTimeago from 'vue-timeago'
|
||||
Vue.use( VueTimeago, {
|
||||
name: 'Timeago',
|
||||
locale: 'en',
|
||||
} )
|
||||
|
||||
// set up an event bus on the window, to be used by the SpeckleUiBindings class to send events here
|
||||
window.EventBus = new Vue( )
|
||||
|
||||
// generic route used for quite a bit of comms
|
||||
window.EventBus.$on( 'update-client', args => {
|
||||
let cl = JSON.parse( args )
|
||||
console.log( cl )
|
||||
window.Store.commit( 'SET_CLIENT_DATA', cl )
|
||||
} )
|
||||
|
||||
// keeps track of the selected objects in revit
|
||||
window.EventBus.$on( 'update-selection-count', args => {
|
||||
let parsed = JSON.parse( args )
|
||||
if ( window.Store )
|
||||
window.Store.commit( "SET_SELECTION_COUNT", parsed.selectedObjectsCount )
|
||||
} )
|
||||
|
||||
window.Store = store
|
||||
window.app = new Vue( {
|
||||
router,
|
||||
store,
|
||||
render: h => h( App )
|
||||
} ).$mount( '#app' )
|
||||
@@ -0,0 +1,16 @@
|
||||
import Vue from 'vue'
|
||||
import Vuetify from 'vuetify'
|
||||
import 'vuetify/dist/vuetify.min.css'
|
||||
|
||||
Vue.use(Vuetify, {
|
||||
theme: {
|
||||
primary: '#0080FF',
|
||||
secondary: '#26c2f2',
|
||||
accent: '#82B1FF',
|
||||
error: '#FF5252',
|
||||
info: '#2196F3',
|
||||
success: '#4CAF50',
|
||||
warning: '#FFC107'
|
||||
},
|
||||
iconfont: 'md',
|
||||
})
|
||||
@@ -0,0 +1,23 @@
|
||||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
import Home from './views/Home.vue'
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
export default new Router({
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: Home
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
name: 'about',
|
||||
// route level code-splitting
|
||||
// this generates a separate chunk (about.[hash].js) for this route
|
||||
// which is lazy-loaded when the route is visited.
|
||||
component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
|
||||
}
|
||||
]
|
||||
})
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import Axios from 'axios'
|
||||
|
||||
Vue.use( Vuex )
|
||||
|
||||
export default new Vuex.Store( {
|
||||
state: {
|
||||
test: {},
|
||||
accounts: [ ],
|
||||
clients: [ ],
|
||||
hostAppName: null,
|
||||
currentFileName: null,
|
||||
errors: [ ],
|
||||
selectionCount: 0
|
||||
},
|
||||
mutations: {
|
||||
ADD_CLIENT( state, client ) {
|
||||
if ( !client.hasOwnProperty( "objects" ) ) client.objects = [ ]
|
||||
state.clients.unshift( client )
|
||||
},
|
||||
|
||||
REMOVE_CLIENT( state, _id ) {
|
||||
let index = state.clients.findIndex( cl => cl._id === _id )
|
||||
if ( index >= 0 )
|
||||
state.clients.splice( index, 1 )
|
||||
else
|
||||
console.error( 'client not found', _id )
|
||||
},
|
||||
|
||||
SET_CLIENT_DATA( state, props ) {
|
||||
let found = state.clients.find( cl => cl._id === props._id )
|
||||
Object.keys( props ).forEach( key => {
|
||||
found[ key ] = props[ key ]
|
||||
} )
|
||||
},
|
||||
|
||||
DELETE_ALL_CLIENTS( state ) {
|
||||
state.clients = [ ]
|
||||
},
|
||||
|
||||
SET_ACCOUNTS( state, accounts ) {
|
||||
state.accounts = accounts
|
||||
},
|
||||
|
||||
SET_ACCOUNT_DATA( state, props ) {
|
||||
let found = state.accounts.find( a => a.AccountId === props.AccountId )
|
||||
Object.keys( props ).forEach( key => {
|
||||
found[ key ] = props[ key ]
|
||||
} )
|
||||
},
|
||||
|
||||
SET_HOST_APP( state, appName ) {
|
||||
state.hostAppName = appName
|
||||
},
|
||||
|
||||
SET_SELECTION_COUNT( state, count ) {
|
||||
state.selectionCount = count
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
bakeReceiver: ( context, client ) => new Promise( async( resolve, reject ) => {
|
||||
await UiBindings.bakeReceiver( JSON.stringify( client ) )
|
||||
client.expired = false
|
||||
client.loading = false
|
||||
context.commit( 'SET_CLIENT_DATA', { _id: client._id, expired: false, loading: false } )
|
||||
} ),
|
||||
|
||||
addSenderClient: ( context, { account, streamName, objects } ) => new Promise( async( resolve, reject ) => {
|
||||
console.log( streamName, objects )
|
||||
let res = await Axios.post( `${account.RestApi}/streams`, { name: streamName }, { headers: { Authorization: account.Token } } )
|
||||
let stream = res.data.resource
|
||||
console.log( stream )
|
||||
|
||||
let client = {...stream }
|
||||
client.objects = objects
|
||||
client.AccountId = account.AccountId
|
||||
client.account = { RestApi: account.RestApi, Email: account.Email, Token: account.Token }
|
||||
client.type = 'sender'
|
||||
client.expired = true
|
||||
client.loading = false
|
||||
client.loadingBlurb = 'This stream might be expired.'
|
||||
client.isLoadingIndeterminate = true
|
||||
client.loadingProgress = 0
|
||||
client.message = ''
|
||||
client.errors = null
|
||||
client.clientId = null
|
||||
|
||||
let docName = await UiBindings.getFileName( )
|
||||
let docId = await UiBindings.getDocumentId( )
|
||||
let clientCreationRes = await Axios.post( `${account.RestApi}/clients`, { documentType: context.state.hostAppName, streamId: stream.streamId, documentName: docName, documentGuid: docId, role: 'sender' }, { headers: { Authorization: account.Token } } )
|
||||
client.clientId = clientCreationRes.data.resource._id
|
||||
|
||||
context.commit( 'ADD_CLIENT', client )
|
||||
|
||||
let dupe = {...client }
|
||||
dupe.account = {...dupe.account }
|
||||
delete dupe.account.Token
|
||||
|
||||
console.log( 'Sending this to ui bindings to add as a receiver' )
|
||||
console.log( client )
|
||||
|
||||
await UiBindings.addSender( JSON.stringify( client ) )
|
||||
return resolve( )
|
||||
} ),
|
||||
|
||||
addReceiverClient: ( context, { account, stream } ) => new Promise( async( resolve, reject ) => {
|
||||
let client = {...stream }
|
||||
|
||||
client.AccountId = account.AccountId
|
||||
client.account = { RestApi: account.RestApi, Email: account.Email, Token: account.Token }
|
||||
client.type = 'receiver'
|
||||
client.expired = true
|
||||
client.loading = false
|
||||
client.loadingBlurb = ''
|
||||
client.isLoadingIndeterminate = true
|
||||
client.loadingProgress = 0
|
||||
client.message = ''
|
||||
client.errors = null
|
||||
client.objects = [ ]
|
||||
client.clientId = null
|
||||
let docName = await UiBindings.getFileName( )
|
||||
let docId = await UiBindings.getDocumentId( )
|
||||
let res = await Axios.post( `${account.RestApi}/clients`, { documentType: context.state.hostAppName, streamId: stream.streamId, documentName: docName, documentGuid: docId, role: 'receiver' }, { headers: { Authorization: account.Token } } )
|
||||
client.clientId = res.data.resource._id
|
||||
context.commit( 'ADD_CLIENT', client )
|
||||
|
||||
let dupe = {...client }
|
||||
dupe.account = {...dupe.account }
|
||||
delete dupe.account.Token
|
||||
await UiBindings.addReceiver( JSON.stringify( client ) )
|
||||
return resolve( )
|
||||
} ),
|
||||
|
||||
removeReceiverClient: ( context, client ) => new Promise( async( resolve, reject ) => {
|
||||
await UiBindings.removeClient( JSON.stringify( client ) )
|
||||
try {
|
||||
await Axios.delete( `${client.account.RestApi}/clients/${client.clientId}`, { headers: { Authorization: client.account.Token } } )
|
||||
// TODO: mark stream as deleted too!
|
||||
} catch {}
|
||||
context.commit( 'REMOVE_CLIENT', client._id )
|
||||
console.log( 'hello refresh - this is important' )
|
||||
} ),
|
||||
|
||||
updateClient: ( context, { client, expire } ) => new Promise( async( resolve, reject ) => {
|
||||
// note: real update, with all the heavy object lifting, happens in .NET
|
||||
let res = await Axios.get( `${client.account.RestApi}/streams/${client.streamId}?fields=name,updatedAt`, { headers: { Authorization: client.account.Token } } )
|
||||
console.log( res.data.resource )
|
||||
let cl = { _id: res.data.resource._id, name: res.data.resource.name, updatedAt: res.data.resource.updatedAt }
|
||||
console.log( expire )
|
||||
if ( expire ) cl.expired = true
|
||||
context.commit( 'SET_CLIENT_DATA', cl )
|
||||
} ),
|
||||
|
||||
flushClients: ( context ) => new Promise( async( resolve, reject ) => {
|
||||
context.commit( 'DELETE_ALL_CLIENTS' )
|
||||
} ),
|
||||
|
||||
getAccounts: ( context ) => new Promise( async( resolve, reject ) => {
|
||||
let res = await UiBindings.getAccounts( )
|
||||
let accounts = JSON.parse( res )
|
||||
|
||||
accounts.forEach( ac => {
|
||||
ac.fullName = ac.Email + ' - ' + ac.ServerName
|
||||
ac.streams = [ ]
|
||||
ac.validated = false
|
||||
context.dispatch( 'getAccountStreams', ac )
|
||||
} )
|
||||
|
||||
context.commit( 'SET_ACCOUNTS', accounts )
|
||||
} ),
|
||||
|
||||
getAccountStreams: ( context, account ) => new Promise( async( resolve, reject ) => {
|
||||
Axios.get( `${account.RestApi}/streams?fields=streamId,name,updatedAt,parent&deleted=false&isComputedResult=false&sort=updatedAt`, { headers: { Authorization: account.Token } } )
|
||||
.then( res => {
|
||||
res.data.resources.forEach( s => s.fullName = `${s.streamId} - ${s.name}` )
|
||||
let sorted = res.data.resources.sort( ( a, b ) => {
|
||||
let ad = new Date( a.updatedAt )
|
||||
let bd = new Date( b.updatedAt )
|
||||
return ad > bd ? -1 : 1
|
||||
} ).filter( s => s.parent === null )
|
||||
context.commit( 'SET_ACCOUNT_DATA', {...account, validated: true, streams: sorted } )
|
||||
resolve( res.data.resources )
|
||||
} )
|
||||
.catch( err => {
|
||||
// console.log( err )
|
||||
context.commit( 'SET_ACCOUNT_DATA', {...account, validated: false } )
|
||||
reject( err )
|
||||
} )
|
||||
} ),
|
||||
|
||||
getApplicationHostName: ( context ) => new Promise( async( resolve, reject ) => {
|
||||
let res = await UiBindings.getApplicationHostName( )
|
||||
context.commit( 'SET_HOST_APP', res )
|
||||
} ),
|
||||
|
||||
getExistingClients: ( context ) => new Promise( async( resolve, reject ) => {
|
||||
let clients = JSON.parse( await UiBindings.getFileClients( ) )
|
||||
console.log( clients )
|
||||
if ( clients.length === 0 ) return resolve( )
|
||||
clients.forEach( existingClient => {
|
||||
try {
|
||||
let account = context.state.accounts.find( ac => ac.Email === existingClient.account.Email && ac.RestApi === existingClient.account.RestApi )
|
||||
if ( account !== null ) {
|
||||
existingClient.account.Token = account.Token
|
||||
context.commit( 'ADD_CLIENT', existingClient )
|
||||
// TODO: update state on server (client: online)
|
||||
} else {
|
||||
console.warn( 'no account found for client. sorrrrry!', existingClient )
|
||||
}
|
||||
} catch {
|
||||
console.warn( 'Error in recreating client ' + existingClient.streamId )
|
||||
}
|
||||
} )
|
||||
} ),
|
||||
|
||||
cloneStream: ( context, client ) => new Promise( async( resolve, reject ) => {
|
||||
let res = await Axios.post( `${client.account.RestApi}/streams/${client.streamId}/clone`, null, { headers: { Authorization: client.account.Token } } )
|
||||
console.log( res.data )
|
||||
let tempClient = { _id: client._id, children: res.data.parent.children }
|
||||
context.commit( 'SET_CLIENT_DATA', tempClient )
|
||||
} )
|
||||
}
|
||||
} )
|
||||
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div class="about">
|
||||
<h1>This is an about page</h1>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<HelloWorld />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HelloWorld from '../components/HelloWorld'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HelloWorld
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
publicPath: ""
|
||||
}
|
||||
Reference in New Issue
Block a user