feat(frontend): various last mile improvements

This commit is contained in:
Dimitrie Stefanescu
2021-08-26 19:53:36 +03:00
parent 661c5edcb1
commit 323dee4e1c
15 changed files with 341 additions and 586 deletions
@@ -0,0 +1,73 @@
<template>
<div>
<v-app-bar style="position: absolute; top: 0; width: 100%; z-index: 90" elevation="0">
<search-bar />
</v-app-bar>
<v-list style="margin-top: 64px" shaped>
<v-list-item class="primary" dark link @click="newStreamDialog = true">
<v-list-item-content>
<v-list-item-title>New Stream</v-list-item-title>
<v-list-item-subtitle class="caption">
Quickly create a new data repository.
</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-icon>
<v-icon class="">mdi-plus-box</v-icon>
</v-list-item-icon>
</v-list-item>
<v-list-item link @click="showServerInviteDialog()">
<v-list-item-content>
<v-list-item-title>Invite</v-list-item-title>
<v-list-item-subtitle class="caption">
Invite a colleague to Speckle!
</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-icon>
<v-icon class="">mdi-email</v-icon>
</v-list-item-icon>
</v-list-item>
</v-list>
<server-invite-dialog ref="serverInviteDialog" />
<v-dialog v-model="newStreamDialog" max-width="500" :fullscreen="$vuetify.breakpoint.xsOnly">
<stream-new-dialog
:open="newStreamDialog"
@created="newStreamDialog = false"
@close="newStreamDialog = false"
/>
</v-dialog>
</div>
</template>
<script>
export default {
components: {
ServerInviteDialog: () => import('@/components/dialogs/ServerInviteDialog'),
StreamNewDialog: () => import('@/components/dialogs/StreamNewDialog'),
SearchBar: () => import('@/components/SearchBar'),
},
props: {
OpenNewStream: {
type: Number,
default: 0
}
},
watch: {
OpenNewStream(val, old) {
this.newStreamDialog = true
}
},
data() {
return {
newStreamDialog: false
}
},
methods: {
showServerInviteDialog() {
this.$refs.serverInviteDialog.show()
}
},
mounted() {}
}
</script>
@@ -1,51 +1,54 @@
<template>
<v-dialog v-model="show" width="500" @keydown.esc="cancel">
<v-card :loading="loading" class="pa-4">
<template slot="progress">
<v-progress-linear indeterminate></v-progress-linear>
</template>
<div v-if="branch && branch.name !== 'main'">
<v-card-title>Edit Branch</v-card-title>
<v-form ref="form" v-model="valid" lazy-validation @submit.prevent="agree">
<v-card-text>
<v-text-field
v-model="branch.name"
label="Name"
:rules="nameRules"
validate-on-blur
required
autofocus
></v-text-field>
<v-textarea v-model="branch.description" rows="2" label="Description"></v-textarea>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text @click="cancel">Cancel</v-btn>
<v-btn color="primary" text :disabled="!valid" type="submit">Save</v-btn>
</v-card-actions>
</v-form>
<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>{{ branch.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 :loading="loading" v-if="branch && branch.name !== 'main'">
<v-toolbar color="primary" dark>
<v-app-bar-nav-icon style="pointer-events: none">
<v-icon>mdi-pencil</v-icon>
</v-app-bar-nav-icon>
<v-toolbar-title>Edit Branch</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn icon @click="show = false"><v-icon>mdi-close</v-icon></v-btn>
</v-toolbar>
<v-form ref="form" v-model="valid" lazy-validation @submit.prevent="agree">
<v-card-text>
<v-text-field
v-model="branch.name"
label="Name"
:rules="nameRules"
validate-on-blur
required
autofocus
></v-text-field>
<v-textarea v-model="branch.description" rows="2" label="Description"></v-textarea>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text small color="error" @click="showDelete = true">Delete</v-btn>
<v-btn @click="cancel">Cancel</v-btn>
<v-btn color="primary" :disabled="!valid" type="submit">Save</v-btn>
</v-card-actions>
</div>
<div v-else>
<v-card-text>You cannot edit the main branch.</v-card-text>
</div>
</v-form>
<v-card-actions class="error--text body-2 pa-2">
<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>{{ branch.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>
</v-card>
<v-card v-else>
<v-card-text>You cannot edit the main branch.</v-card-text>
</v-card>
</v-dialog>
</template>
@@ -1,7 +1,14 @@
<template>
<v-dialog v-model="show" width="500" @keydown.esc="cancel">
<v-card class="pa-4">
<v-card-title class="subtitle-1">Edit Commit</v-card-title>
<v-card >
<v-toolbar color="primary" dark>
<v-app-bar-nav-icon style="pointer-events: none">
<v-icon>mdi-pencil</v-icon>
</v-app-bar-nav-icon>
<v-toolbar-title>Edit Commit</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn icon @click="show = false"><v-icon>mdi-close</v-icon></v-btn>
</v-toolbar>
<v-form ref="form" v-model="valid" lazy-validation @submit.prevent="agree">
<v-card-text class="pl-2 pr-2 pt-0 pb-0">
<v-container>
@@ -13,7 +20,6 @@
:rules="nameRules"
validate-on-blur
required
filled
autofocus
></v-text-field>
</v-col>
@@ -1,5 +1,5 @@
<template>
<v-dialog v-model="showDialog" max-width="400" :fullscreen="$vuetify.breakpoint.smAndDown">
<v-dialog v-model="showDialog" max-width="400" :fullscreen="$vuetify.breakpoint.xsOnly">
<v-card>
<v-toolbar color="primary" dark>
<v-app-bar-nav-icon style="pointer-events: none">
+1 -1
View File
@@ -271,7 +271,7 @@ router.beforeEach((to, from, next) => {
let redirect = localStorage.getItem('shouldRedirectTo')
if (
!uuid &&
!to.matched.some(({ name }) => name === 'stream' || name === 'commit') && //allow public streams to be viewed
!to.matched.some(({ name }) => name === 'stream' || name === 'commit' || name === 'branch') && //allow public streams to be viewed
to.name !== 'Embeded Viewer' &&
to.name !== 'Login' &&
to.name !== 'Register' &&
+6 -2
View File
@@ -47,7 +47,7 @@
<v-list-item link to="/streams" style="height: 59px">
<v-list-item-icon>
<v-icon>mdi-folder-multiple</v-icon>
<v-icon>mdi-folder</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Streams</v-list-item-title>
@@ -127,7 +127,7 @@
</v-list-item-content>
</v-list-item>
<v-list-item link @click="switchTheme" color="primary">
<v-list-item link @click="signOut()" color="primary" v-if="user">
<v-list-item-icon>
<v-icon small class="ml-1">mdi-account-off</v-icon>
</v-list-item-icon>
@@ -171,6 +171,7 @@
</template>
<script>
import gql from 'graphql-tag'
import { signOut } from '@/auth-helpers'
import userQuery from '../graphql/user.gql'
import UserMenuTop from '../components/UserMenuTop'
import SearchBar from '../components/SearchBar'
@@ -240,6 +241,9 @@ export default {
}
},
methods: {
signOut(){
signOut()
},
switchTheme() {
this.$vuetify.theme.dark = !this.$vuetify.theme.dark
localStorage.setItem('darkModeEnabled', this.$vuetify.theme.dark ? 'dark' : 'light')
+104 -61
View File
@@ -7,36 +7,37 @@
v-model="streamNav"
style="left: 56px"
>
<v-app-bar style="position: absolute; top: 0; width: 100%; z-index: 90" elevation="0">
<search-bar />
</v-app-bar>
<v-list style="margin-top: 64px" shaped>
<v-list-item class="primary" dark link @click="newStreamDialog = true">
<v-list-item-content>
<v-list-item-title>New Stream</v-list-item-title>
<v-list-item-subtitle class="caption">
Quickly create a new data repository.
</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-icon>
<v-icon class="">mdi-plus-box</v-icon>
</v-list-item-icon>
</v-list-item>
<v-list-item link @click="showServerInviteDialog()">
<v-list-item-content>
<v-list-item-title>Invite</v-list-item-title>
<v-list-item-subtitle class="caption">
Invite a colleague to Speckle!
</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-icon>
<v-icon class="">mdi-email</v-icon>
</v-list-item-icon>
</v-list-item>
</v-list>
<main-nav-actions :open-new-stream="streamNewDialog" />
<div v-if="user">
<!-- <v-subheader class="caption">Filter:</v-subheader>
<v-list dense rounded>
<v-list-item link>
<v-list-item-icon>
<v-icon small class="">mdi-key</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-subtitle class="caption">Owner</v-list-item-subtitle class="caption">
</v-list-item-content>
</v-list-item>
<v-list-item link>
<v-list-item-icon>
<v-icon small class="">mdi-account</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-subtitle class="caption">Contributor</v-list-item-subtitle class="caption">
</v-list-item-content>
</v-list-item>
<v-list-item link>
<v-list-item-icon>
<v-icon small class="">mdi-circle</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-subtitle class="caption">Reviewer</v-list-item-subtitle class="caption">
</v-list-item-content>
</v-list-item>
</v-list> -->
<v-subheader class="caption">Your stats:</v-subheader>
<v-list dense>
<v-list-item>
@@ -44,7 +45,10 @@
<v-icon small class="">mdi-folder-multiple</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-subtitle class="caption"><b>{{ user.streams.totalCount }}</b> total streams</v-list-item-subtitle class="caption">
<v-list-item-subtitle class="caption">
<b>{{ user.streams.totalCount }}</b>
total streams
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-list-item>
@@ -52,40 +56,59 @@
<v-icon small class="">mdi-source-commit</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-subtitle class="caption"><b>{{ user.commits.totalCount }}</b> total commits</v-list-item-subtitle class="caption">
<v-list-item-subtitle class="caption">
<b>{{ user.commits.totalCount }}</b>
total commits
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</v-list>
<v-list
v-if="userCommits && userCommits.commits.items.length !== 0"
color="transparent"
dense
>
<v-subheader class="mt-3 ml-2">Your latest commits:</v-subheader>
<v-list-item
v-for="(commit, i) in userCommits.commits.items"
:key="i"
:to="`streams/${commit.streamId}/${ commit.branchName === 'globals' ? 'globals' : 'commits' }/${commit.id}`"
v-if="commit"
v-tooltip="`In stream '${commit.streamName}'`"
>
<v-list-item-content>
<v-list-item-title>
{{ commit.message }}
</v-list-item-title>
<v-list-item-subtitle class="caption">
<i>
Updated
<timeago :datetime="commit.createdAt"></timeago>
</i>
on <v-icon style="font-size: 10px;">mdi-source-branch</v-icon>{{commit.branchName}}
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</v-list>
<div class="ml-5"></div>
</div>
</v-navigation-drawer>
<v-app-bar app style="padding-left: 56px" flat>
<v-app-bar-nav-icon @click="streamNav = !streamNav" v-show="!streamNav"></v-app-bar-nav-icon>
<v-toolbar-title class="space-grotesk">
<v-icon>mdi-folder-multiple</v-icon>
<v-icon class="mb-1">mdi-folder</v-icon>
Streams
</v-toolbar-title>
<v-spacer v-if="!streamNav"></v-spacer>
<v-toolbar-items v-if="!streamNav">
<v-btn color="primary" @click="newStreamDialog = true">
<v-btn color="primary" @click="streamNewDialog++">
<v-icon>mdi-plus-box</v-icon>
</v-btn>
</v-toolbar-items>
</v-app-bar>
<server-invite-dialog ref="serverInviteDialog" />
<v-dialog v-model="newStreamDialog" max-width="500">
<stream-new-dialog
v-if="streams && streams.items"
:open="newStreamDialog"
:redirect="streams.items.length > 0"
@created="newStreamDialog = false"
/>
</v-dialog>
<!-- <getting-started-wizard /> -->
<v-row class="px-4" no-gutters>
@@ -137,25 +160,17 @@
</v-container>
</template>
<script>
import ListItemStream from '../components/ListItemStream'
import StreamNewDialog from '../components/dialogs/StreamNewDialog'
import GettingStartedWizard from '../components/GettingStartedWizard'
import gql from 'graphql-tag'
import streamsQuery from '../graphql/streams.gql'
import userQuery from '../graphql/user.gql'
import InfiniteLoading from 'vue-infinite-loading'
import ServerInviteDialog from '../components/dialogs/ServerInviteDialog.vue'
import SearchBar from '../components/SearchBar'
import gql from 'graphql-tag'
export default {
name: 'Streams',
components: {
ListItemStream,
StreamNewDialog,
InfiniteLoading,
ServerInviteDialog,
GettingStartedWizard,
SearchBar
InfiniteLoading: () => import('vue-infinite-loading'),
ListItemStream: () => import('@/components/ListItemStream'),
GettingStartedWizard: () => import('@/components/GettingStartedWizard'),
MainNavActions: () => import('@/components/MainNavActions')
},
apollo: {
streams: {
@@ -169,6 +184,30 @@ export default {
return !this.loggedIn
}
},
userCommits: {
query: gql`
query {
userCommits: user {
id
commits {
totalCount
items {
id
message
sourceApplication
streamId
streamName
branchName
createdAt
}
}
}
}
`,
skip() {
return !this.loggedIn
}
},
$subscribe: {
userStreamAdded: {
query: gql`
@@ -199,10 +238,9 @@ export default {
}
},
data: () => ({
activeTab: 'streams',
streams: [],
newStreamDialog: false,
streamNav: true
streamNav: true,
streamNewDialog: 0
}),
computed: {
loggedIn() {
@@ -217,7 +255,12 @@ export default {
}
},
mounted() {
setTimeout( function() { this.streamNav = !this.$vuetify.breakpoint.smAndDown }.bind(this), 100 )
setTimeout(
function () {
this.streamNav = !this.$vuetify.breakpoint.smAndDown
}.bind(this),
100
)
},
methods: {
showServerInviteDialog() {
+30 -60
View File
@@ -7,34 +7,8 @@
v-model="activityNav"
style="left: 56px"
>
<v-app-bar style="position: absolute; top: 0; width: 100%; z-index: 90" elevation="0">
<search-bar />
</v-app-bar>
<main-nav-actions :open-new-stream="newStreamDialog"/>
<v-list style="margin-top: 64px" shaped>
<v-list-item class="primary" dark link @click="newStreamDialog = true">
<v-list-item-content>
<v-list-item-title>New Stream</v-list-item-title>
<v-list-item-subtitle class="caption">
Quickly create a new data repository.
</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-icon>
<v-icon class="">mdi-plus-box</v-icon>
</v-list-item-icon>
</v-list-item>
<v-list-item link @click="showServerInviteDialog()">
<v-list-item-content>
<v-list-item-title>Invite</v-list-item-title>
<v-list-item-subtitle class="caption">
Invite a colleague to Speckle!
</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-icon>
<v-icon class="">mdi-email</v-icon>
</v-list-item-icon>
</v-list-item>
</v-list>
<v-list v-if="streams && streams.items.length > 0" color="transparent" dense>
<v-subheader class="mt-3 ml-2">Recently updated streams</v-subheader>
<v-list-item
@@ -69,26 +43,15 @@
</v-toolbar-title>
<v-spacer v-if="!activityNav"></v-spacer>
<v-toolbar-items v-if="!activityNav" style="position: relative; left: 0">
<v-btn color="primary" @click="newStreamDialog = true">
<v-btn color="primary" @click="newStreamDialog++">
<v-icon>mdi-plus-box</v-icon>
</v-btn>
</v-toolbar-items>
</v-app-bar>
<server-invite-dialog ref="serverInviteDialog" />
<v-dialog v-model="newStreamDialog" max-width="500" :fullscreen="$vuetify.breakpoint.smAndDown">
<stream-new-dialog
v-if="streams"
:open="newStreamDialog"
:redirect="streams.items.length > 0"
@created="newStreamDialog = false"
@close="newStreamDialog = false"
/>
</v-dialog>
<v-row class="pr-4">
<!-- <v-col cols="12"> -->
<!-- <getting-started-wizard /> -->
<!-- <getting-started-wizard /> -->
<!-- </v-col> -->
<v-col v-if="$apollo.loading && !timeline">
<div class="my-5">
@@ -138,7 +101,26 @@
<latest-blogposts></latest-blogposts>
<v-card rounded="lg" class="mt-2">
<v-card-text class="caption">
<p class="mb-0">At <a href="https://speckle.systems" target="_blank" class="text-decoration-none">Speckle</a>, we're working tirelessly to bring you the best open source data platform for AEC. Tell us what you think on our <a href="https://speckle.community" target="_blank" class="text-decoration-none">forum</a>, and don't forget to give us a on <a href="https://github.com/specklesystems/speckle-sharp" target="_blank" class="text-decoration-none">Github</a>!</p>
<p class="mb-0">
At
<a href="https://speckle.systems" target="_blank" class="text-decoration-none">
Speckle
</a>
we're working tirelessly to bring you the best open source data platform for AEC.
Tell us what you think on our
<a href="https://speckle.community" target="_blank" class="text-decoration-none">
forum
</a>
, and don't forget to give us a on
<a
href="https://github.com/specklesystems/speckle-sharp"
target="_blank"
class="text-decoration-none"
>
Github
</a>
!
</p>
</v-card-text>
</v-card>
</v-col>
@@ -148,32 +130,22 @@
<script>
import gql from 'graphql-tag'
import InfiniteLoading from 'vue-infinite-loading'
import ListItemActivity from '@/components/ListItemActivity'
import GettingStartedWizard from '../components/GettingStartedWizard'
import ServerInviteDialog from '@/components/dialogs/ServerInviteDialog.vue'
import StreamNewDialog from '@/components/dialogs/StreamNewDialog'
import SearchBar from '@/components/SearchBar'
import LatestBlogposts from '@/components/LatestBlogposts'
export default {
name: 'Timeline',
components: {
ListItemActivity,
InfiniteLoading,
ServerInviteDialog,
StreamNewDialog,
GettingStartedWizard,
SearchBar,
LatestBlogposts
InfiniteLoading:()=>import( 'vue-infinite-loading'),
ListItemActivity:()=>import( '@/components/ListItemActivity'),
GettingStartedWizard:()=>import( '@/components/GettingStartedWizard'),
LatestBlogposts: () => import('@/components/LatestBlogposts'),
MainNavActions: () => import('@/components/MainNavActions')
},
props: {
type: String
},
data() {
return {
newStreamDialog: false,
newStreamDialog: 0,
activityNav: true
}
},
@@ -261,9 +233,7 @@ export default {
// console.log(groupedTimeline)
this.groupedTimeline = groupedTimeline
},
showServerInviteDialog() {
this.$refs.serverInviteDialog.show()
},
infiniteHandler($state) {
this.$apollo.queries.timeline.fetchMore({
variables: {
@@ -1,6 +1,6 @@
<template>
<div id="admin-settings">
<v-card class="elevation-0" rounded="lg" v-if="serverInfo">
<v-card rounded="lg" v-if="serverInfo">
<v-toolbar flat :class="`${!$vuetify.theme.dark ? 'grey lighten-5' : ''}`">
<v-toolbar-title>{{ serverInfo.name }}</v-toolbar-title>
</v-toolbar>
@@ -1,7 +1,7 @@
<template>
<v-row>
<v-col cols="12">
<v-timeline v-if="stream" align-top dense>
<v-timeline v-if="stream && groupedActivity && groupedActivity.length !== 0" align-top dense>
<list-item-activity
v-for="activity in groupedActivity"
:key="activity.time"
@@ -22,6 +22,11 @@
<v-skeleton-loader type="article"></v-skeleton-loader>
</v-timeline-item>
</v-timeline>
<div v-if="groupedActivity && groupedActivity.length === 0">
<v-card class="transparent elevation-0 mt-10">
<v-card-text>Nothing to show 🍃</v-card-text>
</v-card>
</div>
</v-col>
</v-row>
</template>
@@ -15,11 +15,12 @@
<portal to="streamActionsBar">
<v-btn
elevation="0"
v-if="stream"
v-if="loggedInUserId && stream && stream.role !== 'stream:reviewer' && stream.branch.name !== 'main'"
color="primary"
small
v-tooltip="'Edit branch'"
@click="editBranch()"
rounded
>
<v-icon small class="mr-2">mdi-pencil</v-icon>
<span class="hidden-md-and-down">Edit</span>
@@ -50,7 +51,7 @@
<!-- TODO: pagination -->
<v-list-item v-if="stream">
<!-- <v-list-item v-if="stream">
<v-list-item-icon class="pl-4" style="width: 40px">
<v-avatar
:color="`grey ${this.$vuetify.theme.dark ? 'darken-4' : 'lighten-4'}`"
@@ -65,8 +66,8 @@
Branch "{{ stream.branch.name }}" created
</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item v-if="stream">
</v-list-item> -->
<!-- <v-list-item v-if="stream">
<v-list-item-icon class="pl-4" style="width: 40px">
<v-avatar
:color="`grey ${this.$vuetify.theme.dark ? 'darken-4' : 'lighten-4'}`"
@@ -81,7 +82,7 @@
TODO: PAGINATION YO
</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item> -->
</v-list>
</v-col>
@@ -145,6 +146,9 @@ export default {
}
},
computed: {
loggedInUserId(){
return localStorage.getItem('uuid')
},
streamId() {
return this.$route.params.streamId
},
+27 -9
View File
@@ -1,7 +1,6 @@
<template>
<div>
<v-row>
<v-col v-if="$apollo.queries.stream.loading" cols="12" class="ma-0 pa-0">
<v-card>
<v-skeleton-loader type="list-item-avatar, card-avatar, article"></v-skeleton-loader>
@@ -10,20 +9,36 @@
<v-col v-else-if="stream.commit" cols="12" class="ma-0 pa-0">
<portal to="streamActionsBar">
<v-btn elevation="0" color="primary" small v-tooltip="'Edit commit'" v-if=" stream && (stream.role === 'stream:owner' || stream.role === 'stream:contributor')" @click="editCommit">
<v-btn
elevation="0"
color="primary"
small
rounded
v-tooltip="'Edit commit'"
v-if="
stream && stream.role!== 'stream:reviewer' && stream.commit.authorId === loggedInUserId
"
@click="editCommit"
>
<v-icon small class="mr-2">mdi-pencil</v-icon>
<span class="hidden-md-and-down">Edit</span>
</v-btn>
</portal>
<portal to="streamTitleBar">
<div>
<router-link :to="`/streams/${stream.id}/branches/${stream.commit.branchName}`" class="text-decoration-none space-grotesk" v-tooltip="'Go to branch ' + stream.commit.branchName">
<router-link
:to="`/streams/${stream.id}/branches/${stream.commit.branchName}`"
class="text-decoration-none space-grotesk"
v-tooltip="'Go to branch ' + stream.commit.branchName"
>
<v-icon small class="primary--text mr-1 mb-1">mdi-source-branch</v-icon>
<b>{{ stream.commit.branchName }}</b>
<b>{{ stream.commit.branchName }}</b>
</router-link>
/
<v-icon small class="mr-1">mdi-source-commit</v-icon>
<span class="space-grotesk mr-2" v-tooltip="'Commit message'">{{ stream.commit.message }}</span>
<span class="space-grotesk mr-2" v-tooltip="'Commit message'">
{{ stream.commit.message }}
</span>
<user-avatar
:id="stream.commit.authorId"
:avatar="stream.commit.authorAvatar"
@@ -44,7 +59,7 @@
<div style="height: 60vh">
<renderer :object-url="commitObjectUrl" @selection="handleSelection" />
</div>
<v-card elevation="0" rounded="lg">
<!-- Selected object -->
<v-expand-transition>
@@ -92,6 +107,7 @@
<error-block :message="'Commit not found'" />
</v-col>
</v-row>
<commit-edit-dialog ref="commitDialog"></commit-edit-dialog>
</div>
</template>
<script>
@@ -108,7 +124,7 @@ export default {
ObjectSimpleViewer: () => import('@/components/ObjectSimpleViewer'),
Renderer: () => import('@/components/Renderer'),
SourceAppAvatar: () => import('@/components/SourceAppAvatar'),
ErrorBlock: () => import('@/components/ErrorBlock'),
ErrorBlock: () => import('@/components/ErrorBlock')
},
data: () => ({
loadedModel: false,
@@ -128,6 +144,9 @@ export default {
}
},
computed: {
loggedInUserId(){
return localStorage.getItem('uuid')
},
commitDate() {
if (!this.stream.commit) return null
let date = new Date(this.stream.commit.createdAt)
@@ -178,5 +197,4 @@ export default {
}
}
</script>
<style scoped>
</style>
<style scoped></style>
@@ -1,380 +0,0 @@
<template>
<v-row v-if="!error">
<v-col v-if="$apollo.loading" cols="12">
<v-skeleton-loader type="heading" class="mb-5"></v-skeleton-loader>
<v-skeleton-loader type="text@3" class="mb-5"></v-skeleton-loader>
<v-skeleton-loader type="list-item-two-line, image"></v-skeleton-loader>
</v-col>
<v-col v-if="stream" cols="12">
<v-row v-if="stream">
<v-col cols="12" sm="12" md="8" lg="9" xl="9">
<v-card class="mb-4" rounded="lg" elevation="0">
<div class="pt-5 ml-7">
<span class="title mb-3 mt-3 mr-3">Branch:</span>
<v-select
v-model="selectedBranch"
:items="branches"
item-value="name"
solo
flat
return-object
background-color="background"
class="d-inline-block mt-2 mr-4 mb-0 pb-0"
style="max-width: 50%"
>
<template #selection="{ item }">
<v-icon class="mr-2">mdi-source-branch</v-icon>
<span class="text-truncate">{{ item.name }}</span>
</template>
<template #item="{ item }">
<div class="pa-2">
<p class="pa-0 ma-0">{{ item.name }}</p>
<p class="caption pa-0 ma-0 grey--text">
{{ item.description }}
</p>
</div>
</template>
</v-select>
<!-- <v-btn
class="pa-3"
color="primary"
text
block
:to="'/streams/' + $route.params.streamId + '/commits/' + latestCommit.id"
>
<v-icon class="mr-2 float-left">mdi-source-commit</v-icon>
See commit details
</v-btn> -->
</div>
<div v-if="latestCommit">
<div style="height: 50vh">
<renderer
:object-url="latestCommitObjectUrl"
:unload-trigger="clearRendererTrigger"
show-selection-helper
/>
</div>
<v-list two-line class="pa-0" color="transparent">
<v-list-item
:to="'/streams/' + $route.params.streamId + '/commits/' + latestCommit.id"
>
<v-list-item-icon>
<user-avatar
:id="latestCommit.authorId"
:avatar="latestCommit.authorAvatar"
:name="latestCommit.authorName"
:size="30"
/>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title class="mb-2 pt-1">
<b>{{ latestCommit.message }}</b>
<i class="caption">&nbsp;(latest)</i>
</v-list-item-title>
<v-list-item-subtitle class="caption">
<b>{{ latestCommit.authorName }}</b>
&nbsp;
<timeago :datetime="latestCommit.createdAt"></timeago>
</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action>
<v-row align="center" justify="center">
<v-chip small class="mr-2 no-hover">
<v-icon small class="mr-2">mdi-source-commit</v-icon>
{{ latestCommit.id }}
</v-chip>
<source-app-avatar :application-name="latestCommit.sourceApplication" />
</v-row>
</v-list-item-action>
</v-list-item>
</v-list>
<!-- LAST 2 COMMITS -->
<v-list dense color="transparent" class="mb-0 pa-0">
<div v-for="(commit, i) in selectedBranch.commits.items" :key="commit.id">
<v-list-item
v-if="i > 0"
:to="'/streams/' + $route.params.streamId + '/commits/' + commit.id"
>
<v-list-item-icon>
<user-avatar
:id="commit.authorId"
:avatar="commit.authorAvatar"
:name="commit.authorName"
:size="30"
/>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title class="mb-2 pt-1">
{{ commit.message }}
</v-list-item-title>
<v-list-item-subtitle class="caption">
<b>{{ commit.authorName }}</b>
&nbsp;
<timeago :datetime="commit.createdAt"></timeago>
</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action>
<v-row align="center" justify="center">
<v-chip small class="mr-2 no-hover">
<v-icon small class="mr-2">mdi-source-commit</v-icon>
{{ commit.id }}
</v-chip>
<source-app-avatar :application-name="commit.sourceApplication" />
</v-row>
</v-list-item-action>
</v-list-item>
<v-divider />
</div>
<v-list-item
v-if="selectedBranch"
color="transparent"
:to="'/streams/' + $route.params.streamId + '/branches/' + selectedBranch.name"
>
<v-row align="center" justify="center">
<span class="font-weight-bold primary--text py-3 my-4">
<v-icon class="mr-2 float-left" color="primary">mdi-source-commit</v-icon>
See all commits on
{{ selectedBranch.name }} ({{ selectedBranch.commits.totalCount }})
</span>
</v-row>
</v-list-item>
</v-list>
</div>
</v-card>
<no-data-placeholder
v-if="!latestCommit && selectedBranch"
:message="'Branch ' + selectedBranch.name + ' has no commits.'"
/>
</v-col>
<v-col cols="12" sm="12" md="4" lg="3" xl="3">
<h4 class="space-grotesk mb-3">About:</h4>
<div>
<v-chip class="mr-3 mb-3 no-hover">
<v-icon small left>mdi-source-branch</v-icon>
{{ stream.branches.totalCount }}
branch{{ stream.branches.totalCount === 1 ? '' : 'es' }}
</v-chip>
<v-chip class="mr-3 mb-3 no-hover">
<v-icon small left>mdi-source-commit</v-icon>
&nbsp;
{{ stream.commits.totalCount }}
commit{{ stream.commits.totalCount === 1 ? '' : 's' }}
</v-chip>
<v-chip class="mr-3 mb-3 no-hover">
<span
v-if="stream.isPublic"
v-tooltip="`Anyone can view this stream. Only you and collaborators can edit it.`"
>
<v-icon small left>mdi-lock-open-variant-outline</v-icon>
&nbsp; public
</span>
<span v-else v-tooltip="`Only collaborators can access this stream.`">
<v-icon small left>mdi-lock-outline</v-icon>
&nbsp; private
</span>
</v-chip>
<v-chip v-if="loggedIn" class="mr-3 mb-3 no-hover">
<v-icon small left>mdi-account-key-outline</v-icon>
{{ stream.role.split(':')[1] }}
</v-chip>
</div>
<div class="my-3">
<div class="caption mb-3">
<span v-tooltip="formatDate(stream.createdAt)">
Created
<timeago :datetime="stream.createdAt"></timeago>
</span>
</div>
<div class="caption">
<span v-tooltip="formatDate(stream.updatedAt)">
Updated
<timeago :datetime="stream.updatedAt"></timeago>
</span>
</div>
</div>
<h4 class="space-grotesk mt-7 mb-3">Collaborators:</h4>
<user-avatar
v-for="collab in stream.collaborators"
:id="collab.id"
:key="collab.id"
:size="30"
:avatar="collab.avatar"
:name="collab.name"
class="ml-1"
></user-avatar>
</v-col>
</v-row>
<v-row v-else-if="error" justify="center">
<v-col cols="12" sm="12" md="8" lg="9" xl="8" class="pt-10">
<error-block :message="error" />
</v-col>
</v-row>
<!-- </v-card> -->
</v-col>
</v-row>
<v-row v-else justify="center">
<v-col cols="12" class="pt-10">
<error-block :message="error" />
</v-col>
</v-row>
</template>
<script>
import streamBranchesQuery from '@/graphql/streamBranches.gql'
import gql from 'graphql-tag'
export default {
name: 'Details',
components: {
UserAvatar: () => import('@/components/UserAvatar'),
SourceAppAvatar: () => import('@/components/SourceAppAvatar'),
NoDataPlaceholder: () => import('@/components/NoDataPlaceholder'),
Renderer: () => import('@/components/Renderer'),
ErrorBlock: () => import('@/components/ErrorBlock')
},
data() {
return {
clearRendererTrigger: 0,
error: '',
selectedBranch: null
}
},
apollo: {
stream: {
query: streamBranchesQuery,
variables() {
return {
id: this.$route.params.streamId
}
}
},
$subscribe: {
streamUpdated: {
query: gql`
subscription($streamId: String!) {
streamUpdated(streamId: $streamId)
}
`,
variables() {
return {
streamId: this.$route.params.streamId
}
},
result() {
this.$apollo.queries.stream.refetch()
},
skip() {
return !this.loggedIn
}
},
branchCreated: {
query: gql`
subscription($streamId: String!) {
branchCreated(streamId: $streamId)
}
`,
variables() {
return {
streamId: this.$route.params.streamId
}
},
result() {
this.$apollo.queries.stream.refetch()
},
skip() {
return !this.loggedIn
}
},
branchDeleted: {
query: gql`
subscription($streamId: String!) {
branchDeleted(streamId: $streamId)
}
`,
variables() {
return {
streamId: this.$route.params.streamId
}
},
result() {
this.$apollo.queries.stream.refetch()
},
skip() {
return !this.loggedIn
}
},
commitCreated: {
query: gql`
subscription($streamId: String!) {
commitCreated(streamId: $streamId)
}
`,
variables() {
return {
streamId: this.$route.params.streamId
}
},
result() {
this.$apollo.queries.stream.refetch()
},
skip() {
return !this.loggedIn
}
}
}
},
computed: {
branches() {
return this.stream.branches.items.filter((b) => !b.name.startsWith('globals'))
},
latestCommit() {
if (!this.selectedBranch || this.selectedBranch.commits.items.length === 0) return null
return this.selectedBranch.commits.items[0]
},
latestCommitObjectUrl() {
if (!this.latestCommit) return null
return `${window.location.origin}/streams/${this.$route.params.streamId}/objects/${this.latestCommit.referencedObject}`
},
loggedIn() {
return localStorage.getItem('uuid') !== null
}
},
watch: {
stream() {
if (!this.stream) return
let branchName = 'main'
let index = this.branches.findIndex((x) => x.name === branchName)
if (index > -1) this.selectedBranch = this.branches[index]
}
},
methods: {
hyperlink(text) {
var exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gi
return text.replace(exp, '<a target="_blank" href="$1">$1</a>')
},
truncate(input, length = 250) {
if (!input) return ''
if (input.length > length) {
return input.substring(0, length) + '...'
}
return input
},
formatDate(d) {
if (!this.stream) return null
let date = new Date(d)
let options = { year: 'numeric', month: 'short', day: 'numeric' }
return date.toLocaleString(undefined, options)
},
changeBranch() {
this.clearRendererTrigger += 42
}
}
}
</script>
@@ -18,17 +18,18 @@
</v-list>
</v-col>
<v-col cols="12" class="" style="height: 40px"></v-col>
<v-col cols="12" lg="4" class="pa-0 ma-0" :order="`${$vuetify.breakpoint.lgAndUp ? 'last' : ''}`">
<v-col cols="12" :lg="loggedIn ? 4 : 12" class="pa-0 ma-0" :order="`${$vuetify.breakpoint.lgAndUp ? 'last' : ''}`">
<v-card class="transparent elevation-0">
<v-toolbar class="transparent elevation-0">
<v-toolbar-title>Latest Active Branches</v-toolbar-title>
<v-spacer />
</v-toolbar>
<v-card-title class="caption" style="margin-top: -30px;">
The stream's last three updated branches.
The stream's last three updated branches
</v-card-title>
<v-row class="pa-4 mt-1">
<v-col cols="12" md="4" lg="12" v-for="branch in latestBranches" :key="branch.name">
<v-col cols="12" sm="4" md="4" :lg="loggedIn ? 12 : 4" v-for="branch in latestBranches" :key="branch.name">
<v-card :to="`/streams/${$route.params.streamId}/branches/${branch.name}`">
<preview-image
:height="120"
@@ -56,14 +57,14 @@
</v-card>
</v-col>
<v-col cols="12" lg="8" class="pr-2">
<v-col cols="12" lg="8" class="pr-2" v-if="loggedIn">
<v-card class="transparent elevation-0">
<v-toolbar class="transparent elevation-0">
<v-toolbar-title>Stream Feed</v-toolbar-title>
<v-spacer />
</v-toolbar>
<v-card-title class="caption" style="margin-top: -30px;">
Recent activity log.
Recent activity log
</v-card-title>
</v-card>
<div style="margin-top:-42px">
@@ -30,7 +30,9 @@
<!-- Top padding hack -->
<div style="display: block; height: 65px"></div>
<div class="px-4 mt-2" v-if="!loggedIn" >
<v-btn large block color="primary" to="/authn/login">Log In</v-btn>
</div>
<!-- Various Stream Details -->
<v-card elevation="0" v-if="stream" class="pa-1 mb-0" color="transparent">
<v-card-text class="caption">
@@ -123,10 +125,10 @@
<v-list-item
link
v-tooltip.bottom="'Create a new branch to help categorise your commits.'"
v-if="stream.role!=='stream:reviewer'"
v-if="stream.role !== 'stream:reviewer'"
>
<v-list-item-icon>
<v-icon small style="padding-top:10px;">mdi-plus-box</v-icon>
<v-icon small style="padding-top: 10px">mdi-plus-box</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>New Branch</v-list-item-title>
@@ -143,8 +145,10 @@
:to="`/streams/${stream.id}/branches/${branch.name}`"
>
<v-list-item-icon>
<v-icon small style="padding-top:10px;" v-if="branch.name !== 'main'">mdi-source-branch</v-icon>
<v-icon small style="padding-top:10px;" v-else>mdi-star</v-icon>
<v-icon small style="padding-top: 10px" v-if="branch.name !== 'main'">
mdi-source-branch
</v-icon>
<v-icon small style="padding-top: 10px" v-else>mdi-star</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>
@@ -233,7 +237,8 @@
<!-- child routes can teleport buttons here -->
</portal-target>
<v-toolbar-items>
<v-btn elevation="0" v-if="stream">
<v-btn large color="primary" to="/authn/login" v-if="!loggedIn && stream && !streamNav">Log In</v-btn>
<v-btn elevation="0" v-if="loggedIn && stream">
<v-icon small class="mr-2">mdi-share-variant</v-icon>
<v-icon small class="mr-2 hidden-sm-and-down" v-if="!stream.isPublic">mdi-lock</v-icon>
<v-icon small class="mr-2 hidden-sm-and-down" v-else>mdi-lock-open</v-icon>
@@ -249,8 +254,8 @@
</transition>
</v-container>
<v-container style="padding-left: 56px" v-else>
<error-placeholder :error-type='error.toLowerCase().includes("not found") ? "404" : "access"'>
<h2>{{error}}</h2>
<error-placeholder :error-type="error.toLowerCase().includes('not found') ? '404' : 'access'">
<h2>{{ error }}</h2>
</error-placeholder>
</v-container>
</v-container>
@@ -347,6 +352,9 @@ export default {
}
},
computed: {
loggedIn() {
return localStorage.getItem('uuid') !== null
},
sortedBranches() {
// TODO: group by `/` (for later)
if (!this.stream) return
@@ -357,7 +365,7 @@ export default {
},
branchesTotalCount() {
if (!this.stream) return 0
return this.stream.branches.items.filter(b => b.name !== 'globals').length
return this.stream.branches.items.filter((b) => b.name !== 'globals').length
},
userId() {
return localStorage.getItem('uuid')
@@ -402,7 +410,7 @@ export default {
}
</script>
<style scoped>
.no-overlay.v-list-item--active::before{
opacity: 0 !important;
}
.no-overlay.v-list-item--active::before {
opacity: 0 !important;
}
</style>