diff --git a/.eslintrc.js b/.eslintrc.js index b78fc6d2e..e3f3e76a6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -39,6 +39,9 @@ module.exports = { }, }, ], + 'func-call-spacing': 'off', + 'vue/component-definition-name-casing': 'off', + 'vue/multi-word-component-names': 'off', '@typescript-eslint/ban-ts-comment': 'warn', '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', @@ -76,6 +79,14 @@ module.exports = { files: ['packages/shell-dev-vue3/**'], rules: { 'vue/valid-template-root': 'off', + 'vue/one-component-per-file': 'off', + 'vue/no-v-model-argument': 'off', + }, + }, + { + files: ['packages/app-frontend/**'], + rules: { + 'vue/no-v-model-argument': 'off', }, }, { @@ -99,6 +110,7 @@ module.exports = { }, rules: { 'no-console': 'off', + 'vue/no-multiple-template-root': 'off', }, }, { diff --git a/package.json b/package.json index 6ee2b0bc4..3a1437c71 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^5.1.0", "eslint-plugin-standard": "^5.0.0", - "eslint-plugin-vue": "^6.0.0", + "eslint-plugin-vue": "^9.17.0", "execa": "^4.0.3", "inquirer": "^6.2.0", "lerna": "^4.0.0", @@ -70,8 +70,9 @@ "semver": "^5.5.1", "start-server-and-test": "^1.7.1", "tailwindcss": "npm:@tailwindcss/postcss7-compat", - "vue-loader": "^15.9.0", - "webpack-dev-server": "^4.0.0-beta.0" + "vue-loader": "^17.2.2", + "webpack-dev-server": "^4.0.0-beta.0", + "svg-inline-loader": "^0.8.2" }, "resolutions": { "cypress": "=3.4.1", diff --git a/packages/app-backend-vue2/package.json b/packages/app-backend-vue2/package.json index cacbebf25..654595d71 100644 --- a/packages/app-backend-vue2/package.json +++ b/packages/app-backend-vue2/package.json @@ -17,6 +17,8 @@ "lodash": "^4.17.21" }, "devDependencies": { + "vue": "^2.7.10", + "vue-loader": "^15.7.1", "@types/node": "^13.9.1", "@types/webpack-env": "^1.15.1", "core-js": "^3.20.2", diff --git a/packages/app-frontend/package.json b/packages/app-frontend/package.json index e39d7eec0..fa7445054 100644 --- a/packages/app-frontend/package.json +++ b/packages/app-frontend/package.json @@ -10,18 +10,23 @@ "@vue/ui": "^0.12.5", "circular-json-es6": "^2.0.2", "d3": "^5.16.0", + "floating-vue": "^5.2.2", "lodash": "^4.17.15", "lru-cache": "^5.1.1", "monaco-editor": "^0.24.0", "pixi.js-legacy": "^6.2.0", - "portal-vue": "^2.1.7", "scroll-into-view-if-needed": "^2.2.28", "semver": "^7.3.5", "stylus": "^0.54.7", "stylus-loader": "^3.0.2", "tinycolor2": "^1.4.2", - "vue": "^2.7.10", - "vue-router": "^3.6.5", - "vue-virtual-scroller": "^1.0.10" + "vue": "^3.3.4", + "vue-resize": "^2.0.0-alpha.1", + "vue-router": "^4.2.5", + "vue-safe-teleport": "^0.1.2", + "vue-virtual-scroller": "^2.0.0-alpha.1" + }, + "devDependencies": { + "@akryum/md-icons-svg": "^1.0.1" } } diff --git a/packages/app-frontend/src/app.ts b/packages/app-frontend/src/app.ts index 621b182ab..20fe47514 100644 --- a/packages/app-frontend/src/app.ts +++ b/packages/app-frontend/src/app.ts @@ -1,8 +1,7 @@ import App from './features/App.vue' - -import Vue from 'vue' +import { App as VueApp, createApp as createVueApp } from 'vue' import { isChrome, initEnv, SharedData, initSharedData, destroySharedData, BridgeEvents } from '@vue-devtools/shared-utils' -import { createRouter } from './router' +import { createRouterInstance } from './router' import { getBridge, setBridge } from './features/bridge' import { setAppConnected, setAppInitializing } from './features/connection' import { setupAppsBridgeEvents } from './features/apps' @@ -12,37 +11,25 @@ import { setupCustomInspectorBridgeEvents } from './features/inspector/custom/co import { setupPluginsBridgeEvents } from './features/plugin' import { setupPlugins } from './plugins' -setupPlugins() - // Capture and log devtool errors when running as actual extension // so that we can debug it by inspecting the background page. // We do want the errors to be thrown in the dev shell though. -if (isChrome) { - Vue.config.errorHandler = (e, vm) => { - getBridge()?.send('ERROR', { - message: e.message, - stack: e.stack, - component: vm.$options.name || (vm.$options as any)._componentTag || 'anonymous', - }) - } -} - -// @ts-ignore -Vue.options.renderError = (h, e) => { - return h('pre', { - class: 'text-white bg-red-500 p-2 rounded text-xs overflow-auto', - }, e.stack) -} - export function createApp () { - const router = createRouter() - - const app = new Vue({ - router, - render: h => h(App as any), - }) - - // @TODO [Vue 3] Setup plugins + const router = createRouterInstance() + + const app = createVueApp(App) + app.use(router) + setupPlugins(app) + + if (isChrome) { + app.config.errorHandler = (e, vm) => { + getBridge()?.send('ERROR', { + message: (e as Error).message, + stack: (e as Error).stack, + component: vm?.$options.name || (vm?.$options as any)._componentTag || 'anonymous', + }) + } + } return app } @@ -51,22 +38,22 @@ export function createApp () { * Connect then init the app. We need to reconnect on every reload, because a * new backend will be injected. */ -export function connectApp (app, shell) { +export function connectApp (app: VueApp, shell) { shell.connect(async bridge => { setBridge(bridge) // @TODO remove // @ts-ignore window.bridge = bridge - if (Object.prototype.hasOwnProperty.call(Vue.prototype, '$shared')) { + if (app.config.globalProperties.$shared) { destroySharedData() } else { - Object.defineProperty(Vue.prototype, '$shared', { + Object.defineProperty(app.config.globalProperties, '$shared', { get: () => SharedData, }) } - initEnv(Vue) + initEnv(app) bridge.on(BridgeEvents.TO_FRONT_TITLE, ({ title }: { title: string }) => { document.title = `${title} - Vue devtools` @@ -75,7 +62,6 @@ export function connectApp (app, shell) { await initSharedData({ bridge, persist: true, - Vue, }) if (SharedData.logDetected) { diff --git a/packages/app-frontend/src/assets/style/index.styl b/packages/app-frontend/src/assets/style/index.styl index 1a3dafe53..f3a677c7b 100644 --- a/packages/app-frontend/src/assets/style/index.styl +++ b/packages/app-frontend/src/assets/style/index.styl @@ -30,6 +30,10 @@ html, body body overflow hidden +#app + width: 100% + height: 100% + button:focus outline none diff --git a/packages/app-frontend/src/features/App.vue b/packages/app-frontend/src/features/App.vue index 1b3c2a9a1..cc42dd156 100644 --- a/packages/app-frontend/src/features/App.vue +++ b/packages/app-frontend/src/features/App.vue @@ -122,7 +122,7 @@ export default defineComponent({ - + diff --git a/packages/app-frontend/src/features/apps/AppSelect.vue b/packages/app-frontend/src/features/apps/AppSelect.vue index 72b255dfe..5b6ff267f 100644 --- a/packages/app-frontend/src/features/apps/AppSelect.vue +++ b/packages/app-frontend/src/features/apps/AppSelect.vue @@ -6,7 +6,7 @@ import { watch, defineComponent, computed } from 'vue' import { BridgeEvents, SharedData } from '@vue-devtools/shared-utils' import { useApps, pendingSelectAppId, scanLegacyApps } from '@front/features/apps' import { useOrientation } from '@front/features/layout/orientation' -import { useRouter } from '@front/util/router' +import { useRouter } from 'vue-router' import { useBridge } from '../bridge' import { useVueVersionCheck } from './vue-version-check' diff --git a/packages/app-frontend/src/features/apps/AppSelectPane.vue b/packages/app-frontend/src/features/apps/AppSelectPane.vue index 1326329fe..9581de477 100644 --- a/packages/app-frontend/src/features/apps/AppSelectPane.vue +++ b/packages/app-frontend/src/features/apps/AppSelectPane.vue @@ -4,7 +4,7 @@ import AppSelectPaneItem from './AppSelectPaneItem.vue' import { watch, defineComponent, ref, computed } from 'vue' import { BridgeEvents, SharedData } from '@vue-devtools/shared-utils' import { useApps, pendingSelectAppId, scanLegacyApps } from '@front/features/apps' -import { useRouter } from '@front/util/router' +import { useRouter } from 'vue-router' import { useBridge } from '../bridge' export default defineComponent({ diff --git a/packages/app-frontend/src/features/apps/AppSelectPaneItem.vue b/packages/app-frontend/src/features/apps/AppSelectPaneItem.vue index 07a18a3e9..170f8d88c 100644 --- a/packages/app-frontend/src/features/apps/AppSelectPaneItem.vue +++ b/packages/app-frontend/src/features/apps/AppSelectPaneItem.vue @@ -14,6 +14,7 @@ export default defineComponent({ default: false, }, }, + emits: ['select'], setup (props) { const { getLatestVersion } = useVueVersionCheck() @@ -89,7 +90,7 @@ export default defineComponent({ .app-button { @apply rounded-none text-left h-auto py-1.5; - > >>> .content { + > :deep(.content) { @apply min-w-full justify-start; > .default-slot { diff --git a/packages/app-frontend/src/features/apps/index.ts b/packages/app-frontend/src/features/apps/index.ts index 32ae6eeab..f3a201848 100644 --- a/packages/app-frontend/src/features/apps/index.ts +++ b/packages/app-frontend/src/features/apps/index.ts @@ -1,7 +1,7 @@ import { ref, computed } from 'vue' import { BridgeEvents, Bridge } from '@vue-devtools/shared-utils' import { getBridge } from '@front/features/bridge' -import { useRoute, useRouter } from '@front/util/router' +import { useRoute, useRouter } from 'vue-router' import { fetchLayers } from '../timeline/composable' export interface AppRecord { @@ -15,7 +15,7 @@ const apps = ref([]) export function useCurrentApp () { const route = useRoute() - const currentAppId = computed(() => route.value.params.appId) + const currentAppId = computed(() => route.params.appId as string) const currentApp = computed(() => apps.value.find(a => currentAppId.value === a.id)) return { @@ -71,7 +71,7 @@ function fetchApps () { getBridge().send(BridgeEvents.TO_BACK_APP_LIST, {}) } -export const pendingSelectAppId = ref(null) +export const pendingSelectAppId = ref(null) const pendingSelectPromises: (() => void)[] = [] diff --git a/packages/app-frontend/src/features/chrome/pane-visibility.ts b/packages/app-frontend/src/features/chrome/pane-visibility.ts index e162b2d07..e0c40578b 100644 --- a/packages/app-frontend/src/features/chrome/pane-visibility.ts +++ b/packages/app-frontend/src/features/chrome/pane-visibility.ts @@ -1,7 +1,7 @@ import { isChrome } from '@vue-devtools/shared-utils' let panelShown = !isChrome -let pendingAction = null +let pendingAction: (() => void | Promise) | null = null if (isChrome) { chrome.runtime.onMessage.addListener(request => { diff --git a/packages/app-frontend/src/features/code/CodeEditor.vue b/packages/app-frontend/src/features/code/CodeEditor.vue index 68aa1c4fd..04a2a80d0 100644 --- a/packages/app-frontend/src/features/code/CodeEditor.vue +++ b/packages/app-frontend/src/features/code/CodeEditor.vue @@ -1,6 +1,5 @@ diff --git a/packages/app-frontend/src/features/components/ComponentsInspector.vue b/packages/app-frontend/src/features/components/ComponentsInspector.vue index af45ccfdf..f9b2d5178 100644 --- a/packages/app-frontend/src/features/components/ComponentsInspector.vue +++ b/packages/app-frontend/src/features/components/ComponentsInspector.vue @@ -135,7 +135,7 @@ export default defineComponent({ - +
Component names:
@@ -184,9 +184,9 @@ export default defineComponent({
- + - + - + - +
-
+ diff --git a/packages/app-frontend/src/features/components/ui/components/VueLoadingBar.vue b/packages/app-frontend/src/features/components/ui/components/VueLoadingBar.vue new file mode 100644 index 000000000..20c92f3a6 --- /dev/null +++ b/packages/app-frontend/src/features/components/ui/components/VueLoadingBar.vue @@ -0,0 +1,29 @@ + + + diff --git a/packages/app-frontend/src/features/components/ui/components/VueLoadingIndicator.vue b/packages/app-frontend/src/features/components/ui/components/VueLoadingIndicator.vue new file mode 100644 index 000000000..751b25232 --- /dev/null +++ b/packages/app-frontend/src/features/components/ui/components/VueLoadingIndicator.vue @@ -0,0 +1,6 @@ + diff --git a/packages/app-frontend/src/features/components/ui/components/VueModal.vue b/packages/app-frontend/src/features/components/ui/components/VueModal.vue new file mode 100644 index 000000000..2f6cdc732 --- /dev/null +++ b/packages/app-frontend/src/features/components/ui/components/VueModal.vue @@ -0,0 +1,96 @@ + + + diff --git a/packages/app-frontend/src/features/components/ui/components/VueSelect.vue b/packages/app-frontend/src/features/components/ui/components/VueSelect.vue new file mode 100644 index 000000000..3dd6de5c0 --- /dev/null +++ b/packages/app-frontend/src/features/components/ui/components/VueSelect.vue @@ -0,0 +1,83 @@ + + + diff --git a/packages/app-frontend/src/features/components/ui/components/VueSelectButton.vue b/packages/app-frontend/src/features/components/ui/components/VueSelectButton.vue new file mode 100644 index 000000000..4ab71f864 --- /dev/null +++ b/packages/app-frontend/src/features/components/ui/components/VueSelectButton.vue @@ -0,0 +1,52 @@ + + + diff --git a/packages/app-frontend/src/features/components/ui/components/VueSwitch.vue b/packages/app-frontend/src/features/components/ui/components/VueSwitch.vue new file mode 100644 index 000000000..d7bc45194 --- /dev/null +++ b/packages/app-frontend/src/features/components/ui/components/VueSwitch.vue @@ -0,0 +1,86 @@ + + + diff --git a/packages/app-frontend/src/features/components/ui/components/icons.ts b/packages/app-frontend/src/features/components/ui/components/icons.ts new file mode 100644 index 000000000..cef330919 --- /dev/null +++ b/packages/app-frontend/src/features/components/ui/components/icons.ts @@ -0,0 +1,34 @@ +const icons = require.context( + '@akryum/md-icons-svg/svg/', + true, + /materialicons\/24px\.svg$/, +) + +export default { + install () { + const sprites = [''] + let spriteIndex = 0 + // Load all the SVG symbols + icons.keys().forEach((key, index) => { + let result = icons(key) + const [, iconName] = /(\w+)\/materialicons/.exec(key) + const [, content] = /(.*)<\/svg>/.exec(result) + result = `${content}` + sprites[spriteIndex] += result + if ((index + 1) % 40 === 0) { + sprites.push('') + spriteIndex++ + } + }) + for (const html of sprites) { + const iconsWrapper = document.createElement('div') + iconsWrapper.style.display = 'none' + iconsWrapper.innerHTML = html + document.body.insertBefore(iconsWrapper, document.body.firstChild) + } + }, +} + +export function generateHtmlIcon (icon: string) { + return `
` +} diff --git a/packages/app-frontend/src/features/components/ui/composables/useDisableScroll.ts b/packages/app-frontend/src/features/components/ui/composables/useDisableScroll.ts new file mode 100644 index 000000000..56c1c21e2 --- /dev/null +++ b/packages/app-frontend/src/features/components/ui/composables/useDisableScroll.ts @@ -0,0 +1,29 @@ +import { onBeforeUnmount, onMounted } from 'vue' + +let count = 0 + +function getScrollingElements () { + return document.querySelectorAll('.vue-ui-disable-scroll, body') +} + +function updateScroll () { + if (count === 0) { + getScrollingElements().forEach((el) => + el.classList.remove('vue-ui-no-scroll'), + ) + } else if (count === 1) { + getScrollingElements().forEach((el) => el.classList.add('vue-ui-no-scroll')) + } +} + +export function useDisableScroll () { + onMounted(() => { + count++ + updateScroll() + }) + + onBeforeUnmount(() => { + count-- + updateScroll() + }) +} diff --git a/packages/app-frontend/src/features/components/ui/composables/useDisabled.ts b/packages/app-frontend/src/features/components/ui/composables/useDisabled.ts new file mode 100644 index 000000000..81132f843 --- /dev/null +++ b/packages/app-frontend/src/features/components/ui/composables/useDisabled.ts @@ -0,0 +1,34 @@ +/** + * (Use with the DisabledChild mixin) + * Allow disabling an entire tree of components implementing the DisabledChild mixin. + */ + +import { provide, reactive, watch, inject, computed } from 'vue' + +export const useDisabledParent = (props: { disabled?: boolean }) => { + const injectedDisableData = reactive({ + value: props.disabled || false, + }) + + provide('VueDisableMixin', { + data: injectedDisableData, + }) + + watch( + () => props.disabled, + (value, oldValue) => { + if (value !== oldValue) injectedDisableData.value = value + }, + ) +} + +export const useDisabledChild = (props: { disabled?: boolean }) => { + const injectDisable = inject<{ data: { value: boolean } } | undefined>( + 'VueDisableMixin', + null, + ) + + return { + finalDisabled: computed(() => props.disabled || (injectDisable && injectDisable.data.value)), + } +} diff --git a/packages/app-frontend/src/features/components/ui/index.ts b/packages/app-frontend/src/features/components/ui/index.ts new file mode 100644 index 000000000..fca5eea88 --- /dev/null +++ b/packages/app-frontend/src/features/components/ui/index.ts @@ -0,0 +1,59 @@ +import { Plugin } from 'vue' +import VueIcons from './components/icons' +import VueDisable from './components/VueDisable.vue' +import VueButton from './components/VueButton.vue' +import VueDropdown from './components/VueDropdown.vue' +import VueDropdownButton from './components/VueDropdownButton.vue' +import VueFormField from './components/VueFormField.vue' +import VueLoadingIndicator from './components/VueLoadingIndicator.vue' +import VueGroup from './components/VueGroup.vue' +import VueGroupButton from './components/VueGroupButton.vue' +import VueIcon from './components/VueIcon.vue' +import VueInput from './components/VueInput.vue' +import VueLoadingBar from './components/VueLoadingBar.vue' +import VueSwitch from './components/VueSwitch.vue' +import VueSelect from './components/VueSelect.vue' +import VueSelectButton from './components/VueSelectButton.vue' +import VueModal from './components/VueModal.vue' +import FloatingVue from 'floating-vue' +import 'floating-vue/dist/style.css' +export { generateHtmlIcon } from './components/icons' + +const ui: Plugin = { + install (app) { + app.use(VueIcons) + app.component('VueButton', VueButton) + app.component('VueDisable', VueDisable) + app.component('VueDropdown', VueDropdown) + app.component('VueFormField', VueFormField) + app.component('VueDropdownButton', VueDropdownButton) + app.component('VueLoadingIndicator', VueLoadingIndicator) + app.component('VueGroup', VueGroup) + app.component('VueGroupButton', VueGroupButton) + app.component('VueIcon', VueIcon) + app.component('VueInput', VueInput) + app.component('VueLoadingBar', VueLoadingBar) + app.component('VueSwitch', VueSwitch) + app.component('VueSelect', VueSelect) + app.component('VueSelectButton', VueSelectButton) + app.component('VueModal', VueModal) + + app.use(FloatingVue, { + container: 'body', + instantMove: true, + themes: { + tooltip: { + delay: { + show: 1000, + hide: 800, + }, + }, + dropdown: { + handleResize: false, + }, + }, + }) + }, +} + +export default ui diff --git a/packages/app-frontend/src/features/header/AppHeader.vue b/packages/app-frontend/src/features/header/AppHeader.vue index e50f4d0f3..3a00a882c 100644 --- a/packages/app-frontend/src/features/header/AppHeader.vue +++ b/packages/app-frontend/src/features/header/AppHeader.vue @@ -6,9 +6,9 @@ import PluginSourceIcon from '@front/features/plugin/PluginSourceIcon.vue' import PluginSourceDescription from '../plugin/PluginSourceDescription.vue' import { computed, ref, watch, defineComponent } from 'vue' -import type { RawLocation, Route } from 'vue-router' +import type { RouteLocationRaw, RouteLocation } from 'vue-router' import { BridgeEvents } from '@vue-devtools/shared-utils' -import { useRoute } from '@front/util/router' +import { useRoute } from 'vue-router' import { useBridge } from '@front/features/bridge' import { useInspectors } from '@front/features/inspector/custom/composable' import { useTabs } from './tabs' @@ -18,8 +18,8 @@ import { useOrientation } from '../layout/orientation' interface HeaderRoute { icon: string label: string - targetRoute: RawLocation - matchRoute: (route: Route) => boolean + targetRoute: RouteLocationRaw + matchRoute: (route: RouteLocation) => boolean pluginId?: string } @@ -60,7 +60,7 @@ export default defineComponent({ matchRoute: route => route.params.inspectorId === i.id, })))) - const currentHeaderRoute = computed(() => headerRoutes.value.find(r => r.matchRoute(route.value))) + const currentHeaderRoute = computed(() => headerRoutes.value.find(r => r.matchRoute(route))) const lastHeaderRoute = ref(null) watch(currentHeaderRoute, value => { @@ -104,7 +104,7 @@ export default defineComponent({ -