feat(frontend): typescript support in frontend (#803)

This commit is contained in:
Kristaps Fabians Geikins
2022-06-14 17:30:51 +03:00
committed by GitHub
parent 41e7df433a
commit 8d7f97b2cd
26 changed files with 7646 additions and 381 deletions
+2
View File
@@ -15,6 +15,8 @@ repos:
- '@typescript-eslint/eslint-plugin@5.21.0'
- '@typescript-eslint/parser@5.21.0'
- typescript@4.5.4
- '@rushstack/eslint-patch@1.1.3'
- '@vue/eslint-config-typescript@11.0.0'
- repo: https://github.com/pre-commit/mirrors-prettier
rev: 'v2.6.2' # Use the sha / tag you want to point at
+1
View File
@@ -7,6 +7,7 @@ coverage
packages/server/reports*
packages/objectloader/examples/browser/objectloader.web.js
packages/viewer/example/speckleviewer.web.js
packages/frontend/**/generated
package-lock.json
yarn.lock
+25 -4
View File
@@ -1,4 +1,5 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
/**
* Extends repo root config, only put changes here that are scoped to this specific package
@@ -12,22 +13,42 @@ const config = {
node: false,
commonjs: false
},
ignorePatterns: ['nginx'],
ignorePatterns: ['nginx', 'generated/**/*'],
// Specifying full "extends" value from base config to change order
extends: ['plugin:vue/recommended', 'eslint:recommended', 'prettier'],
extends: ['eslint:recommended', 'prettier'],
parserOptions: {
sourceType: 'module'
},
overrides: [
{
files: '*.vue',
plugins: ['vue'],
extends: ['plugin:vue/recommended', '@vue/eslint-config-typescript', 'prettier']
},
{
files: './*.{js, ts}',
env: {
node: true,
commonjs: true
}
},
{
files: '*.ts',
plugins: ['@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier'
],
parser: '@typescript-eslint/parser'
},
{
files: '*.d.ts',
rules: {
'@typescript-eslint/no-unused-vars': 'off'
}
}
],
plugins: ['vue']
]
}
module.exports = config
+36 -2
View File
@@ -24,16 +24,50 @@ Comprehensive developer and user documentation can be found in our:
Make sure you follow the Developing and Debugging section in the project root readme.
### Compiles and hot-reloads for development
### Running
Dev server with hot reload:
```
yarn serve
yarn dev
```
Build static build & serve it (for development, otherwise use docker image):
```
yarn build && yarn serve
```
### 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.
#### TS in Vue
1. Set `<script lang="ts">` in your .vue SFC
1. Make sure you do `export default Vue.extends({...})` (or something else that is explicity typed to be a Vue component) not just `export default`, otherwise it's not clear to TS that the exported object is a Vue component
1. If Vetur reports incorrect errors, check this out: https://vuejs.github.io/vetur/guide/FAQ.html
Note: If you're defining a Vue component in a non-standard way (e.g. `vueWithMixins([]).extends({...})`), make sure you add a `// @vue/component` comment right above the Vue component object definition so that ESLint shows Vue appropriate linting rules, otherwise it won't.
#### 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
### Packaging for production
If you plan to package the frontend to use in a production setting, see our [Server deployment instructions](https://speckle.guide/dev/server-setup.html) (chapter `Run your speckle-server fork`)
### Troubleshooting
#### Vue TypeScript types get stuck in VSCode
Restart the Vetur Vue Language Server (VLS) through the command palette. Vetur is a bit janky and sometimes it gets stuck and isn't able to find new types/code.
#### Property 'xxx' does not exist on type 'CombinedVueInstance'
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.
## 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.
+12
View File
@@ -0,0 +1,12 @@
overwrite: true
schema: 'http://localhost:3000/graphql'
documents:
- 'src/graphql/**/*.gql'
- 'src/**/*.{ts,tsx,js,jsx,vue}'
generates:
src/graphql/generated/graphql.ts:
plugins:
- 'typescript'
- 'typescript-operations'
- 'typescript-document-nodes'
- 'typescript-vue-apollo-smart-ops'
+50 -18
View File
@@ -1,3 +1,48 @@
const TARGET = 'es2019'
function prepareJs(jsRule, api) {
// Delete babel related loaders
jsRule.uses.delete('thread-loader').delete('babel-loader')
// Add caching config
jsRule
.use('cache-loader')
.loader('cache-loader')
.options(
api.genCacheConfig('js-esbuild-loader', {
target: TARGET,
esbuildLoaderVersion: require('esbuild-loader/package.json').version
})
)
// Add new esbuild loader
jsRule.use('esbuild-loader').loader('esbuild-loader').options({
target: TARGET
})
}
function prepareTs(tsRule, api) {
// Delete related loaders
tsRule.uses.delete('ts-loader').delete('babel-loader')
// Add caching config
tsRule
.use('cache-loader')
.loader('cache-loader')
.options(
api.genCacheConfig('ts-esbuild-loader', {
target: TARGET,
esbuildLoaderVersion: require('esbuild-loader/package.json').version
})
)
// Add new esbuild loader
tsRule.use('esbuild-loader').loader('esbuild-loader').options({
target: TARGET,
loader: 'tsx'
})
}
/**
* Dev builds use esbuild instead of babel for improved speed
* @param {import('@vue/cli-service/lib/PluginAPI')} api
@@ -9,27 +54,14 @@ function plugin(api) {
if (!isDevBuild) return
api.chainWebpack((config) => {
const target = 'es2019'
const jsRule = config.module.rule('js').test(/\.m?jsx?$/)
prepareJs(jsRule, api)
// Delete babel related loaders
jsRule.uses.delete('thread-loader').delete('babel-loader')
const tsRule = config.module.rule('ts').test(/\.ts$/)
prepareTs(tsRule, api)
// Add caching config
jsRule
.use('cache-loader')
.loader('cache-loader')
.options(
api.genCacheConfig('js-esbuild-loader', {
target,
esbuildLoaderVersion: require('esbuild-loader/package.json').version
})
)
// Add new esbuild loader
jsRule.use('esbuild-loader').loader('esbuild-loader').options({
target
})
// No TSX support currently, we can look into it if needed
config.module.rules.delete('tsx')
})
}
-10
View File
@@ -1,10 +0,0 @@
{
"extends": "../../jsconfig.base.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"]
}
+25 -6
View File
@@ -3,18 +3,18 @@
"version": "2.5.4",
"private": true,
"scripts": {
"dev": "vue-cli-service serve --mode development",
"serve": "ws -p 8080 -d dist -r '/embed(.*) -> /embedApp.html' '/([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",
"lint:ts": "tsc --noEmit",
"build:dev": "vue-cli-service build --mode development --silent",
"build:profile": "yarn build -- --profile",
"build:dev:profile": "yarn build:dev -- --profile",
"build:profile": "yarn build -- --profile",
"dev": "vue-cli-service serve --mode development",
"inspect": "vue-cli-service inspect --mode production",
"inspect:dev": "vue-cli-service inspect --mode development",
"lint": "eslint . --ext .js,.ts,.vue"
},
"engines": {
"node": "^16.0.0"
"gqlgen": "graphql-codegen --config codegen.yml"
},
"dependencies": {
"@speckle/viewer": "workspace:^",
@@ -59,12 +59,24 @@
"vuex": "^3.6.2"
},
"devDependencies": {
"@graphql-codegen/cli": "2.6.2",
"@graphql-codegen/introspection": "2.1.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.2.13",
"@mdi/font": "^5.8.55",
"@rushstack/eslint-patch": "^1.1.3",
"@types/lodash": "^4.14.180",
"@typescript-eslint/eslint-plugin": "^5.21.0",
"@typescript-eslint/parser": "^5.21.0",
"@vue/cli": "^4.5.17",
"@vue/cli-plugin-babel": "~4.3.0",
"@vue/cli-plugin-router": "~4.3.0",
"@vue/cli-plugin-typescript": "~4.5.17",
"@vue/cli-plugin-vuex": "~4.3.0",
"@vue/cli-service": "~4.3.0",
"@vue/eslint-config-typescript": "^11.0.0",
"babel-eslint": "^10.1.0",
"babel-plugin-lodash": "^3.3.4",
"duplicate-dependencies-webpack-plugin": "^1.0.2",
@@ -80,6 +92,10 @@
"sass": "~1.32.6",
"sass-loader": "^8.0.0",
"speed-measure-webpack-plugin": "^1.5.0",
"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.21.3",
"vue-cli-plugin-vuetify": "^2.0.8",
"vue-template-compiler": "^2.6.12",
@@ -87,6 +103,9 @@
"webpack": "^4.46.0",
"webpack-bundle-analyzer": "^4.5.0"
},
"engines": {
"node": "^16.0.0"
},
"vuePlugins": {
"service": [
"./esbuildPlugin.js"
+1 -1
View File
@@ -1,4 +1,4 @@
query Stream($streamId: String!, $branchName: String!, $cursor: String) {
query StreamWithBranch($streamId: String!, $branchName: String!, $cursor: String) {
stream(id: $streamId) {
id
name
File diff suppressed because it is too large Load Diff
@@ -1,4 +1,4 @@
query Object($streamId: String!, $id: String!) {
query StreamObject($streamId: String!, $id: String!) {
stream(id: $streamId) {
id
object(id: $id) {
@@ -1,4 +1,4 @@
query Object($streamId: String!, $id: String!) {
query StreamObjectNoData($streamId: String!, $id: String!) {
stream(id: $streamId) {
id
name
-34
View File
@@ -1,34 +0,0 @@
query Stream($id: String!) {
stream(id: $id) {
id
name
description
isPublic
createdAt
updatedAt
role
collaborators {
id
name
role
company
avatar
}
branches {
totalCount
}
commits(limit: 1) {
totalCount
items {
id
message
referencedObject
authorName
authorAvatar
authorId
branchName
createdAt
}
}
}
}
@@ -1,43 +0,0 @@
query Stream($id: String!) {
stream(id: $id) {
id
name
description
isPublic
createdAt
updatedAt
role
collaborators {
id
name
role
company
avatar
}
branches {
totalCount
items {
id
name
description
commits(limit: 4) {
totalCount
items {
id
authorId
authorName
authorAvatar
createdAt
message
referencedObject
branchName
sourceApplication
}
}
}
}
commits {
totalCount
}
}
}
@@ -1,4 +1,4 @@
query Stream($id: String!) {
query StreamWithCollaborators($id: String!) {
stream(id: $id) {
id
name
+1 -1
View File
@@ -65,7 +65,7 @@ export const MainUserDataQuery = gql`
* Main metadata + extra info shown on profile page
*/
export const ProfileSelfQuery = gql`
query MainUserData {
query ExtraUserData {
user {
...CommonUserFields
totalOwnedStreamsFavorites
@@ -0,0 +1,65 @@
import Vue, { VueConstructor } from 'vue'
export type Nullable<T> = T | null
export type Optional<T> = T | undefined
// Copied from Vue typings & improved ergonomics
export type CombinedVueInstance<
Instance extends Vue = Vue,
Data = unknown,
Methods = unknown,
Computed = unknown,
Props = unknown
> = Data & Methods & Computed & Props & Instance
export type ExtendedVue<
Instance extends Vue = Vue,
Data = unknown,
Methods = unknown,
Computed = unknown,
Props = unknown
> = VueConstructor<CombinedVueInstance<Instance, Data, Methods, Computed, Props> & Vue>
export type VueWithMixins<
A extends VueConstructor = VueConstructor,
B extends VueConstructor = VueConstructor,
C extends VueConstructor = VueConstructor,
D extends VueConstructor = VueConstructor,
E extends VueConstructor = VueConstructor
> = VueConstructor<
Vue &
InstanceType<A> &
InstanceType<B> &
InstanceType<C> &
InstanceType<D> &
InstanceType<E>
>
/**
* Create Vue base class with the specified mixins and correctly returned TypeScript types
* TODO: Can this be re-written to accept an arbitrary amount of mixins?
* @returns
*/
export function vueWithMixins<
A extends VueConstructor = VueConstructor,
B extends VueConstructor = VueConstructor,
C extends VueConstructor = VueConstructor,
D extends VueConstructor = VueConstructor,
E extends VueConstructor = VueConstructor
>(
mixin1?: A,
mixin2?: B,
mixin3?: C,
mixin4?: D,
mixin5?: E
): VueWithMixins<A, B, C, D, E> {
const mixins = [mixin1, mixin2, mixin3, mixin4, mixin5].filter(
(m): m is A | B | C | D | E => !!m
)
// eslint-disable-next-line vue/require-name-property
return Vue.extend({
mixins
}) as VueWithMixins<A, B, C, D, E>
}
@@ -3,7 +3,7 @@
<portal v-if="canRenderToolbarPortal" to="toolbar">
Favorite Streams
<span v-if="streams.length" class="caption">
({{ user.favoriteStreams.totalCount }})
({{ user && user.favoriteStreams && user.favoriteStreams.totalCount }})
</span>
</portal>
<!-- No streams -->
@@ -34,83 +34,107 @@
</v-row>
</div>
</template>
<script>
import { UserFavoriteStreamsQuery } from '@/graphql/user'
<script lang="ts">
import Vue from 'vue'
import {
STANDARD_PORTAL_KEYS,
buildPortalStateMixin
} from '@/main/utils/portalStateManager'
import {
UserFavoriteStreamsQuery,
UserFavoriteStreamsQueryVariables,
useUserFavoriteStreamsQuery
} 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 { vueWithMixins } from '@/helpers/typeHelpers'
export default {
name: 'TheFavoriteStreams',
components: {
FavoriteStreamsPlaceholder: () =>
import('@/main/components/stream/favorites/FavoriteStreamsPlaceholder.vue'),
InfiniteLoading: () => import('vue-infinite-loading'),
StreamPreviewCard: () => import('@/main/components/common/StreamPreviewCard.vue')
},
mixins: [
buildPortalStateMixin([STANDARD_PORTAL_KEYS.Toolbar], 'favorite-streams', 0)
],
apollo: {
user: {
query: UserFavoriteStreamsQuery
}
},
computed: {
streams() {
return this.user?.favoriteStreams?.items || []
export default vueWithMixins(
buildPortalStateMixin([STANDARD_PORTAL_KEYS.Toolbar], 'favorite-streams', 0)
).extend(
// @vue/component
{
name: 'TheFavoriteStreams',
components: {
FavoriteStreamsPlaceholder: () =>
import('@/main/components/stream/favorites/FavoriteStreamsPlaceholder.vue'),
InfiniteLoading: () => import('vue-infinite-loading'),
StreamPreviewCard: () => import('@/main/components/common/StreamPreviewCard.vue')
},
/**
* Whether or not there are more streams to load
*/
allStreamsLoaded() {
return (
this.streams.length &&
this.streams.length >= this.user.favoriteStreams.totalCount
)
}
},
methods: {
infiniteHandler($state) {
if (this.allStreamsLoaded) {
$state.loaded()
$state.complete()
return
data() {
return {
user: undefined as UserFavoriteStreamsQuery['user']
}
// Fetch more favorites
this.$apollo.queries.user.fetchMore({
variables: {
cursor: this.user.favoriteStreams.cursor
},
updateQuery: (previousResult, { fetchMoreResult }) => {
const newFavorites = fetchMoreResult.user.favoriteStreams
const oldFavorites = previousResult.user.favoriteStreams
const { items: newItems } = newFavorites
const { items: allItems } = oldFavorites
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 {
user: {
...previousResult.user,
favoriteStreams: {
...fetchMoreResult.user.favoriteStreams,
items: allItems
}
}
}
},
apollo: {
user: useUserFavoriteStreamsQuery()
},
computed: {
streams(): NonNullable<
Get<UserFavoriteStreamsQuery, 'user.favoriteStreams.items'>
> {
return this.user?.favoriteStreams?.items || []
},
/**
* Whether or not there are more streams to load
*/
allStreamsLoaded(): boolean {
return !!(
this.streams.length &&
this.streams.length >= (this.user?.favoriteStreams?.totalCount || 0)
)
}
},
methods: {
infiniteHandler($state: StateChanger) {
if (this.allStreamsLoaded) {
$state.loaded()
$state.complete()
return
}
})
// Fetch more favorites
const userQuery: SmartQuery<
Vue,
UserFavoriteStreamsQuery,
UserFavoriteStreamsQueryVariables
> = this.$apollo.queries.user
userQuery.fetchMore({
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
}
})
}
}
}
}
)
</script>
@@ -1,7 +1,6 @@
import Vue from 'vue'
import camelCase from 'lodash/camelCase'
import upperFirst from 'lodash/upperFirst'
import reduce from 'lodash/reduce'
import { camelCase, upperFirst, reduce } from 'lodash'
import { Optional, CombinedVueInstance, ExtendedVue } from '@/helpers/typeHelpers'
export const STANDARD_PORTAL_KEYS = {
Toolbar: 'toolbar',
@@ -16,37 +15,35 @@ export const STANDARD_PORTAL_KEYS = {
* manager which manages multiple claims to the same portal space and only allows one source to use it at a time.
*/
/**
* @type {Object<string, Object<string, {priority: number }>>}
*/
const claims = {}
const claims: Record<string, Record<string, { priority: number }>> = {}
/**
* @type {{currentPortals: Object<string, string>}}
*/
const portalsState = Vue.observable({
currentPortals: {}
currentPortals: {} as Record<string, string>
})
function recalculateState() {
const newPortals = {}
const newPortals: Record<string, string> = {}
for (const [portalKey, portalClaims] of Object.entries(claims)) {
let highestPriority = undefined
let highestPriorityIdentity = undefined
let highestPriority: Optional<{
identity: string
priority: number
}> = undefined
for (const [identity, data] of Object.entries(portalClaims)) {
if (!highestPriorityIdentity || data.priority > highestPriority) {
highestPriorityIdentity = identity
highestPriority = data.priority
if (!highestPriority || data.priority > highestPriority.priority) {
highestPriority = { priority: data.priority, identity }
} else if (
highestPriority === data.priority &&
highestPriorityIdentity === identity
highestPriority.priority === data.priority &&
highestPriority.identity === identity
) {
console.error(
'Multiple portals with the same priority encountered, your portals might not act deterministically'
)
}
}
newPortals[portalKey] = highestPriorityIdentity
if (highestPriority?.identity) {
newPortals[portalKey] = highestPriority.identity
}
}
Vue.set(portalsState, 'currentPortals', newPortals)
@@ -54,11 +51,8 @@ function recalculateState() {
/**
* Claim access to a specific toolbar
* @param {string} portal
* @param {string} identity
* @param {number} priority
*/
export function claimPortal(portal, identity, priority) {
export function claimPortal(portal: string, identity: string, priority: number) {
// Register claim
if (!claims[portal]) {
claims[portal] = {}
@@ -71,11 +65,8 @@ export function claimPortal(portal, identity, priority) {
/**
* Claim access to multiple toolbars
* @param {string[]} portals
* @param {string} identity
* @param {number} priority
*/
export function claimPortals(portals, identity, priority) {
export function claimPortals(portals: string[], identity: string, priority: number) {
for (const portal of portals) {
claimPortal(portal, identity, priority)
}
@@ -83,10 +74,8 @@ export function claimPortals(portals, identity, priority) {
/**
* Remove claim to a specific toolbar
* @param {string} portal
* @param {string} identity
*/
export function unclaimPortal(portal, identity) {
export function unclaimPortal(portal: string, identity: string) {
if (claims[portal]) {
delete claims[portal][identity]
}
@@ -95,10 +84,8 @@ export function unclaimPortal(portal, identity) {
/**
* Remove claims to multiple toolbars
* @param {string[]} portals
* @param {string} identity
*/
export function unclaimPortals(portals, identity) {
export function unclaimPortals(portals: string[], identity: string) {
for (const portal of portals) {
unclaimPortal(portal, identity)
}
@@ -106,30 +93,42 @@ export function unclaimPortals(portals, identity) {
/**
* Check if portal source is allowed to render in the target portal
* @param {string} portal
* @param {string} identity
* @returns {boolean}
*/
export function canRenderPortalSource(portal, identity) {
export function canRenderPortalSource(portal: string, identity: string): boolean {
return portalsState.currentPortals[portal] === identity
}
/**
* Build mixin for tracking whether portal targets can be rendered to
* @param {string[]} portals Portal identifier keys
* @param {string} identity The unique identity of the portal source
* @param {number} priority Priority starting from 0. Higher priorities will take precedence
* @param portals Portal identifier keys
* @param identity The unique identity of the portal source
* @param priority Priority starting from 0. Higher priorities will take precedence
* over other portal sources.
*/
export function buildPortalStateMixin(portals, identity, priority) {
return {
export function buildPortalStateMixin(
portals: string[],
identity: string,
priority: number
): ExtendedVue<
Vue,
unknown,
unknown,
{
allowedPortals: Record<string, boolean>
canRenderToolbarPortal?: boolean
canRenderActionsPortal?: boolean
canRenderNavPortal?: boolean
canRenderSubnavPortal?: boolean
}
> {
return Vue.extend({
computed: {
/**
* Object with keys of portal names and values representing if the component
* is allowed to use those portals
*/
allowedPortals() {
const res = {}
const res: Record<string, boolean> = {}
for (const portal of portals) {
res[portal] = portalsState.currentPortals[portal] === identity
}
@@ -143,12 +142,19 @@ export function buildPortalStateMixin(portals, identity, priority) {
portals,
(res, portal) => {
const computedKey = `canRender${upperFirst(camelCase(portal))}Portal`
res[computedKey] = function () {
res[computedKey] = function (
this: CombinedVueInstance<
Vue,
unknown,
unknown,
{ allowedPortals: Record<string, boolean> }
>
) {
return !!this.allowedPortals[portal]
}
return res
},
{}
{} as Record<string, () => boolean>
)
},
mounted() {
@@ -157,5 +163,5 @@ export function buildPortalStateMixin(portals, identity, priority) {
beforeDestroy() {
unclaimPortals(portals, identity)
}
}
})
}
+12
View File
@@ -0,0 +1,12 @@
/* eslint-disable @typescript-eslint/no-empty-interface */
import Vue, { VNode } from 'vue'
declare global {
namespace JSX {
interface Element extends VNode {}
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: unknown
}
}
}
@@ -0,0 +1,4 @@
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}
+35
View File
@@ -0,0 +1,35 @@
{
"compilerOptions": {
"target": "es6",
"module": "es2020",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"allowJs": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"lib": ["esnext", "dom", "dom.iterable", "scripthost"],
"types": [
"vue-apollo",
"vue-apollo-smart-ops",
"vue-infinite-loading",
"type-fest",
"vue"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": ["node_modules"]
}
@@ -70,7 +70,7 @@ type StreamCollaborator {
type StreamCollection {
totalCount: Int!
cursor: String
items: [Stream]
items: [Stream!]
}
extend type Mutation {
+8
View File
@@ -101,6 +101,14 @@ The suggested IDE for working in this repo is VSCode, because the repo has speci
This repository relies on [VSCode multi-root workspaces](https://code.visualstudio.com/docs/editor/multi-root-workspaces), so for the best DX and proper formatting/linting make sure you open the project using the `workspace.code-workspace` file instead of just opening the folder in VSCode.
### Troubleshooting
#### lint-on-commit fails with "File ignored because of a matchin ignore pattern"
There's currently a limitation in ESLint & lint-staged that causes this - the warning is thrown whenver your Git changes contain a file that is ignored by ESLint (https://github.com/eslint/eslint/issues/15010).
To get around this you can use the `--no-verify` flag of `git commit`, for now, but make sure all other issues found by the linter are fixed.
### Contributing
Please make sure you read the [contribution guidelines](https://github.com/specklesystems/speckle-server/blob/main/CONTRIBUTING.md) for an overview of the best practices we try to follow.
+1
View File
@@ -3,6 +3,7 @@
module.exports = {
settings: {
'vetur.useWorkspaceDependencies': true
// 'vetur.experimental.templateInterpolationService': true, // Enable for typechecking in templates
},
projects: ['./packages/frontend']
}
+4464 -141
View File
File diff suppressed because it is too large Load Diff