Merge pull request #278 from specklesystems/izzy/globalicious

Feat(Globals): Add global variables on the web!
This commit is contained in:
Claire Kuang
2021-06-04 13:53:14 -04:00
committed by GitHub
15 changed files with 877 additions and 22 deletions
+13
View File
@@ -12945,6 +12945,11 @@
"is-plain-obj": "^1.0.0"
}
},
"sortablejs": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.10.2.tgz",
"integrity": "sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A=="
},
"source-list-map": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
@@ -14729,6 +14734,14 @@
"@seregpie/claw": "^3.0.0"
}
},
"vuedraggable": {
"version": "2.24.3",
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-2.24.3.tgz",
"integrity": "sha512-6/HDXi92GzB+Hcs9fC6PAAozK1RLt1ewPTLjK0anTYguXLAeySDmcnqE8IC0xa7shvSzRjQXq3/+dsZ7ETGF3g==",
"requires": {
"sortablejs": "1.10.2"
}
},
"vuetify": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.4.0.tgz",
+1
View File
@@ -25,6 +25,7 @@
"vue-matomo": "^3.14.0-0",
"vue-router": "^3.4.9",
"vue-timeago": "^5.1.2",
"vuedraggable": "^2.24.3",
"vuetify": "^2.3.21",
"vuetify-image-input": "^19.1.0",
"vuex": "^3.6.0"
@@ -0,0 +1,292 @@
<template>
<v-card rounded="lg" class="pa-3 mb-3" elevation="0">
<v-dialog v-model="saveDialog" max-width="500">
<globals-save-dialog
:branch-name="branchName"
:stream-id="$route.params.streamId"
:commit-obj="globalsCommit"
@close="closeSaveDialog"
/>
</v-dialog>
<v-card-title>Globals</v-card-title>
<v-card-subtitle v-if="commitMessage">
<v-icon dense class="text-subtitle-1">mdi-source-commit</v-icon>
{{ commitMessage }}
</v-card-subtitle>
<v-card-text>
These global variables can be used for storing design values, project requirements, notes, or
any info you want to keep track of alongside your geometry. Variable values can be text, numbers,
lists, or booleans. Click the box icon next to any field to turn it into a nested group of
fields, and drag and drop fields in and out of groups as you please! Note that field order
may not always be preserved.
</v-card-text>
<v-card-text v-if="!(userRole === 'contributor') && !(userRole === 'owner')">
You are free to play around with the globals here, but you do not have the required stream
permission to save your changes.
</v-card-text>
<v-card-actions>
<v-switch
v-model="deleteEntries"
v-tooltip="'Toggle delete mode'"
class="ml-3"
dense
inset
color="error"
:label="`DELETE`"
></v-switch>
<v-spacer />
<v-btn v-tooltip="'Clear all globals'" color="primary" small @click="clearGlobals">
clear
</v-btn>
<v-btn v-tooltip="'Undo any changes'" color="primary" small @click="resetGlobals">
reset all
</v-btn>
<v-btn
v-if="userRole === 'contributor' || userRole === 'owner'"
v-tooltip="'Save your changes with a message'"
small
:disabled="!globalsAreValid"
color="primary"
@click="
saveDialog = true
deleteEntries = false
"
>
save
</v-btn>
</v-card-actions>
<v-card-text>
<globals-entry
v-if="!$apollo.loading"
:entries="globalsArray"
:path="[]"
:remove="deleteEntries"
@add-prop="addProp"
@remove-prop="removeProp"
@field-to-object="fieldToObject"
@object-to-field="objectToField"
/>
<div v-else>
<v-skeleton-loader type="list-item-three-line" />
</div>
</v-card-text>
</v-card>
</template>
<script>
import crs from 'crypto-random-string'
import objectQuery from '../graphql/objectSingle.gql'
export default {
name: 'GlobalsBuilder',
components: {
GlobalsEntry: () => import('../components/GlobalsEntry'),
GlobalsSaveDialog: () => import('../components/dialogs/GlobalsSaveDialog')
},
apollo: {
object: {
query: objectQuery,
variables() {
return {
streamId: this.streamId,
id: this.objectId
}
},
update(data) {
delete data.stream.object.data.__closure
this.globalsArray = this.nestedGlobals(data.stream.object.data)
return data.stream.object
},
skip() {
return this.objectId == null
}
}
},
props: {
userRole: {
type: String,
default: null
},
branchName: {
type: String,
default: null
},
objectId: {
type: String,
default: null
},
streamId: {
type: String,
default: null
},
commitMessage: {
type: String,
default: null
}
},
data() {
return {
globalsArray: [],
globalsAreValid: true,
saveDialog: false,
deleteEntries: false,
sample: {
Region: 'London',
'Project Code': 'GB123456',
'Linked Projects': ['GB654321', 'EU424242'],
'Project Lead': 'Sir Spockle II',
'Pretty Cool?': true,
Climate: {
'Summer DBT [C]': 35,
'Summer WBT [C]': 20,
'Winter DBT [C]': -4,
'Winter WBT [C]': -4,
Enthalpy: {
'Summer Enthalpy [kJ/kg]': 56.87,
'Winter Enthalpy [kJ/kg]': 2.74
}
}
}
}
},
computed: {
globalsCommit() {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
this.globalsAreValid = true
let base = this.globalsToBase(this.globalsArray)
return base
}
},
mounted() {
if (!this.objectId) {
this.globalsArray = this.nestedGlobals(this.sample)
}
},
methods: {
nestedGlobals(data) {
if (!data) return []
let entries = Object.entries(data)
let arr = []
for (let [key, val] of entries) {
if (key.startsWith('__')) continue
if (['totalChildrenCount', 'speckle_type', 'id'].includes(key)) continue
if (!Array.isArray(val) && typeof val === 'object' && val !== null) {
if (val.speckle_type && val.speckle_type === 'reference') {
arr.push({
key,
valid: true,
id: crs({ length: 10 }),
value: val,
globals: this.nestedGlobals(val),
type: 'object' //TODO: handle references
})
} else {
arr.push({
key,
valid: true,
id: crs({ length: 10 }),
value: val,
globals: this.nestedGlobals(val),
type: 'object'
})
}
} else {
arr.push({
key,
valid: true,
id: crs({ length: 10 }),
value: val,
type: 'field'
})
}
}
return arr
},
globalsToBase(arr) {
let base = {
// eslint-disable-next-line camelcase
speckle_type: 'Base',
id: null
}
for (let entry of arr) {
if (!entry.value && !entry.globals) return
if (!entry.valid) this.globalsAreValid = false
if (Array.isArray(entry.value)) base[entry.key] = entry.value
else if (entry.type == 'object') {
base[entry.key] = this.globalsToBase(entry.globals)
} else if (typeof entry.value === 'string' && entry.value.includes(',')) {
base[entry.key] = entry.value
.replace(/\s/g, '')
.split(',')
.map((el) => (isNaN(el) ? el : parseFloat(el)))
} else if (typeof entry.value === 'boolean') {
base[entry.key] = entry.value
} else {
base[entry.key] = isNaN(entry.value) ? entry.value : parseFloat(entry.value)
}
}
return base
},
resetGlobals() {
this.deleteEntries = false
this.globalsArray = this.object?.data
? this.nestedGlobals(this.object.data)
: this.nestedGlobals(this.sample)
},
clearGlobals() {
this.globalsArray = this.nestedGlobals({ placeholder: 'something cool goes here...' })
},
addProp(kwargs) {
let globals = this.getNestedGlobals(kwargs.path)
globals.splice(globals.length, 0, kwargs.field)
},
removeProp(kwargs) {
let globals = this.getNestedGlobals(kwargs.path)
globals.splice(kwargs.index, 1)
},
fieldToObject(kwargs) {
let globals = this.getNestedGlobals(kwargs.path)
globals.splice(kwargs.index, 1, kwargs.obj)
},
objectToField(kwargs) {
let globals = this.getNestedGlobals(kwargs.path)
globals.splice(kwargs.index, 1, ...kwargs.fields)
},
getNestedGlobals(path) {
let entry = this.globalsArray
if (!path) return entry
let depth = path.length
if (depth > 0) {
let id = path.shift()
entry = entry.find((e) => e.id == id)
}
if (depth > 1) {
path.forEach((id) => {
entry = entry.globals.find((e) => e.id == id)
})
}
if (!Array.isArray(entry)) entry = entry.globals
return entry
},
closeSaveDialog() {
this.saveDialog = false
this.$emit('new-commit')
}
}
}
</script>
<style scoped></style>
@@ -0,0 +1,292 @@
<template>
<v-container>
<draggable
:list="entries"
class="dragArea pl-0"
tag="ul"
group="globals"
v-bind="dragOptions"
@start="drag = true"
@end="drag = false"
>
<div v-for="(entry, index) in entries" :key="entry.id">
<transition type="transition" :name="!drag ? 'flip-list' : null">
<div v-if="!entry.globals">
<div class="d-flex align-center">
<v-btn
v-if="remove"
class="entry-delete mr-5"
fab
rounded
x-small
color="error"
@click="emitRemoveAt(index)"
>
<v-icon>mdi-minus</v-icon>
</v-btn>
<v-text-field
ref="keyInput"
v-model="entry.key"
:rules="rules.keys(index, entries)"
:error-messages="
entry.valid
? null
: entry.key
? 'Each property name must be unique'
: 'This property needs a name!'
"
class="entry-key mr-5"
hint="property name"
filled
dense
rounded
/>
<v-text-field v-model="entry.value" class="entry-value mr-5" hint="property value" />
<v-btn
v-if="!remove"
v-tooltip="'Transform this field into an object'"
icon
small
@click="emitFieldToObject(entry, index)"
>
<v-icon color="primary">mdi-cube-outline</v-icon>
</v-btn>
</div>
</div>
<v-card v-else rounded="lg" class="pa-3 my-6" elevation="4">
<v-row align="center">
<v-col>
<v-card-title
v-if="!editTitle"
@mouseenter="mouseOver = true"
@mouseleave="mouseOver = false"
>
<v-btn
v-if="remove"
class="entry-delete mr-5"
fab
rounded
x-small
color="error"
@click="emitRemoveAt(index)"
>
<v-icon>mdi-minus</v-icon>
</v-btn>
{{ entry.key }}
<v-btn v-if="mouseOver" icon small color="primary" @click="editTitle = true">
<v-icon small>mdi-pencil</v-icon>
</v-btn>
</v-card-title>
<v-card-title v-else>
<v-text-field
ref="keyInput"
v-model="entry.key"
:rules="rules.keys(index, entries)"
:error-messages="
entry.valid
? null
: entry.key
? 'Each property name must be unique'
: 'This property needs a name!'
"
></v-text-field>
<v-btn icon color="primary" @click="editTitle = false">
<v-icon small>mdi-check</v-icon>
</v-btn>
</v-card-title>
</v-col>
<v-col cols="auto">
<v-btn
v-tooltip="'Flatten this object into fields'"
class="mr-3"
icon
small
@click="emitObjectToField(entry, index)"
>
<v-icon color="primary">mdi-arrow-collapse-down</v-icon>
</v-btn>
</v-col>
</v-row>
<globals-entry
:entries="entry.globals"
:path="[...path, entry.id]"
:remove="remove"
v-on="$listeners"
/>
</v-card>
</transition>
</div>
</draggable>
<div
v-if="!remove"
slot="footer"
key="footer"
class="btn-group list-group-item mt-3"
role="group"
aria-label="Basic example"
>
<v-btn
v-tooltip="'Add a new field to this object'"
color="primary"
rounded
fab
small
@click="emitAddProp"
>
<v-icon>mdi-plus</v-icon>
</v-btn>
</div>
</v-container>
</template>
<script>
import draggable from 'vuedraggable'
import crs from 'crypto-random-string'
export default {
name: 'GlobalsEntry',
components: { draggable },
props: {
entries: {
type: Array,
default: null
},
path: {
type: Array,
default: null
},
streamId: {
type: String,
default: null
},
remove: {
type: Boolean,
default: false
},
invalidIds: {
type: Array,
default: () => []
}
},
data() {
return {
editTitle: false,
mouseOver: false,
drag: false,
valid: true,
rules: {
keys(index, entries) {
return [
(v) => {
let result = !!v || 'Properties need to have a name!'
entries[index].valid = result === true
return result
},
(v) => {
let filtered = entries.filter((_, i) => i != index)
let result =
filtered.findIndex((e) => e.key === v) === -1 || 'Each property name must be unique'
entries[index].valid = !!v && result === true
return result
}
]
}
},
errors: []
}
},
computed: {
dragOptions() {
return {
animation: 150,
disabled: false,
ghostClass: 'ghost'
}
}
},
methods: {
emitAddProp() {
var bimNouns = ['parameter', 'BIM', 'triple O', 'digital twin', 'LOD 9000', 'automation', 'structure', 'layer', 'interop']
var bimAdjs = ['parametric', 'chonky', '3D', 'liminal', 'brutalist', 'postmodern', 'discrete', 'dank']
var bimExclamations = ['wow', 'much', 'yes', 'towards a new']
var randomPhrase =
bimExclamations[Math.floor(Math.random() * bimExclamations.length)] +
' ' +
bimAdjs[Math.floor(Math.random() * bimAdjs.length)] +
' ' +
bimNouns[Math.floor(Math.random() * bimNouns.length)]
let field = {
key: `placeholder ${crs({ length: 6 })}`,
type: 'field',
value: randomPhrase,
valid: true,
id: crs({ length: 10 })
}
this.$emit('add-prop', { field: field, path: this.path })
},
emitRemoveAt(index) {
this.$emit('remove-prop', { path: this.path, index: index })
},
emitFieldToObject(entry, index) {
let obj = {
key: entry.key,
type: 'object',
id: entry.id,
valid: entry.valid,
globals: [
{
key: `placeholder ${crs({ length: 6 })}`,
type: 'field',
value: entry.value,
id: crs({ length: 10 }),
valid: true
}
]
}
this.$emit('field-to-object', { obj: obj, path: this.path, index: index })
},
emitObjectToField(entry, index) {
let fields = entry.globals
this.$emit('object-to-field', { fields: fields, path: this.path, index: index })
}
}
}
</script>
<style scoped>
.v-card {
background-color: rgba(0, 0, 0, 0.1);
}
.v-card__title {
font-weight: 500;
font-size: large;
letter-spacing: 1px;
text-transform: uppercase;
}
.v-text-field {
font-weight: 300;
}
.entry-key {
font-weight: 500;
position: relative;
top: 0.6rem;
}
.entry-delete {
position: relative;
top: -0.2rem;
}
.dragArea {
min-height: 50px;
}
.ghost {
opacity: 0.5;
}
.flip-list-move {
transition: transform 0.5s;
}
</style>
@@ -1,5 +1,5 @@
<template>
<v-list-item :to="`/streams/${streamId}/commits/${commit.id}`">
<v-list-item :to="route ? route : `/streams/${streamId}/commits/${commit.id}`">
<v-list-item-icon>
<user-avatar
:id="commit.authorId"
@@ -36,7 +36,7 @@ import SourceAppAvatar from './SourceAppAvatar'
export default {
components: { UserAvatar, SourceAppAvatar },
props: ['commit', 'streamId'],
props: ['commit', 'streamId', 'route'],
computed: {
commitDate() {
if (!this.commit) return null
@@ -30,7 +30,11 @@
small
color="primary"
class="pa-2"
:to="`/streams/${stream.id}/commits/${stream.commits.items[0].id}`"
:to="
stream.commits.items[0].branchName.startsWith('globals')
? `/streams/${stream.id}/${stream.commits.items[0].branchName}/${stream.commits.items[0].id}`
: `/streams/${stream.id}/commits/${stream.commits.items[0].id}`
"
>
<v-icon small class="mr-1">mdi-source-commit</v-icon>
{{ stream.commits.items[0].id }}
@@ -38,7 +42,11 @@
on
<router-link
class="text-decoration-none"
:to="`/streams/${stream.id}/branches/${stream.commits.items[0].branchName}`"
:to="
stream.commits.items[0].branchName.startsWith('globals')
? `/streams/${stream.id}/${stream.commits.items[0].branchName}`
: `/streams/${stream.id}/branches/${stream.commits.items[0].branchName}`
"
>
<v-icon small color="primary">mdi-source-branch</v-icon>
{{ stream.commits.items[0].branchName }}
@@ -93,6 +101,7 @@
<v-icon small class="mr-2 float-left">mdi-cog-outline</v-icon>
Edit
</v-btn>
<v-dialog v-model="editStreamDialog" max-width="500">
<stream-edit-dialog
:stream-id="stream.id"
@@ -104,6 +113,18 @@
</v-dialog>
</v-card-text>
<v-card-text v-show="isHomeRoute">
<v-btn
v-tooltip="'Edit stream global variables!'"
block
small
elevation="0"
:to="`/streams/${stream.id}/globals`"
>
Globals
</v-btn>
</v-card-text>
<v-card-title v-show="isHomeRoute"><h5>Collaborators</h5></v-card-title>
<v-card-text v-show="isHomeRoute">
<v-row no-gutters>
@@ -44,6 +44,9 @@ export default {
name: null,
nameRules: [
(v) => !!v || 'Branches need a name too!',
(v) =>
(v && !v.startsWith('globals')) ||
'Globals is a reserved branch name. Please choose a different name.',
(v) =>
(v && this.branchNames.findIndex((e) => e === v) === -1) ||
'A branch with this name already exists',
@@ -0,0 +1,94 @@
<template>
<v-card :loading="loading">
<template slot="progress">
<v-progress-linear indeterminate></v-progress-linear>
</template>
<v-card-title>Save Globals</v-card-title>
<v-form ref="form" v-model="valid" lazy-validation @submit.prevent="saveGlobals">
<v-card-text>
<v-text-field
v-model="message"
label="Message"
:rules="nameRules"
validate-on-blur
required
autofocus
></v-text-field>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="primary" text :disabled="!valid" type="submit">Save</v-btn>
</v-card-actions>
</v-form>
</v-card>
</template>
<script>
import gql from 'graphql-tag'
export default {
props: {
streamId: {
type: String,
default: null
},
commitObj: {
type: Object,
default: null
},
branchName: {
type: String,
default: null
}
},
data() {
return {
valid: false,
loading: false,
name: null,
nameRules: [(v) => (v && v.length >= 3) || 'Message must be at least 3 characters'],
message: null
}
},
computed: {},
methods: {
async saveGlobals() {
if (!this.$refs.form.validate()) return
this.loading = true
this.$matomo && this.$matomo.trackPageView('globals/save')
let res = await this.$apollo.mutate({
mutation: gql`
mutation ObjectCreate($params: ObjectCreateInput!) {
objectCreate(objectInput: $params)
}
`,
variables: {
params: {
streamId: this.streamId,
objects: [this.commitObj]
}
}
})
await this.$apollo.mutate({
mutation: gql`
mutation CommitCreate($commit: CommitCreateInput!) {
commitCreate(commit: $commit)
}
`,
variables: {
commit: {
streamId: this.streamId,
branchName: this.branchName,
objectId: res.data.objectCreate[0],
message: this.message,
sourceApplication: 'web'
}
}
})
this.loading = false
this.$emit('close')
}
}
}
</script>
@@ -24,6 +24,7 @@ query Streams($cursor: String) {
createdAt
message
authorId
branchName
authorName
authorAvatar
referencedObject
+17 -1
View File
@@ -83,9 +83,25 @@ const routes = [
},
component: () => import('../views/StreamMain.vue')
},
{
path: 'globals/',
name: 'globals',
meta: {
title: 'Globals | Speckle'
},
component: () => import('../views/Globals.vue')
},
{
path: 'globals/:commitId',
name: 'previous globals',
meta: {
title: 'Globals | Speckle'
},
component: () => import('../views/Globals.vue')
},
{
path: 'branches/',
name: 'branchs',
name: 'branches',
meta: {
title: 'Branches | Speckle'
},
+4 -4
View File
@@ -7,7 +7,7 @@
<v-card v-else rounded="lg" class="pa-4 mb-4" elevation="0">
<v-dialog v-model="dialogBranch" max-width="500">
<new-branch-dialog
:branch-names="branches.items.map((b) => b.name)"
:branch-names="branches.map((b) => b.name)"
:stream-id="$route.params.streamId"
@close="closeBranchDialog"
/>
@@ -40,10 +40,10 @@
</v-card>
<v-card v-if="!$apollo.queries.stream.loading" class="mt-5 pa-4" elevation="0" rounded="lg">
<v-subheader class="text-uppercase">Branches ({{ branches.items.length }})</v-subheader>
<v-subheader class="text-uppercase">Branches ({{ branches.length }})</v-subheader>
<v-card-text>
<v-list two-line color="transparent">
<template v-for="item in branches.items">
<template v-for="item in branches">
<v-list-item
:key="item.id"
:to="`/streams/${$route.params.streamId}/branches/${encodeURIComponent(item.name)}`"
@@ -144,7 +144,7 @@ export default {
},
computed: {
branches() {
return this.stream.branches
return this.stream.branches.items.filter((b) => !b.name.startsWith('globals'))
},
breadcrumbs() {
return [
+122
View File
@@ -0,0 +1,122 @@
<template>
<v-container>
<div v-if="!objectId && !$apollo.loading && !revealBuilder">
<v-card :loading="loading">
<template slot="progress">
<v-progress-linear indeterminate></v-progress-linear>
</template>
<v-card-title>You don't have any globals on this stream!</v-card-title>
<v-card-text class="subtitle-1">
Globals are useful for storing design values, project requirements, notes, or any info you
want to keep track of alongside your geometry. Would you like to create some now?
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn color="primary" @click="createClicked">create globals</v-btn>
</v-card-actions>
</v-card>
</div>
<div v-if="objectId || revealBuilder">
<globals-builder
:branch-name="branchName"
:stream-id="streamId"
:object-id="objectId"
:commit-message="commit ? commit.message : null"
:user-role="$attrs['user-role']"
@new-commit="newCommit"
/>
<v-card v-if="!$apollo.loading && branch.commits.items.length">
<v-card-title>History</v-card-title>
<v-card-text>
<list-item-commit
v-for="item in branch.commits.items"
:key="item.id"
:route="`/streams/${streamId}/globals/${item.id}`"
:commit="item"
:stream-id="streamId"
/>
</v-card-text>
</v-card>
</div>
</v-container>
</template>
<script>
import gql from 'graphql-tag'
import branchQuery from '../graphql/branch.gql'
export default {
name: 'Globals',
components: {
GlobalsBuilder: () => import('../components/GlobalsBuilder'),
ListItemCommit: () => import('../components/ListItemCommit')
},
apollo: {
branch: {
query: branchQuery,
variables() {
return {
streamId: this.streamId,
branchName: this.branchName
}
},
update(data) {
return data.stream.branch
}
}
},
data() {
return {
branchName: 'globals', //TODO: handle multipile globals branches,
revealBuilder: false,
loading: false
}
},
computed: {
streamId() {
return this.$route.params.streamId
},
commit() {
return this.$route.params.commitId
? this.branch?.commits?.items?.filter((c) => c.id == this.$route.params.commitId)[0]
: this.branch?.commits?.items[0]
},
objectId() {
return this.commit?.referencedObject
}
},
methods: {
async createClicked() {
if (!this.branch) {
this.loading = true
this.$matomo && this.$matomo.trackPageView('globals/branch/create')
await this.$apollo.mutate({
mutation: gql`
mutation branchCreate($params: BranchCreateInput!) {
branchCreate(branch: $params)
}
`,
variables: {
params: {
streamId: this.streamId,
name: 'globals',
description: 'Stream globals'
}
}
})
this.$apollo.queries.branch.refetch()
this.loading = false
}
this.revealBuilder = true
},
newCommit() {
this.$apollo.queries.branch.refetch()
if (this.$route.params.commitId) this.$router.push(`/streams/${this.streamId}/globals`)
}
}
}
</script>
<style scoped></style>
+6 -6
View File
@@ -9,7 +9,7 @@
<v-select
v-if="branches"
v-model="selectedBranch"
:items="branches.items"
:items="branches"
item-value="name"
solo
flat
@@ -41,7 +41,7 @@
:to="'/streams/' + $route.params.streamId + '/branches'"
>
<v-icon class="mr-2 float-left">mdi-source-branch</v-icon>
{{ branches.totalCount }} branch{{ branches.totalCount > 1 ? 'es' : '' }}
{{ branches.length }} branch{{ branches.length > 1 ? 'es' : '' }}
</v-btn>
</v-sheet>
@@ -228,7 +228,7 @@ export default {
}
},
update(data) {
return data.stream.branches
return data.stream.branches.items.filter((b) => !b.name.startsWith('globals'))
}
},
description: {
@@ -297,7 +297,7 @@ export default {
},
branchNames() {
if (!this.branches) return []
return this.branches.items.map((b) => b.name)
return this.branches.map((b) => b.name)
},
compiledStreamDescription() {
if (!this.description) return ''
@@ -344,8 +344,8 @@ export default {
selectBranch() {
if (!this.branches) return
let branchName = this.$route.params.branchName ? this.$route.params.branchName : 'main'
let index = this.branches.items.findIndex((x) => x.name === branchName)
if (index > -1) this.selectedBranch = this.branches.items[index]
let index = this.branches.findIndex((x) => x.name === branchName)
if (index > -1) this.selectedBranch = this.branches[index]
else this.error = 'Branch ' + branchName + ' does not exist'
},
changeBranch() {
+7 -1
View File
@@ -39,7 +39,13 @@
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title class="subtitle-2">
<router-link :to="'streams/' + a.streamId + '/commits/' + a.id">
<router-link
:to="
a.branchName.startsWith('globals')
? `streams/${a.streamId}/${a.branchName}/${a.id}`
: `streams/${a.streamId}/commits/${a.id}`
"
>
{{ a.message }}
</router-link>
</v-list-item-title>
-6
View File
@@ -2008,12 +2008,6 @@
"@sinonjs/commons": "^1.7.0"
}
},
"@speckle/objectloader": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@speckle/objectloader/-/objectloader-2.0.3.tgz",
"integrity": "sha512-hSyJU0ktZOYbgjDtwrHHXowRu5L0lxoO32N2JPJUjURoy+M1ZpvJVGyT4jKG03HvH30j9rynja0PVjVoW9LdEw==",
"dev": true
},
"@types/anymatch": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz",