Merge pull request #278 from specklesystems/izzy/globalicious
Feat(Globals): Add global variables on the web!
This commit is contained in:
Generated
+13
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
},
|
||||
|
||||
@@ -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 [
|
||||
|
||||
@@ -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>
|
||||
@@ -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() {
|
||||
|
||||
@@ -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>
|
||||
|
||||
Generated
-6
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user