feat: apollo client v3 + vue apollo v4 (#831)
This commit is contained in:
committed by
GitHub
parent
79f558c726
commit
b55f12d6bc
@@ -36,6 +36,13 @@ const config = {
|
||||
commonjs: true
|
||||
}
|
||||
},
|
||||
{
|
||||
files: './build-config/**/*.{js, ts}',
|
||||
env: {
|
||||
node: true,
|
||||
commonjs: true
|
||||
}
|
||||
},
|
||||
{
|
||||
files: '*.ts',
|
||||
plugins: ['@typescript-eslint'],
|
||||
|
||||
@@ -38,6 +38,12 @@ Build static build & serve it (for development, otherwise use docker image):
|
||||
yarn build && yarn serve
|
||||
```
|
||||
|
||||
### Apollo Client
|
||||
|
||||
We're on Apollo Client v3 and Vue Apollo v4 (both the options API and composition API) in this package, so pretty much all of the latest and greatest features are there and ready to be used.
|
||||
|
||||
**Note**: Do not import anything from `@apollo/client`, use `@apollo/client/core` instead! Otherwise you risk bundling in React dependencies, which we definitely do not need!
|
||||
|
||||
### TypeScript
|
||||
|
||||
This project also supports TypeScript, both in Vue SFCs and outside them. It's preferred that you use it when writing new code and also migrate JS files when there's a good oppurtunity to do so.
|
||||
@@ -52,7 +58,7 @@ Note: If you're defining a Vue component in a non-standard way (e.g. `vueWithMix
|
||||
|
||||
#### Improved GraphQL DX w/ TS
|
||||
|
||||
Run `yarn gqlgen` to generate relevant TS types from the GraphQL Schema (introspected from server which must be running) and operations defined in the frontend. Check this out for more info: https://www.graphql-code-generator.com/plugins/typescript-vue-apollo-smart-ops#examples
|
||||
Run `yarn gqlgen` to generate relevant TS types from the GraphQL Schema (introspected from server which must be running) and operations defined in the frontend. Afterwards make sure you import them from the generated `graphql.ts` file, not the original file where you defined the operation/fragment.
|
||||
|
||||
### Packaging for production
|
||||
|
||||
@@ -68,6 +74,12 @@ Restart the Vetur Vue Language Server (VLS) through the command palette. Vetur i
|
||||
|
||||
If you are getting a lot of Property 'xxx' does not exist on type 'CombinedVueInstance' errors, it's an issue with Vue's typing and TypeScript. You can work around it by annotating the return type for each computed/data property, making sure data/props keys are defined even if they're empty.
|
||||
|
||||
#### Vue Apollo Options API fetchMore() doesn't update the state/template quickly enough
|
||||
|
||||
This seems to be an issue that appeared after the Apollo Client v2 -> Apollo Client v3 upgrade. It only becomes an issue when you're trying to use vue-infinite-loader with fetchMore(), because the infinite loader triggers an infinite amount of requests with the old cursor, because the cursor hasn't been updated yet at that point.
|
||||
|
||||
The workaround is simple - use the Vue Apollo Composition API fetchMore
|
||||
|
||||
## Community
|
||||
|
||||
If in trouble, the Speckle Community hangs out on [the forum](https://speckle.community). Do join and introduce yourself! We're happy to help.
|
||||
|
||||
@@ -1,23 +1,7 @@
|
||||
const path = require('path')
|
||||
|
||||
// Load .env files
|
||||
const { loadEnv } = require('vue-cli-plugin-apollo/utils/load-env')
|
||||
const env = loadEnv([
|
||||
path.resolve(__dirname, '.env'),
|
||||
path.resolve(__dirname, '.env.local')
|
||||
])
|
||||
|
||||
module.exports = {
|
||||
client: {
|
||||
service: env.VUE_APP_APOLLO_ENGINE_SERVICE,
|
||||
service: 'speckle-server',
|
||||
url: 'http://localhost:3000/graphql',
|
||||
includes: ['src/**/*.{js,jsx,ts,tsx,vue,gql}']
|
||||
},
|
||||
service: {
|
||||
name: env.VUE_APP_APOLLO_ENGINE_SERVICE,
|
||||
localSchemaFile: path.resolve(__dirname, './node_modules/.temp/graphql/schema.json')
|
||||
},
|
||||
engine: {
|
||||
endpoint: process.env.APOLLO_ENGINE_API_ENDPOINT,
|
||||
apiKey: env.VUE_APP_APOLLO_ENGINE_KEY
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
const TARGET = 'es2019'
|
||||
|
||||
/**
|
||||
* GQL file support (previously this was managed by the vue apollo cli plugin)
|
||||
* @param {import('@vue/cli-service/lib/PluginAPI')} api
|
||||
**/
|
||||
function plugin(api) {
|
||||
api.chainWebpack((config) => {
|
||||
const gqlRule = config.module.rule('gql').test(/\.(gql|graphql)$/)
|
||||
|
||||
// add caching
|
||||
gqlRule
|
||||
.use('cache-loader')
|
||||
.loader('cache-loader')
|
||||
.options(
|
||||
api.genCacheConfig('gql-cache-loader', {
|
||||
target: TARGET,
|
||||
graphqltagVersion: require('graphql-tag/package.json').version
|
||||
})
|
||||
)
|
||||
|
||||
// add gql loader
|
||||
gqlRule.use('gql-loader').loader('graphql-tag/loader')
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = plugin
|
||||
@@ -9,9 +9,7 @@ generates:
|
||||
- 'typescript'
|
||||
- 'typescript-operations'
|
||||
- 'typescript-document-nodes'
|
||||
- 'typescript-vue-apollo-smart-ops'
|
||||
- 'typed-document-node'
|
||||
config:
|
||||
vueApolloErrorHandlerFunction: handleApolloError
|
||||
vueApolloErrorHandlerFunctionImportFrom: '@/config/vueApolloSmartOpsConfig'
|
||||
scalars:
|
||||
JSONObject: Record<string, unknown>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"gqlgen": "graphql-codegen --config codegen.yml"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.6.9",
|
||||
"@speckle/viewer": "workspace:^",
|
||||
"@tiptap/core": "^2.0.0-beta.176",
|
||||
"@tiptap/extension-bold": "^2.0.0-beta.26",
|
||||
@@ -33,20 +34,25 @@
|
||||
"@tiptap/extension-underline": "^2.0.0-beta.23",
|
||||
"@tiptap/vue-2": "^2.0.0-beta.79",
|
||||
"@tryghost/content-api": "^1.5.12",
|
||||
"@vue/apollo-composable": "^4.0.0-alpha.19",
|
||||
"@vue/apollo-option": "^4.0.0-alpha.20",
|
||||
"@vuejs-community/vue-filter-date-format": "^1.6.3",
|
||||
"@vuejs-community/vue-filter-date-parse": "^1.1.6",
|
||||
"apexcharts": "^3.33.1",
|
||||
"apollo-upload-client": "^17.0.0",
|
||||
"dompurify": "^2.3.6",
|
||||
"graphql": "^15.0.0",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"lodash": "^4.17.21",
|
||||
"numeral": "^2.0.6",
|
||||
"portal-vue": "^2.1.7",
|
||||
"regenerator-runtime": "^0.13.9",
|
||||
"subscriptions-transport-ws": "^0.11.0",
|
||||
"tween": "^0.9.0",
|
||||
"uuid": "^8.3.2",
|
||||
"v-tooltip": "^2.0.3",
|
||||
"vue": "^2.7.5",
|
||||
"vue-apexcharts": "^1.6.1",
|
||||
"vue-apollo": "^3.0.5",
|
||||
"vue-histogram-slider": "^0.3.8",
|
||||
"vue-infinite-loading": "^2.4.5",
|
||||
"vue-mixpanel": "1.0.7",
|
||||
@@ -61,12 +67,13 @@
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "2.6.2",
|
||||
"@graphql-codegen/introspection": "2.1.1",
|
||||
"@graphql-codegen/typed-document-node": "^2.3.1",
|
||||
"@graphql-codegen/typescript": "2.5.1",
|
||||
"@graphql-codegen/typescript-document-nodes": "2.2.13",
|
||||
"@graphql-codegen/typescript-operations": "2.4.2",
|
||||
"@graphql-codegen/typescript-vue-apollo-smart-ops": "^2.3.1",
|
||||
"@mdi/font": "^5.8.55",
|
||||
"@rushstack/eslint-patch": "^1.1.3",
|
||||
"@types/apollo-upload-client": "^17.0.1",
|
||||
"@types/dompurify": "^2.3.3",
|
||||
"@types/lodash": "^4.14.180",
|
||||
"@types/mixpanel-browser": "^2.38.0",
|
||||
@@ -88,7 +95,6 @@
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-vue": "^9.2.0",
|
||||
"graphql-tag": "^2.11.0",
|
||||
"local-web-server": "^5.2.0",
|
||||
"lodash-webpack-plugin": "^0.11.6",
|
||||
"prettier": "^2.5.1",
|
||||
@@ -98,8 +104,6 @@
|
||||
"type-fest": "^2.13.1",
|
||||
"typescript": "~4.1.5",
|
||||
"vti": "^0.1.5",
|
||||
"vue-apollo-smart-ops": "^0.2.0-beta.1",
|
||||
"vue-cli-plugin-apollo": "~0.22.2",
|
||||
"vue-cli-plugin-vuetify": "^2.5.1",
|
||||
"vuetify-loader": "^1.9.1",
|
||||
"webpack": "^4.46.0",
|
||||
@@ -110,7 +114,8 @@
|
||||
},
|
||||
"vuePlugins": {
|
||||
"service": [
|
||||
"./esbuildPlugin.js"
|
||||
"./build-config/esbuildPlugin.js",
|
||||
"./build-config/gqlPlugin.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// @see https://github.com/Akryum/vue-cli-plugin-apollo/issues/452
|
||||
import 'regenerator-runtime/runtime'
|
||||
|
||||
import Vue from 'vue'
|
||||
import VTooltip from 'v-tooltip'
|
||||
import VueMixpanel from 'vue-mixpanel'
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
import Vue from 'vue'
|
||||
import { createApolloProvider, ApolloProvider } from '@vue/apollo-option'
|
||||
import { ApolloClient, ApolloLink, InMemoryCache, split } from '@apollo/client/core'
|
||||
import { setContext } from '@apollo/client/link/context'
|
||||
import { WebSocketLink } from '@apollo/client/link/ws'
|
||||
import { SubscriptionClient } from 'subscriptions-transport-ws'
|
||||
import { LocalStorageKeys } from '@/helpers/mainConstants'
|
||||
import { createUploadLink } from 'apollo-upload-client'
|
||||
import { AppLocalStorage } from '@/utils/localStorage'
|
||||
import { getMainDefinition } from '@apollo/client/utilities'
|
||||
import { OperationDefinitionNode, Kind } from 'graphql'
|
||||
import {
|
||||
buildAbstractCollectionMergeFunction,
|
||||
incomingOverwritesExistingMergeFunction
|
||||
} from '@/main/lib/core/helpers/apolloSetupHelper'
|
||||
|
||||
// Name of the localStorage item
|
||||
const AUTH_TOKEN = LocalStorageKeys.AuthToken
|
||||
// Http endpoint
|
||||
const httpEndpoint = `${window.location.origin}/graphql`
|
||||
// WS endpoint
|
||||
const wsEndpoint = `${window.location.origin.replace('http', 'ws')}/graphql`
|
||||
// app version
|
||||
const appVersion = process.env.SPECKLE_SERVER_VERSION || 'unknown'
|
||||
|
||||
function hasAuthToken() {
|
||||
return !!AppLocalStorage.get(AUTH_TOKEN)
|
||||
}
|
||||
|
||||
function createCache(): InMemoryCache {
|
||||
return new InMemoryCache({
|
||||
/**
|
||||
* This is where you configure how various GQL fields should be read, written to or merged when new data comes in.
|
||||
* If you define a merge function here, you don't need to duplicate the merge logic inside an `update()` callback
|
||||
* of a fetchMore call, for example.
|
||||
*
|
||||
* Feel free to re-use utilities in `apolloSetupHelper` for defining merge functions or even use the ones that come from `@apollo/client/utilities`.
|
||||
*
|
||||
* Read more: https://www.apollographql.com/docs/react/caching/cache-field-behavior
|
||||
*/
|
||||
typePolicies: {
|
||||
Query: {
|
||||
fields: {
|
||||
user: {
|
||||
read(original, { args, toReference }) {
|
||||
if (args?.id) {
|
||||
return toReference({ __typename: 'User', id: args.id })
|
||||
}
|
||||
|
||||
return original
|
||||
}
|
||||
},
|
||||
stream: {
|
||||
read(original, { args, toReference }) {
|
||||
if (args?.id) {
|
||||
return toReference({ __typename: 'Stream', id: args.id })
|
||||
}
|
||||
|
||||
return original
|
||||
}
|
||||
},
|
||||
streams: {
|
||||
keyArgs: false,
|
||||
merge: buildAbstractCollectionMergeFunction('StreamCollection', {
|
||||
checkIdentity: true
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
User: {
|
||||
fields: {
|
||||
timeline: {
|
||||
keyArgs: false,
|
||||
merge: buildAbstractCollectionMergeFunction('ActivityCollection')
|
||||
},
|
||||
commits: {
|
||||
keyArgs: false,
|
||||
merge: buildAbstractCollectionMergeFunction('CommitCollectionUser', {
|
||||
checkIdentity: true
|
||||
})
|
||||
},
|
||||
favoriteStreams: {
|
||||
keyArgs: false,
|
||||
merge: buildAbstractCollectionMergeFunction('StreamCollection', {
|
||||
checkIdentity: true
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
Stream: {
|
||||
fields: {
|
||||
activity: {
|
||||
keyArgs: false,
|
||||
merge: buildAbstractCollectionMergeFunction('ActivityCollection')
|
||||
},
|
||||
commits: {
|
||||
keyArgs: false,
|
||||
merge: buildAbstractCollectionMergeFunction('CommitCollection', {
|
||||
checkIdentity: true
|
||||
})
|
||||
},
|
||||
pendingCollaborators: {
|
||||
merge: incomingOverwritesExistingMergeFunction
|
||||
}
|
||||
}
|
||||
},
|
||||
Branch: {
|
||||
fields: {
|
||||
commits: {
|
||||
keyArgs: false,
|
||||
merge: buildAbstractCollectionMergeFunction('CommitCollection', {
|
||||
checkIdentity: true
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
BranchCollection: {
|
||||
merge: true
|
||||
},
|
||||
ServerStats: {
|
||||
merge: true
|
||||
},
|
||||
WebhookEventCollection: {
|
||||
merge: true
|
||||
},
|
||||
ServerInfo: {
|
||||
merge: true
|
||||
},
|
||||
CommentThreadActivityMessage: {
|
||||
merge: true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function createWsClient(): SubscriptionClient {
|
||||
return new SubscriptionClient(wsEndpoint, {
|
||||
reconnect: true,
|
||||
connectionParams: () => {
|
||||
const authToken = AppLocalStorage.get(AUTH_TOKEN)
|
||||
const Authorization = authToken ? `Bearer ${authToken}` : null
|
||||
return Authorization ? { Authorization, headers: { Authorization } } : {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function createLink(wsClient?: SubscriptionClient): ApolloLink {
|
||||
// Prepare links
|
||||
const httpLink = createUploadLink({
|
||||
uri: httpEndpoint
|
||||
})
|
||||
const authLink = setContext(async (_, { headers }) => {
|
||||
const authToken = AppLocalStorage.get(AUTH_TOKEN)
|
||||
const authHeader = authToken ? { Authorization: `Bearer ${authToken}` } : {}
|
||||
return {
|
||||
headers: {
|
||||
...headers,
|
||||
...authHeader
|
||||
}
|
||||
}
|
||||
})
|
||||
let link = authLink.concat(httpLink)
|
||||
|
||||
if (wsClient) {
|
||||
const wsLink = new WebSocketLink(wsClient)
|
||||
link = split(
|
||||
({ query }) => {
|
||||
const definition = getMainDefinition(query) as OperationDefinitionNode
|
||||
const { kind, operation } = definition
|
||||
|
||||
return kind === Kind.OPERATION_DEFINITION && operation === 'subscription'
|
||||
},
|
||||
wsLink,
|
||||
link
|
||||
)
|
||||
}
|
||||
|
||||
return link
|
||||
}
|
||||
|
||||
function createApolloClient() {
|
||||
const cache = createCache()
|
||||
const wsClient = createWsClient()
|
||||
const link = createLink(wsClient)
|
||||
|
||||
const apolloClient = new ApolloClient({
|
||||
link,
|
||||
cache,
|
||||
ssrForceFetchDelay: 100,
|
||||
connectToDevTools: process.env.NODE_ENV !== 'production',
|
||||
name: 'web',
|
||||
version: appVersion
|
||||
})
|
||||
|
||||
return {
|
||||
apolloClient,
|
||||
wsClient
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Vue Apollo provider instance
|
||||
*/
|
||||
export function createProvider(): ApolloProvider {
|
||||
// Create apollo client
|
||||
const { apolloClient, wsClient } = createApolloClient()
|
||||
apolloClient.wsClient = hasAuthToken() ? wsClient : null
|
||||
|
||||
// Create vue apollo provider
|
||||
const apolloProvider = createApolloProvider({
|
||||
defaultClient: apolloClient
|
||||
})
|
||||
|
||||
return apolloProvider
|
||||
}
|
||||
|
||||
export function installVueApollo(apolloProvider: ApolloProvider): void {
|
||||
// Install apollo provider (it's done weirdly cause it's meant to be used with vue 3)
|
||||
Vue.config.globalProperties ||= {}
|
||||
Vue.prototype.$apolloProvider = apolloProvider
|
||||
apolloProvider.install(Vue)
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import {
|
||||
ApolloErrorType,
|
||||
ApolloOperationErrorHandlerFunction,
|
||||
ProcessedApolloError
|
||||
} from 'vue-apollo-smart-ops'
|
||||
|
||||
/**
|
||||
* Error handler used in our auto-generated graphql operation functions
|
||||
*/
|
||||
export const handleApolloError: ApolloOperationErrorHandlerFunction = (error) => {
|
||||
const allErrors: ProcessedApolloError[] = []
|
||||
|
||||
if (error.networkError) {
|
||||
const networkError: ProcessedApolloError = {
|
||||
type: ApolloErrorType.NETWORK_ERROR,
|
||||
error: error.networkError,
|
||||
message: error.message
|
||||
}
|
||||
allErrors.push(networkError)
|
||||
} else {
|
||||
for (const gqlError of error.graphQLErrors || []) {
|
||||
const basicError: ProcessedApolloError = {
|
||||
type: ApolloErrorType.SERVER_ERROR,
|
||||
error: gqlError,
|
||||
path: gqlError.path,
|
||||
message: gqlError.message
|
||||
}
|
||||
allErrors.push(basicError)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
allErrors
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
|
||||
export const branchCreatedSubscription = gql`
|
||||
subscription BranchCreated($streamId: String!) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
|
||||
export const COMMENT_FULL_INFO_FRAGMENT = gql`
|
||||
fragment CommentFullInfo on Comment {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
|
||||
export const limitedUserFieldsFragment = gql`
|
||||
fragment LimitedUserFields on LimitedUser {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import { usersOwnInviteFieldsFragment } from '@/graphql/fragments/user'
|
||||
|
||||
export const streamInviteQuery = gql`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
|
||||
export const mainServerInfoFieldsFragment = gql`
|
||||
fragment MainServerInfoFields on ServerInfo {
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
limitedUserFieldsFragment,
|
||||
streamCollaboratorFieldsFragment
|
||||
} from '@/graphql/fragments/user'
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
|
||||
/**
|
||||
* Common stream fields when querying for streams
|
||||
@@ -75,6 +75,36 @@ export const streamWithCollaboratorsQuery = gql`
|
||||
${streamCollaboratorFieldsFragment}
|
||||
`
|
||||
|
||||
export const streamWithActivityQuery = gql`
|
||||
query StreamWithActivity($id: String!, $cursor: DateTime) {
|
||||
stream(id: $id) {
|
||||
id
|
||||
name
|
||||
createdAt
|
||||
commits {
|
||||
totalCount
|
||||
}
|
||||
branches {
|
||||
totalCount
|
||||
}
|
||||
activity(cursor: $cursor) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
actionType
|
||||
userId
|
||||
streamId
|
||||
resourceId
|
||||
resourceType
|
||||
time
|
||||
info
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
/**
|
||||
* Remove authenticated user from the collaborators list
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { limitedUserFieldsFragment } from '@/graphql/fragments/user'
|
||||
import { commonStreamFieldsFragment } from '@/graphql/streams'
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
|
||||
export const commonUserFieldsFragment = gql`
|
||||
fragment CommonUserFields on User {
|
||||
@@ -139,3 +139,25 @@ export const adminUsersListQuery = gql`
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const userTimelineQuery = gql`
|
||||
query UserTimeline($cursor: DateTime) {
|
||||
user {
|
||||
id
|
||||
timeline(cursor: $cursor) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
actionType
|
||||
userId
|
||||
streamId
|
||||
resourceId
|
||||
resourceType
|
||||
time
|
||||
info
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
query {
|
||||
user {
|
||||
id
|
||||
email
|
||||
name
|
||||
bio
|
||||
company
|
||||
avatar
|
||||
verified
|
||||
profiles
|
||||
role
|
||||
streams(limit: 25) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
id
|
||||
name
|
||||
description
|
||||
isPublic
|
||||
createdAt
|
||||
updatedAt
|
||||
collaborators {
|
||||
id
|
||||
name
|
||||
company
|
||||
avatar
|
||||
role
|
||||
}
|
||||
commits {
|
||||
totalCount
|
||||
}
|
||||
branches {
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
}
|
||||
commits(limit: 25) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
id
|
||||
message
|
||||
streamId
|
||||
streamName
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,7 @@
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
<script>
|
||||
import { mainServerInfoQuery } from '@/graphql/server'
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
apollo: {
|
||||
serverInfo: {
|
||||
query: mainServerInfoQuery
|
||||
}
|
||||
}
|
||||
}
|
||||
export default {}
|
||||
</script>
|
||||
<style lang="css">
|
||||
.v-timeline:before {
|
||||
|
||||
@@ -6,7 +6,9 @@ import store from '@/main/store'
|
||||
import { LocalStorageKeys } from '@/helpers/mainConstants'
|
||||
import * as MixpanelManager from '@/mixpanelManager'
|
||||
|
||||
import { createProvider } from '@/vue-apollo'
|
||||
import { provide } from 'vue'
|
||||
import { DefaultApolloClient } from '@vue/apollo-composable'
|
||||
import { createProvider, installVueApollo } from '@/config/apolloConfig'
|
||||
import {
|
||||
checkAccessCodeAndGetTokens,
|
||||
prefetchUserAndSetSuuid
|
||||
@@ -26,6 +28,8 @@ Vue.use(VueFilterDateFormat)
|
||||
|
||||
import PerfectScrollbar from 'vue2-perfect-scrollbar'
|
||||
import 'vue2-perfect-scrollbar/dist/vue2-perfect-scrollbar.css'
|
||||
// adds various helper methods
|
||||
import '@/plugins/helpers'
|
||||
|
||||
Vue.use(PerfectScrollbar)
|
||||
|
||||
@@ -46,13 +50,13 @@ Vue.filter('capitalize', (value) => {
|
||||
return value.charAt(0).toUpperCase() + value.slice(1)
|
||||
})
|
||||
|
||||
// adds various helper methods
|
||||
import '@/plugins/helpers'
|
||||
|
||||
const AuthToken = localStorage.getItem(LocalStorageKeys.AuthToken)
|
||||
const RefreshToken = localStorage.getItem(LocalStorageKeys.RefreshToken)
|
||||
const apolloProvider = createProvider()
|
||||
|
||||
const apolloProvider = createProvider()
|
||||
installVueApollo(apolloProvider)
|
||||
|
||||
// TODO: Sort out error handling here, if something goes wrong it just goes into an infinite loop
|
||||
if (AuthToken) {
|
||||
prefetchUserAndSetSuuid(apolloProvider.defaultClient)
|
||||
.then(() => {
|
||||
@@ -62,6 +66,7 @@ if (AuthToken) {
|
||||
if (RefreshToken) {
|
||||
// TODO: try to rotate token & prefetch user, etc.
|
||||
}
|
||||
|
||||
window.location = `${window.location.origin}/authn/login`
|
||||
})
|
||||
} else {
|
||||
@@ -88,7 +93,9 @@ function postAuthInit() {
|
||||
router,
|
||||
vuetify,
|
||||
store,
|
||||
apolloProvider,
|
||||
setup() {
|
||||
provide(DefaultApolloClient, apolloProvider.defaultClient)
|
||||
},
|
||||
render: (h) => h(App)
|
||||
}).$mount('#app')
|
||||
}
|
||||
|
||||
@@ -284,7 +284,7 @@ import UserAvatar from '@/main/components/common/UserAvatar'
|
||||
import UserPill from '@/main/components/activity/UserPill'
|
||||
import SourceAppAvatar from '@/main/components/common/SourceAppAvatar'
|
||||
import PreviewImage from '@/main/components/common/PreviewImage'
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import ListItemActivityDescription from '@/main/components/activity/ListItemActivityDescription.vue'
|
||||
|
||||
export default {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</v-chip>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import UserAvatar from '@/main/components/common/UserAvatar'
|
||||
|
||||
export default {
|
||||
|
||||
@@ -19,9 +19,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import { formatNumber } from '@/plugins/formatNumber.js'
|
||||
|
||||
const EXCLUDED_SERVER_STATS_KEYS = ['__typename']
|
||||
|
||||
export default {
|
||||
name: 'ActivityCard',
|
||||
components: {
|
||||
@@ -134,12 +136,7 @@ export default {
|
||||
streamHistory
|
||||
}
|
||||
}
|
||||
`,
|
||||
update(data) {
|
||||
const stats = data.serverStats
|
||||
delete stats.__typename
|
||||
return stats
|
||||
}
|
||||
`
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -147,7 +144,10 @@ export default {
|
||||
let result = []
|
||||
const months = this.past12Months()
|
||||
if (this.serverStats) {
|
||||
result = Object.keys(this.serverStats).map((key) => {
|
||||
const statsKeys = Object.keys(this.serverStats).filter(
|
||||
(k) => !EXCLUDED_SERVER_STATS_KEYS.includes(k)
|
||||
)
|
||||
result = statsKeys.map((key) => {
|
||||
const category = this.serverStats[key]
|
||||
const processed = []
|
||||
months?.forEach((month) => {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
|
||||
export default {
|
||||
name: 'GeneralInfoCard',
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
|
||||
export default {
|
||||
name: 'VersionInfoCard',
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import { documentToBasicString } from '@/main/lib/common/text-editor/documentHelper'
|
||||
import { COMMENT_FULL_INFO_FRAGMENT } from '@/graphql/comments'
|
||||
|
||||
@@ -178,6 +178,9 @@ export default {
|
||||
},
|
||||
result({ data }) {
|
||||
if (!data || !data.commentThreadActivity) return
|
||||
|
||||
// Note: This kind of direct apollo result mutation is only allowed, because
|
||||
// of the 'no-cache' fetch policy, which means that there's no cache mutation actually happening
|
||||
if (data.commentThreadActivity.type === 'reply-added') {
|
||||
this.commentDetails.replies.totalCount++
|
||||
this.commentDetails.updatedAt = Date.now()
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import SmartTextEditor from '@/main/components/common/text-editor/SmartTextEditor.vue'
|
||||
import { SMART_EDITOR_SCHEMA } from '@/main/lib/viewer/comments/commentsHelper'
|
||||
import CommentThreadReplyAttachments from '@/main/components/comments/CommentThreadReplyAttachments.vue'
|
||||
|
||||
@@ -377,7 +377,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import debounce from 'lodash/debounce'
|
||||
import CommentThreadReply from '@/main/components/comments/CommentThreadReply.vue'
|
||||
import CommentEditor from '@/main/components/comments/CommentEditor.vue'
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
</v-container>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
export default {
|
||||
props: {
|
||||
showImage: {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<v-autocomplete
|
||||
v-model="selectedSearchResult"
|
||||
:loading="$apollo.loading"
|
||||
:items="streams.items"
|
||||
:items="items"
|
||||
:search-input.sync="search"
|
||||
no-filter
|
||||
counter="3"
|
||||
@@ -78,7 +78,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -91,7 +91,7 @@ export default {
|
||||
search: '',
|
||||
hasSearched: false,
|
||||
liff: false,
|
||||
streams: { items: [] },
|
||||
items: [],
|
||||
selectedSearchResult: null
|
||||
}),
|
||||
apollo: {
|
||||
@@ -117,15 +117,18 @@ export default {
|
||||
skip() {
|
||||
return !this.search || this.search.length < 3
|
||||
},
|
||||
result({ data }) {
|
||||
this.items = [...data.streams.items]
|
||||
},
|
||||
debounce: 300
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
selectedSearchResult(val) {
|
||||
const myStream = this.streams.items.find((s) => s.id === val.id)
|
||||
const myStream = this.items.find((s) => s.id === val.id)
|
||||
this.$emit('select', myStream)
|
||||
|
||||
this.streams.items = []
|
||||
this.items = []
|
||||
this.search = ''
|
||||
|
||||
if (val && this.gotostreamonclick) this.$router.push(`/streams/${val.id}`)
|
||||
@@ -133,7 +136,7 @@ export default {
|
||||
search(val) {
|
||||
this.hasSearched = true
|
||||
if (val === '42') this.liff = true
|
||||
if (!val || val === '') this.streams.items = []
|
||||
if (!val || val === '') this.items = []
|
||||
}
|
||||
},
|
||||
methods: {}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<v-avatar :size="size" color="grey lighten-3">
|
||||
<v-img v-if="avatar" :src="avatar" />
|
||||
<v-img v-if="hasValidAvatar" :src="avatar" />
|
||||
<v-img v-else :src="`https://robohash.org/${seed}.png?size=${size}x${size}`" />
|
||||
</v-avatar>
|
||||
</template>
|
||||
@@ -20,6 +20,18 @@ export default {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasValidAvatar() {
|
||||
if (!this.avatar) return false
|
||||
|
||||
const validPrefixes = ['http', 'data:']
|
||||
for (const validPrefix of validPrefixes) {
|
||||
if (this.avatar.startsWith(validPrefix)) return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<user-stream-invite-banners @invite-used="onInviteUsed" />
|
||||
<v-row dense>
|
||||
<v-col v-if="$apollo.loading && !timeline">
|
||||
<v-col v-if="isApolloLoading && !timeline">
|
||||
<div class="my-5">
|
||||
<v-timeline align-top dense>
|
||||
<v-timeline-item v-for="i in 6" :key="i" medium>
|
||||
@@ -73,11 +73,14 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import UserStreamInviteBanners from '@/main/components/stream/UserStreamInviteBanners.vue'
|
||||
import InfiniteLoading from 'vue-infinite-loading'
|
||||
import NoDataPlaceholder from '@/main/components/common/NoDataPlaceholder.vue'
|
||||
import ListItemActivity from '@/main/components/activity/ListItemActivity.vue'
|
||||
import { UserTimelineDocument } from '@/graphql/generated/graphql'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import { computed } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'FeedTimeline',
|
||||
@@ -87,77 +90,26 @@ export default {
|
||||
NoDataPlaceholder,
|
||||
UserStreamInviteBanners
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
newStreamDialog: 0,
|
||||
activityNav: true
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
quickUser: {
|
||||
query: gql`
|
||||
query {
|
||||
quickUser: user {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
`
|
||||
},
|
||||
timeline: {
|
||||
query: gql`
|
||||
query ($cursor: DateTime) {
|
||||
user {
|
||||
id
|
||||
timeline(cursor: $cursor) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
actionType
|
||||
userId
|
||||
streamId
|
||||
resourceId
|
||||
resourceType
|
||||
time
|
||||
info
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
fetchPolicy: 'cache-and-network',
|
||||
update(data) {
|
||||
return data.user.timeline
|
||||
setup() {
|
||||
// Timeline query
|
||||
const {
|
||||
result: timelineResult,
|
||||
fetchMore: timelineFetchMore,
|
||||
refetch: timelineRefetch,
|
||||
loading: timelineLoading
|
||||
} = useQuery(
|
||||
UserTimelineDocument,
|
||||
{
|
||||
cursor: null
|
||||
},
|
||||
result({ data }) {
|
||||
this.groupSimilarActivities(data)
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
watch: {
|
||||
timeline(val) {
|
||||
if (val.totalCount === 0 && !localStorage.getItem('onboarding')) {
|
||||
this.$router.push('/onboarding')
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(
|
||||
function () {
|
||||
this.activityNav = !this.$vuetify.breakpoint.smAndDown
|
||||
}.bind(this),
|
||||
10
|
||||
{ fetchPolicy: 'cache-and-network' }
|
||||
)
|
||||
},
|
||||
methods: {
|
||||
onInviteUsed() {
|
||||
// Refetch feed
|
||||
this.$apollo.queries.timeline.refetch()
|
||||
},
|
||||
groupSimilarActivities(data) {
|
||||
if (!data) return
|
||||
const timeline = computed(() => {
|
||||
return timelineResult.value?.user?.timeline || null
|
||||
})
|
||||
const groupedTimeline = computed(() => {
|
||||
const data = timelineResult.value
|
||||
if (!data) return []
|
||||
|
||||
const skippableActionTypes = ['stream_invite_sent', 'stream_invite_declined']
|
||||
const groupedTimeline = data.user.timeline.items.reduce(function (prev, curr) {
|
||||
@@ -206,31 +158,76 @@ export default {
|
||||
}
|
||||
return prev
|
||||
}, [])
|
||||
// console.log(groupedTimeline)
|
||||
this.groupedTimeline = groupedTimeline
|
||||
|
||||
return groupedTimeline
|
||||
})
|
||||
|
||||
// Quick user info
|
||||
const { result: quickUserResult, loading: quickUserLoading } = useQuery(gql`
|
||||
query {
|
||||
quickUser: user {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
`)
|
||||
const quickUser = computed(() => quickUserResult.value?.quickUser || null)
|
||||
|
||||
return {
|
||||
quickUser,
|
||||
groupedTimeline,
|
||||
timeline,
|
||||
timelineFetchMore,
|
||||
timelineRefetch,
|
||||
quickUserLoading,
|
||||
timelineLoading
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
newStreamDialog: 0,
|
||||
activityNav: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isApolloLoading() {
|
||||
return this.$apollo.loading || this.quickUserLoading || this.timelineLoading
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
timeline(val) {
|
||||
if (val.totalCount === 0 && !localStorage.getItem('onboarding')) {
|
||||
this.$router.push('/onboarding')
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(
|
||||
function () {
|
||||
this.activityNav = !this.$vuetify.breakpoint.smAndDown
|
||||
}.bind(this),
|
||||
10
|
||||
)
|
||||
},
|
||||
methods: {
|
||||
onInviteUsed() {
|
||||
// Refetch feed
|
||||
this.timelineRefetch()
|
||||
},
|
||||
|
||||
infiniteHandler($state) {
|
||||
this.$apollo.queries.timeline.fetchMore({
|
||||
async infiniteHandler($state) {
|
||||
const result = await this.timelineFetchMore({
|
||||
variables: {
|
||||
cursor: this.timeline.cursor
|
||||
},
|
||||
// Transform the previous result with new data
|
||||
updateQuery: (previousResult, { fetchMoreResult }) => {
|
||||
const newItems = fetchMoreResult.user.timeline.items
|
||||
|
||||
//set vue-infinite state
|
||||
if (newItems.length === 0) $state.complete()
|
||||
else $state.loaded()
|
||||
|
||||
fetchMoreResult.user.timeline.items = [
|
||||
...previousResult.user.timeline.items,
|
||||
...newItems
|
||||
]
|
||||
|
||||
return fetchMoreResult
|
||||
}
|
||||
})
|
||||
|
||||
const newItems = result.data?.user?.timeline?.items || []
|
||||
if (!newItems.length) {
|
||||
$state.complete()
|
||||
} else {
|
||||
$state.loaded()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<div slot="no-results">There are no activities to load</div>
|
||||
</infinite-loading>
|
||||
</v-timeline>
|
||||
<v-timeline v-else-if="$apollo.loading" align-top dense>
|
||||
<v-timeline v-else-if="isApolloLoading" align-top dense>
|
||||
<v-timeline-item v-for="i in 6" :key="i" medium>
|
||||
<v-skeleton-loader type="article"></v-skeleton-loader>
|
||||
</v-timeline-item>
|
||||
@@ -37,7 +37,10 @@
|
||||
</v-row>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { StreamWithActivityDocument } from '@/graphql/generated/graphql'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import { useRoute } from '@/main/lib/core/composables/router'
|
||||
import { computed } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'StreamActivity',
|
||||
@@ -45,56 +48,22 @@ export default {
|
||||
ListItemActivity: () => import('@/main/components/activity/ListItemActivity'),
|
||||
InfiniteLoading: () => import('vue-infinite-loading')
|
||||
},
|
||||
data() {
|
||||
return { groupedActivity: null }
|
||||
},
|
||||
apollo: {
|
||||
stream: {
|
||||
query: gql`
|
||||
query Stream($id: String!, $cursor: DateTime) {
|
||||
stream(id: $id) {
|
||||
id
|
||||
name
|
||||
createdAt
|
||||
commits {
|
||||
totalCount
|
||||
}
|
||||
branches {
|
||||
totalCount
|
||||
}
|
||||
activity(cursor: $cursor) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
actionType
|
||||
userId
|
||||
streamId
|
||||
resourceId
|
||||
resourceType
|
||||
time
|
||||
info
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables() {
|
||||
return {
|
||||
id: this.$route.params.streamId
|
||||
}
|
||||
},
|
||||
result({ data }) {
|
||||
this.groupSimilarActivities(data)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
groupSimilarActivities(data) {
|
||||
if (!data) return
|
||||
setup() {
|
||||
// Stream activity query & derived computeds
|
||||
const route = useRoute()
|
||||
const {
|
||||
result,
|
||||
fetchMore: activityFetchMore,
|
||||
loading: activityLoading
|
||||
} = useQuery(StreamWithActivityDocument, () => ({
|
||||
id: route.params.streamId,
|
||||
cursor: null
|
||||
}))
|
||||
const stream = computed(() => result.value?.stream || null)
|
||||
|
||||
const skippableActionTypes = ['stream_invite_sent', 'stream_invite_declined']
|
||||
const groupedActivity = data.stream.activity.items.reduce(function (prev, curr) {
|
||||
const skippableActionTypes = ['stream_invite_sent', 'stream_invite_declined']
|
||||
const groupedActivity = computed(() =>
|
||||
(stream.value?.activity?.items || []).reduce(function (prev, curr) {
|
||||
if (skippableActionTypes.includes(curr.actionType)) {
|
||||
return prev
|
||||
}
|
||||
@@ -130,30 +99,35 @@ export default {
|
||||
}
|
||||
return prev
|
||||
}, [])
|
||||
// console.log(groupedTimeline)
|
||||
this.groupedActivity = groupedActivity
|
||||
},
|
||||
infiniteHandler($state) {
|
||||
this.$apollo.queries.stream.fetchMore({
|
||||
)
|
||||
|
||||
return {
|
||||
stream,
|
||||
groupedActivity,
|
||||
activityFetchMore,
|
||||
activityLoading
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isApolloLoading() {
|
||||
return this.$apollo.loading || this.activityLoading
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async infiniteHandler($state) {
|
||||
const result = await this.activityFetchMore({
|
||||
variables: {
|
||||
id: this.$route.params.streamId,
|
||||
cursor: this.stream.activity.cursor
|
||||
},
|
||||
// Transform the previous result with new data
|
||||
updateQuery: (previousResult, { fetchMoreResult }) => {
|
||||
const newItems = fetchMoreResult.stream.activity.items
|
||||
|
||||
//set vue-infinite state
|
||||
if (newItems.length === 0) $state.complete()
|
||||
else $state.loaded()
|
||||
|
||||
fetchMoreResult.stream.activity.items = [
|
||||
...previousResult.stream.activity.items,
|
||||
...newItems
|
||||
]
|
||||
|
||||
return fetchMoreResult
|
||||
}
|
||||
})
|
||||
|
||||
const newItems = result.data?.stream?.activity?.items
|
||||
if (!newItems.length) {
|
||||
$state.complete()
|
||||
} else {
|
||||
$state.loaded()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import StreamInviteBanner from '@/main/components/stream/StreamInviteBanner.vue'
|
||||
import { useUserStreamInvitesQuery } from '@/graphql/generated/graphql'
|
||||
import { UserStreamInvitesDocument } from '@/graphql/generated/graphql'
|
||||
import { StreamInviteType } from '@/main/lib/stream/mixins/streamInviteMixin'
|
||||
|
||||
export default Vue.extend({
|
||||
@@ -24,7 +24,9 @@ export default Vue.extend({
|
||||
streamInvites: [] as NonNullable<StreamInviteType[]>
|
||||
}),
|
||||
apollo: {
|
||||
streamInvites: useUserStreamInvitesQuery()
|
||||
streamInvites: {
|
||||
query: UserStreamInvitesDocument
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onInviteUsed({ accept }: { accept: boolean }, invite: StreamInviteType) {
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import webhookQuery from '@/graphql/webhook.gql'
|
||||
|
||||
export default {
|
||||
|
||||
@@ -45,7 +45,8 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import SectionCard from '@/main/components/common/SectionCard.vue'
|
||||
import { leaveStreamMutation } from '@/graphql/generated/graphql'
|
||||
import { LeaveStreamDocument } from '@/graphql/generated/graphql'
|
||||
import { convertThrowIntoFetchResult } from '@/main/lib/common/apollo/helpers/apolloOperationHelper'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'LeaveStreamPanel',
|
||||
@@ -65,9 +66,14 @@ export default Vue.extend({
|
||||
async leaveStream() {
|
||||
const { streamId } = this
|
||||
|
||||
const { data, errors } = await leaveStreamMutation(this, {
|
||||
variables: { streamId }
|
||||
})
|
||||
const results = await this.$apollo
|
||||
.mutate({
|
||||
mutation: LeaveStreamDocument,
|
||||
variables: { streamId }
|
||||
})
|
||||
.catch(convertThrowIntoFetchResult)
|
||||
|
||||
const { data, errors } = results
|
||||
|
||||
if (data?.streamLeave) {
|
||||
this.$triggerNotification({
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import { canBeFavorited } from '@/helpers/streamHelpers'
|
||||
import { userFavoriteStreamsQuery } from '@/graphql/user'
|
||||
import { commonStreamFieldsFragment } from '@/graphql/streams'
|
||||
|
||||
@@ -94,9 +94,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import { randomString } from '@/helpers/randomHelpers'
|
||||
import objectQuery from '@/graphql/objectSingle.gql'
|
||||
import { omit } from 'lodash'
|
||||
|
||||
export default {
|
||||
name: 'GlobalsBuilder',
|
||||
@@ -114,10 +115,13 @@ export default {
|
||||
}
|
||||
},
|
||||
update(data) {
|
||||
delete data.stream.object.data.__closure
|
||||
this.globalsArray = this.nestedGlobals(data.stream.object.data)
|
||||
return data.stream.object
|
||||
},
|
||||
result({ data }) {
|
||||
this.globalsArray = this.nestedGlobals(
|
||||
omit(data.stream.object.data, ['__closure'])
|
||||
)
|
||||
},
|
||||
skip() {
|
||||
return !this.objectId
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import { fullServerInfoQuery } from '@/graphql/server'
|
||||
|
||||
export default {
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import { fullServerInfoQuery } from '@/graphql/server'
|
||||
|
||||
export default {
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import AppEditDialog from '@/main/components/user/AppEditDialog'
|
||||
|
||||
export default {
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import { fullServerInfoQuery } from '@/graphql/server'
|
||||
|
||||
export default {
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</section-card>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</section-card>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
export default {
|
||||
components: {
|
||||
SectionCard: () => import('@/main/components/common/SectionCard'),
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import UserDeleteDialog from '@/main/dialogs/UserDeleteDialog'
|
||||
import { signOut } from '@/plugins/authHelpers'
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
export default {
|
||||
components: {
|
||||
VImageInput: () => import('vuetify-image-input/a-la-carte'),
|
||||
|
||||
@@ -231,7 +231,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import * as THREE from 'three'
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import { debounce, throttle } from 'lodash'
|
||||
import { getCamArray } from './viewerFrontendHelpers'
|
||||
import CommentEditor from '@/main/components/comments/CommentEditor.vue'
|
||||
|
||||
@@ -170,7 +170,7 @@
|
||||
<script>
|
||||
import * as THREE from 'three'
|
||||
import { debounce, throttle } from 'lodash'
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import { VIEWER_UPDATE_THROTTLE_TIME } from '@/main/lib/viewer/comments/commentsHelper'
|
||||
import { buildResizeHandlerMixin } from '@/main/lib/common/web-apis/mixins/windowResizeHandler'
|
||||
import { documentToBasicString } from '@/main/lib/common/text-editor/documentHelper'
|
||||
@@ -224,6 +224,10 @@ export default {
|
||||
},
|
||||
result({ data }) {
|
||||
if (!data) return
|
||||
|
||||
// Only reason why it's OK to mutate apollo results here, is because
|
||||
// of the 'no-cache' fetchPolicy, which means that none of the data here is actually
|
||||
// mutating the Apollo Cache
|
||||
for (const c of data.comments.items) {
|
||||
c.expanded = false
|
||||
c.hovered = false
|
||||
@@ -262,7 +266,7 @@ export default {
|
||||
skip() {
|
||||
return !this.$loggedIn()
|
||||
},
|
||||
updateQuery(prevResult, { subscriptionData }) {
|
||||
updateQuery(_, { subscriptionData }) {
|
||||
if (!subscriptionData.data?.commentActivity) return
|
||||
|
||||
const { comment: newComment, type } = subscriptionData.data.commentActivity
|
||||
@@ -277,7 +281,7 @@ export default {
|
||||
newComment.archived = false
|
||||
|
||||
if (type === 'comment-added') {
|
||||
if (prevResult.comments.items.find((c) => c.id === newComment.id)) {
|
||||
if (this.localComments.find((c) => c.id === newComment.id)) {
|
||||
return
|
||||
}
|
||||
if (!newComment.archived && newComment.data.location)
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
|
||||
export default {
|
||||
name: 'ObjectProperties',
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import * as THREE from 'three'
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import debounce from 'lodash/debounce'
|
||||
|
||||
|
||||
@@ -46,7 +46,10 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import { computed } from 'vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
InfiniteLoading: () => import('vue-infinite-loading'),
|
||||
@@ -62,13 +65,13 @@ export default {
|
||||
default: () => null
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
stream: {
|
||||
query: gql`
|
||||
setup(props) {
|
||||
const { result: streamResult, fetchMore: streamFetchMore } = useQuery(
|
||||
gql`
|
||||
query ($streamId: String!, $cursor: String) {
|
||||
stream(id: $streamId) {
|
||||
id
|
||||
commits(cursor: $cursor, limit: 2) {
|
||||
commits(cursor: $cursor, limit: 6) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
@@ -84,51 +87,31 @@ export default {
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables() {
|
||||
return { streamId: this.streamId }
|
||||
},
|
||||
skip() {
|
||||
return !this.streamId
|
||||
}
|
||||
() => ({ streamId: props.streamId }),
|
||||
() => ({ enabled: !!props.streamId })
|
||||
)
|
||||
const stream = computed(() => streamResult.value?.stream)
|
||||
|
||||
return {
|
||||
stream,
|
||||
streamFetchMore
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
async mounted() {},
|
||||
methods: {
|
||||
infiniteHandler($state) {
|
||||
this.$apollo.queries.stream.fetchMore({
|
||||
async infiniteHandler($state) {
|
||||
const result = await this.streamFetchMore({
|
||||
variables: {
|
||||
cursor: this.stream.commits.cursor,
|
||||
streamId: this.streamId
|
||||
},
|
||||
// Transform the previous result with new data
|
||||
updateQuery: (previousResult, { fetchMoreResult }) => {
|
||||
const newItems = fetchMoreResult.stream.commits.items
|
||||
if (newItems.length === 0) $state.complete()
|
||||
else $state.loaded()
|
||||
|
||||
const allItems = [...previousResult.stream.commits.items]
|
||||
for (const commit of newItems) {
|
||||
if (allItems.findIndex((c) => c.id === commit.id) === -1)
|
||||
allItems.push(commit)
|
||||
}
|
||||
|
||||
return {
|
||||
stream: {
|
||||
__typename: previousResult.stream.__typename,
|
||||
id: previousResult.stream.id,
|
||||
commits: {
|
||||
__typename: previousResult.stream.commits.__typename,
|
||||
cursor: fetchMoreResult.stream.commits.cursor,
|
||||
totalCount: fetchMoreResult.stream.commits.totalCount,
|
||||
items: allItems
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const newItems = result.data?.stream?.commits?.items || []
|
||||
if (!newItems.length) {
|
||||
$state.complete()
|
||||
} else {
|
||||
$state.loaded()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,9 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import { computed } from 'vue'
|
||||
export default {
|
||||
components: {
|
||||
InfiniteLoading: () => import('vue-infinite-loading'),
|
||||
@@ -67,52 +69,67 @@ export default {
|
||||
default: () => null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
skip: true,
|
||||
cursor: new Date().toISOString(),
|
||||
commits: []
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
this.fetchBranchCommits()
|
||||
},
|
||||
methods: {
|
||||
async fetchBranchCommits() {
|
||||
const res = await this.$apollo.query({
|
||||
query: gql`
|
||||
query {
|
||||
stream(id: "${this.streamId}") {
|
||||
id
|
||||
branch(name: "${this.branchName}") {
|
||||
name
|
||||
commits( cursor: "${this.cursor}", limit: 2) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
sourceApplication
|
||||
id
|
||||
createdAt
|
||||
authorId
|
||||
branchName
|
||||
message
|
||||
referencedObject
|
||||
}
|
||||
setup(props) {
|
||||
const { result: commitsResult, fetchMore: commitsFetchMore } = useQuery(
|
||||
gql`
|
||||
query BranchAllCommits($sid: String!, $branchName: String, $cursor: String) {
|
||||
stream(id: $sid) {
|
||||
id
|
||||
branch(name: $branchName) {
|
||||
name
|
||||
commits(cursor: $cursor, limit: 6) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
sourceApplication
|
||||
id
|
||||
createdAt
|
||||
authorId
|
||||
branchName
|
||||
message
|
||||
referencedObject
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
`,
|
||||
() => ({
|
||||
sid: props.streamId,
|
||||
branchName: props.branchName,
|
||||
cursor: null
|
||||
})
|
||||
const items = res.data.stream.branch.commits.items
|
||||
this.cursor = res.data.stream.branch.commits.cursor
|
||||
items.forEach((item) => this.commits.push(item))
|
||||
return items
|
||||
},
|
||||
)
|
||||
const commits = computed(
|
||||
() => commitsResult.value?.stream?.branch?.commits?.items || []
|
||||
)
|
||||
|
||||
const cursor = computed(
|
||||
() => commitsResult?.value?.stream?.branch?.commits?.cursor || null
|
||||
)
|
||||
|
||||
return {
|
||||
commits,
|
||||
cursor,
|
||||
commitsFetchMore
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async infiniteHandler($state) {
|
||||
const items = await this.fetchBranchCommits()
|
||||
if (items.length === 0) $state.complete()
|
||||
else $state.loaded()
|
||||
const result = await this.commitsFetchMore({
|
||||
variables: {
|
||||
sid: this.streamId,
|
||||
branchName: this.branchName,
|
||||
cursor: this.cursor
|
||||
}
|
||||
})
|
||||
|
||||
const newItems = result?.data?.stream?.branch?.commits?.items || []
|
||||
if (!newItems.length) {
|
||||
$state.complete()
|
||||
} else {
|
||||
$state.loaded()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import streamObjectQuery from '@/graphql/objectSingleNoData.gql'
|
||||
export default {
|
||||
name: 'StreamOverlayViewer',
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import isNull from 'lodash/isNull'
|
||||
import isUndefined from 'lodash/isUndefined'
|
||||
import clone from 'lodash/clone'
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
export default {
|
||||
props: {
|
||||
stream: {
|
||||
|
||||
@@ -63,13 +63,13 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import Vue, { PropType } from 'vue'
|
||||
import { email, maxLength, noXss, required } from '@/main/lib/common/vuetify/validators'
|
||||
import { Nullable, Optional } from '@/helpers/typeHelpers'
|
||||
import { VFormInstance } from '@/helpers/vuetifyHelpers'
|
||||
import type { Get } from 'type-fest'
|
||||
import type { FetchResult } from 'apollo-link'
|
||||
import type { FetchResult } from '@apollo/client/core'
|
||||
import { UserSearchQuery } from '@/graphql/generated/graphql'
|
||||
import BasicUserInfoRow from '@/main/components/user/BasicUserInfoRow.vue'
|
||||
import { StreamEvents } from '@/main/lib/core/helpers/eventHubHelper'
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
|
||||
@@ -50,18 +50,14 @@
|
||||
placeholder="Search by name or by email"
|
||||
/>
|
||||
<div v-if="$apollo.loading">Searching.</div>
|
||||
<v-list v-if="userSearch && userSearch.items" one-line>
|
||||
<v-list-item v-if="userSearch.items.length === 0">
|
||||
<v-list v-if="userSearch && users" one-line>
|
||||
<v-list-item v-if="users.length === 0">
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>No users found.</v-list-item-title>
|
||||
<v-list-item-subtitle>Try a different search query.</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
v-for="item in userSearch.items"
|
||||
:key="item.id"
|
||||
@click="addCollab(item)"
|
||||
>
|
||||
<v-list-item v-for="item in users" :key="item.id" @click="addCollab(item)">
|
||||
<v-list-item-avatar>
|
||||
<user-avatar
|
||||
:id="item.id"
|
||||
@@ -116,7 +112,7 @@
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import { userSearchQuery } from '@/graphql/user'
|
||||
|
||||
export default {
|
||||
@@ -145,6 +141,9 @@ export default {
|
||||
skip() {
|
||||
return !this.search || this.search.length < 3
|
||||
},
|
||||
result({ data }) {
|
||||
this.users = [...data.userSearch.items]
|
||||
},
|
||||
debounce: 300
|
||||
}
|
||||
},
|
||||
@@ -157,14 +156,15 @@ export default {
|
||||
nameRules: [],
|
||||
isPublic: true,
|
||||
collabs: [],
|
||||
isLoading: false
|
||||
isLoading: false,
|
||||
users: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
open() {
|
||||
this.name = null
|
||||
this.search = null
|
||||
if (this.userSearch) this.userSearch.items = null
|
||||
this.users = null
|
||||
this.collabs = []
|
||||
}
|
||||
},
|
||||
@@ -183,10 +183,10 @@ export default {
|
||||
if (user.id === localStorage.getItem('uuid')) return
|
||||
const indx = this.collabs.findIndex((u) => u.id === user.id)
|
||||
if (indx !== -1) return
|
||||
user.role = 'stream:contributor'
|
||||
|
||||
this.collabs.push(user)
|
||||
this.search = null
|
||||
this.userSearch.items = null
|
||||
this.users = null
|
||||
},
|
||||
removeCollab(user) {
|
||||
const indx = this.collabs.findIndex((u) => u.id === user.id)
|
||||
|
||||
@@ -218,7 +218,7 @@
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import { commonStreamFieldsFragment } from '@/graphql/streams'
|
||||
import InviteDialog from '@/main/dialogs/InviteDialog.vue'
|
||||
import UserAvatar from '@/main/components/common/UserAvatar.vue'
|
||||
|
||||
@@ -15,13 +15,12 @@
|
||||
</v-col>
|
||||
<v-col cols="11" sm="8" md="6" lg="4" xl="3">
|
||||
<router-view></router-view>
|
||||
<!-- Temporary revert of our no v-html policy: -->
|
||||
<p
|
||||
v-if="serverInfo"
|
||||
class="caption text-center mt-2"
|
||||
v-html="serverInfo.termsOfService"
|
||||
>
|
||||
<!-- Temporary revert of our no v-html policy -->
|
||||
</p>
|
||||
></p>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
@@ -31,6 +30,8 @@
|
||||
import LoginBlurb from '@/main/components/auth/LoginBlurb.vue'
|
||||
import { mainServerInfoQuery } from '@/graphql/server'
|
||||
|
||||
// TODO: Need to fix the v-html usage ASAP
|
||||
|
||||
export default {
|
||||
name: 'TheAuth',
|
||||
components: { LoginBlurb },
|
||||
|
||||
@@ -61,9 +61,9 @@
|
||||
</v-app>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import { mainUserDataQuery } from '@/graphql/user'
|
||||
import { mainServerInfoQuery } from '@/graphql/server'
|
||||
import { setDarkTheme } from '@/main/utils/themeStateManager'
|
||||
|
||||
export default {
|
||||
name: 'TheMain',
|
||||
@@ -77,9 +77,6 @@ export default {
|
||||
import('@/main/components/user/EmailVerificationBanner')
|
||||
},
|
||||
apollo: {
|
||||
serverInfo: {
|
||||
query: mainServerInfoQuery
|
||||
},
|
||||
user: {
|
||||
query: mainUserDataQuery,
|
||||
skip() {
|
||||
@@ -148,10 +145,8 @@ export default {
|
||||
methods: {
|
||||
switchTheme() {
|
||||
this.$vuetify.theme.dark = !this.$vuetify.theme.dark
|
||||
localStorage.setItem(
|
||||
'darkModeEnabled',
|
||||
this.$vuetify.theme.dark ? 'dark' : 'light'
|
||||
)
|
||||
setDarkTheme(this.$vuetify.theme.dark, true)
|
||||
|
||||
this.$mixpanel.people.set(
|
||||
'Theme Web',
|
||||
this.$vuetify.theme.dark ? 'dark' : 'light'
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ApolloError, FetchResult } from '@apollo/client/core'
|
||||
import { GraphQLError } from 'graphql'
|
||||
|
||||
/**
|
||||
* Convert an error thrown during $apollo.mutate() into a fetch result
|
||||
*/
|
||||
export function convertThrowIntoFetchResult(err: unknown): FetchResult {
|
||||
let gqlErrors: readonly GraphQLError[]
|
||||
if (err instanceof ApolloError) {
|
||||
gqlErrors = err.graphQLErrors
|
||||
} else if (err instanceof Error) {
|
||||
gqlErrors = [new GraphQLError(err.message)]
|
||||
} else {
|
||||
gqlErrors = [new GraphQLError(err + '')]
|
||||
}
|
||||
|
||||
return {
|
||||
data: undefined,
|
||||
errors: gqlErrors
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Optional } from '@/helpers/typeHelpers'
|
||||
import { ComposableInvokedOutOfScopeError } from '@/main/lib/core/errors/composition'
|
||||
import { getCurrentInstance, reactive } from 'vue'
|
||||
import VueRouter, { Route } from 'vue-router'
|
||||
|
||||
let currentRoute: Optional<Route>
|
||||
|
||||
/**
|
||||
* Get router (not reactive)
|
||||
*/
|
||||
export function useRouter(): VueRouter {
|
||||
const vm = getCurrentInstance()
|
||||
if (!vm) throw new ComposableInvokedOutOfScopeError()
|
||||
|
||||
return vm.proxy.$router
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current route object (reactive)
|
||||
*/
|
||||
export function useRoute(): Route {
|
||||
if (currentRoute) return currentRoute
|
||||
|
||||
const router = useRouter()
|
||||
const vm = getCurrentInstance()
|
||||
if (!vm) throw new ComposableInvokedOutOfScopeError()
|
||||
|
||||
const newRoute = reactive({ ...vm.proxy.$route } as Route)
|
||||
router.afterEach((to) => {
|
||||
Object.assign(newRoute, to)
|
||||
})
|
||||
currentRoute = newRoute
|
||||
|
||||
return newRoute
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { BaseError } from '@/helpers/errorHelper'
|
||||
|
||||
export class ComposableInvokedOutOfScopeError extends BaseError {
|
||||
static defaultMessage =
|
||||
'getCurrentInstance() returned null. Method must be called at the top of a setup function'
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Optional } from '@/helpers/typeHelpers'
|
||||
import { FieldMergeFunction } from '@apollo/client/core'
|
||||
|
||||
interface AbstractCollection<T extends string> {
|
||||
__typename: T
|
||||
totalCount: number
|
||||
cursor: string | null
|
||||
items: Record<string, unknown>[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an Apollo merge function for a field that returns a collection like AbstractCollection
|
||||
* @param {{}} [param0]
|
||||
* @param {boolean} [param0.checkIdentity] Set to true if you want to double check that items with IDs
|
||||
* that already appear in old results, don't get added again
|
||||
* @param {string} [param0.identityProp] Optionally change the prop that should be used to compare
|
||||
* equality between items
|
||||
*/
|
||||
export function buildAbstractCollectionMergeFunction<T extends string>(
|
||||
typeName: T,
|
||||
{ checkIdentity = false, identityProp = '__ref' } = {}
|
||||
): FieldMergeFunction<Optional<AbstractCollection<T>>, AbstractCollection<T>> {
|
||||
return (
|
||||
existing: Optional<AbstractCollection<T>>,
|
||||
incoming: AbstractCollection<T>
|
||||
) => {
|
||||
const existingItems = existing?.items || []
|
||||
const incomingItems = incoming?.items || []
|
||||
|
||||
let finalItems: Record<string, unknown>[]
|
||||
if (checkIdentity) {
|
||||
finalItems = [...existingItems]
|
||||
for (const newItem of incomingItems) {
|
||||
if (
|
||||
finalItems.findIndex(
|
||||
(item) => item[identityProp] === newItem[identityProp]
|
||||
) === -1
|
||||
) {
|
||||
finalItems.push(newItem)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
finalItems = [...existingItems, ...incomingItems]
|
||||
}
|
||||
|
||||
return {
|
||||
__typename: incoming?.__typename || existing?.__typename || typeName,
|
||||
totalCount: incoming.totalCount || 0,
|
||||
cursor: incoming.cursor || null,
|
||||
items: finalItems
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge function that just takes incoming data and overrides all of old data with it
|
||||
* Useful for array fields w/o pagination, where a new array response is supposed to replace
|
||||
* the entire old one
|
||||
*/
|
||||
export const incomingOverwritesExistingMergeFunction: FieldMergeFunction = (
|
||||
_existing: unknown,
|
||||
incoming: unknown
|
||||
) => incoming
|
||||
@@ -1,5 +1,5 @@
|
||||
import Vue from 'vue'
|
||||
import { useIsLoggedInQuery } from '@/graphql/generated/graphql'
|
||||
import { IsLoggedInDocument, IsLoggedInQuery } from '@/graphql/generated/graphql'
|
||||
/**
|
||||
* Mixin for checking if user is logged in through Apollo Client. Use the reactive 'isLoggedIn' data property
|
||||
* to check if a user is logged in.
|
||||
@@ -13,8 +13,9 @@ export const IsLoggedInMixin = Vue.extend({
|
||||
isLoggedIn: false
|
||||
}),
|
||||
apollo: {
|
||||
isLoggedIn: useIsLoggedInQuery<Vue & { isLoggedIn: boolean }>({
|
||||
update: (data) => !!data.user?.id
|
||||
})
|
||||
isLoggedIn: {
|
||||
query: IsLoggedInDocument,
|
||||
update: (data: IsLoggedInQuery) => !!data.user?.id
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import {
|
||||
StreamInviteQuery,
|
||||
useStreamInviteMutation,
|
||||
StreamInviteDocument,
|
||||
UserStreamInvitesQuery,
|
||||
UserStreamInvitesDocument
|
||||
UserStreamInvitesDocument,
|
||||
UseStreamInviteDocument
|
||||
} from '@/graphql/generated/graphql'
|
||||
import { MaybeFalsy, Nullable, vueWithMixins } from '@/helpers/typeHelpers'
|
||||
import { convertThrowIntoFetchResult } from '@/main/lib/common/apollo/helpers/apolloOperationHelper'
|
||||
import { StreamEvents } from '@/main/lib/core/helpers/eventHubHelper'
|
||||
import { IsLoggedInMixin } from '@/main/lib/core/mixins/isLoggedInMixin'
|
||||
import { Get } from 'type-fest'
|
||||
@@ -68,76 +69,79 @@ export const UsersStreamInviteMixin = vueWithMixins(IsLoggedInMixin).extend({
|
||||
async processInvite(accept: boolean) {
|
||||
if (!this.token) return
|
||||
|
||||
const { data, errors } = await useStreamInviteMutation(this, {
|
||||
variables: {
|
||||
accept,
|
||||
streamId: this.streamId,
|
||||
token: this.token
|
||||
},
|
||||
update: (cache, { data }) => {
|
||||
if (!data?.streamInviteUse) return
|
||||
const { data, errors } = await this.$apollo
|
||||
.mutate({
|
||||
mutation: UseStreamInviteDocument,
|
||||
variables: {
|
||||
accept,
|
||||
streamId: this.streamId,
|
||||
token: this.token
|
||||
},
|
||||
update: (cache, { data }) => {
|
||||
if (!data?.streamInviteUse) return
|
||||
|
||||
// It's weird that i'm emitting from inside the update handler, but if I invoke the emit
|
||||
// at the bottom of `processInvite()`, the event won't be fired because of a race condition
|
||||
// between the cache updates below and the queries that rely on the cached invites in the parent
|
||||
// component. Basically - I have to do it this way or the event won't be handled
|
||||
this.$emit('invite-used', { accept })
|
||||
// It's weird that i'm emitting from inside the update handler, but if I invoke the emit
|
||||
// at the bottom of `processInvite()`, the event won't be fired because of a race condition
|
||||
// between the cache updates below and the queries that rely on the cached invites in the parent
|
||||
// component. Basically - I have to do it this way or the event won't be handled
|
||||
this.$emit('invite-used', { accept })
|
||||
|
||||
// Remove invite from various cached queries we might have
|
||||
// 1. Single stream invite query
|
||||
const singleStreamInviteCacheFilter = {
|
||||
query: StreamInviteDocument,
|
||||
variables: { streamId: this.streamId, token: this.token }
|
||||
}
|
||||
let singleStreamInviteQueryData: MaybeFalsy<StreamInviteQuery> = undefined
|
||||
try {
|
||||
singleStreamInviteQueryData = cache.readQuery<StreamInviteQuery>(
|
||||
singleStreamInviteCacheFilter
|
||||
)
|
||||
} catch (err) {
|
||||
// suppressed
|
||||
}
|
||||
|
||||
if (singleStreamInviteQueryData?.streamInvite) {
|
||||
cache.writeQuery({
|
||||
...singleStreamInviteCacheFilter,
|
||||
data: {
|
||||
streamInvite: null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 2. All user's stream invites query
|
||||
let allUsersStreamInvitesQueryData: MaybeFalsy<UserStreamInvitesQuery> =
|
||||
undefined
|
||||
try {
|
||||
allUsersStreamInvitesQueryData = cache.readQuery<UserStreamInvitesQuery>({
|
||||
query: UserStreamInvitesDocument
|
||||
})
|
||||
} catch (err) {
|
||||
// suppressed
|
||||
}
|
||||
|
||||
if (allUsersStreamInvitesQueryData?.streamInvites) {
|
||||
const removableInviteIdx =
|
||||
allUsersStreamInvitesQueryData.streamInvites.findIndex(
|
||||
(i) => i.inviteId === this.inviteId
|
||||
// Remove invite from various cached queries we might have
|
||||
// 1. Single stream invite query
|
||||
const singleStreamInviteCacheFilter = {
|
||||
query: StreamInviteDocument,
|
||||
variables: { streamId: this.streamId, token: this.token }
|
||||
}
|
||||
let singleStreamInviteQueryData: MaybeFalsy<StreamInviteQuery> = undefined
|
||||
try {
|
||||
singleStreamInviteQueryData = cache.readQuery<StreamInviteQuery>(
|
||||
singleStreamInviteCacheFilter
|
||||
)
|
||||
if (removableInviteIdx !== -1) {
|
||||
const newInvites = allUsersStreamInvitesQueryData.streamInvites.slice()
|
||||
newInvites.splice(removableInviteIdx, 1)
|
||||
} catch (err) {
|
||||
// suppressed
|
||||
}
|
||||
|
||||
cache.writeQuery<UserStreamInvitesQuery>({
|
||||
query: UserStreamInvitesDocument,
|
||||
if (singleStreamInviteQueryData?.streamInvite) {
|
||||
cache.writeQuery({
|
||||
...singleStreamInviteCacheFilter,
|
||||
data: {
|
||||
...allUsersStreamInvitesQueryData,
|
||||
streamInvites: newInvites
|
||||
streamInvite: null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 2. All user's stream invites query
|
||||
let allUsersStreamInvitesQueryData: MaybeFalsy<UserStreamInvitesQuery> =
|
||||
undefined
|
||||
try {
|
||||
allUsersStreamInvitesQueryData = cache.readQuery<UserStreamInvitesQuery>({
|
||||
query: UserStreamInvitesDocument
|
||||
})
|
||||
} catch (err) {
|
||||
// suppressed
|
||||
}
|
||||
|
||||
if (allUsersStreamInvitesQueryData?.streamInvites) {
|
||||
const removableInviteIdx =
|
||||
allUsersStreamInvitesQueryData.streamInvites.findIndex(
|
||||
(i) => i.inviteId === this.inviteId
|
||||
)
|
||||
if (removableInviteIdx !== -1) {
|
||||
const newInvites = allUsersStreamInvitesQueryData.streamInvites.slice()
|
||||
newInvites.splice(removableInviteIdx, 1)
|
||||
|
||||
cache.writeQuery<UserStreamInvitesQuery>({
|
||||
query: UserStreamInvitesDocument,
|
||||
data: {
|
||||
...allUsersStreamInvitesQueryData,
|
||||
streamInvites: newInvites
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(convertThrowIntoFetchResult)
|
||||
|
||||
if (data?.streamInviteUse) {
|
||||
this.$triggerNotification({
|
||||
|
||||
@@ -132,6 +132,7 @@
|
||||
<script>
|
||||
import { mainUserDataQuery } from '@/graphql/user'
|
||||
import InviteDialog from '@/main/dialogs/InviteDialog.vue'
|
||||
import { setDarkTheme } from '@/main/utils/themeStateManager'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -172,10 +173,8 @@ export default {
|
||||
methods: {
|
||||
switchTheme() {
|
||||
this.$vuetify.theme.dark = !this.$vuetify.theme.dark
|
||||
localStorage.setItem(
|
||||
'darkModeEnabled',
|
||||
this.$vuetify.theme.dark ? 'dark' : 'light'
|
||||
)
|
||||
setDarkTheme(this.$vuetify.theme.dark, true)
|
||||
|
||||
this.$mixpanel.people.set(
|
||||
'Theme Web',
|
||||
this.$vuetify.theme.dark ? 'dark' : 'light'
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
setDarkTheme
|
||||
<template>
|
||||
<div class="elevation-10">
|
||||
<portal-target name="nav-bottom">
|
||||
@@ -45,18 +44,9 @@ setDarkTheme
|
||||
</template>
|
||||
<script>
|
||||
import { signOut } from '@/plugins/authHelpers'
|
||||
import { mainUserDataQuery } from '@/graphql/user'
|
||||
import { setDarkTheme } from '@/main/utils/themeStateManager'
|
||||
|
||||
export default {
|
||||
apollo: {
|
||||
user: {
|
||||
query: mainUserDataQuery,
|
||||
skip() {
|
||||
return !this.loggedIn
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
signOut() {
|
||||
this.$mixpanel.track('Log Out', { type: 'action' })
|
||||
|
||||
@@ -221,7 +221,7 @@
|
||||
</portal>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import {
|
||||
STANDARD_PORTAL_KEYS,
|
||||
buildPortalStateMixin
|
||||
|
||||
@@ -60,11 +60,13 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import {
|
||||
STANDARD_PORTAL_KEYS,
|
||||
buildPortalStateMixin
|
||||
} from '@/main/utils/portalStateManager'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import { computed } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'TheCommits',
|
||||
@@ -74,66 +76,51 @@ export default {
|
||||
NoDataPlaceholder: () => import('@/main/components/common/NoDataPlaceholder')
|
||||
},
|
||||
mixins: [buildPortalStateMixin([STANDARD_PORTAL_KEYS.Toolbar], 'commits', 0)],
|
||||
apollo: {
|
||||
user: {
|
||||
query: gql`
|
||||
query ($cursor: String) {
|
||||
user {
|
||||
id
|
||||
name
|
||||
commits(limit: 10, cursor: $cursor) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
id
|
||||
referencedObject
|
||||
message
|
||||
streamName
|
||||
streamId
|
||||
createdAt
|
||||
sourceApplication
|
||||
branchName
|
||||
commentCount
|
||||
}
|
||||
setup() {
|
||||
const { result, fetchMore: userFetchMore } = useQuery(gql`
|
||||
query ($cursor: String) {
|
||||
user {
|
||||
id
|
||||
name
|
||||
commits(limit: 10, cursor: $cursor) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
id
|
||||
referencedObject
|
||||
message
|
||||
streamName
|
||||
streamId
|
||||
createdAt
|
||||
sourceApplication
|
||||
branchName
|
||||
commentCount
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
`)
|
||||
const user = computed(() => result.value?.user)
|
||||
|
||||
return {
|
||||
user,
|
||||
userFetchMore
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
infiniteHandler($state) {
|
||||
this.$apollo.queries.user.fetchMore({
|
||||
async infiniteHandler($state) {
|
||||
const result = await this.userFetchMore({
|
||||
variables: {
|
||||
cursor: this.user.commits.cursor
|
||||
},
|
||||
// Transform the previous result with new data
|
||||
updateQuery: (previousResult, { fetchMoreResult }) => {
|
||||
const newItems = fetchMoreResult.user.commits.items
|
||||
if (newItems.length === 0) $state.complete()
|
||||
else $state.loaded()
|
||||
|
||||
const allItems = [...previousResult.user.commits.items]
|
||||
for (const commit of newItems) {
|
||||
if (allItems.findIndex((c) => c.id === commit.id) === -1)
|
||||
allItems.push(commit)
|
||||
}
|
||||
|
||||
return {
|
||||
user: {
|
||||
__typename: previousResult.user.__typename,
|
||||
name: previousResult.user.name,
|
||||
id: previousResult.user.id,
|
||||
commits: {
|
||||
__typename: previousResult.user.commits.__typename,
|
||||
cursor: fetchMoreResult.user.commits.cursor,
|
||||
totalCount: fetchMoreResult.user.commits.totalCount,
|
||||
items: allItems
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const newItems = result.data?.user?.commits?.items || []
|
||||
if (!newItems.length) {
|
||||
$state.complete()
|
||||
} else {
|
||||
$state.loaded()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,16 +36,16 @@ usePortalState
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import Vue, { defineComponent } from 'vue'
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import { STANDARD_PORTAL_KEYS, usePortalState } from '@/main/utils/portalStateManager'
|
||||
import {
|
||||
UserFavoriteStreamsQuery,
|
||||
UserFavoriteStreamsQueryVariables,
|
||||
useUserFavoriteStreamsQuery
|
||||
UserFavoriteStreamsDocument
|
||||
} from '@/graphql/generated/graphql'
|
||||
import type { StateChanger } from 'vue-infinite-loading'
|
||||
import type { Get } from 'type-fest'
|
||||
import type { SmartQuery } from 'vue-apollo/types/vue-apollo'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import { Nullable } from '@/helpers/typeHelpers'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TheFavoriteStreams',
|
||||
@@ -61,15 +61,13 @@ export default defineComponent({
|
||||
'favorite-streams',
|
||||
0
|
||||
)
|
||||
return { canRenderToolbarPortal }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
user: undefined as UserFavoriteStreamsQuery['user']
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
user: useUserFavoriteStreamsQuery()
|
||||
|
||||
const { result, fetchMore: userFetchMore } = useQuery(UserFavoriteStreamsDocument, {
|
||||
cursor: null as Nullable<string>
|
||||
})
|
||||
const user = computed(() => result.value?.user)
|
||||
|
||||
return { canRenderToolbarPortal, user, userFetchMore }
|
||||
},
|
||||
computed: {
|
||||
streams(): NonNullable<
|
||||
@@ -88,7 +86,7 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
infiniteHandler($state: StateChanger) {
|
||||
async infiniteHandler($state: StateChanger) {
|
||||
if (this.allStreamsLoaded) {
|
||||
$state.loaded()
|
||||
$state.complete()
|
||||
@@ -96,44 +94,18 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
// Fetch more favorites
|
||||
const userQuery: SmartQuery<
|
||||
Vue,
|
||||
UserFavoriteStreamsQuery,
|
||||
UserFavoriteStreamsQueryVariables
|
||||
> = this.$apollo.queries.user
|
||||
userQuery.fetchMore({
|
||||
const result = await this.userFetchMore({
|
||||
variables: {
|
||||
cursor: this.user?.favoriteStreams?.cursor || null
|
||||
},
|
||||
updateQuery: (previousResult, { fetchMoreResult }) => {
|
||||
const newFavorites = fetchMoreResult?.user?.favoriteStreams
|
||||
const oldFavorites = previousResult.user?.favoriteStreams
|
||||
|
||||
let { items: newItems } = newFavorites || {}
|
||||
let { items: allItems } = oldFavorites || {}
|
||||
newItems ||= []
|
||||
allItems ||= []
|
||||
|
||||
for (const stream of newItems) {
|
||||
if (allItems.findIndex((s) => s.id === stream.id) === -1)
|
||||
allItems.push(stream)
|
||||
}
|
||||
|
||||
// set vue-infinite state
|
||||
newItems.length === 0 ? $state.complete() : $state.loaded()
|
||||
|
||||
return {
|
||||
...previousResult,
|
||||
user: {
|
||||
...previousResult.user,
|
||||
favoriteStreams: {
|
||||
...(fetchMoreResult?.user?.favoriteStreams || {}),
|
||||
items: allItems
|
||||
}
|
||||
}
|
||||
} as UserFavoriteStreamsQuery
|
||||
}
|
||||
})
|
||||
|
||||
const newItems = result?.data?.user?.favoriteStreams?.items || []
|
||||
if (!newItems.length) {
|
||||
$state.complete()
|
||||
} else {
|
||||
$state.loaded()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -92,6 +92,8 @@ import UserStreamInviteBanners from '@/main/components/stream/UserStreamInviteBa
|
||||
import InfiniteLoading from 'vue-infinite-loading'
|
||||
import StreamPreviewCard from '@/main/components/common/StreamPreviewCard.vue'
|
||||
import NoDataPlaceholder from '@/main/components/common/NoDataPlaceholder.vue'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import { computed } from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'TheStreams',
|
||||
@@ -103,13 +105,26 @@ export default {
|
||||
},
|
||||
mixins: [buildPortalStateMixin([STANDARD_PORTAL_KEYS.Toolbar], 'streams', 0)],
|
||||
apollo: {
|
||||
streams: {
|
||||
query: streamsQuery
|
||||
},
|
||||
user: {
|
||||
query: mainUserDataQuery
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const {
|
||||
result,
|
||||
fetchMore: streamsFetchMore,
|
||||
refetch: streamsRefetch
|
||||
} = useQuery(streamsQuery, {
|
||||
cursor: null
|
||||
})
|
||||
const streams = computed(() => result.value?.streams)
|
||||
|
||||
return {
|
||||
streams,
|
||||
streamsFetchMore,
|
||||
streamsRefetch
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
streamFilter: 1,
|
||||
@@ -142,14 +157,14 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
if (this.$route.query.refresh) {
|
||||
this.$apollo.queries.streams.refetch()
|
||||
this.streamsRefetch()
|
||||
this.$router.replace({ path: this.$route.path, query: null })
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onInviteUsed() {
|
||||
// Refetch streams
|
||||
this.$apollo.queries.streams.refetch()
|
||||
this.streamsRefetch()
|
||||
},
|
||||
checkFilter(role) {
|
||||
if (this.streamFilter === 1) return true
|
||||
@@ -158,47 +173,25 @@ export default {
|
||||
if (this.streamFilter === 4 && role === 'stream:reviewer') return true
|
||||
return false
|
||||
},
|
||||
// TODO: Prevent extra load if we've hit all items
|
||||
infiniteHandler($state) {
|
||||
async infiniteHandler($state) {
|
||||
if (this.allStreamsLoaded) {
|
||||
$state.loaded()
|
||||
$state.complete()
|
||||
return
|
||||
}
|
||||
|
||||
this.$apollo.queries.streams.fetchMore({
|
||||
const result = await this.streamsFetchMore({
|
||||
variables: {
|
||||
cursor: this.streams?.cursor
|
||||
},
|
||||
// Transform the previous result with new data
|
||||
updateQuery: (previousResult, { fetchMoreResult }) => {
|
||||
const newItems = fetchMoreResult.streams.items
|
||||
const allItems = [...previousResult.streams.items]
|
||||
const newTotalCount = fetchMoreResult.streams.totalCount
|
||||
|
||||
for (const stream of newItems) {
|
||||
if (allItems.findIndex((s) => s.id === stream.id) === -1)
|
||||
allItems.push(stream)
|
||||
}
|
||||
|
||||
// Update infinite loader state
|
||||
if (newItems.length === 0) {
|
||||
$state.complete()
|
||||
} else {
|
||||
$state.loaded()
|
||||
}
|
||||
|
||||
return {
|
||||
streams: {
|
||||
__typename: previousResult.streams.__typename,
|
||||
totalCount: newTotalCount,
|
||||
cursor: fetchMoreResult.streams.cursor,
|
||||
// Merging the new streams
|
||||
items: allItems
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const newItems = result?.data?.streams?.items || []
|
||||
if (!newItems.length) {
|
||||
$state.complete()
|
||||
} else {
|
||||
$state.loaded()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import {
|
||||
STANDARD_PORTAL_KEYS,
|
||||
buildPortalStateMixin
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import { isEmailValid } from '@/plugins/authHelpers'
|
||||
import { mainServerInfoQuery } from '@/graphql/server'
|
||||
import {
|
||||
@@ -98,9 +98,10 @@ import {
|
||||
} from '@/main/utils/portalStateManager'
|
||||
import { maxLength, noXss } from '@/main/lib/common/vuetify/validators'
|
||||
import {
|
||||
batchInviteToServerMutation,
|
||||
batchInviteToStreamsMutation
|
||||
BatchInviteToStreamsDocument,
|
||||
BatchInviteToServerDocument
|
||||
} from '@/graphql/generated/graphql'
|
||||
import { convertThrowIntoFetchResult } from '@/main/lib/common/apollo/helpers/apolloOperationHelper'
|
||||
|
||||
export default {
|
||||
name: 'AdminInvites',
|
||||
@@ -220,12 +221,18 @@ export default {
|
||||
},
|
||||
async sendInvites(paramsArray, streamId) {
|
||||
const { data, errors } = streamId
|
||||
? await batchInviteToStreamsMutation(this, {
|
||||
variables: { paramsArray }
|
||||
})
|
||||
: await batchInviteToServerMutation(this, {
|
||||
variables: { paramsArray }
|
||||
})
|
||||
? await this.$apollo
|
||||
.mutate({
|
||||
mutation: BatchInviteToStreamsDocument,
|
||||
variables: { paramsArray }
|
||||
})
|
||||
.catch(convertThrowIntoFetchResult)
|
||||
: await this.$apollo
|
||||
.mutate({
|
||||
mutation: BatchInviteToServerDocument,
|
||||
variables: { paramsArray }
|
||||
})
|
||||
.catch(convertThrowIntoFetchResult)
|
||||
|
||||
if (!data?.serverInviteBatchCreate && !data?.streamInviteBatchCreate) {
|
||||
const errMsg =
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import { mainServerInfoQuery } from '@/graphql/server'
|
||||
import pick from 'lodash/pick'
|
||||
import {
|
||||
@@ -53,7 +53,7 @@ import {
|
||||
} from '@/main/utils/portalStateManager'
|
||||
|
||||
export default {
|
||||
name: 'ServerInfoAdminCard',
|
||||
name: 'ServerSettings',
|
||||
components: {
|
||||
SectionCard: () => import('@/main/components/common/SectionCard')
|
||||
},
|
||||
@@ -95,10 +95,11 @@ export default {
|
||||
apollo: {
|
||||
serverInfo: {
|
||||
query: mainServerInfoQuery,
|
||||
update(data) {
|
||||
delete data.serverInfo.__typename
|
||||
this.serverModifications = Object.assign({}, data.serverInfo)
|
||||
return data.serverInfo
|
||||
result({ data }) {
|
||||
const newModifications = Object.assign({}, data.serverInfo)
|
||||
delete newModifications.__typename
|
||||
|
||||
this.serverModifications = newModifications
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import debounce from 'lodash/debounce'
|
||||
import {
|
||||
STANDARD_PORTAL_KEYS,
|
||||
|
||||
@@ -102,20 +102,21 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import debounce from 'lodash/debounce'
|
||||
import {
|
||||
STANDARD_PORTAL_KEYS,
|
||||
buildPortalStateMixin
|
||||
} from '@/main/utils/portalStateManager'
|
||||
import {
|
||||
deleteInviteMutation,
|
||||
resendInviteMutation,
|
||||
useAdminUsersListQuery
|
||||
DeleteInviteDocument,
|
||||
ResendInviteDocument,
|
||||
AdminUsersListDocument
|
||||
} from '@/graphql/generated/graphql'
|
||||
import SectionCard from '@/main/components/common/SectionCard.vue'
|
||||
import UsersListItem from '@/main/components/admin/UsersListItem.vue'
|
||||
import { Roles } from '@/helpers/mainConstants'
|
||||
import { convertThrowIntoFetchResult } from '@/main/lib/common/apollo/helpers/apolloOperationHelper'
|
||||
|
||||
// TODO: This needs a redesign, it's pretty unusable on small screens
|
||||
|
||||
@@ -190,9 +191,12 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
async deleteInvite({ inviteId }) {
|
||||
const { data, errors } = await deleteInviteMutation(this, {
|
||||
variables: { inviteId }
|
||||
})
|
||||
const { data, errors } = await this.$apollo
|
||||
.mutate({
|
||||
mutation: DeleteInviteDocument,
|
||||
variables: { inviteId }
|
||||
})
|
||||
.catch(convertThrowIntoFetchResult)
|
||||
|
||||
if (data?.inviteDelete) {
|
||||
this.refetch()
|
||||
@@ -210,9 +214,12 @@ export default {
|
||||
}
|
||||
},
|
||||
async resendInvite({ inviteId }) {
|
||||
const { data, errors } = await resendInviteMutation(this, {
|
||||
variables: { inviteId }
|
||||
})
|
||||
const { data, errors } = await this.$apollo
|
||||
.mutate({
|
||||
mutation: ResendInviteDocument,
|
||||
variables: { inviteId }
|
||||
})
|
||||
.catch(convertThrowIntoFetchResult)
|
||||
|
||||
if (data?.inviteResend) {
|
||||
this.$triggerNotification({
|
||||
@@ -311,7 +318,8 @@ export default {
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
adminUsers: useAdminUsersListQuery({
|
||||
adminUsers: {
|
||||
query: AdminUsersListDocument,
|
||||
variables() {
|
||||
return {
|
||||
limit: this.queryLimit,
|
||||
@@ -319,7 +327,7 @@ export default {
|
||||
query: this.q
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import UserAvatar from '@/main/components/auth/UserAvatarAuthoriseApp'
|
||||
import UserAvatarIcon from '@/main/components/common/UserAvatarIcon'
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import debounce from 'lodash/debounce'
|
||||
|
||||
export default {
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import { isEmailValid } from '@/plugins/authHelpers'
|
||||
|
||||
export default {
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import AuthStrategies from '@/main/components/auth/AuthStrategies.vue'
|
||||
import { randomString } from '@/helpers/randomHelpers'
|
||||
import { isEmailValid } from '@/plugins/authHelpers'
|
||||
|
||||
@@ -181,7 +181,7 @@
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import debounce from 'lodash/debounce'
|
||||
import { randomString } from '@/helpers/randomHelpers'
|
||||
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-timeline
|
||||
v-if="stream && groupedActivity && groupedActivity.length !== 0"
|
||||
align-top
|
||||
dense
|
||||
>
|
||||
<list-item-activity
|
||||
v-for="activity in groupedActivity"
|
||||
:key="activity.time"
|
||||
:activity="activity"
|
||||
:activity-group="activity"
|
||||
class="my-1"
|
||||
></list-item-activity>
|
||||
<infinite-loading
|
||||
v-if="
|
||||
stream.activity && stream.activity.items.length < stream.activity.totalCount
|
||||
"
|
||||
@infinite="infiniteHandler"
|
||||
>
|
||||
<div slot="no-more">This is all your activity!</div>
|
||||
<div slot="no-results">There are no ctivities to load</div>
|
||||
</infinite-loading>
|
||||
</v-timeline>
|
||||
<v-timeline v-else-if="$apollo.loading" align-top dense>
|
||||
<v-timeline-item v-for="i in 6" :key="i" medium>
|
||||
<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>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
// TODO: Is this unused?
|
||||
export default {
|
||||
name: 'TheActivity',
|
||||
components: {
|
||||
ListItemActivity: () => import('@/main/components/activity/ListItemActivity'),
|
||||
InfiniteLoading: () => import('vue-infinite-loading')
|
||||
},
|
||||
data() {
|
||||
return { groupedActivity: null }
|
||||
},
|
||||
apollo: {
|
||||
stream: {
|
||||
query: gql`
|
||||
query Stream($id: String!, $cursor: DateTime) {
|
||||
stream(id: $id) {
|
||||
id
|
||||
name
|
||||
createdAt
|
||||
commits {
|
||||
totalCount
|
||||
}
|
||||
branches {
|
||||
totalCount
|
||||
}
|
||||
activity(cursor: $cursor) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
actionType
|
||||
userId
|
||||
streamId
|
||||
resourceId
|
||||
resourceType
|
||||
time
|
||||
info
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables() {
|
||||
return {
|
||||
id: this.$route.params.streamId
|
||||
}
|
||||
},
|
||||
result({ data }) {
|
||||
this.groupSimilarActivities(data)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
groupSimilarActivities(data) {
|
||||
const groupedActivity = data.stream.activity.items.reduce(function (prev, curr) {
|
||||
//first item
|
||||
if (!prev.length) {
|
||||
prev.push([curr])
|
||||
return prev
|
||||
}
|
||||
const test = prev[prev.length - 1][0]
|
||||
let action = 'split' // split | combine | skip
|
||||
if (curr.actionType === test.actionType && curr.streamId === test.streamId) {
|
||||
if (curr.actionType.includes('stream_permissions')) {
|
||||
//skip multiple stream_permission actions on the same user, just pick the last!
|
||||
if (
|
||||
prev[prev.length - 1].some(
|
||||
(x) => x.info.targetUser === curr.info.targetUser
|
||||
)
|
||||
)
|
||||
action = 'skip'
|
||||
else action = 'combine'
|
||||
} //stream, branch, commit
|
||||
else if (
|
||||
curr.actionType.includes('_update') ||
|
||||
curr.actionType === 'commit_create'
|
||||
)
|
||||
action = 'combine'
|
||||
}
|
||||
if (action === 'combine') {
|
||||
prev[prev.length - 1].push(curr)
|
||||
} else if (action === 'split') {
|
||||
prev.push([curr])
|
||||
}
|
||||
return prev
|
||||
}, [])
|
||||
|
||||
this.groupedActivity = groupedActivity
|
||||
},
|
||||
infiniteHandler($state) {
|
||||
this.$apollo.queries.stream.fetchMore({
|
||||
variables: {
|
||||
cursor: this.stream.activity.cursor
|
||||
},
|
||||
// Transform the previous result with new data
|
||||
updateQuery: (previousResult, { fetchMoreResult }) => {
|
||||
const newItems = fetchMoreResult.stream.activity.items
|
||||
|
||||
//set vue-infinite state
|
||||
if (newItems.length === 0) $state.complete()
|
||||
else $state.loaded()
|
||||
|
||||
fetchMoreResult.stream.activity.items = [
|
||||
...previousResult.stream.activity.items,
|
||||
...newItems
|
||||
]
|
||||
|
||||
return fetchMoreResult
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="">
|
||||
<div>
|
||||
<branch-toolbar
|
||||
v-if="canRenderToolbarPortal && stream && stream.branch"
|
||||
:stream="stream"
|
||||
@@ -83,14 +83,14 @@
|
||||
|
||||
<no-data-placeholder
|
||||
v-if="
|
||||
!$apollo.loading && stream.branch && stream.branch.commits.totalCount === 0
|
||||
!isApolloLoading && stream.branch && stream.branch.commits.totalCount === 0
|
||||
"
|
||||
>
|
||||
<h2 class="space-grotesk">Branch "{{ stream.branch.name }}" has no commits.</h2>
|
||||
</no-data-placeholder>
|
||||
</v-row>
|
||||
<error-placeholder
|
||||
v-if="!$apollo.loading && (error || stream.branch === null)"
|
||||
v-if="!isApolloLoading && (error || stream.branch === null)"
|
||||
error-type="404"
|
||||
>
|
||||
<h2>{{ error || `Branch "${$route.params.branchName}" does not exist.` }}</h2>
|
||||
@@ -98,12 +98,15 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import branchQuery from '@/graphql/branch.gql'
|
||||
import {
|
||||
STANDARD_PORTAL_KEYS,
|
||||
buildPortalStateMixin
|
||||
} from '@/main/utils/portalStateManager'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from '@/main/lib/core/composables/router'
|
||||
|
||||
export default {
|
||||
name: 'TheBranch',
|
||||
@@ -117,6 +120,31 @@ export default {
|
||||
CommitPreviewCard: () => import('@/main/components/common/CommitPreviewCard')
|
||||
},
|
||||
mixins: [buildPortalStateMixin([STANDARD_PORTAL_KEYS.Toolbar], 'stream-branch', 1)],
|
||||
setup() {
|
||||
const route = useRoute()
|
||||
const {
|
||||
result,
|
||||
fetchMore: streamFetchMore,
|
||||
refetch: streamRefetch,
|
||||
loading: streamLoading
|
||||
} = useQuery(
|
||||
branchQuery,
|
||||
() => ({
|
||||
streamId: route.params.streamId,
|
||||
branchName: (route.params.branchName || '').toLowerCase(),
|
||||
cursor: null
|
||||
}),
|
||||
{ fetchPolicy: 'network-only' }
|
||||
)
|
||||
const stream = computed(() => result.value?.stream)
|
||||
|
||||
return {
|
||||
stream,
|
||||
streamFetchMore,
|
||||
streamRefetch,
|
||||
streamLoading
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
branchEditDialog: false,
|
||||
@@ -125,16 +153,6 @@ export default {
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
stream: {
|
||||
query: branchQuery,
|
||||
variables() {
|
||||
return {
|
||||
streamId: this.streamId,
|
||||
branchName: this.$route.params.branchName.toLowerCase()
|
||||
}
|
||||
},
|
||||
fetchPolicy: 'network-only'
|
||||
},
|
||||
$subscribe: {
|
||||
commitCreated: {
|
||||
query: gql`
|
||||
@@ -148,7 +166,7 @@ export default {
|
||||
}
|
||||
},
|
||||
result() {
|
||||
this.$apollo.queries.stream.refetch()
|
||||
this.streamRefetch()
|
||||
},
|
||||
error(err) {
|
||||
this.$eventHub.$emit('notification', {
|
||||
@@ -171,7 +189,7 @@ export default {
|
||||
}
|
||||
},
|
||||
result() {
|
||||
this.$apollo.queries.stream.refetch()
|
||||
this.streamRefetch()
|
||||
},
|
||||
error(err) {
|
||||
this.$eventHub.$emit('notification', {
|
||||
@@ -185,6 +203,9 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isApolloLoading() {
|
||||
return this.$apollo.loading || this.streamLoading
|
||||
},
|
||||
loggedInUserId() {
|
||||
return localStorage.getItem('uuid')
|
||||
},
|
||||
@@ -192,36 +213,22 @@ export default {
|
||||
return this.$route.params.streamId
|
||||
},
|
||||
latestCommitObjectUrl() {
|
||||
if (
|
||||
this.stream &&
|
||||
this.stream.branch &&
|
||||
this.stream.branch.commits.items &&
|
||||
this.stream.branch.commits.items.length > 0
|
||||
)
|
||||
if ((this.stream?.branch?.commits?.items || []).length > 0)
|
||||
return `${window.location.origin}/streams/${this.stream.id}/objects/${this.stream.branch.commits.items[0].referencedObject}`
|
||||
else return null
|
||||
},
|
||||
latestCommit() {
|
||||
if (
|
||||
this.stream.branch.commits.items &&
|
||||
this.stream.branch.commits.items.length > 0
|
||||
)
|
||||
if ((this.stream?.branch?.commits?.items || []).length > 0)
|
||||
return this.stream.branch.commits.items[0]
|
||||
else return null
|
||||
},
|
||||
allPreviousCommits() {
|
||||
if (
|
||||
this.stream.branch.commits.items &&
|
||||
this.stream.branch.commits.items.length > 0
|
||||
)
|
||||
if ((this.stream?.branch?.commits?.items || []).length > 0)
|
||||
return this.stream.branch.commits.items.slice(1)
|
||||
else return null
|
||||
},
|
||||
allCommits() {
|
||||
if (
|
||||
this.stream.branch.commits.items &&
|
||||
this.stream.branch.commits.items.length > 0
|
||||
)
|
||||
if ((this.stream?.branch?.commits?.items || []).length > 0)
|
||||
return this.stream.branch.commits.items
|
||||
else return []
|
||||
}
|
||||
@@ -231,44 +238,21 @@ export default {
|
||||
this.$router.push(`/streams/${this.$route.params.streamId}/globals`)
|
||||
},
|
||||
methods: {
|
||||
infiniteHandler($state) {
|
||||
this.$apollo.queries.stream.fetchMore({
|
||||
async infiniteHandler($state) {
|
||||
const result = await this.streamFetchMore({
|
||||
variables: {
|
||||
cursor: this.stream.branch.commits.cursor
|
||||
},
|
||||
// Transform the previous result with new data
|
||||
updateQuery: (previousResult, { fetchMoreResult }) => {
|
||||
const newItems = fetchMoreResult.stream.branch.commits.items
|
||||
if (newItems.length === 0) $state.complete()
|
||||
else $state.loaded()
|
||||
|
||||
const allItems = [...previousResult.stream.branch.commits.items]
|
||||
for (const commit of newItems) {
|
||||
if (allItems.findIndex((c) => c.id === commit.id) === -1)
|
||||
allItems.push(commit)
|
||||
}
|
||||
|
||||
return {
|
||||
stream: {
|
||||
__typename: previousResult.stream.__typename,
|
||||
name: previousResult.stream.name,
|
||||
id: previousResult.stream.id,
|
||||
branch: {
|
||||
id: fetchMoreResult.stream.branch.id,
|
||||
name: fetchMoreResult.stream.branch.name,
|
||||
description: fetchMoreResult.stream.branch.description,
|
||||
__typename: previousResult.stream.branch.__typename,
|
||||
commits: {
|
||||
__typename: previousResult.stream.branch.commits.__typename,
|
||||
cursor: fetchMoreResult.stream.branch.commits.cursor,
|
||||
totalCount: fetchMoreResult.stream.branch.commits.totalCount,
|
||||
items: allItems
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
streamId: this.streamId,
|
||||
branchName: this.$route.params.branchName.toLowerCase(),
|
||||
cursor: this.stream?.branch?.commits?.cursor
|
||||
}
|
||||
})
|
||||
|
||||
const newItems = result?.data?.stream?.branch?.commits?.items || []
|
||||
if (!newItems.length) {
|
||||
$state.complete()
|
||||
} else {
|
||||
$state.loaded()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
</v-container>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import { fullServerInfoQuery } from '@/graphql/server'
|
||||
import {
|
||||
STANDARD_PORTAL_KEYS,
|
||||
@@ -135,16 +135,16 @@ import InviteDialog from '@/main/dialogs/InviteDialog.vue'
|
||||
import { userSearchQuery } from '@/graphql/user'
|
||||
import StreamRoleCollaborators from '@/main/components/stream/collaborators/StreamRoleCollaborators.vue'
|
||||
import {
|
||||
cancelStreamInviteMutation,
|
||||
useStreamWithCollaboratorsQuery,
|
||||
StreamWithCollaboratorsDocument,
|
||||
updateStreamPermissionMutation
|
||||
CancelStreamInviteDocument,
|
||||
UpdateStreamPermissionDocument
|
||||
} from '@/graphql/generated/graphql'
|
||||
import { StreamEvents } from '@/main/lib/core/helpers/eventHubHelper'
|
||||
import { Roles } from '@/helpers/mainConstants'
|
||||
import LeaveStreamPanel from '@/main/components/stream/collaborators/LeaveStreamPanel.vue'
|
||||
import { IsLoggedInMixin } from '@/main/lib/core/mixins/isLoggedInMixin'
|
||||
import { vueWithMixins } from '@/helpers/typeHelpers'
|
||||
import { convertThrowIntoFetchResult } from '@/main/lib/common/apollo/helpers/apolloOperationHelper'
|
||||
|
||||
export default vueWithMixins(IsLoggedInMixin).extend({
|
||||
// @vue/component
|
||||
@@ -171,7 +171,8 @@ export default vueWithMixins(IsLoggedInMixin).extend({
|
||||
inviteDialogUser: null
|
||||
}),
|
||||
apollo: {
|
||||
stream: useStreamWithCollaboratorsQuery({
|
||||
stream: {
|
||||
query: StreamWithCollaboratorsDocument,
|
||||
// Custom error policy so that a failing pendingCollaborators resolver (due to access rights)
|
||||
// doesn't kill the entire query
|
||||
errorPolicy: 'all',
|
||||
@@ -181,7 +182,7 @@ export default vueWithMixins(IsLoggedInMixin).extend({
|
||||
id: this.streamId
|
||||
}
|
||||
}
|
||||
}),
|
||||
},
|
||||
userSearch: {
|
||||
query: userSearchQuery,
|
||||
variables() {
|
||||
@@ -277,40 +278,43 @@ export default vueWithMixins(IsLoggedInMixin).extend({
|
||||
const { streamId } = this
|
||||
|
||||
this.loading = true
|
||||
const { data, errors } = await cancelStreamInviteMutation(this, {
|
||||
variables: {
|
||||
streamId,
|
||||
inviteId
|
||||
},
|
||||
update(store, result) {
|
||||
if (!result.data?.streamInviteCancel) return
|
||||
const { data, errors } = await this.$apollo
|
||||
.mutate({
|
||||
mutation: CancelStreamInviteDocument,
|
||||
variables: {
|
||||
streamId,
|
||||
inviteId
|
||||
},
|
||||
update(store, result) {
|
||||
if (!result.data?.streamInviteCancel) return
|
||||
|
||||
// Read current stream info
|
||||
const cachedData = store.readQuery({
|
||||
query: StreamWithCollaboratorsDocument,
|
||||
variables: { id: streamId }
|
||||
})
|
||||
const pendingCollaborators = cachedData?.stream?.pendingCollaborators
|
||||
if (!pendingCollaborators) return
|
||||
// Read current stream info
|
||||
const cachedData = store.readQuery({
|
||||
query: StreamWithCollaboratorsDocument,
|
||||
variables: { id: streamId }
|
||||
})
|
||||
const pendingCollaborators = cachedData?.stream?.pendingCollaborators
|
||||
if (!pendingCollaborators) return
|
||||
|
||||
// Remove collaborator
|
||||
const newPendingCollaborators = pendingCollaborators.filter(
|
||||
(c) => c.inviteId !== inviteId
|
||||
)
|
||||
const newData = {
|
||||
stream: {
|
||||
...cachedData.stream,
|
||||
pendingCollaborators: newPendingCollaborators
|
||||
// Remove collaborator
|
||||
const newPendingCollaborators = pendingCollaborators.filter(
|
||||
(c) => c.inviteId !== inviteId
|
||||
)
|
||||
const newData = {
|
||||
stream: {
|
||||
...cachedData.stream,
|
||||
pendingCollaborators: newPendingCollaborators
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
store.writeQuery({
|
||||
query: StreamWithCollaboratorsDocument,
|
||||
variables: { id: streamId },
|
||||
data: newData
|
||||
})
|
||||
}
|
||||
})
|
||||
store.writeQuery({
|
||||
query: StreamWithCollaboratorsDocument,
|
||||
variables: { id: streamId },
|
||||
data: newData
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(convertThrowIntoFetchResult)
|
||||
|
||||
if (!data?.streamInviteCancel) {
|
||||
const gqlError = errors?.[0]
|
||||
@@ -378,15 +382,18 @@ export default vueWithMixins(IsLoggedInMixin).extend({
|
||||
},
|
||||
async updateUserPermission(userId, role) {
|
||||
this.$mixpanel.track('Permission Action', { type: 'action', name: 'update' })
|
||||
const { data, errors } = await updateStreamPermissionMutation(this, {
|
||||
variables: {
|
||||
params: {
|
||||
streamId: this.stream.id,
|
||||
userId,
|
||||
role
|
||||
const { data, errors } = await this.$apollo
|
||||
.mutate({
|
||||
mutation: UpdateStreamPermissionDocument,
|
||||
variables: {
|
||||
params: {
|
||||
streamId: this.stream.id,
|
||||
userId,
|
||||
role
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(convertThrowIntoFetchResult)
|
||||
|
||||
if (!data?.streamUpdatePermission) {
|
||||
const errMsg = errors?.[0]?.message || 'An unexpected issue occurred'
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import {
|
||||
STANDARD_PORTAL_KEYS,
|
||||
buildPortalStateMixin
|
||||
@@ -171,7 +171,7 @@ export default {
|
||||
variables() {
|
||||
return { streamId: this.$route.params.streamId }
|
||||
},
|
||||
updateQuery(prevResult, { subscriptionData }) {
|
||||
updateQuery(_, { subscriptionData }) {
|
||||
const { comment } = subscriptionData.data.commentActivity
|
||||
if (this.localComments.findIndex((lc) => comment.id === lc.id) === -1) {
|
||||
this.localComments.push(comment)
|
||||
|
||||
@@ -147,7 +147,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import branchQuery from '@/graphql/branch.gql'
|
||||
import {
|
||||
STANDARD_PORTAL_KEYS,
|
||||
|
||||
@@ -174,7 +174,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import {
|
||||
STANDARD_PORTAL_KEYS,
|
||||
buildPortalStateMixin
|
||||
|
||||
@@ -37,27 +37,27 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import StreamInviteBanner from '@/main/components/stream/StreamInviteBanner.vue'
|
||||
import { StreamEvents } from '@/main/lib/core/helpers/eventHubHelper'
|
||||
import Vue from 'vue'
|
||||
import { Nullable, MaybeFalsy } from '@/helpers/typeHelpers'
|
||||
import { ApolloError } from 'vue-apollo-smart-ops'
|
||||
import {
|
||||
useStreamInviteQuery,
|
||||
useMainUserDataQuery,
|
||||
StreamInviteDocument,
|
||||
MainUserDataDocument,
|
||||
MainUserDataQuery,
|
||||
StreamQuery,
|
||||
StreamDocument,
|
||||
StreamQueryVariables
|
||||
StreamQueryVariables,
|
||||
StreamDocument
|
||||
} from '@/graphql/generated/graphql'
|
||||
import type { ApolloQueryResult } from 'apollo-client'
|
||||
import type { ApolloQueryResult, ApolloError } from '@apollo/client/core'
|
||||
import type { Get } from 'type-fest'
|
||||
import StreamInvitePlaceholder from '@/main/components/stream/StreamInvitePlaceholder.vue'
|
||||
import { StreamInviteType } from '@/main/lib/stream/mixins/streamInviteMixin'
|
||||
import { getInviteTokenFromRoute } from '@/main/lib/auth/services/authService'
|
||||
|
||||
// Cause of a limitation of vue-apollo-smart-ops, this needs to be duplicated
|
||||
// Cause of a limitation of Vue Apollo Options API TS types, this needs to be duplicated
|
||||
// (the better option is to just use the Composition API)
|
||||
type VueThis = Vue & {
|
||||
streamId: string
|
||||
inviteToken: Nullable<string>
|
||||
@@ -118,14 +118,15 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
streamInvite: useStreamInviteQuery<VueThis>({
|
||||
variables() {
|
||||
streamInvite: {
|
||||
query: StreamInviteDocument,
|
||||
variables(this: VueThis) {
|
||||
return {
|
||||
streamId: this.streamId,
|
||||
token: this.inviteToken
|
||||
}
|
||||
}
|
||||
}),
|
||||
},
|
||||
stream: {
|
||||
query: StreamDocument,
|
||||
variables(this: VueThis): StreamQueryVariables {
|
||||
@@ -142,7 +143,9 @@ export default Vue.extend({
|
||||
}
|
||||
}
|
||||
},
|
||||
user: useMainUserDataQuery(),
|
||||
user: {
|
||||
query: MainUserDataDocument
|
||||
},
|
||||
$subscribe: {
|
||||
branchCreated: {
|
||||
query: gql`
|
||||
|
||||
@@ -130,7 +130,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import { COMMENT_FULL_INFO_FRAGMENT } from '@/graphql/comments'
|
||||
|
||||
export default {
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import {
|
||||
STANDARD_PORTAL_KEYS,
|
||||
buildPortalStateMixin
|
||||
|
||||
@@ -108,8 +108,8 @@
|
||||
style="cursor: default"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
<v-icon :color="wh.statusIcon.color" class="pt-2">
|
||||
{{ wh.statusIcon.icon }}
|
||||
<v-icon :color="getStatusIcon(wh).color" class="pt-2">
|
||||
{{ getStatusIcon(wh).icon }}
|
||||
</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
@@ -291,12 +291,6 @@ export default {
|
||||
streamId: this.$route.params.streamId
|
||||
}
|
||||
},
|
||||
update(data) {
|
||||
data.stream.webhooks.items.forEach((wh) => {
|
||||
wh.statusIcon = this.getStatusIcon(wh)
|
||||
})
|
||||
return data.stream
|
||||
},
|
||||
error(err) {
|
||||
if (err.message) this.error = err.message.replace('GraphQL error: ', '')
|
||||
else this.error = err
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import ListItemStream from '@/main/components/user/ListItemStream'
|
||||
import gql from 'graphql-tag'
|
||||
import { gql } from '@apollo/client/core'
|
||||
import {
|
||||
STANDARD_PORTAL_KEYS,
|
||||
buildPortalStateMixin
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user