Skip to content

Commit 2ee2f79

Browse files
committed
feat: apps sidepane
1 parent 470924f commit 2ee2f79

File tree

7 files changed

+276
-9
lines changed

7 files changed

+276
-9
lines changed

packages/app-frontend/src/features/App.vue

+24-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import AppConnecting from './connection/AppConnecting.vue'
44
import AppDisconnected from './connection/AppDisconnected.vue'
55
import ErrorOverlay from './error/ErrorOverlay.vue'
66
import WelcomeSlideshow from './welcome/WelcomeSlideshow.vue'
7+
import SplitPane from './layout/SplitPane.vue'
8+
import AppSelectPane from './apps/AppSelectPane.vue'
79
810
import { onMounted, defineComponent, ref, watch } from '@vue/composition-api'
911
import {
@@ -16,6 +18,7 @@ import {
1618
} from '@vue-devtools/shared-utils'
1719
import { darkMode } from '@front/util/theme'
1820
import { useAppConnection } from './connection'
21+
import { showAppsSelector } from './header/header'
1922
2023
const chromeTheme = isChrome ? chrome.devtools.panels.themeName : undefined
2124
@@ -31,6 +34,8 @@ export default defineComponent({
3134
AppDisconnected,
3235
ErrorOverlay,
3336
WelcomeSlideshow,
37+
SplitPane,
38+
AppSelectPane,
3439
},
3540
3641
setup () {
@@ -79,6 +84,7 @@ export default defineComponent({
7984
isConnected,
8085
isInitializing,
8186
welcomeHidden,
87+
showAppsSelector,
8288
}
8389
},
8490
})
@@ -104,7 +110,24 @@ export default defineComponent({
104110
<template v-else>
105111
<AppHeader class="flex-none relative z-10 border-b border-gray-200 dark:border-gray-800" />
106112

107-
<router-view class="flex-1 overflow-auto" />
113+
<SplitPane
114+
save-id="app-select-pane"
115+
:default-split="12"
116+
:min="5"
117+
:max="40"
118+
collapsable-left
119+
class="flex-1 overflow-hidden"
120+
@left-collapsed="showAppsSelector = $event"
121+
>
122+
<template #left>
123+
<AppSelectPane
124+
class="h-full"
125+
/>
126+
</template>
127+
<template #right>
128+
<router-view class="h-full overflow-auto" />
129+
</template>
130+
</SplitPane>
108131
</template>
109132

110133
<portal-target name="root" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<script lang="ts">
2+
import AppSelectPaneItem from './AppSelectPaneItem.vue'
3+
4+
import { watch, defineComponent, ref, computed } from '@vue/composition-api'
5+
import { BridgeEvents, SharedData } from '@vue-devtools/shared-utils'
6+
import { useApps, pendingSelectAppId, scanLegacyApps } from '@front/features/apps'
7+
import { useRouter } from '@front/util/router'
8+
import { useBridge } from '../bridge'
9+
10+
export default defineComponent({
11+
components: {
12+
AppSelectPaneItem,
13+
},
14+
15+
setup () {
16+
const router = useRouter()
17+
const { bridge } = useBridge()
18+
19+
const {
20+
apps,
21+
currentAppId,
22+
currentApp,
23+
selectApp,
24+
} = useApps()
25+
26+
watch(currentAppId, value => {
27+
if (pendingSelectAppId.value !== value) {
28+
pendingSelectAppId.value = value
29+
bridge.send(BridgeEvents.TO_BACK_APP_SELECT, value)
30+
}
31+
}, {
32+
immediate: true,
33+
})
34+
35+
let initDefaultAppId = false
36+
37+
watch(apps, () => {
38+
if ((!currentApp.value || (SharedData.pageConfig?.defaultSelectedAppId && !initDefaultAppId)) && apps.value.length) {
39+
let targetId: string
40+
if (SharedData.pageConfig?.defaultSelectedAppId) {
41+
targetId = SharedData.pageConfig.defaultSelectedAppId
42+
initDefaultAppId = true
43+
} else if (currentAppId.value !== apps.value[0].id) {
44+
targetId = apps.value[0].id
45+
}
46+
if (targetId) {
47+
router.push({
48+
params: {
49+
appId: targetId,
50+
componentId: null,
51+
},
52+
})
53+
}
54+
}
55+
}, {
56+
immediate: true,
57+
})
58+
59+
// Search
60+
const search = ref('')
61+
const filteredApps = computed(() => {
62+
if (!search.value) return apps.value
63+
const searchValue = search.value.toLowerCase()
64+
return apps.value.filter(app => {
65+
return app.name.toLowerCase().includes(searchValue)
66+
})
67+
})
68+
69+
return {
70+
currentApp,
71+
filteredApps,
72+
selectApp,
73+
search,
74+
scanLegacyApps,
75+
}
76+
},
77+
})
78+
</script>
79+
80+
<template>
81+
<div class="flex flex-col">
82+
<div class="flex-none border-b border-gray-200 dark:border-gray-800 flex items-center space-x-1 h-[40px] pr-1">
83+
<VueInput
84+
v-model="search"
85+
icon-left="search"
86+
placeholder="Find apps..."
87+
select-all
88+
class="search flat flex-1"
89+
/>
90+
91+
<VueButton
92+
v-if="$shared.legacyApps"
93+
v-tooltip="'Scan apps'"
94+
class="flat icon-button"
95+
icon-left="cached"
96+
@click="scanLegacyApps()"
97+
/>
98+
</div>
99+
100+
<div class="overflow-y-auto flex-1">
101+
<AppSelectPaneItem
102+
v-for="item of filteredApps"
103+
:key="item.id"
104+
:app="item"
105+
:selected="item === currentApp"
106+
@select="selectApp(item.id)"
107+
/>
108+
</div>
109+
</div>
110+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<script lang="ts">
2+
import { defineComponent, computed } from '@vue/composition-api'
3+
import { useVueVersionCheck } from './vue-version-check'
4+
5+
export default defineComponent({
6+
props: {
7+
app: {
8+
type: Object,
9+
required: true,
10+
},
11+
12+
selected: {
13+
type: Boolean,
14+
default: false,
15+
},
16+
},
17+
18+
setup (props) {
19+
const { getLatestVersion } = useVueVersionCheck()
20+
const latestVersion = computed(() => getLatestVersion(props.app.version))
21+
const hasNewVersion = computed(() => latestVersion.value !== props.app.version)
22+
23+
return {
24+
latestVersion,
25+
hasNewVersion,
26+
}
27+
},
28+
})
29+
</script>
30+
31+
<template>
32+
<VueButton
33+
class="app-button leading-tight w-full"
34+
:class="{
35+
'flat': !selected,
36+
'text-green-500': selected,
37+
}"
38+
@click="$emit('select')"
39+
>
40+
<div class="flex items-center">
41+
<span class="truncate flex-1">{{ app.name }}</span>
42+
<span class="flex-none flex items-center">
43+
<img
44+
src="~@front/assets/vue-logo.svg"
45+
class="w-6 h-6"
46+
alt="Vue"
47+
>
48+
<span>{{ app.version }}</span>
49+
<span
50+
v-if="hasNewVersion"
51+
class="ml-2 text-sm text-green-500 flex items-center space-x-0.5"
52+
>
53+
<VueIcon
54+
icon="new_releases"
55+
class="w-5 h-5"
56+
/>
57+
<span>{{ latestVersion }}</span>
58+
</span>
59+
</span>
60+
61+
<template v-if="$shared.debugInfo">
62+
<span
63+
v-tooltip="'id'"
64+
class="text-white px-1 rounded bg-gray-500 mx-1"
65+
>
66+
{{ app.id }}
67+
</span>
68+
</template>
69+
</div>
70+
<div
71+
v-if="app.iframe"
72+
class="flex items-center space-x-1 text-2xs font-mono text-gray-500"
73+
>
74+
<VueIcon
75+
icon="web"
76+
class="w-4 h-4"
77+
/>
78+
<span>{{ app.iframe }}</span>
79+
</div>
80+
</VueButton>
81+
</template>
82+
83+
<style lang="postcss" scoped>
84+
.app-button {
85+
@apply rounded-none text-left h-auto py-1.5;
86+
87+
> >>> .content {
88+
@apply min-w-full justify-start;
89+
90+
> .default-slot {
91+
@apply w-full;
92+
}
93+
}
94+
}
95+
</style>

packages/app-frontend/src/features/header/AppHeader.vue

+7-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { useRoute } from '@front/util/router'
1212
import { useBridge } from '@front/features/bridge'
1313
import { useInspectors } from '@front/features/inspector/custom/composable'
1414
import { useTabs } from './tabs'
15+
import { showAppsSelector } from './header'
1516
1617
export default defineComponent({
1718
components: {
@@ -68,6 +69,7 @@ export default defineComponent({
6869
inspectorRoutes,
6970
currentInspectorRoute,
7071
lastInspectorRoute,
72+
showAppsSelector,
7173
}
7274
},
7375
})
@@ -79,12 +81,15 @@ export default defineComponent({
7981

8082
<AppHistoryNav />
8183

82-
<AppSelect />
84+
<template v-if="showAppsSelector">
85+
<AppSelect />
8386

84-
<img src="~@front/assets/breadcrumb-separator.svg">
87+
<img src="~@front/assets/breadcrumb-separator.svg">
88+
</template>
8589

8690
<AppMainMenu
8791
:last-inspector-route="lastInspectorRoute"
92+
:label-shown="!showAppsSelector"
8893
/>
8994

9095
<template v-if="currentInspectorRoute">

packages/app-frontend/src/features/header/AppMainMenu.vue

+23-4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ export default defineComponent({
88
type: Object,
99
default: null,
1010
},
11+
12+
labelShown: {
13+
type: Boolean,
14+
default: false,
15+
},
1116
},
1217
1318
setup (props) {
@@ -41,15 +46,29 @@ export default defineComponent({
4146
v-tooltip="'Inspector'"
4247
:to="targetInspectorRoute"
4348
value="inspector"
44-
class="icon-button flat"
49+
class="flat"
50+
:class="{
51+
'icon-button': !labelShown,
52+
}"
4553
icon-left="explore"
46-
/>
54+
>
55+
<template v-if="labelShown">
56+
Inspector
57+
</template>
58+
</VueGroupButton>
4759
<VueGroupButton
4860
v-tooltip="'Timeline'"
4961
:to="{ name: 'timeline' }"
5062
value="timeline"
51-
class="icon-button flat"
63+
class="flat"
64+
:class="{
65+
'icon-button': !labelShown,
66+
}"
5267
icon-left="line_style"
53-
/>
68+
>
69+
<template v-if="labelShown">
70+
Timeline
71+
</template>
72+
</VueGroupButton>
5473
</VueGroup>
5574
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { ref } from '@vue/composition-api'
2+
3+
export const showAppsSelector = ref(true)

packages/app-frontend/src/features/layout/SplitPane.vue

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="ts">
2-
import { ref, computed, defineComponent, PropType } from '@vue/composition-api'
2+
import { ref, computed, defineComponent, PropType, watch } from '@vue/composition-api'
33
import { useOrientation } from './orientation'
44
import { useSavedRef } from '@front/util/reactivity'
55
@@ -42,7 +42,7 @@ export default defineComponent({
4242
},
4343
},
4444
45-
setup (props) {
45+
setup (props, { emit }) {
4646
const { orientation } = useOrientation()
4747
4848
const split = ref(props.defaultSplit)
@@ -59,6 +59,18 @@ export default defineComponent({
5959
}
6060
}
6161
62+
watch(leftCollapsed, value => {
63+
emit('left-collapsed', value)
64+
}, {
65+
immediate: true,
66+
})
67+
68+
watch(rightCollapsed, value => {
69+
emit('right-collapsed', value)
70+
}, {
71+
immediate: true,
72+
})
73+
6274
const boundSplit = computed(() => {
6375
if (split.value < props.min) {
6476
return props.min

0 commit comments

Comments
 (0)