feat(frontend): deleting stale components

This commit is contained in:
Dimitrie Stefanescu
2022-01-31 22:58:19 +00:00
parent 53d2eae49c
commit 1d3987af90
19 changed files with 33 additions and 728 deletions
@@ -10,8 +10,8 @@
</template>
<script>
import { signOut } from '@/auth-helpers'
import userQuery from '../graphql/userById.gql'
import UserAvatarIcon from '@/components/UserAvatarIcon'
import userQuery from '@/graphql/userById.gql'
import UserAvatarIcon from '@/cleanup/components/common/UserAvatarIcon'
export default {
components: { UserAvatarIcon },
@@ -123,7 +123,7 @@ export default {
},
cleanKey(key) {
if (key === 'totalChildrenCount') return 'children count'
if (key === 'speckle_type') return 'type'
if (key === 'speckle_type') return 'speckle type'
return key
}
}
@@ -1,5 +1,8 @@
<template>
<v-app id="speckle-auth">
<v-app
id="speckle-auth"
:class="`${$vuetify.theme.dark ? 'background-dark' : 'background-light'}`"
>
<v-container fill-height fluid>
<v-row align="center" justify="center">
<v-col v-if="showBlurb" cols="12" md="6" lg="6" xl="4" class="hidden-sm-and-down">
@@ -19,7 +22,7 @@
</template>
<script>
import gql from 'graphql-tag'
import Blurb from '../components/auth/Blurb'
import Blurb from '@/cleanup/components/auth/Blurb'
export default {
components: { Blurb },
@@ -50,3 +53,16 @@ export default {
}
}
</script>
<style scoped>
.background-light {
background: #8e9eab;
background: -webkit-linear-gradient(to top right, #eeeeee, #c8e8ff);
background: linear-gradient(to top right, #ffffff, #c8e8ff);
}
.background-dark {
background: #141e30;
background: -webkit-linear-gradient(to top left, #243b55, #141e30);
background: linear-gradient(to top left, #243b55, #141e30);
}
</style>
@@ -66,8 +66,8 @@
</template>
<script>
import gql from 'graphql-tag'
import UserAvatar from '../../components/UserAvatarAuthoriseApp'
import UserAvatarIcon from '@/components/UserAvatarIcon'
import UserAvatar from '@/cleanup/components/auth/UserAvatarAuthoriseApp'
import UserAvatarIcon from '@/cleanup/components/common/UserAvatarIcon'
export default {
name: 'AuthorizeApp',
@@ -164,7 +164,7 @@ import gql from 'graphql-tag'
import debounce from 'lodash.debounce'
import crs from 'crypto-random-string'
import Strategies from '@/components/auth/Strategies'
import Strategies from '@/cleanup/components/auth/Strategies'
import { isEmailValid } from '@/auth-helpers'
export default {
@@ -1,65 +0,0 @@
<template>
<span v-if="pretty">{{ tweeningValue | prettynum(value) }}</span>
<span v-else>{{ tweeningValue }}</span>
</template>
<script>
import TWEEN from "tween";
export default {
name: "AnimatedNumber",
props: {
value: {
type: Number,
default: 0
},
duration: {
type: Number,
default: 1000
},
delay: {
type: Number,
default: 300
},
pretty: {
type: Boolean,
default: true
}
},
data() {
return {
tweeningValue: 0
};
},
watch: {
value(oldVal, newVal) {
this.tween(oldVal, newVal)
}
},
mounted() {
setTimeout(()=> this.tween(0, this.value), this.delay)
},
methods: {
tween(startValue, endValue) {
var vm = this;
function animate() {
if (TWEEN.update()) {
requestAnimationFrame(animate);
}
}
new TWEEN.Tween({ tweeningValue: startValue })
.to({ tweeningValue: endValue }, this.duration).easing(TWEEN.Easing.Quintic.Out)
.onUpdate(function() {
vm.tweeningValue = this.tweeningValue.toFixed(0);
})
.start();
animate();
}
}
}
</script>
<style scoped>
</style>
@@ -1,132 +0,0 @@
<template>
<div
v-if="activity && activity.items.length !== 0"
v-tooltip="`Received ${activity.items.length} times. Click to see all commit receives.`"
@click="showAllActivityDialog = true"
style="margin-right: -10px;"
>
<div
style="cursor: pointer; min-height: 33px; line-height: 33px"
:class="`${$vuetify.theme.dark ? 'black' : 'white'} rounded-xl px-2`"
>
<v-icon small>mdi-call-received</v-icon>
<user-avatar
v-for="userId in receivedUsersUnique.slice(0, 3)"
v-show="$vuetify.breakpoint.smAndUp"
:id="userId"
:key="userId"
:show-hover="false"
:size="25"
></user-avatar>
<v-avatar
v-if="receivedUsersUnique.length > 3"
v-show="$vuetify.breakpoint.smAndUp"
class="ml-1"
size="25"
color="primary"
>
<span class="white--text caption">+{{ receivedUsersUnique.length - 3 }}</span>
</v-avatar>
<v-avatar v-show="$vuetify.breakpoint.xsOnly" class="ml-1" size="25" color="primary">
<span class="white--text caption">{{ receivedUsersUnique.length }}</span>
</v-avatar>
</div>
<v-dialog
v-model="showAllActivityDialog"
max-width="500"
:fullscreen="$vuetify.breakpoint.xsOnly"
>
<v-card v-if="activity">
<v-toolbar>
<v-app-bar-nav-icon><v-icon>mdi-download</v-icon></v-app-bar-nav-icon>
<v-toolbar-title>All Received Receipts</v-toolbar-title>
<v-spacer />
<v-btn icon @click="showAllActivityDialog = false"><v-icon>mdi-close</v-icon></v-btn>
</v-toolbar>
<v-list>
<v-list-item v-for="(act, i) in activity.items" :key="i">
<v-list-item-icon>
<user-avatar :id="act.userId"></user-avatar>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title class="captionxxx">
{{ act.message.split(' was ')[1] }}
</v-list-item-title>
<v-list-item-subtitle>
<timeago :datetime="act.time"></timeago>
</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action>
<source-app-avatar class="mt-3 mb-3" :application-name="act.info.sourceApplication" />
</v-list-item-action>
</v-list-item>
</v-list>
</v-card>
</v-dialog>
</div>
</template>
<script>
import gql from 'graphql-tag'
import UserAvatar from './UserAvatar'
import SourceAppAvatar from './SourceAppAvatar'
export default {
components: { UserAvatar, SourceAppAvatar },
props: {
commitId: {
type: String,
default: null
},
streamId: {
type: String,
default: null
}
},
data() {
return { showAllActivityDialog: false }
},
apollo: {
activity: {
query: gql`
query CommitActivity($streamId: String!, $commitId: String!) {
stream(id: $streamId) {
id
commit(id: $commitId) {
id
activity(actionType: "commit_receive", limit: 200) {
items {
info
time
userId
message
}
}
}
}
}
`,
update: (data) => data.stream.commit.activity,
variables() {
return {
streamId: this.streamId,
commitId: this.commitId
}
},
skip() {
if (!this.streamId || !this.commitId) return true
return false
}
}
},
computed: {
receivedUsersUnique() {
if (!(this.activity && this.activity.items && this.activity.items.length > 0)) return []
let set = new Set()
this.activity.items.forEach((item) => set.add(item.userId))
return Array.from(set)
}
}
}
</script>
@@ -276,10 +276,10 @@
</template>
<script>
import UserAvatar from './UserAvatar'
import UserAvatar from '@/cleanup/components/common/UserAvatar'
import UserPill from './UserPill'
import SourceAppAvatar from './SourceAppAvatar'
import PreviewImage from './PreviewImage'
import SourceAppAvatar from '@/cleanup/components/common/SourceAppAvatar'
import PreviewImage from '@/cleanup/components/common/PreviewImage'
import gql from 'graphql-tag'
export default {
@@ -1,70 +0,0 @@
<template>
<div>
<v-list rounded dense>
<v-subheader>General</v-subheader>
<v-list-item class="elevation-3xxx" link @click="newStreamDialog = true">
<v-list-item-icon>
<v-icon small class="">mdi-plus-box</v-icon>
</v-list-item-icon>
<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>
<v-list-item class="elevation-3xxx" link @click="showServerInviteDialog()">
<v-list-item-icon>
<v-icon small class="">mdi-email</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Send 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>
</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
}
},
data() {
return {
newStreamDialog: false
}
},
watch: {
OpenNewStream(val, old) {
this.newStreamDialog = true
}
},
mounted() {},
methods: {
showServerInviteDialog() {
this.$refs.serverInviteDialog.show()
}
}
}
</script>
@@ -1,78 +0,0 @@
<template>
<section-card expandable class="my-2" dense>
<template #header>Access Tokens</template>
<template #actions>
<v-spacer />
<v-btn small color="primary" @click="tokenDialog = true">new token</v-btn>
</template>
<v-card rounded="lg" :class="`${!$vuetify.theme.dark ? 'grey lighten-5' : ''}`">
<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!
<b>
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>
<list-item-token
v-for="token in tokens"
:key="token.id"
:token="token"
@deleted="refreshList"
/>
</v-list>
</v-card-text>
<v-card-text v-else>You have no api tokens.</v-card-text>
<v-dialog v-model="tokenDialog" width="500">
<token-dialog @token-added="refreshList" @close="tokenDialog = false" />
</v-dialog>
</v-card>
</section-card>
</template>
<script>
import gql from 'graphql-tag'
import ListItemToken from './ListItemPersonalAccessToken'
import TokenDialog from './dialogs/TokenDialog'
export default {
components: {
ListItemToken,
TokenDialog,
SectionCard: () => import('@/cleanup/components/common/SectionCard')
},
data() {
return {
tokenDialog: false
}
},
apollo: {
tokens: {
query: gql`
query {
user {
id
apiTokens {
id
name
lastUsed
lastChars
createdAt
scopes
}
}
}
`,
update: (data) => data.user.apiTokens
}
},
methods: {
refreshList() {
this.$apollo.queries.tokens.refetch()
}
}
}
</script>
@@ -1,129 +0,0 @@
<template>
<v-card class="elevation-0 mt-3 mb-5 transparent">
<v-card-title>Your Apps</v-card-title>
<v-card-text class="">
Here you can review the apps that you have granted access to.
<v-btn
v-if="!hasManager"
plain
small
:href="`speckle://accounts?add_server_account=${rootUrl}`"
>
Add Account To Desktop Manager
</v-btn>
</v-card-text>
<v-card-text v-if="$apollo.loading">Loading...</v-card-text>
<v-card-text v-if="authorizedApps && authorizedApps.length !== 0">
<v-row>
<v-col
v-for="app in authorizedApps"
:key="app.id"
cols="12"
sm="6"
class="d-flex"
style="flex-direction: column"
>
<v-card flex-grow-1 d-flex flex-column>
<v-card-text>
<h3 class="mb-3">
<v-icon v-if="app.trustByDefault" class="mr-1 primary--text" small>
mdi-shield
</v-icon>
{{ app.name }}
</h3>
<p class="text-truncate">
{{ app.description }}
<span v-show="app.id === 'spklwebapp'" class="caption">
(This is the app your're currently using!)
</span>
</p>
</v-card-text>
<v-spacer></v-spacer>
<v-card-actions>
<v-btn plain small color="error" @click="showRevokeAccessDialog(app)">
Revoke access
</v-btn>
<v-btn plain small :href="app.redirectUrl" target="_blank">Open</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
<v-dialog v-model="showRevokeDialog" max-width="500">
<v-card>
<v-card-title>Revoke Access</v-card-title>
<v-card-text>
Revoking access to your app will log you out of it on all devices. Are you sure you want
to proceed?
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn plain small color="error" @click="revokeAccess()">Revoke access</v-btn>
<v-btn plain small>Cancel</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-card-text>
</v-card>
</template>
<script>
import gql from 'graphql-tag'
export default {
components: {},
data() {
return {
appDialog: false,
showRevokeDialog: false,
appToRevoke: null
}
},
apollo: {
authorizedApps: {
query: gql`
query {
user {
id
authorizedApps {
id
description
name
redirectUrl
trustByDefault
}
}
}
`,
update: (data) => data.user.authorizedApps.filter((app) => app.id !== 'spklwebapp')
}
},
computed: {
rootUrl() {
return window.location.origin
},
hasManager() {
if (!this.authorizedApps) return null
return this.authorizedApps.findIndex((a) => a.id === 'sdm') !== -1
}
},
methods: {
showRevokeAccessDialog(app) {
this.showRevokeDialog = true
this.appToRevoke = app
},
async revokeAccess() {
this.showRevokeDialog = false
this.$matomo && this.$matomo.trackPageView('user/app/revoke')
this.$mixpanel.track('App Action', { type: 'action', name: 'revoke', hostApp: 'web' })
await this.$apollo.mutate({
mutation: gql`
mutation{ appRevokeAccess(appId: "${this.appToRevoke.id}")}
`
})
this.refreshList()
},
refreshList() {
this.$apollo.queries.authorizedApps.refetch()
}
}
}
</script>
@@ -1,80 +0,0 @@
<template>
<div>
<div v-if="!user">
<v-skeleton-loader type="card"></v-skeleton-loader>
</div>
<div v-else>
<v-card
rounded="lg"
:class="`${!$vuetify.theme.dark ? 'grey lighten-5' : ''} mt-5`"
style="overflow: hidden"
>
<v-toolbar flat class="error--text" dark dense>
<v-toolbar-title>Delete Account</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn icon @click="showDelete = !showDelete"><v-icon>mdi-chevron-down</v-icon></v-btn>
</v-toolbar>
<div v-show="showDelete">
<v-card-text>
This action cannot be undone. We will delete all streams where you are the sole owner,
and any associated data.
</v-card-text>
<v-card-actions>
<v-btn block @click="deleteUser">Delete account</v-btn>
</v-card-actions>
</div>
</v-card>
<user-delete-dialog ref="userDeleteDialog"></user-delete-dialog>
</div>
</div>
</template>
<script>
import gql from 'graphql-tag'
import UserDeleteDialog from '../components/dialogs/UserDeleteDialog'
import { signOut } from '@/auth-helpers'
export default {
components: { UserDeleteDialog },
props: {
user: {
type: Object,
default: null
}
},
data() {
return {
showDelete: false
}
},
computed: {},
methods: {
deleteUser() {
this.$refs.userDeleteDialog.open(this.user).then((dialog) => {
if (!dialog.result) return
this.$matomo && this.$matomo.trackPageView('user/delete')
this.$mixpanel.track('User Action', { type: 'action', name: 'delete', hostApp: 'web' })
this.isLoading = true
this.$apollo
.mutate({
mutation: gql`
mutation userDelete($myUserConfirmation: UserDeleteInput!) {
userDelete(userConfirmation: $myUserConfirmation)
}
`,
variables: {
myUserConfirmation: {
email: dialog.email
}
}
})
.then(() => {
this.isLoading = false
signOut(this.$mixpanel)
})
})
}
}
}
</script>
@@ -1,157 +0,0 @@
<template>
<div>
<v-dialog v-model="avatarDialog" max-width="400">
<v-card>
<v-card-title>Choose a new profile picture</v-card-title>
<v-card-text class="pl-10 pr-0 mt-5">
<v-image-input
v-model="imageData"
:image-quality="0.85"
:image-height="256"
:image-width="256"
full-width
full-height
clearable
image-format="jpeg"
/>
</v-card-text>
<v-card-actions>
<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>
</v-card-actions>
</v-card>
</v-dialog>
<div v-if="!user">
<v-skeleton-loader type="card"></v-skeleton-loader>
</div>
<v-card
v-else
class="elevation-0"
rounded="lg"
:class="`${!$vuetify.theme.dark ? 'grey lighten-5' : ''}`"
>
<v-toolbar flat :class="`${!$vuetify.theme.dark ? 'grey lighten-4' : ''}`">
<v-toolbar-title>
<span v-if="isSelf">Hi</span>
{{ user.name }}
<span v-if="isSelf">!</span>
</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn v-if="isSelf" small rounded color="primary" @click="editUser">
<v-icon small class="mr-2">mdi-cog-outline</v-icon>
Edit
</v-btn>
</v-toolbar>
<v-row class="pa-4" align="stretch">
<v-col cols="12" sm="8">
<p v-if="user.company" class="subtitle-1">Company: {{ user.company }}</p>
<p v-if="user.email && isSelf">Email: {{ user.email }}</p>
<p v-if="user.bio">Bio: {{ user.bio }}</p>
<p v-else>This user keeps an air of mystery around themselves.</p>
<span v-if="isSelf" class="caption">ID: {{ user.id }}</span>
<br />
</v-col>
<v-col cols="12" sm="4" class="d-flex justify-center">
<div @click="avatarDialog = isSelf ? true : false">
<user-avatar-icon
v-tooltip="`${isSelf ? 'Change your profile picture' : ''}`"
:style="`${isSelf ? 'cursor: pointer;' : ''}`"
:size="100"
:avatar="user.avatar"
:seed="user.id"
></user-avatar-icon>
</div>
</v-col>
</v-row>
<v-card-actions></v-card-actions>
<user-edit-dialog ref="userDialog" :user="user"></user-edit-dialog>
</v-card>
</div>
</template>
<script>
import gql from 'graphql-tag'
import UserEditDialog from '../components/dialogs/UserEditDialog'
import VImageInput from 'vuetify-image-input/a-la-carte'
import UserAvatarIcon from '@/components/UserAvatarIcon'
export default {
components: { UserAvatarIcon, UserEditDialog, VImageInput },
props: {
user: {
type: Object,
default: null
}
},
data() {
return {
avatarDialog: false,
imageData: null
}
},
computed: {
isSelf() {
if (!this.user) return false
return this.user.id === localStorage.getItem('uuid')
}
},
methods: {
async updateAvatar() {
try {
await this.$apollo.mutate({
mutation: gql`
mutation userUpdate($update: UserUpdateInput!) {
userUpdate(user: $update)
}
`,
variables: {
update: {
avatar: this.imageData
}
}
})
this.$emit('update')
} catch (e) {
console.log(e)
}
this.avatarDialog = false
},
//using vue dialogs just like .net modals
async editUser() {
this.$refs.userDialog.open(this.user).then((dialog) => {
if (!dialog.result) return
this.$matomo && this.$matomo.trackPageView('user/update')
this.$mixpanel.track('User Action', { type: 'action', name: 'update', hostApp: 'web' })
this.isLoading = true
this.$apollo
.mutate({
mutation: gql`
mutation userUpdate($myUser: UserUpdateInput!) {
userUpdate(user: $myUser)
}
`,
variables: {
myUser: {
name: dialog.user.name,
bio: dialog.user.bio,
company: dialog.user.company
}
}
})
.then(() => {
this.isLoading = false
this.$emit('update')
})
})
}
}
}
</script>
+6 -6
View File
@@ -8,7 +8,7 @@ const routes = [
path: '/authn',
name: 'Auth',
redirect: '/authn/login',
component: () => import('@/views/Auth.vue'),
component: () => import('@/cleanup/pages/auth/Auth.vue'),
children: [
{
path: 'login',
@@ -16,7 +16,7 @@ const routes = [
meta: {
title: 'Login | Speckle'
},
component: () => import('@/views/auth/Login.vue')
component: () => import('@/cleanup/pages/auth/Login.vue')
},
{
path: 'register',
@@ -24,7 +24,7 @@ const routes = [
meta: {
title: 'Register | Speckle'
},
component: () => import('@/views/auth/Registration.vue')
component: () => import('@/cleanup/pages/auth/Registration.vue')
},
{
path: 'resetpassword',
@@ -32,7 +32,7 @@ const routes = [
meta: {
title: 'Register | Speckle'
},
component: () => import('@/views/auth/ResetPasswordRequest.vue')
component: () => import('@/cleanup/pages/auth/ResetPasswordRequest.vue')
},
{
path: 'resetpassword/finalize',
@@ -40,7 +40,7 @@ const routes = [
meta: {
title: 'Register | Speckle'
},
component: () => import('@/views/auth/ResetPasswordFinalization.vue')
component: () => import('@/cleanup/pages/auth/ResetPasswordFinalization.vue')
},
{
path: 'verify/:appId/:challenge',
@@ -48,7 +48,7 @@ const routes = [
meta: {
title: 'Authorizing App | Speckle'
},
component: () => import('@/views/auth/AuthorizeApp.vue')
component: () => import('@/cleanup/pages/auth/AuthorizeApp.vue')
}
]
},