feat(frontend): various improvements

branches edit/delete/create; fixed and removed dupe apollo queries and minimised requests; fixed
dialog operations
This commit is contained in:
Dimitrie Stefanescu
2020-12-20 22:34:18 +02:00
parent 147f215ff9
commit 074a712fac
21 changed files with 560 additions and 627 deletions
+28 -94
View File
@@ -3,12 +3,7 @@
<v-app-bar app color="background2">
<v-container class="py-0 fill-height hidden-sm-and-down">
<v-btn text to="/" active-class="no-active">
<v-img
contain
max-height="30"
max-width="30"
src="./assets/logo.svg"
/>
<v-img contain max-height="30" max-width="30" src="./assets/logo.svg" />
<div class="mt-1">
<span class="primary--text"><b></b></span>
</div>
@@ -38,12 +33,7 @@
</v-col>
<v-col class="text-center">
<v-btn text to="/" active-class="no-active" icon>
<v-img
contain
max-height="40"
max-width="40"
src="./assets/logo.svg"
/>
<v-img contain max-height="40" max-width="40" src="./assets/logo.svg" />
</v-btn>
</v-col>
<v-col class="text-right" style="margin-top: 8px">
@@ -52,11 +42,7 @@
</v-row>
</v-container>
</v-app-bar>
<v-card
v-show="showMobileMenu"
style="position: relative; top: 40px"
class="pa-5"
>
<v-card v-show="showMobileMenu" style="position: relative; top: 40px" class="pa-5">
<v-row>
<v-col v-for="link in navLinks" :key="link.name" cols="12">
<v-btn text block :to="link.link">
@@ -75,22 +61,22 @@
</v-app>
</template>
<script>
import userQuery from "./graphql/user.gql"
import gql from "graphql-tag"
import UserMenuTop from "./components/UserMenuTop"
import SearchBar from "./components/SearchBar"
import userQuery from './graphql/user.gql'
import gql from 'graphql-tag'
import UserMenuTop from './components/UserMenuTop'
import SearchBar from './components/SearchBar'
export default {
components: { UserMenuTop, SearchBar },
data: () => ({
search: "",
search: '',
showMobileMenu: false,
streams: { items: [] },
selectedSearchResult: null,
navLinks: [
{ link: "/streams", name: "streams" },
{ link: "/profile", name: "profile" },
{ link: "/help", name: "help" }
{ link: '/streams', name: 'streams' },
{ link: '/profile', name: 'profile' },
{ link: '/help', name: 'help' }
]
}),
apollo: {
@@ -126,7 +112,7 @@ export default {
},
computed: {
background() {
let theme = this.$vuetify.theme.dark ? "dark" : "light"
let theme = this.$vuetify.theme.dark ? 'dark' : 'light'
return `background-color: ${this.$vuetify.theme.themes[theme].background};`
}
},
@@ -159,70 +145,18 @@ export default {
margin-bottom: 10px;
}
.v-card__text,
.v-card__title {
word-break: normal !important;
}
.streamid {
font-family: monospace !important;
}
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
text-decoration-color: rgba(10, 102, 255, 0.25);
}
.no-decor a:hover {
text-decoration: none;
}
.v-btn--active.no-active::before {
opacity: 0 !important;
}
.hoverable-border {
border: 1px transparent;
}
.hoverable-border:hover {
border: 1px blue;
}
/* .theme--dark {
/color: #cfcdcc !important;
} */
/* don't like fat text */
.v-list-item--dense .v-list-item__title,
.v-list-item--dense .v-list-item__subtitle,
.v-list--dense .v-list-item .v-list-item__title,
.v-list--dense .v-list-item .v-list-item__subtitle {
font-weight: 400 !important;
}
/*WHYYYY*/
.v-tooltip__content {
pointer-events: all !important;
opacity: 1 !important;
}
/* DARK MODE HARD FIXES */
.theme--dark.v-list {
background-color: #303132 !important;
}
/*.v-application code {
background-color: #969696;
color: #171717;
padding: 0 0.4rem;
}*/
/* TOOLTIPs */
.tooltip {
display: block !important;
z-index: 10000;
font-family: "Roboto", sans-serif !important;
font-family: 'Roboto', sans-serif !important;
font-size: 0.75rem !important;
}
@@ -243,11 +177,11 @@ a:hover {
z-index: 1;
}
.tooltip[x-placement^="top"] {
.tooltip[x-placement^='top'] {
margin-bottom: 5px;
}
.tooltip[x-placement^="top"] .tooltip-arrow {
.tooltip[x-placement^='top'] .tooltip-arrow {
border-width: 5px 5px 0 5px;
border-left-color: transparent !important;
border-right-color: transparent !important;
@@ -258,11 +192,11 @@ a:hover {
margin-bottom: 0;
}
.tooltip[x-placement^="bottom"] {
.tooltip[x-placement^='bottom'] {
margin-top: 5px;
}
.tooltip[x-placement^="bottom"] .tooltip-arrow {
.tooltip[x-placement^='bottom'] .tooltip-arrow {
border-width: 0 5px 5px 5px;
border-left-color: transparent !important;
border-right-color: transparent !important;
@@ -273,11 +207,11 @@ a:hover {
margin-bottom: 0;
}
.tooltip[x-placement^="right"] {
.tooltip[x-placement^='right'] {
margin-left: 5px;
}
.tooltip[x-placement^="right"] .tooltip-arrow {
.tooltip[x-placement^='right'] .tooltip-arrow {
border-width: 5px 5px 5px 0;
border-left-color: transparent !important;
border-top-color: transparent !important;
@@ -288,11 +222,11 @@ a:hover {
margin-right: 0;
}
.tooltip[x-placement^="left"] {
.tooltip[x-placement^='left'] {
margin-right: 5px;
}
.tooltip[x-placement^="left"] .tooltip-arrow {
.tooltip[x-placement^='left'] .tooltip-arrow {
border-width: 5px 0 5px 5px;
border-top-color: transparent !important;
border-right-color: transparent !important;
@@ -315,13 +249,13 @@ a:hover {
border-color: #f9f9f9;
}
.tooltip[aria-hidden="true"] {
.tooltip[aria-hidden='true'] {
visibility: hidden;
opacity: 0;
transition: opacity 0.15s, visibility 0.15s;
}
.tooltip[aria-hidden="false"] {
.tooltip[aria-hidden='false'] {
visibility: visible;
opacity: 1;
transition: opacity 0.15s;
@@ -27,7 +27,7 @@
<h3>Are you sure?</h3>
You cannot undo this action. This will permanently delete the
<b>{{ token.name }}</b>
app. Any scripts relying on it will stop working.
token. Any scripts relying on it will stop working.
<v-divider class="my-3"></v-divider>
<v-btn text color="error" @click="revokeToken">Delete</v-btn>
<v-btn @click="showRevokeConfirm = false">Cancel</v-btn>
+23 -11
View File
@@ -1,11 +1,15 @@
<template>
<v-card rounded="lg" class="pa-4" elevation="0" color="transparent">
<div v-if="!stream">
<v-skeleton-loader type="card, article, article"></v-skeleton-loader>
</div>
<div v-if="stream">
<v-card rounded="lg" class="pa-4" elevation="0" color="transparent" :loading="$apollo.loading">
<template slot="progress">
<v-progress-linear indeterminate></v-progress-linear>
</template>
<div>
<v-card-title class="mr-8">
<router-link v-show="!isHomeRoute" :to="'/streams/' + stream.id">
<router-link
v-show="!isHomeRoute"
:to="'/streams/' + stream.id"
class="text-decoration-none"
>
{{ stream.name }}
</router-link>
<div v-show="isHomeRoute">
@@ -94,6 +98,7 @@
</v-card>
</template>
<script>
import streamQuery from '../graphql/stream.gql'
import EditStreamDialog from '../components/dialogs/EditStreamDialog'
import StreamShareDialog from '../components/dialogs/StreamShareDialog'
import UserAvatar from '../components/UserAvatar'
@@ -105,15 +110,21 @@ export default {
UserAvatar
},
props: {
stream: {
type: Object,
default: () => null
},
userRole: {
type: String,
default: null
}
},
apollo: {
stream: {
query: streamQuery,
variables() {
return {
id: this.$route.params.streamId
}
}
}
},
data: () => ({
editStreamDialog: false,
dialogShare: false
@@ -136,8 +147,9 @@ export default {
methods: {
editClosed() {
this.editStreamDialog = false
this.$emit('refresh')
this.$apollo.queries.stream.refetch()
}
}
}
</script>
<style scoped></style>
+9 -10
View File
@@ -2,17 +2,16 @@
<v-card color="background2" class="elevation-0 mt-3">
<v-card-title>Personal Access Tokens</v-card-title>
<v-card-text>
Personal Access Tokens can be used to access the Speckle API on this
server; they function like ordinary OAuth access tokens. Use them in your
scripts or apps!
Personal Access Tokens can be used to access the Speckle API on this server; they function
like ordinary OAuth access tokens. Use them in your scripts or apps!
<b>
Treat them like a password: do not post them anywhere where they could
be accessed by others (e.g., public repos).
Treat them like a password: do not post them anywhere where they could be accessed by others
(e.g., public repos).
</b>
</v-card-text>
<v-card-text v-if="$apollo.loading">Loading...</v-card-text>
<v-card-text v-if="tokens && tokens.length != 0">
<v-list three-line>
<v-list three-line class="transparent">
<list-item-token
v-for="token in tokens"
:key="token.id"
@@ -23,7 +22,7 @@
</v-card-text>
<v-card-text v-else>You have no api tokens.</v-card-text>
<v-card-text>
<v-btn @click="tokenDialog = true" class="mb-5">new token</v-btn>
<v-btn class="mb-5" @click="tokenDialog = true">new token</v-btn>
<v-dialog v-model="tokenDialog" persistent width="500">
<token-dialog @token-added="refreshList" @close="tokenDialog = false" />
</v-dialog>
@@ -31,9 +30,9 @@
</v-card>
</template>
<script>
import gql from "graphql-tag"
import ListItemToken from "./ListItemPersonalAccessToken"
import TokenDialog from "./dialogs/TokenDialog"
import gql from 'graphql-tag'
import ListItemToken from './ListItemPersonalAccessToken'
import TokenDialog from './dialogs/TokenDialog'
export default {
components: { ListItemToken, TokenDialog },
+7 -12
View File
@@ -2,18 +2,13 @@
<v-card color="background2" class="elevation-0 mt-3 mb-5">
<v-card-title>Applications</v-card-title>
<v-card-text>
Register and manage third-party Speckle Apps that, once authorised by a
user on this server, can act on their behalf.
Register and manage third-party Speckle Apps that, once authorised by a user on this server,
can act on their behalf.
</v-card-text>
<v-card-text v-if="$apollo.loading">Loading...</v-card-text>
<v-card-text v-if="apps && apps.length !== 0">
<v-list two-line>
<list-item-user-app
v-for="app in apps"
:key="app.id"
:app="app"
@deleted="refreshList"
/>
<v-list two-line class="transparent">
<list-item-user-app v-for="app in apps" :key="app.id" :app="app" @deleted="refreshList" />
</v-list>
</v-card-text>
<v-card-text v-else>You have no apps.</v-card-text>
@@ -26,9 +21,9 @@
</v-card>
</template>
<script>
import gql from "graphql-tag"
import ListItemUserApp from "./ListItemUserApp"
import NewAppDialog from "./dialogs/NewAppDialog"
import gql from 'graphql-tag'
import ListItemUserApp from './ListItemUserApp'
import NewAppDialog from './dialogs/NewAppDialog'
export default {
components: { ListItemUserApp, NewAppDialog },
+12 -16
View File
@@ -1,30 +1,26 @@
<template>
<v-card color="transparent" class="elevation-0">
<v-card color="transparent" class="elevation-0 text-center">
<div v-if="!user">
<v-skeleton-loader type="card"></v-skeleton-loader>
</div>
<div v-else>
<v-card-title class="text-center mb-5 mt-5 pt-5 pb-5">
<v-card-title class="text-center mb-5 mt-5 pt-15 pb-15">
<v-btn
v-tooltip="'Change your profile picture.'"
color="transparent"
text
block
:disabled="!isSelf"
class="elevation-0 pa-0 ma-0"
>
<v-avatar class="elevation-1" size="124" @click="avatarDialog = true">
<v-avatar class="elevation-0" size="100" @click="avatarDialog = true">
<v-img v-if="user.avatar" :src="user.avatar" />
<v-img
v-else
:src="`https://robohash.org/` + user.id + `.png?size=64x64`"
/>
<v-img v-else :src="`https://robohash.org/` + user.id + `.png?size=64x64`" />
</v-avatar>
</v-btn>
<v-dialog v-model="avatarDialog" max-width="400">
<v-card>
<v-card-title class="text-center">
Choose a new profile picture
</v-card-title>
<v-card-title class="text-center">Choose a new profile picture</v-card-title>
<v-card-text class="text-center pa-0 ma-0 mt-5">
<v-image-input
v-model="imageData"
@@ -38,7 +34,7 @@
/>
</v-card-text>
<v-card-actions>
<span class="caption" v-if="imageData">You look wonderful!</span>
<span v-if="imageData" class="caption">You look wonderful!</span>
<v-spacer></v-spacer>
<v-btn text @click="avatarDialog = false">cancel</v-btn>
<v-btn :disabled="!imageData" @click="updateAvatar">Save</v-btn>
@@ -46,7 +42,7 @@
</v-card>
</v-dialog>
</v-card-title>
<v-card-title>
<v-card-title class="text-center justify-center">
{{ user.name }}
</v-card-title>
<v-card-text>
@@ -75,9 +71,9 @@
</v-card>
</template>
<script>
import gql from "graphql-tag"
import UserDialog from "../components/dialogs/UserDialog"
import VImageInput from "vuetify-image-input/a-la-carte"
import gql from 'graphql-tag'
import UserDialog from '../components/dialogs/UserDialog'
import VImageInput from 'vuetify-image-input/a-la-carte'
export default {
components: { UserDialog, VImageInput },
@@ -97,7 +93,7 @@ export default {
computed: {
isSelf() {
if (!this.user) return false
return this.user.id === localStorage.getItem("uuid")
return this.user.id === localStorage.getItem('uuid')
}
},
methods: {
@@ -1,186 +0,0 @@
<template>
<v-dialog v-model="show" width="500" @keydown.esc="cancel">
<v-card class="pa-4" color="background2">
<v-card-title class="subtitle-1">
{{ isEdit ? `Edit` : `New` }} Branch
</v-card-title>
<v-card-text class="pl-2 pr-2 pt-0 pb-0">
<v-form
ref="form"
v-model="valid"
lazy-validation
@submit.prevent="agree"
>
<v-container>
<v-row>
<v-col cols="12" class="pb-0">
<v-text-field
v-model="branch.name"
label="Name"
:rules="nameRules"
required
filled
:disabled="branch.name == 'main'"
autofocus
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" class="pt-0 pb-0">
<v-textarea
v-model="branch.description"
filled
rows="2"
label="Description"
></v-textarea>
</v-col>
</v-row>
<v-row v-if="isEdit && branch.name != 'main'">
<v-col cols="12" class="pt-2 pb-2">
<div v-if="!pendingDelete">
<v-btn
color="error"
depressed
class="mt-5"
@click="pendingDelete = true"
>
Delete Branch
</v-btn>
<p
class="ml-4 mt-0 pt-0 caption"
style="display: inline-flex; width: 250px"
>
Delete this branch forever, no going back here!
</p>
</div>
<div v-if="pendingDelete">
<v-btn color="error" depressed @click.native="doDelete">
Yes
</v-btn>
<v-btn class="ml-5" depressed @click="pendingDelete = false">
No
</v-btn>
<p
class="ml-4 mt-0 pt-0 caption"
style="display: inline-flex; width: 150px"
>
Are you sure?
</p>
</div>
</v-col>
</v-row>
</v-container>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="primary" text @click.native="agree">
{{ isEdit ? `Save` : `Create` }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
props: ["branches"],
data() {
return {
dialog: false,
branch: {},
name: "",
nameRules: [],
description: "",
valid: true,
isEdit: false,
pendingDelete: false
}
},
computed: {
show: {
get() {
return this.dialog
},
set(value) {
this.dialog = value
if (value === false) {
this.cancel()
}
}
}
},
watch: {
"branch.name"(val) {
this.nameRules = []
}
},
methods: {
open(branch, streamId) {
//set defaults
this.dialog = true
this.pendingDelete = false
this.isEdit = false
this.branch = {}
if (this.$refs.form) this.$refs.form.resetValidation()
if (branch && streamId) {
this.branch = {
id: branch.id,
streamId: streamId,
name: branch.name,
description: branch.description
}
this.isEdit = true
}
return new Promise((resolve, reject) => {
this.resolve = resolve
this.reject = reject
})
},
agree() {
//prevents annoying validation message from popping at each keystroke
//to be used in conjunction with the watch event above and the timer function
//source: https://stackoverflow.com/a/57555332
this.nameRules = [
(v) => !!v || "Branches need a name too!",
(v) =>
(v &&
this.branches.filter((e) => e.name === v && e.id !== this.branch.id)
.length === 0) ||
"A branch with this name already exists",
(v) => (v && v.length <= 25) || "Name must be less than 25 characters",
(v) => (v && v.length >= 3) || "Name must be at least 3 characters"
]
let self = this
setTimeout(function () {
if (self.$refs.form.validate()) {
self.resolve({
result: true,
branch: self.branch
})
self.dialog = false
}
})
},
cancel() {
this.resolve({
result: false
})
this.dialog = false
},
doDelete() {
this.resolve({
result: true,
delete: true
})
this.dialog = false
}
}
}
</script>
@@ -0,0 +1,158 @@
<template>
<v-card :loading="loading">
<template slot="progress">
<v-progress-linear indeterminate></v-progress-linear>
</template>
<div v-if="branch.name !== 'main'">
<v-card-title>Edit Branch</v-card-title>
<v-card-text>
<v-form ref="form" v-model="valid" lazy-validation>
<v-text-field
v-model="name"
label="Name"
:rules="nameRules"
required
autofocus
></v-text-field>
<v-textarea v-model="description" rows="2" label="Description"></v-textarea>
</v-form>
</v-card-text>
<v-card-actions>
<v-btn color="primary" block :disabled="!valid" @click="updateBranch">Save</v-btn>
</v-card-actions>
<v-card-actions class="error--text body-2 pa-2">
<v-btn block x-small text color="error" @click="showDelete = true">Delete Branch</v-btn>
<v-dialog v-model="showDelete" max-width="500">
<v-card>
<v-card-title>Are you sure?</v-card-title>
<v-card-text>
You cannot undo this action. The branch
<b>{{ name }}</b>
will be permanently deleted.
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn @click="showDelete = false">Cancel</v-btn>
<v-btn color="error" text @click="deleteBranch">Delete</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-card-actions>
</div>
<div v-else>
<v-card-text>You cannot edit the main branch.</v-card-text>
</div>
</v-card>
</template>
<script>
import gql from 'graphql-tag'
export default {
props: {
streamId: {
type: String,
default: null
},
branch: {
type: Object,
default() {
return {
name: null,
description: null
}
}
}
},
data() {
return {
valid: true,
loading: false,
name: this.branch.name,
showDelete: false,
nameRules: [
(v) => !!v || 'Branches need a name too!',
(v) =>
(v && this.allBranchNames.findIndex((e) => e === v) === -1) ||
'A branch with this name already exists',
(v) => (v && v.length <= 25) || 'Name must be less than 25 characters',
(v) => (v && v.length >= 3) || 'Name must be at least 3 characters',
],
description: this.branch.description,
isEdit: false,
pendingDelete: false,
allBranchNames: []
}
},
apollo: {
allBranchNames: {
query: gql`
query branchNames($id: String!) {
stream(id: $id) {
id
branches {
items {
name
}
}
}
}
`,
variables() {
return {
id: this.$route.params.streamId
}
},
update(data) {
return data.stream.branches.items
.map((b) => b.name)
.filter((name) => name !== this.branch.name)
}
}
},
computed: {},
methods: {
async deleteBranch() {
this.loading = true
try {
await this.$apollo.mutate({
mutation: gql`
mutation branchDelete($params: BranchDeleteInput!) {
branchDelete(branch: $params)
}
`,
variables: {
params: {
streamId: this.$route.params.streamId,
id: this.branch.id
}
}
})
} catch (e) {
console.log(e)
}
this.$emit('close', { deleted: true })
this.loading = false
},
async updateBranch() {
this.loading = true
await this.$apollo.mutate({
mutation: gql`
mutation branchUpdate($params: BranchUpdateInput!) {
branchUpdate(branch: $params)
}
`,
variables: {
params: {
streamId: this.$route.params.streamId,
id: this.branch.id,
name: this.name,
description: this.description
}
}
})
this.loading = false
this.$emit('close', { name: this.name })
}
}
}
</script>
@@ -0,0 +1,80 @@
<template>
<v-card :loading="loading">
<template slot="progress">
<v-progress-linear indeterminate></v-progress-linear>
</template>
<v-card-title>New Branch</v-card-title>
<v-card-text>
<v-form ref="form" v-model="valid" lazy-validation>
<v-text-field
v-model="name"
label="Name"
:rules="nameRules"
required
autofocus
></v-text-field>
<v-textarea v-model="description" rows="2" label="Description"></v-textarea>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="primary" text :disabled="!valid" @click="createBranch">Save</v-btn>
</v-card-actions>
</v-card>
</template>
<script>
import gql from 'graphql-tag'
export default {
props: {
streamId: {
type: String,
default: null
},
branchNames: {
type: Array,
default: () => []
}
},
data() {
return {
valid: true,
loading: false,
name: null,
nameRules: [
(v) => !!v || 'Branches need a name too!',
(v) =>
(v && this.branchNames.findIndex((e) => e === v) === -1) ||
'A branch with this name already exists',
(v) => (v && v.length <= 25) || 'Name must be less than 25 characters',
(v) => (v && v.length >= 3) || 'Name must be at least 3 characters'
],
description: null,
isEdit: false,
pendingDelete: false
}
},
computed: {},
methods: {
async createBranch() {
this.loading = true
await this.$apollo.mutate({
mutation: gql`
mutation branchCreate($params: BranchCreateInput!) {
branchCreate(branch: $params)
}
`,
variables: {
params: {
streamId: this.streamId,
name: this.name,
description: this.description
}
}
})
this.loading = false
this.$emit('close')
}
}
}
</script>
@@ -34,11 +34,11 @@
</v-card-actions>
</v-form>
<v-divider class="my-5" v-show="!showDelete" />
<v-card-title v-show="!showDelete" class="error--text body-2">
<v-btn block x-small outlined color="error" @click="showDelete = true">Delete Stream</v-btn>
<v-card-title v-show="!showDelete" class="error--text body-2 pa-2">
<v-btn block x-small text color="error" @click="showDelete = true">Delete Stream</v-btn>
</v-card-title>
<v-card-text v-show="showDelete" class="caption py-5">
<h3 class="error--text">Deleting Stream {{ internalName }}</h3>
<h2 class="error--text py-3">Deleting Stream {{ internalName }}</h2>
<span class="error--text">
Type the name of the stream below to confirm you really want to delete it.
<b>You cannot undo this action.</b>
@@ -48,6 +48,9 @@
label="confirm stream name"
class="pr-5"
></v-text-field>
</v-card-text>
<v-card-actions v-show="showDelete">
<v-spacer></v-spacer>
<v-btn
class="mr-3"
color="error"
@@ -58,7 +61,7 @@
delete
</v-btn>
<v-btn @click="showDelete = false">Cancel</v-btn>
</v-card-text>
</v-card-actions>
</v-card>
</template>
<script>
@@ -1,10 +1,27 @@
<template>
<v-card class="pa-4" color="background2">
<v-card-title class="subtitle-1">Edit Description</v-card-title>
<v-card-text>
<v-card :loading="loading">
<template slot="progress">
<v-progress-linear indeterminate></v-progress-linear>
</template>
<v-card-title>Edit Description</v-card-title>
<v-card-text class="py-0 my-0">
<v-row>
<v-col cols="12" sm="12" md="6">
<p>Markdown is enabled.</p>
<p class="caption">
Use Markdown! Tips:
<code>#, ##, ###</code>
prefix headings, links:
<code>[speckle](https://speckle.systems)</code>
, images:
<code>![image title](image url)</code>
, list items are prefixed by
<code>-</code>
on new lines,
<b>bold</b>
text by surrounding it with
<code>**</code>
, etc.
</p>
<v-textarea
v-model="innerStreamDescription"
auto-grow
@@ -14,13 +31,13 @@
></v-textarea>
</v-col>
<v-col cols="12" sm="12" md="6">
<p>Preview</p>
<p class="caption">Preview</p>
<div class="marked-preview" v-html="compiledMarkdown"></div>
</v-col>
</v-row>
</v-card-text>
<v-card-actions>
<v-btn @click.native="save">Save & Close</v-btn>
<v-card-actions class="pb-10">
<v-btn block @click.native="save">Save & Close</v-btn>
</v-card-actions>
</v-card>
</template>
@@ -31,37 +48,33 @@ import gql from 'graphql-tag'
export default {
props: {
id: String,
id: {
type: String,
default: null
},
description: {
type: String,
default: null
}
},
data: () => ({
innerStreamDescription: null
}),
data() {
return {
innerStreamDescription: this.description,
loading: false
}
},
computed: {
compiledMarkdown() {
if (!this.innerStreamDescription) return ''
let md = marked(this.innerStreamDescription)
return DOMPurify.sanitize(md)
},
streamDescription: {
get() {
return this.innerStreamDescription
},
set(value) {
this.innerStreamDescription = value
}
}
},
mounted() {
this.innerStreamDescription = this.description
},
methods: {
async save() {
this.loading = true
try {
this.$apollo.mutate({
await this.$apollo.mutate({
mutation: gql`
mutation editDescription($input: StreamUpdateInput!) {
streamUpdate(stream: $input)
@@ -74,10 +87,11 @@ export default {
}
}
})
this.$emit('close', this.innerStreamDescription)
} catch (e) {
console.log(e)
}
this.loading = false
this.$emit('close')
}
}
}
@@ -1,5 +1,5 @@
<template>
<v-card class="" color="background2" :loading="loading">
<v-card :loading="loading">
<template slot="progress">
<v-progress-linear indeterminate></v-progress-linear>
</template>
-9
View File
@@ -15,15 +15,6 @@ query Stream($id: String!) {
}
branches {
totalCount
cursor
items {
id
name
description
commits {
totalCount
}
}
}
commits {
totalCount
+16
View File
@@ -0,0 +1,16 @@
query StreamBranches($id: String!) {
stream(id: $id) {
id
branches {
totalCount
items{
id
name
description
commits {
totalCount
}
}
}
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
query Stream($id: String!) {
query StreamCommits($id: String!) {
stream(id: $id) {
id
commits {
+69 -52
View File
@@ -12,6 +12,12 @@
<v-card-text>
{{ branch.description }}
</v-card-text>
<v-card-actions>
<v-btn small @click="dialogEdit = true">Edit</v-btn>
<v-dialog v-model="dialogEdit" max-width="500">
<branch-edit-dialog :branch="branch" @close="closeEdit" />
</v-dialog>
</v-card-actions>
</v-card>
<v-card>
<v-expansion-panels flat focusable>
@@ -22,9 +28,8 @@
<v-expansion-panel-content>
<p class="caption mt-4">
<b>Grasshopper & Dynamo:</b>
Copy and paste this page's url into a text panel and connect
that to the "Stream" input of a receiver component or sender
component.
Copy and paste this page's url into a text panel and connect that to the "Stream"
input of a receiver component or sender component.
<b>Senders</b>
will push commits to this branch, whereas
<b>receivers</b>
@@ -43,15 +48,16 @@
</v-expansion-panels>
</v-card>
<v-card class="pa-4" elevation="0" rounded="lg" color="background2">
<v-subheader class="text-uppercase">
Commits ({{ branch.commits.totalCount }})
</v-subheader>
<v-subheader class="text-uppercase">Commits ({{ branch.commits.totalCount }})</v-subheader>
<v-card-text v-if="branch.commits.totalCount === 0">
It's a bit lonely here: there are no commits on this branch.
</v-card-text>
<v-card-text>
<list-item-commit
v-for="item in branch.commits.items"
:key="item.id"
:commit="item"
:stream-id="stream.id"
:stream-id="streamId"
></list-item-commit>
</v-card-text>
</v-card>
@@ -59,29 +65,21 @@
</v-row>
</template>
<script>
import gql from "graphql-tag"
import SidebarStream from "../components/SidebarStream"
import streamQuery from "../graphql/stream.gql"
import branchQuery from "../graphql/branch.gql"
import ListItemCommit from "../components/ListItemCommit"
import CommitDialog from "../components/dialogs/CommitDialog"
import gql from 'graphql-tag'
import branchQuery from '../graphql/branch.gql'
import ListItemCommit from '../components/ListItemCommit'
import BranchEditDialog from '../components/dialogs/BranchEditDialog'
export default {
name: "Commit",
components: { SidebarStream, CommitDialog, ListItemCommit },
data: () => ({ selectedBranch: 0 }),
name: 'Branch',
components: { ListItemCommit, BranchEditDialog },
data() {
return {
dialogEdit: false
}
},
apollo: {
stream: {
prefetch: true,
query: streamQuery,
variables() {
return {
id: this.$route.params.streamId
}
}
},
branch: {
prefetch: true,
query: branchQuery,
variables() {
return {
@@ -89,36 +87,55 @@ export default {
branchName: this.$route.params.branchName
}
},
update: (data) => data.stream.branch
update: (data) => data.stream.branch,
error(error) {
this.$router.push({ path: '/error' })
}
}
},
computed: {
streamId() {
return this.$route.params.streamId
}
},
computed: {},
methods: {
editBranch() {
this.$refs.commitDialog
.open(this.stream.commit, this.stream.id)
.then((dialog) => {
if (!dialog.result) return
this.$apollo
.mutate({
mutation: gql`
mutation commitUpdate($myCommit: CommitUpdateInput!) {
commitUpdate(commit: $myCommit)
}
`,
variables: {
myCommit: { ...dialog.commit }
}
})
.then((data) => {
this.$apollo.queries.stream.refetch()
})
.catch((error) => {
// Error
console.error(error)
})
closeEdit({ name, deleted }) {
this.dialogEdit = false
if (deleted) {
this.$router.push({ path: `/streams/${this.streamId}` })
return
}
if (name !== this.$route.params.branchName) {
this.$router.push({
path: `/streams/${this.streamId}/branches/${encodeURIComponent(name)}`
})
return
}
this.$apollo.queries.branch.refetch()
},
editBranch() {
this.$refs.commitDialog.open(this.stream.commit, this.stream.id).then((dialog) => {
if (!dialog.result) return
this.$apollo
.mutate({
mutation: gql`
mutation commitUpdate($myCommit: CommitUpdateInput!) {
commitUpdate(commit: $myCommit)
}
`,
variables: {
myCommit: { ...dialog.commit }
}
})
.then((data) => {
this.$apollo.queries.stream.refetch()
})
.catch((error) => {
// Error
console.error(error)
})
})
}
}
}
+5 -11
View File
@@ -1,21 +1,15 @@
<template>
<v-container>
<v-row>
<v-col cols="3">
<server-info-card></server-info-card>
</v-col>
<v-col cols="9">
<v-row align="center" justify="center">
<v-col cols="12" md="8">
<v-sheet rounded="lg" class="pa-15 text-center" color="background2">
<h1>Need Help?</h1>
<p class="ma-10 subtitle-1 font-weight-light">
Get free help from the
<a href="https://discourse.speckle.works/">
Speckle Community forum
</a>
<a href="https://discourse.speckle.works/">Speckle Community forum</a>
or
<a href="mailto:hello@speckle.systems">contact us</a>
to set up a support agreement and one of our engineers will help
right away 🚀!
to set up a support agreement and one of our engineers will help right away 🚀!
</p>
</v-sheet>
</v-col>
@@ -23,7 +17,7 @@
</v-container>
</template>
<script>
import ServerInfoCard from "../components/ServerInfoCard"
import ServerInfoCard from '../components/ServerInfoCard'
export default {
components: { ServerInfoCard }
+1 -1
View File
@@ -14,7 +14,7 @@
<new-stream-dialog :open="newStreamDialog" />
</v-dialog>
<v-btn href="https://twitter.com/specklesystems" target="_blank" text small>
<v-btn href="https://twitter.com/specklesystems" target="_blank" block text>
<v-icon small class="mr-2">mdi-twitter</v-icon>
Speckle on Twitter!
</v-btn>
+10 -7
View File
@@ -7,13 +7,16 @@
<v-col cols="12" sm="12" md="8" lg="9" xl="7">
<v-card v-if="user" class="mb-3">
<v-card-text class="body-1">
You have
<v-icon small>mdi-compare-vertical</v-icon>
<b>{{ user.streams.totalCount }}</b>
streams and
<v-icon small>mdi-source-commit</v-icon>
<b>{{ user.commits.totalCount }}</b>
commits.
<span>
You have
<v-icon small>mdi-compare-vertical</v-icon>
<b>{{ user.streams.totalCount }}</b>
streams and
<v-icon small>mdi-source-commit</v-icon>
<b>{{ user.commits.totalCount }}</b>
commits.
</span>
<v-btn icon to="/streams"><v-icon>mdi-arrow-right</v-icon></v-btn>
</v-card-text>
</v-card>
<v-alert type="info">Heads up! The sections below are intended for developers.</v-alert>
+5 -62
View File
@@ -1,24 +1,11 @@
<template>
<v-container v-if="error">
<v-card>
<v-card-title>Something went wrong.</v-card-title>
</v-card>
</v-container>
<v-container v-else>
<v-row v-if="$apollo.loading">
<v-col cols="12" sm="12" md="4" lg="3" xl="2">
<v-skeleton-loader type="card, article"></v-skeleton-loader>
</v-col>
<v-col cols="12" sm="12" md="8" lg="9" xl="7">
<v-skeleton-loader type="article, article"></v-skeleton-loader>
</v-col>
</v-row>
<v-container>
<v-row v-if="stream">
<v-col cols="12" sm="12" md="4" lg="3" xl="2">
<sidebar-stream :stream="stream" :user-role="userRole" @refresh="refresh"></sidebar-stream>
<sidebar-stream :user-role="userRole"></sidebar-stream>
</v-col>
<v-col cols="12" sm="12" md="8" lg="9" xl="7">
<router-view :stream="stream" :user-role="userRole"></router-view>
<router-view :user-role="userRole"></router-view>
</v-col>
</v-row>
</v-container>
@@ -26,7 +13,6 @@
<script>
import SidebarStream from '../components/SidebarStream'
import streamQuery from '../graphql/stream.gql'
import streamCommitsQuery from '../graphql/streamCommits.gql'
export default {
name: 'Stream',
@@ -34,48 +20,19 @@ export default {
SidebarStream
},
data() {
return {
error: null,
dialogDescription: false,
selectedBranch: 0,
stream: {
id: null,
branches: {
totalCount: 0,
items: []
},
commits: {
totalCount: 0,
items: []
}
},
commits: {
totalCount: 0,
items: []
}
}
return {}
},
apollo: {
stream: {
prefetch: true,
query: streamQuery,
variables() {
return {
id: this.$route.params.streamId
}
},
error(error) {
error() {
this.$router.push({ path: '/error' })
}
},
commits: {
query: streamCommitsQuery,
variables() {
return {
id: this.$route.params.streamId
}
},
update: (data) => data.stream.commits
}
},
computed: {
@@ -87,20 +44,6 @@ export default {
if (contrib) return contrib.role.split(':')[1]
else return null
}
},
methods: {
refresh() {
this.$apollo.queries.stream.refetch()
}
}
}
</script>
<style scoped>
.v-item-group {
float: left;
}
.clear {
clear: both;
}
</style>
+89 -125
View File
@@ -1,14 +1,17 @@
<template>
<v-row>
<v-col v-if="!stream || $apollo.loading" cols="12">
<!-- <v-col v-if="!stream || $apollo.loading" cols="12">
<v-skeleton-loader type="article, article"></v-skeleton-loader>
</v-col>
<v-col v-else sm="12">
<v-card rounded="lg" class="pa-4 mb-4" elevation="0" color="background2">
<v-card-title v-if="!stream.description">Description</v-card-title>
<v-card-text v-if="!stream.description">No description provided.</v-card-text>
</v-col> -->
<v-col sm="12">
<v-card v-if="$apollo.queries.description.loading">
<v-skeleton-loader type="article"></v-skeleton-loader>
</v-card>
<v-card v-else rounded="lg" class="pa-4 mb-4" elevation="0" color="background2">
<v-card-title v-if="!description">Description</v-card-title>
<v-card-text v-if="!description">No description provided.</v-card-text>
<v-card-text
v-if="stream.description"
v-if="description"
class="marked-preview"
v-html="compiledStreamDescription"
></v-card-text>
@@ -16,27 +19,33 @@
<v-btn small @click="dialogDescription = true">Edit Description</v-btn>
<v-dialog v-model="dialogDescription">
<stream-description-dialog
:id="stream.id"
:description="stream.description"
:id="$route.params.streamId"
:description="description"
@close="closeDescription"
/>
</v-dialog>
</v-card-actions>
</v-card>
<v-card rounded="lg" class="pa-4 mb-4" elevation="0" color="background2">
<v-card v-if="$apollo.queries.branches.loading">
<v-skeleton-loader type="article"></v-skeleton-loader>
</v-card>
<v-card v-else rounded="lg" class="pa-4 mb-4" elevation="0" color="background2">
<v-card-title>
<v-icon class="mr-2">mdi-source-branch</v-icon>
Branches
</v-card-title>
<v-card-text>
Branches allow you to manage parallel versions of data in a single stream, by organising
them within a topic.
A branch represents an independent line of data. You can think of them as an independent
directory, staging area and project history.
</v-card-text>
<v-card-text>
<v-list two-line color="transparent">
<template v-for="item in branches">
<v-list-item :key="item.id" :to="`/streams/${stream.id}/branches/${item.name}`">
<template v-for="item in branches.items">
<v-list-item
:key="item.id"
:to="`/streams/${$route.params.streamId}/branches/${encodeURIComponent(item.name)}`"
>
<v-list-item-content>
<v-list-item-title>
<b>{{ item.name }}</b>
@@ -54,25 +63,39 @@
</v-list-item>
</template>
</v-list>
<v-btn v-if="userRole === 'contributor' || userRole === 'owner'" small @click="newBranch">
<v-btn
v-if="userRole === 'contributor' || userRole === 'owner'"
small
@click="dialogBranch = true"
>
new branch
</v-btn>
<branch-dialog ref="branchDialog" :branches="branches"></branch-dialog>
<v-dialog v-model="dialogBranch" max-width="500">
<new-branch-dialog
:branch-names="branches.items.map((b) => b.name)"
:stream-id="$route.params.streamId"
@close="closeBranchDialog"
/>
</v-dialog>
</v-card-text>
</v-card>
<v-card rounded="lg" class="pa-4 mb-4" elevation="0" color="background2">
<v-card v-if="$apollo.queries.commits.loading">
<v-skeleton-loader type="article"></v-skeleton-loader>
</v-card>
<v-card v-else rounded="lg" class="pa-4 mb-4" elevation="0" color="background2">
<v-card-title>
Latest activity &nbsp;&nbsp;&nbsp;
<span class="font-weight-light ml-2 body-1">({{ commits.totalCount }} total)</span>
</v-card-title>
<v-card-text>All the commits from this stream are below.</v-card-text>
<v-card-text v-if="stream.commits">
<v-card-text v-if="commits">
<list-item-commit
v-for="item in commits.items"
:key="item.id"
:commit="item"
:stream-id="stream.id"
:stream-id="$route.params.streamId"
></list-item-commit>
</v-card-text>
</v-card>
@@ -83,15 +106,16 @@
import marked from 'marked'
import DOMPurify from 'dompurify'
import gql from 'graphql-tag'
import BranchDialog from '../components/dialogs/BranchDialog'
import NewBranchDialog from '../components/dialogs/BranchNewDialog'
import StreamDescriptionDialog from '../components/dialogs/StreamDescriptionDialog'
import ListItemCommit from '../components/ListItemCommit'
import streamCommitsQuery from '../graphql/streamCommits.gql'
import streamBranchesQuery from '../graphql/streamBranches.gql'
export default {
name: 'StreamMain',
components: {
BranchDialog,
NewBranchDialog,
ListItemCommit,
StreamDescriptionDialog
},
@@ -108,6 +132,7 @@ export default {
data() {
return {
dialogDescription: false,
dialogBranch: false,
selectedBranch: 0
}
},
@@ -120,123 +145,62 @@ export default {
}
},
update: (data) => data.stream.commits
},
branches: {
query: streamBranchesQuery,
variables() {
return {
id: this.$route.params.streamId
}
},
update(data) {
data.stream.branches.items = data.stream.branches.items.reverse()
return data.stream.branches
}
},
description: {
query: gql`
query($id: String!) {
stream(id: $id) {
id
description
}
}
`,
variables() {
return {
id: this.$route.params.streamId
}
},
update: (data) => data.stream.description
}
},
computed: {
compiledStreamDescription() {
if (!this.stream.description) return ''
let md = marked(this.stream.description)
return DOMPurify.sanitize(md)
branchNames() {
if (!this.branches) return []
return this.branches.items.map((b) => b.name)
},
branches() {
//reverse without changing original array
return this.stream.branches.items.slice().reverse()
compiledStreamDescription() {
if (!this.description) return ''
let md = marked(this.description)
return DOMPurify.sanitize(md)
}
},
mounted() {
this.$matomo && this.$matomo.trackPageView('streams/single')
this.$apollo.queries.branches.refetch()
this.$apollo.queries.description.refetch()
this.$apollo.queries.commits.refetch()
},
methods: {
closeDescription(newDescription) {
this.stream.description = newDescription
closeDescription() {
this.dialogDescription = false
this.$apollo.queries.description.refetch()
},
newBranch() {
this.$refs.branchDialog.open().then((dialog) => {
if (!dialog.result) return
this.$apollo
.mutate({
mutation: gql`
mutation branchCreate($myBranch: BranchCreateInput!) {
branchCreate(branch: $myBranch)
}
`,
variables: {
myBranch: {
streamId: this.stream.id,
...dialog.branch
}
}
})
.then((data) => {
// Result
console.log(data)
this.$apollo.queries.stream.refetch()
})
.catch((error) => {
// Error
console.error(error)
// We restore the initial user input
//this.newTag = newTag
})
})
},
editBranch() {
this.$refs.branchDialog
.open(this.branches[this.selectedBranch], this.stream.id)
.then((dialog) => {
if (!dialog.result) return
//DELETE BRANCH
if (dialog.delete) {
this.$apollo
.mutate({
mutation: gql`
mutation branchDelete($myBranch: BranchDeleteInput!) {
branchDelete(branch: $myBranch)
}
`,
variables: {
myBranch: {
id: this.branches[this.selectedBranch].id,
streamId: this.stream.id
}
}
})
.then((data) => {
this.selectedBranch = 0
this.$apollo.queries.stream.refetch()
})
.catch((error) => {
// Error
console.error(error)
})
return
}
//EDIT BRANCH
this.$apollo
.mutate({
mutation: gql`
mutation branchUpdate($myBranch: BranchUpdateInput!) {
branchUpdate(branch: $myBranch)
}
`,
variables: {
myBranch: { ...dialog.branch }
}
})
.then((data) => {
this.$apollo.queries.stream.refetch()
})
.catch((error) => {
// Error
console.error(error)
})
})
closeBranchDialog() {
this.dialogBranch = false
this.$apollo.queries.branches.refetch()
}
}
}
</script>
<style scoped>
.v-item-group {
float: left;
}
.clear {
clear: both;
}
</style>