From 69a10f7f08fbf57d293fee4cd2d8917588476c86 Mon Sep 17 00:00:00 2001 From: Fabians Date: Wed, 27 Jul 2022 16:42:08 +0300 Subject: [PATCH] feat(frontend): comments in viewer embed + refactored frontend viewer foundations --- packages/frontend/README.md | 2 - packages/frontend/codegen.yml | 5 +- .../nginx/templates/nginx.conf.template | 6 - packages/frontend/package.json | 7 +- packages/frontend/public/embedApp.html | 147 ----- packages/frontend/src/bootstrapper.ts | 15 +- packages/frontend/src/config/apolloConfig.ts | 173 ++--- packages/frontend/src/embed/EmbedApp.vue | 9 - packages/frontend/src/embed/EmbedViewer.vue | 269 -------- .../frontend/src/embed/EmbedViewerCore.vue | 234 ------- packages/frontend/src/embed/embedApp.js | 23 - packages/frontend/src/embed/embedRouter.js | 21 - packages/frontend/src/embed/embedVuetify.js | 42 -- packages/frontend/src/embed/speckleUtils.js | 70 -- .../frontend/src/graphql/generated/graphql.ts | 75 +++ .../src/graphql/local-only-schema/schema.gql | 30 + packages/frontend/src/graphql/streams.js | 38 ++ packages/frontend/src/helpers/typeHelpers.ts | 8 + packages/frontend/src/main/app.js | 7 +- .../auth/UserAvatarAuthoriseApp.vue | 7 +- .../components/comments/CommentListItem.vue | 14 +- .../CommentThreadAttachmentPreview.vue | 7 +- .../comments/CommentThreadReply.vue | 8 +- .../comments/CommentThreadViewer.vue | 412 ++++++------ .../comments/CommentsViewerNavbar.vue | 26 +- .../components/common/CommitPreviewCard.vue | 4 +- .../main/components/common/PreviewImage.vue | 7 +- .../main/components/common/SpeckleViewer.vue | 52 +- .../src/main/components/common/UserAvatar.vue | 13 +- .../common/utility/PrioritizedPortal.vue | 54 ++ .../src/main/components/feed/FeedTimeline.vue | 3 +- .../onboarding/GettingStartedSteps.vue | 6 +- .../stream/uploads/FileProcessingItem.vue | 3 +- .../stream/uploads/FileUploadItem.vue | 4 +- .../user/EmailVerificationBanner.vue | 5 +- .../src/main/components/user/UserInfoCard.vue | 4 +- .../main/components/viewer/CanonicalViews.vue | 17 +- .../components/viewer/CommentAddOverlay.vue | 75 ++- .../components/viewer/CommentsOverlay.vue | 86 ++- .../components/viewer/CommitInfoResource.vue | 42 +- .../viewer/CommitObjectViewerScope.vue | 35 + .../viewer/FilterCategoryActive.vue | 46 +- .../components/viewer/FilterNumericActive.vue | 28 +- .../components/viewer/ObjectInfoResource.vue | 44 +- .../components/viewer/ObjectPropertiesRow.vue | 40 +- .../components/viewer/ObjectSelection.vue | 27 +- .../main/components/viewer/ResourceGroup.vue | 15 +- .../main/components/viewer/ViewerBubbles.vue | 88 ++- .../main/components/viewer/ViewerControls.vue | 30 +- .../main/components/viewer/ViewerFilters.vue | 23 +- .../main/components/viewer/ViewsDisplay.vue | 7 +- .../viewer/dialogs/StreamOverlayViewer.vue | 3 +- .../embed/EmbeddedCommitObjectViewer.vue | 143 +++++ .../viewer/viewerFrontendHelpers.js | 16 - .../frontend/src/main/dialogs/NewStream.vue | 3 +- .../frontend/src/main/layouts/TheBasic.vue | 25 + .../frontend/src/main/layouts/TheMain.vue | 97 +-- .../src/main/lib/core/composables/auth.ts | 12 + .../src/main/lib/core/composables/core.ts | 12 + .../src/main/lib/core/composables/dom.ts | 106 +++ .../src/main/lib/core/composables/tracking.ts | 13 + .../commit-object-viewer/composables/embed.ts | 16 + .../commit-object-viewer/stateManager.ts | 604 ++++++++++++++++++ .../lib/viewer/core/composables/viewer.ts | 51 ++ .../lib/viewer/core/helpers/cameraHelper.ts | 24 + .../src/main/pages/auth/AuthorizeApp.vue | 5 +- .../frontend/src/main/pages/auth/TheLogin.vue | 3 +- .../src/main/pages/auth/TheRegistration.vue | 3 +- .../main/pages/stream/CommitObjectViewer.vue | 493 ++++++++------ .../src/main/pages/stream/TheBranch.vue | 3 +- .../main/pages/stream/TheCollaborators.vue | 3 +- .../src/main/pages/stream/TheComments.vue | 4 +- .../src/main/pages/stream/TheEmbed.vue | 318 +++++++++ .../src/main/pages/stream/TheStreamHome.vue | 5 +- .../src/main/pages/user/TheProfileUser.vue | 3 +- packages/frontend/src/main/router/index.js | 32 +- packages/frontend/src/main/store/index.js | 283 -------- .../src/main/utils/themeStateManager.js | 19 +- packages/frontend/src/plugins/authHelpers.js | 49 +- .../src/plugins/resourceIdentifier.js | 3 - packages/frontend/src/sass/variables.scss | 17 + .../src/type-augmentations/shims-gql.ts | 6 + .../frontend/src/type-augmentations/vue.d.ts | 2 +- .../src/type-augmentations/window.d.ts | 4 +- packages/frontend/vue.config.js | 15 +- packages/viewer/src/modules/Viewer.ts | 9 +- yarn.lock | 72 ++- 87 files changed, 2894 insertions(+), 1995 deletions(-) delete mode 100644 packages/frontend/public/embedApp.html delete mode 100644 packages/frontend/src/embed/EmbedApp.vue delete mode 100644 packages/frontend/src/embed/EmbedViewer.vue delete mode 100644 packages/frontend/src/embed/EmbedViewerCore.vue delete mode 100644 packages/frontend/src/embed/embedApp.js delete mode 100644 packages/frontend/src/embed/embedRouter.js delete mode 100644 packages/frontend/src/embed/embedVuetify.js delete mode 100644 packages/frontend/src/embed/speckleUtils.js create mode 100644 packages/frontend/src/graphql/local-only-schema/schema.gql create mode 100644 packages/frontend/src/main/components/common/utility/PrioritizedPortal.vue create mode 100644 packages/frontend/src/main/components/viewer/CommitObjectViewerScope.vue create mode 100644 packages/frontend/src/main/components/viewer/embed/EmbeddedCommitObjectViewer.vue delete mode 100644 packages/frontend/src/main/components/viewer/viewerFrontendHelpers.js create mode 100644 packages/frontend/src/main/layouts/TheBasic.vue create mode 100644 packages/frontend/src/main/lib/core/composables/auth.ts create mode 100644 packages/frontend/src/main/lib/core/composables/core.ts create mode 100644 packages/frontend/src/main/lib/core/composables/dom.ts create mode 100644 packages/frontend/src/main/lib/core/composables/tracking.ts create mode 100644 packages/frontend/src/main/lib/viewer/commit-object-viewer/composables/embed.ts create mode 100644 packages/frontend/src/main/lib/viewer/commit-object-viewer/stateManager.ts create mode 100644 packages/frontend/src/main/lib/viewer/core/composables/viewer.ts create mode 100644 packages/frontend/src/main/lib/viewer/core/helpers/cameraHelper.ts create mode 100644 packages/frontend/src/main/pages/stream/TheEmbed.vue delete mode 100644 packages/frontend/src/main/store/index.js delete mode 100644 packages/frontend/src/plugins/resourceIdentifier.js create mode 100644 packages/frontend/src/type-augmentations/shims-gql.ts diff --git a/packages/frontend/README.md b/packages/frontend/README.md index 302a234b1..90ee651ec 100644 --- a/packages/frontend/README.md +++ b/packages/frontend/README.md @@ -6,8 +6,6 @@ We're working to stabilize the 2.0 API, and until then there will be breaking changes. -Note that this package contains two vue apps, the main frontend (located under @/main), and the viewer embed app (@/embed). - Notes: - In **development** mode, the Speckle Server will proxy the frontend from `localhost:3000` to `localhost:8080`. If you don't see anything, ensure you've run `yarn serve` in the frontend package. diff --git a/packages/frontend/codegen.yml b/packages/frontend/codegen.yml index a35b2461b..c27b49216 100644 --- a/packages/frontend/codegen.yml +++ b/packages/frontend/codegen.yml @@ -1,5 +1,7 @@ overwrite: true -schema: 'http://localhost:3000/graphql' +schema: + - 'http://localhost:3000/graphql' + - 'src/graphql/local-only-schema/schema.gql' documents: - 'src/graphql/**/*.gql' - 'src/**/*.{ts,tsx,js,jsx,vue}' @@ -13,3 +15,4 @@ generates: config: scalars: JSONObject: Record + UnknownLocalObject: unknown diff --git a/packages/frontend/nginx/templates/nginx.conf.template b/packages/frontend/nginx/templates/nginx.conf.template index 4cd2981e9..2f72faac7 100644 --- a/packages/frontend/nginx/templates/nginx.conf.template +++ b/packages/frontend/nginx/templates/nginx.conf.template @@ -82,12 +82,6 @@ server { expires 1y; } - location /embed { - default_type text/html; - alias /usr/share/nginx/html/embedApp.html; - add_header Cache-Control "no-store, no-cache, must-revalidate"; - } - location ~ ^/streams/.* { default_type text/html; content_by_lua_block { diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 6ed2bef5b..54aaee778 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -3,7 +3,7 @@ "version": "2.5.4", "private": true, "scripts": { - "serve": "ws -p 8080 -d dist -r '/embed(.*) -> /embedApp.html' '/([a-zA-Z0-9-_/]*)(\\?.*)? -> /app.html' ", + "serve": "ws -p 8080 -d dist -r '/([a-zA-Z0-9-_/]*)(\\?.*)? -> /app.html' ", "build": "vue-cli-service build --mode production --silent", "lint": "eslint . --ext .js,.ts,.vue,.tsx,.jsx", "lint:vue": "vti diagnostics", @@ -38,6 +38,7 @@ "@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", + "@vueuse/core": "^9.0.0", "apexcharts": "^3.33.1", "apollo-upload-client": "^17.0.0", "dompurify": "^2.3.6", @@ -61,8 +62,7 @@ "vue2-perfect-scrollbar": "^1.5.2", "vuedraggable": "^2.24.3", "vuetify": "^2.3.21", - "vuetify-image-input": "^19.1.0", - "vuex": "^3.6.2" + "vuetify-image-input": "^19.1.0" }, "devDependencies": { "@graphql-codegen/cli": "2.6.2", @@ -84,7 +84,6 @@ "@vue/cli-plugin-babel": "~4.3.1", "@vue/cli-plugin-router": "~4.3.1", "@vue/cli-plugin-typescript": "~4.5.19", - "@vue/cli-plugin-vuex": "~4.3.1", "@vue/cli-service": "~4.3.1", "@vue/eslint-config-typescript": "^11.0.0", "babel-eslint": "^10.1.0", diff --git a/packages/frontend/public/embedApp.html b/packages/frontend/public/embedApp.html deleted file mode 100644 index 6edbb209c..000000000 --- a/packages/frontend/public/embedApp.html +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - - - <%= htmlWebpackPlugin.options.title %> - - - - - - - -
-
- -
-
- - - diff --git a/packages/frontend/src/bootstrapper.ts b/packages/frontend/src/bootstrapper.ts index ab301950c..8429ccc4f 100644 --- a/packages/frontend/src/bootstrapper.ts +++ b/packages/frontend/src/bootstrapper.ts @@ -5,25 +5,13 @@ import PortalVue from 'portal-vue' import { formatNumber } from '@/plugins/formatNumber' /** - * Global bootstrapping that is used in all of the frontend apps (main/embed) + * Global bootstrapping for the frontend app */ // Filter to turn any number into a nice string like '10k', '5.5m' // Accepts 'max' parameter to set it's formatting while being animated Vue.filter('prettynum', formatNumber) -// Async HistogramSlider load -// TODO: Instead of bundling it globally on all pages, only import it where needed -Vue.component('HistogramSlider', async () => { - await import( - /* webpackChunkName: "vue-histogram-slider" */ 'vue-histogram-slider/dist/histogram-slider.css' - ) - const component = await import( - /* webpackChunkName: "vue-histogram-slider" */ 'vue-histogram-slider' - ) - return component -}) - // process.env.NODE_ENV is injected by Webpack Vue.config.productionTip = process.env.NODE_ENV === 'development' @@ -33,6 +21,7 @@ Vue.use(VTooltip, { defaultHtml: false }) +// In highly restrictive sandboxed environments mixpanel init might fail due to document.cookie access Vue.use(VueMixpanel, { token: 'acd87c5a50b56df91a795e999812a3a4', config: { diff --git a/packages/frontend/src/config/apolloConfig.ts b/packages/frontend/src/config/apolloConfig.ts index f3442eb17..5bb5282db 100644 --- a/packages/frontend/src/config/apolloConfig.ts +++ b/packages/frontend/src/config/apolloConfig.ts @@ -1,6 +1,12 @@ import Vue from 'vue' import { createApolloProvider, ApolloProvider } from '@vue/apollo-option' -import { ApolloClient, ApolloLink, InMemoryCache, split } from '@apollo/client/core' +import { + ApolloClient, + ApolloLink, + InMemoryCache, + split, + TypePolicies +} from '@apollo/client/core' import { setContext } from '@apollo/client/link/context' import { WebSocketLink } from '@apollo/client/link/ws' import { SubscriptionClient } from 'subscriptions-transport-ws' @@ -13,6 +19,8 @@ import { buildAbstractCollectionMergeFunction, incomingOverwritesExistingMergeFunction } from '@/main/lib/core/helpers/apolloSetupHelper' +import { merge } from 'lodash' +import { statePolicies as commitObjectViewerStatePolicies } from '@/main/lib/viewer/commit-object-viewer/stateManager' // Name of the localStorage item const AUTH_TOKEN = LocalStorageKeys.AuthToken @@ -38,88 +46,101 @@ function createCache(): InMemoryCache { * * 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 }) - } + typePolicies: merge( + { + 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 } + }, + stream: { + read(original, { args, toReference }) { + if (args?.id) { + return toReference({ __typename: 'Stream', id: args.id }) + } - return original + return original + } + }, + streams: { + keyArgs: false, + merge: buildAbstractCollectionMergeFunction('StreamCollection', { + checkIdentity: true + }) } - }, - streams: { - keyArgs: false, - merge: buildAbstractCollectionMergeFunction('StreamCollection') } + }, + 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 } }, - User: { - fields: { - timeline: { - keyArgs: false, - merge: buildAbstractCollectionMergeFunction('ActivityCollection') - }, - commits: { - keyArgs: false, - merge: buildAbstractCollectionMergeFunction('CommitCollectionUser') - }, - favoriteStreams: { - keyArgs: false, - merge: buildAbstractCollectionMergeFunction('StreamCollection') - } - } - }, - Stream: { - fields: { - activity: { - keyArgs: false, - merge: buildAbstractCollectionMergeFunction('ActivityCollection') - }, - commits: { - keyArgs: false, - merge: buildAbstractCollectionMergeFunction('CommitCollection') - }, - pendingCollaborators: { - merge: incomingOverwritesExistingMergeFunction - } - } - }, - Branch: { - fields: { - commits: { - keyArgs: false, - merge: buildAbstractCollectionMergeFunction('CommitCollection') - } - } - }, - BranchCollection: { - merge: true - }, - ServerStats: { - merge: true - }, - WebhookEventCollection: { - merge: true - }, - ServerInfo: { - merge: true - }, - CommentThreadActivityMessage: { - merge: true - } - } + commitObjectViewerStatePolicies + ) }) } diff --git a/packages/frontend/src/embed/EmbedApp.vue b/packages/frontend/src/embed/EmbedApp.vue deleted file mode 100644 index 733365cf9..000000000 --- a/packages/frontend/src/embed/EmbedApp.vue +++ /dev/null @@ -1,9 +0,0 @@ - - diff --git a/packages/frontend/src/embed/EmbedViewer.vue b/packages/frontend/src/embed/EmbedViewer.vue deleted file mode 100644 index 0c2d6306f..000000000 --- a/packages/frontend/src/embed/EmbedViewer.vue +++ /dev/null @@ -1,269 +0,0 @@ - - - - - diff --git a/packages/frontend/src/embed/EmbedViewerCore.vue b/packages/frontend/src/embed/EmbedViewerCore.vue deleted file mode 100644 index a378f4537..000000000 --- a/packages/frontend/src/embed/EmbedViewerCore.vue +++ /dev/null @@ -1,234 +0,0 @@ - - - diff --git a/packages/frontend/src/embed/embedApp.js b/packages/frontend/src/embed/embedApp.js deleted file mode 100644 index f1a363f2a..000000000 --- a/packages/frontend/src/embed/embedApp.js +++ /dev/null @@ -1,23 +0,0 @@ -import '@/bootstrapper' -import Vue from 'vue' - -import App from './EmbedApp.vue' -import vuetify from './embedVuetify' -import router from './embedRouter' - -import '@/plugins/helpers' -import store from '@/main/store' -import * as MixpanelManager from '@/mixpanelManager' - -// Init mixpanel -MixpanelManager.initialize({ - hostApp: 'web-embed', - hostAppDisplayName: 'Embed App' -}) - -new Vue({ - router, - vuetify, - store, - render: (h) => h(App) -}).$mount('#app') diff --git a/packages/frontend/src/embed/embedRouter.js b/packages/frontend/src/embed/embedRouter.js deleted file mode 100644 index 68c082c33..000000000 --- a/packages/frontend/src/embed/embedRouter.js +++ /dev/null @@ -1,21 +0,0 @@ -import Vue from 'vue' -import VueRouter from 'vue-router' - -Vue.use(VueRouter) - -const routes = [ - { - path: '*', - meta: { - title: 'Embed View | Speckle' - }, - component: () => import('@/embed/EmbedViewer.vue') - } -] - -const router = new VueRouter({ - mode: 'history', - routes -}) - -export default router diff --git a/packages/frontend/src/embed/embedVuetify.js b/packages/frontend/src/embed/embedVuetify.js deleted file mode 100644 index ca37c3d7b..000000000 --- a/packages/frontend/src/embed/embedVuetify.js +++ /dev/null @@ -1,42 +0,0 @@ -import '@mdi/font/css/materialdesignicons.css' -import * as ThemeStateManager from '@/main/utils/themeStateManager' -import Vue from 'vue' -import Vuetify from 'vuetify/lib' - -Vue.use(Vuetify) - -ThemeStateManager.initialize() -const isDarkMode = ThemeStateManager.isDarkTheme() - -export default new Vuetify({ - icons: { - iconfont: 'mdi' - }, - theme: { - options: { customProperties: true }, - dark: isDarkMode, - themes: { - light: { - primary: '#047EFB', //blue - secondary: '#7BBCFF', //light blue - accent: '#FCF25E', //yellow - error: '#FF5555', //red - warning: '#FF9100', //orange - info: '#313BCF', //dark blue - success: '#4caf50', - background: '#eeeeee', - text: '#FFFFFF' - }, - dark: { - primary: '#047EFB', //blue - secondary: '#7BBCFF', //light blue - accent: '#FCF25E', //yellow - error: '#FF5555', //red - warning: '#FF9100', //orange - info: '#313BCF', //dark blue - success: '#4caf50', - background: '#3a3b3c' - } - } - } -}) diff --git a/packages/frontend/src/embed/speckleUtils.js b/packages/frontend/src/embed/speckleUtils.js deleted file mode 100644 index 0f0de1bde..000000000 --- a/packages/frontend/src/embed/speckleUtils.js +++ /dev/null @@ -1,70 +0,0 @@ -const SERVER_URL = window.location.origin - -// Unauthorised fetch, without token to prevent use of localStorage or exposing elsewhere. -async function speckleFetch(query, variables) { - const res = await fetch(`${SERVER_URL}/graphql`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - query, - variables - }) - }) - return await res.json() -} - -export const getServerInfo = () => speckleFetch(serverInfoQuery) - -export const getStreamObj = (id) => speckleFetch(streamQuery, { id }) - -export const getBranchObj = (id, branch) => speckleFetch(branchQuery, { id, branch }) - -export const getCommitObj = (id, commit) => speckleFetch(commitQuery, { id, commit }) - -const serverInfoQuery = ` - query ServerInfo { - serverInfo { - name - } - } -` - -const streamQuery = ` - query Stream($id: String!) { - stream(id: $id) { - commits(limit: 1) { - totalCount - items { - referencedObject - } - } - } - } -` - -const branchQuery = ` - query Stream($id: String!, $branch: String!) { - stream(id: $id) { - branch(name: $branch) { - commits(limit: 1) { - totalCount - items { - referencedObject - } - } - } - } - } -` - -const commitQuery = ` - query Stream($id: String!, $commit: String!) { - stream(id: $id) { - commit(id: $commit) { - referencedObject - } - } - } -` diff --git a/packages/frontend/src/graphql/generated/graphql.ts b/packages/frontend/src/graphql/generated/graphql.ts index f2dbe9228..76c1c1c59 100644 --- a/packages/frontend/src/graphql/generated/graphql.ts +++ b/packages/frontend/src/graphql/generated/graphql.ts @@ -358,6 +358,27 @@ export type CommitDeleteInput = { streamId: Scalars['String']; }; +/** Local-only state used in CommitObjectViewer */ +export type CommitObjectViewerState = { + __typename?: 'CommitObjectViewerState'; + addingComment: Scalars['Boolean']; + appliedFilter?: Maybe; + colorLegend: Scalars['JSONObject']; + commentReactions: Array; + emojis: Array; + hideCategoryKey?: Maybe; + hideCategoryValues: Array; + hideKey?: Maybe; + hideValues: Array; + isolateCategoryKey?: Maybe; + isolateCategoryValues: Array; + isolateKey?: Maybe; + isolateValues: Array; + preventCommentCollapse: Scalars['Boolean']; + selectedCommentMetaData?: Maybe; + viewerBusy: Scalars['Boolean']; +}; + export type CommitReceivedInput = { commitId: Scalars['String']; message?: InputMaybe; @@ -814,6 +835,7 @@ export type Query = { * - get the comments targeting any of a set of provided resources (comments/objects): **pass in an array of resources.** */ comments?: Maybe; + commitObjectViewerState: CommitObjectViewerState; serverInfo: ServerInfo; serverStats: ServerStats; /** @@ -958,6 +980,12 @@ export type Scope = { name: Scalars['String']; }; +export type SelectedCommentMetaData = { + __typename?: 'SelectedCommentMetaData'; + id: Scalars['String']; + selectionLocation: Scalars['JSONObject']; +}; + export type ServerApp = { __typename?: 'ServerApp'; author?: Maybe; @@ -1688,6 +1716,21 @@ export type UpdateStreamPermissionMutationVariables = Exact<{ export type UpdateStreamPermissionMutation = { __typename?: 'Mutation', streamUpdatePermission?: boolean | null }; +export type StreamFirstCommitQueryVariables = Exact<{ + id: Scalars['String']; +}>; + + +export type StreamFirstCommitQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, commits?: { __typename?: 'CommitCollection', totalCount: number, items?: Array<{ __typename?: 'Commit', id: string, referencedObject: string } | null> | null } | null } | null }; + +export type StreamBranchFirstCommitQueryVariables = Exact<{ + id: Scalars['String']; + branch: Scalars['String']; +}>; + + +export type StreamBranchFirstCommitQuery = { __typename?: 'Query', stream?: { __typename?: 'Stream', id: string, branch?: { __typename?: 'Branch', commits?: { __typename?: 'CommitCollection', totalCount: number, items?: Array<{ __typename?: 'Commit', id: string, referencedObject: string } | null> | null } | null } | null } | null }; + export type CommonUserFieldsFragment = { __typename?: 'User', id: string, suuid?: string | null, email?: string | null, name?: string | null, bio?: string | null, company?: string | null, avatar?: string | null, verified?: boolean | null, profiles?: Record | null, role?: string | null, streams?: { __typename?: 'StreamCollection', totalCount: number } | null, commits?: { __typename?: 'CommitCollectionUser', totalCount: number, items?: Array<{ __typename?: 'CommitCollectionUserNode', id: string, createdAt?: any | null } | null> | null } | null }; export type UserFavoriteStreamsQueryVariables = Exact<{ @@ -2211,6 +2254,36 @@ export const UpdateStreamPermission = gql` streamUpdatePermission(permissionParams: $params) } `; +export const StreamFirstCommit = gql` + query StreamFirstCommit($id: String!) { + stream(id: $id) { + id + commits(limit: 1) { + totalCount + items { + id + referencedObject + } + } + } +} + `; +export const StreamBranchFirstCommit = gql` + query StreamBranchFirstCommit($id: String!, $branch: String!) { + stream(id: $id) { + id + branch(name: $branch) { + commits(limit: 1) { + totalCount + items { + id + referencedObject + } + } + } + } +} + `; export const UserFavoriteStreams = gql` query UserFavoriteStreams($cursor: String) { user { @@ -2418,6 +2491,8 @@ export const StreamWithCollaboratorsDocument = {"kind":"Document","definitions": export const StreamWithActivityDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"StreamWithActivity"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"DateTime"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stream"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"commits"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"branches"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"activity"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ActivityMainFields"}}]}}]}}]}}]}},...ActivityMainFieldsFragmentDoc.definitions]} as unknown as DocumentNode; export const LeaveStreamDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"LeaveStream"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streamLeave"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"streamId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}]}]}}]} as unknown as DocumentNode; export const UpdateStreamPermissionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateStreamPermission"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"params"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"StreamUpdatePermissionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streamUpdatePermission"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"permissionParams"},"value":{"kind":"Variable","name":{"kind":"Name","value":"params"}}}]}]}}]} as unknown as DocumentNode; +export const StreamFirstCommitDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"StreamFirstCommit"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stream"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"commits"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"referencedObject"}}]}}]}}]}}]}}]} as unknown as DocumentNode; +export const StreamBranchFirstCommitDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"StreamBranchFirstCommit"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"branch"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stream"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"branch"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"branch"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"commits"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"referencedObject"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const UserFavoriteStreamsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserFavoriteStreams"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CommonUserFields"}},{"kind":"Field","name":{"kind":"Name","value":"favoriteStreams"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"10"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CommonStreamFields"}}]}}]}}]}}]}},...CommonUserFieldsFragmentDoc.definitions,...CommonStreamFieldsFragmentDoc.definitions]} as unknown as DocumentNode; export const MainUserDataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MainUserData"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CommonUserFields"}}]}}]}},...CommonUserFieldsFragmentDoc.definitions]} as unknown as DocumentNode; export const ExtraUserDataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ExtraUserData"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CommonUserFields"}},{"kind":"Field","name":{"kind":"Name","value":"totalOwnedStreamsFavorites"}}]}}]}},...CommonUserFieldsFragmentDoc.definitions]} as unknown as DocumentNode; diff --git a/packages/frontend/src/graphql/local-only-schema/schema.gql b/packages/frontend/src/graphql/local-only-schema/schema.gql new file mode 100644 index 000000000..fe22e62b2 --- /dev/null +++ b/packages/frontend/src/graphql/local-only-schema/schema.gql @@ -0,0 +1,30 @@ +extend type Query { + commitObjectViewerState: CommitObjectViewerState! +} + +""" +Local-only state used in CommitObjectViewer +""" +type CommitObjectViewerState { + viewerBusy: Boolean! + appliedFilter: JSONObject + isolateKey: String + isolateValues: [String!]! + hideKey: String + hideValues: [String!]! + colorLegend: JSONObject! + isolateCategoryKey: String + isolateCategoryValues: [String!]! + hideCategoryKey: String + hideCategoryValues: [String!]! + selectedCommentMetaData: SelectedCommentMetaData + addingComment: Boolean! + preventCommentCollapse: Boolean! + commentReactions: [String!]! + emojis: [String!]! +} + +type SelectedCommentMetaData { + id: String! + selectionLocation: JSONObject! +} diff --git a/packages/frontend/src/graphql/streams.js b/packages/frontend/src/graphql/streams.js index 957422bc2..8ca8c6cd6 100644 --- a/packages/frontend/src/graphql/streams.js +++ b/packages/frontend/src/graphql/streams.js @@ -118,3 +118,41 @@ export const updateStreamPermissionMutation = gql` streamUpdatePermission(permissionParams: $params) } ` + +/** + * Get a stream's first commit + */ +export const streamFirstCommitQuery = gql` + query StreamFirstCommit($id: String!) { + stream(id: $id) { + id + commits(limit: 1) { + totalCount + items { + id + referencedObject + } + } + } + } +` + +/** + * Get a stream branch's first commit + */ +export const streamBranchFirstCommitQuery = gql` + query StreamBranchFirstCommit($id: String!, $branch: String!) { + stream(id: $id) { + id + branch(name: $branch) { + commits(limit: 1) { + totalCount + items { + id + referencedObject + } + } + } + } + } +` diff --git a/packages/frontend/src/helpers/typeHelpers.ts b/packages/frontend/src/helpers/typeHelpers.ts index 32eae2bd9..59bc3c511 100644 --- a/packages/frontend/src/helpers/typeHelpers.ts +++ b/packages/frontend/src/helpers/typeHelpers.ts @@ -1,3 +1,4 @@ +import { ReactiveVar } from '@apollo/client/core' import Vue, { VueConstructor } from 'vue' export type Nullable = T | null @@ -6,6 +7,13 @@ export type Optional = T | undefined export type MaybeFalsy = T | null | undefined | false | '' | 0 +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type GetReactiveVarType> = V extends ReactiveVar< + infer T +> + ? T + : unknown + // Copied from Vue typings & improved ergonomics export type CombinedVueInstance< Instance extends Vue = Vue, diff --git a/packages/frontend/src/main/app.js b/packages/frontend/src/main/app.js index 3a9ee3509..acf83a013 100644 --- a/packages/frontend/src/main/app.js +++ b/packages/frontend/src/main/app.js @@ -2,7 +2,6 @@ import '@/bootstrapper' import Vue from 'vue' import App from '@/main/App.vue' -import store from '@/main/store' import { LocalStorageKeys } from '@/helpers/mainConstants' import * as MixpanelManager from '@/mixpanelManager' @@ -30,6 +29,7 @@ import PerfectScrollbar from 'vue2-perfect-scrollbar' import 'vue2-perfect-scrollbar/dist/vue2-perfect-scrollbar.css' // adds various helper methods import '@/plugins/helpers' +import { AppLocalStorage } from '@/utils/localStorage' Vue.use(PerfectScrollbar) @@ -50,8 +50,8 @@ Vue.filter('capitalize', (value) => { return value.charAt(0).toUpperCase() + value.slice(1) }) -const AuthToken = localStorage.getItem(LocalStorageKeys.AuthToken) -const RefreshToken = localStorage.getItem(LocalStorageKeys.RefreshToken) +const AuthToken = AppLocalStorage.get(LocalStorageKeys.AuthToken) +const RefreshToken = AppLocalStorage.get(LocalStorageKeys.RefreshToken) const apolloProvider = createProvider() installVueApollo(apolloProvider) @@ -92,7 +92,6 @@ function postAuthInit() { new Vue({ router, vuetify, - store, setup() { provide(DefaultApolloClient, apolloProvider.defaultClient) }, diff --git a/packages/frontend/src/main/components/auth/UserAvatarAuthoriseApp.vue b/packages/frontend/src/main/components/auth/UserAvatarAuthoriseApp.vue index 3b26246a2..d10e1f46f 100644 --- a/packages/frontend/src/main/components/auth/UserAvatarAuthoriseApp.vue +++ b/packages/frontend/src/main/components/auth/UserAvatarAuthoriseApp.vue @@ -16,6 +16,7 @@ import { signOut } from '@/plugins/authHelpers' import userQuery from '@/graphql/userById.gql' import UserAvatarIcon from '@/main/components/common/UserAvatarIcon' +import { AppLocalStorage } from '@/utils/localStorage' export default { components: { UserAvatarIcon }, @@ -26,15 +27,15 @@ export default { }, id: { type: String, - default: () => localStorage.getItem('uuid') + default: () => AppLocalStorage.get('uuid') } }, computed: { isSelf() { - return this.id === localStorage.getItem('uuid') + return this.id === AppLocalStorage.get('uuid') }, loggedInUserId() { - return localStorage.getItem('uuid') + return AppLocalStorage.get('uuid') } }, apollo: { diff --git a/packages/frontend/src/main/components/comments/CommentListItem.vue b/packages/frontend/src/main/components/comments/CommentListItem.vue index de25cc4d9..f0a0355a8 100644 --- a/packages/frontend/src/main/components/comments/CommentListItem.vue +++ b/packages/frontend/src/main/components/comments/CommentListItem.vue @@ -132,6 +132,10 @@ export default { default: () => { return { role: null } } + }, + streamId: { + type: String, + required: true } }, apollo: { @@ -147,7 +151,7 @@ export default { fetchPolicy: 'no-cache', variables() { return { - streamId: this.$route.params.streamId, + streamId: this.streamId, id: this.comment.id } }, @@ -169,7 +173,7 @@ export default { `, variables() { return { - streamId: this.$route.params.streamId, + streamId: this.streamId, commentId: this.comment.id } }, @@ -217,7 +221,7 @@ export default { (r) => r.resourceType !== 'stream' ) const first = res.shift() - let route = `/streams/${this.$route.params.streamId}/${first.resourceType}s/${first.resourceId}?cId=${this.commentDetails.id}` + let route = `/streams/${this.streamId}/${first.resourceType}s/${first.resourceId}?cId=${this.commentDetails.id}` if (res.length !== 0) { route += `&overlay=${res.map((r) => r.resourceId).join(',')}` } @@ -242,7 +246,7 @@ export default { } `, variables: { - streamId: this.$route.params.streamId, + streamId: this.streamId, commentId: this.comment.id } }) @@ -256,7 +260,7 @@ export default { } `, variables: { - streamId: this.$route.params.streamId, + streamId: this.streamId, commentId: this.comment.id } }) diff --git a/packages/frontend/src/main/components/comments/CommentThreadAttachmentPreview.vue b/packages/frontend/src/main/components/comments/CommentThreadAttachmentPreview.vue index fc76df4fd..102414284 100644 --- a/packages/frontend/src/main/components/comments/CommentThreadAttachmentPreview.vue +++ b/packages/frontend/src/main/components/comments/CommentThreadAttachmentPreview.vue @@ -43,6 +43,7 @@ import { getBlobUrl, downloadBlobWithUrl } from '@/main/lib/common/file-upload/blobStorageApi' +import { useCommitObjectViewerParams } from '@/main/lib/viewer/commit-object-viewer/stateManager' export default Vue.extend({ name: 'CommentThreadAttachmentPreview', @@ -54,6 +55,10 @@ export default Vue.extend({ }, isOpen: { type: Boolean, required: true } }, + setup() { + const { streamId, resourceId } = useCommitObjectViewerParams() + return { streamId, resourceId } + }, data: () => ({ prettyFileSize, blobUrl: null, @@ -83,7 +88,7 @@ export default Vue.extend({ try { if (this.isImage) { this.blobUrl = await getBlobUrl(this.attachment.id, { - streamId: this.$route.params.streamId + streamId: this.streamId }) } } catch (e) { diff --git a/packages/frontend/src/main/components/comments/CommentThreadReply.vue b/packages/frontend/src/main/components/comments/CommentThreadReply.vue index 12cf6a493..6162b8b97 100644 --- a/packages/frontend/src/main/components/comments/CommentThreadReply.vue +++ b/packages/frontend/src/main/components/comments/CommentThreadReply.vue @@ -83,6 +83,7 @@ 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' +import { useCommitObjectViewerParams } from '@/main/lib/viewer/commit-object-viewer/stateManager' export default { components: { @@ -95,6 +96,10 @@ export default { stream: { type: Object, default: () => null }, index: { type: Number, default: 0 } }, + setup() { + const { streamId, resourceId, isEmbed } = useCommitObjectViewerParams() + return { streamId, resourceId, isEmbed } + }, data() { return { hover: false, @@ -104,6 +109,7 @@ export default { }, computed: { canArchive() { + if (this.isEmbed) return false if (!this.reply || !this.stream) return false if (this.stream.role === 'stream:owner' || this.reply.authorId === this.$userId()) return true @@ -120,7 +126,7 @@ export default { } `, variables: { - streamId: this.$route.params.streamId, + streamId: this.streamId, commentId: this.reply.id } }) diff --git a/packages/frontend/src/main/components/comments/CommentThreadViewer.vue b/packages/frontend/src/main/components/comments/CommentThreadViewer.vue index cfa77409c..e98b5c270 100644 --- a/packages/frontend/src/main/components/comments/CommentThreadViewer.vue +++ b/packages/frontend/src/main/components/comments/CommentThreadViewer.vue @@ -52,103 +52,105 @@ @deleted="handleReplyDeleteEvent" /> -
- +
+
+ + mdi-menu + + + + + + Powered by Speckle + + + +
+ + + +
+ + + mdi-close + + + + +
+
+ + +
+ +
+ + + + mdi-open-in-new + + + + + + diff --git a/packages/frontend/src/main/components/viewer/viewerFrontendHelpers.js b/packages/frontend/src/main/components/viewer/viewerFrontendHelpers.js deleted file mode 100644 index ab57e4882..000000000 --- a/packages/frontend/src/main/components/viewer/viewerFrontendHelpers.js +++ /dev/null @@ -1,16 +0,0 @@ -export function getCamArray() { - const controls = window.__viewer.cameraHandler.activeCam.controls - const pos = controls.getPosition() - const target = controls.getTarget() - const c = [ - parseFloat(pos.x.toFixed(5)), - parseFloat(pos.y.toFixed(5)), - parseFloat(pos.z.toFixed(5)), - parseFloat(target.x.toFixed(5)), - parseFloat(target.y.toFixed(5)), - parseFloat(target.z.toFixed(5)), - window.__viewer.cameraHandler.activeCam.name === 'ortho' ? 1 : 0, - controls._zoom - ] - return c -} diff --git a/packages/frontend/src/main/dialogs/NewStream.vue b/packages/frontend/src/main/dialogs/NewStream.vue index 715fc297c..9bf3eb2bb 100644 --- a/packages/frontend/src/main/dialogs/NewStream.vue +++ b/packages/frontend/src/main/dialogs/NewStream.vue @@ -114,6 +114,7 @@ diff --git a/packages/frontend/src/main/layouts/TheMain.vue b/packages/frontend/src/main/layouts/TheMain.vue index 1ba9bb556..cc0666796 100644 --- a/packages/frontend/src/main/layouts/TheMain.vue +++ b/packages/frontend/src/main/layouts/TheMain.vue @@ -1,7 +1,7 @@