Skip to content
This repository was archived by the owner on May 1, 2025. It is now read-only.

Commit e44215a

Browse files
committed
feat: allow guides to toggle features
1 parent 6e50d0d commit e44215a

File tree

12 files changed

+211
-10
lines changed

12 files changed

+211
-10
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@ The development server will be running at [http://localhost:3000](http://localho
4242

4343
- [ ] Content
4444
- [x] Switch playgrounds on different guides
45-
- [ ] Allow each guide to toggle features
45+
- [x] Allow each guide to toggle features
4646
- [ ] Allow each guide to configure file filter
4747
- [ ] Solution for each guide
4848
- [ ] A button of "Edit this page"
4949
- [ ] Verification for tutorial tasks
5050
- [ ] Search feature
51-
- [ ] Embedded Nuxt Docs
51+
- [ ] Embedded Nuxt Docs (update CORS headers)
5252
- [ ] Command K System
5353
- [ ] About Page
5454
- [ ] Welcome Screen

components/PanelEditor.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ const panelInitEditor = computed(() => isMounted.value || {
6363
<span text-sm>Editor</span>
6464
</div>
6565
<Splitpanes
66+
of-hidden
6667
@resize="startDragging"
6768
@resized="endDragging"
6869
>

components/TheNav.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const timeAgo = useTimeAgo(buildTime)
1616

1717
<div flex-auto />
1818
<button
19-
v-if="play.status === 'ready'"
19+
v-if="play.status === 'ready' && play.features.download !== false"
2020
rounded p2
2121
hover="bg-active"
2222
title="Download as ZIP"
@@ -44,6 +44,7 @@ const timeAgo = useTimeAgo(buildTime)
4444
</template>
4545
</VDropdown>
4646
<button
47+
v-if="play.features.terminal !== false"
4748
rounded p2
4849
title="Toggle terminal"
4950
hover="bg-active"

content/.template/files/app.vue

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script setup lang="ts">
2+
const msg = 'INDEX PAGE'
3+
</script>
4+
5+
<template>
6+
<div>{{ msg.toUpperCase() }}</div>
7+
</template>

content/.template/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { GuideMeta } from '~/types/guides'
2+
3+
export const meta: GuideMeta = {
4+
startingFile: 'app.vue',
5+
features: {
6+
terminal: false,
7+
fileTree: false,
8+
},
9+
}

content/views/3.routing/.template/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@ import type { GuideMeta } from '~/types/guides'
22

33
export const meta: GuideMeta = {
44
startingFile: 'pages/index.vue',
5+
features: {
6+
fileTree: true,
7+
},
58
}

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,10 @@
6161
},
6262
"resolutions": {
6363
"shikiji": "^0.9.12"
64+
},
65+
"pnpm": {
66+
"patchedDependencies": {
67+
68+
}
6469
}
6570
}

pages/[...slug].vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const templatesMap = Object.fromEntries(
88
key
99
.replace(/^\/content/, '')
1010
.replace(/\/\.template\/index\.ts$/, '')
11-
.replace(/\/\d+\./g, '/'),
11+
.replace(/\/\d+\./g, '/') || '',
1212
loader,
1313
]),
1414
)
@@ -18,6 +18,7 @@ if (process.dev)
1818
console.log('templates', Object.keys(templatesMap))
1919
2020
async function mount(path: string) {
21+
path = path.replace(/\/$/, '')
2122
if (templatesMap[path])
2223
play.mountGuide(await templatesMap[path]().then((m: any) => m.meta))
2324
else

patches/[email protected]

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
diff --git a/dist/splitpanes.css b/dist/splitpanes.css
2+
index 2354cb734b79a4109c2a511d5fc49d944372d8d0..50d37289adc60f94183c93d10896b30aede99f32 100644
3+
--- a/dist/splitpanes.css
4+
+++ b/dist/splitpanes.css
5+
@@ -1 +1,135 @@
6+
-.splitpanes{display:flex;width:100%;height:100%}.splitpanes--vertical{flex-direction:row}.splitpanes--horizontal{flex-direction:column}.splitpanes--dragging *{user-select:none}.splitpanes__pane{width:100%;height:100%;overflow:hidden}.splitpanes--vertical .splitpanes__pane{transition:width .2s ease-out}.splitpanes--horizontal .splitpanes__pane{transition:height .2s ease-out}.splitpanes--dragging .splitpanes__pane{transition:none}.splitpanes__splitter{touch-action:none}.splitpanes--vertical>.splitpanes__splitter{min-width:1px;cursor:col-resize}.splitpanes--horizontal>.splitpanes__splitter{min-height:1px;cursor:row-resize}.splitpanes.default-theme .splitpanes__pane{background-color:#f2f2f2}.splitpanes.default-theme .splitpanes__splitter{background-color:#fff;box-sizing:border-box;position:relative;flex-shrink:0}.splitpanes.default-theme .splitpanes__splitter:before,.splitpanes.default-theme .splitpanes__splitter:after{content:"";position:absolute;top:50%;left:50%;background-color:#00000026;transition:background-color .3s}.splitpanes.default-theme .splitpanes__splitter:hover:before,.splitpanes.default-theme .splitpanes__splitter:hover:after{background-color:#00000040}.splitpanes.default-theme .splitpanes__splitter:first-child{cursor:auto}.default-theme.splitpanes .splitpanes .splitpanes__splitter{z-index:1}.default-theme.splitpanes--vertical>.splitpanes__splitter,.default-theme .splitpanes--vertical>.splitpanes__splitter{width:7px;border-left:1px solid #eee;margin-left:-1px}.default-theme.splitpanes--vertical>.splitpanes__splitter:before,.default-theme.splitpanes--vertical>.splitpanes__splitter:after,.default-theme .splitpanes--vertical>.splitpanes__splitter:before,.default-theme .splitpanes--vertical>.splitpanes__splitter:after{transform:translateY(-50%);width:1px;height:30px}.default-theme.splitpanes--vertical>.splitpanes__splitter:before,.default-theme .splitpanes--vertical>.splitpanes__splitter:before{margin-left:-2px}.default-theme.splitpanes--vertical>.splitpanes__splitter:after,.default-theme .splitpanes--vertical>.splitpanes__splitter:after{margin-left:1px}.default-theme.splitpanes--horizontal>.splitpanes__splitter,.default-theme .splitpanes--horizontal>.splitpanes__splitter{height:7px;border-top:1px solid #eee;margin-top:-1px}.default-theme.splitpanes--horizontal>.splitpanes__splitter:before,.default-theme.splitpanes--horizontal>.splitpanes__splitter:after,.default-theme .splitpanes--horizontal>.splitpanes__splitter:before,.default-theme .splitpanes--horizontal>.splitpanes__splitter:after{transform:translate(-50%);width:30px;height:1px}.default-theme.splitpanes--horizontal>.splitpanes__splitter:before,.default-theme .splitpanes--horizontal>.splitpanes__splitter:before{margin-top:-2px}.default-theme.splitpanes--horizontal>.splitpanes__splitter:after,.default-theme .splitpanes--horizontal>.splitpanes__splitter:after{margin-top:1px}
7+
+.splitpanes {
8+
+ display: flex;
9+
+ width: 100%;
10+
+ height: 100%
11+
+}
12+
+
13+
+.splitpanes--vertical {
14+
+ flex-direction: row
15+
+}
16+
+
17+
+.splitpanes--horizontal {
18+
+ flex-direction: column
19+
+}
20+
+
21+
+.splitpanes--dragging * {
22+
+ user-select: none
23+
+}
24+
+
25+
+.splitpanes__pane {
26+
+ width: 100%;
27+
+ height: 100%;
28+
+ overflow: hidden
29+
+}
30+
+
31+
+.splitpanes--vertical>.splitpanes__pane {
32+
+ transition: width .2s ease-out
33+
+}
34+
+
35+
+.splitpanes--horizontal>.splitpanes__pane {
36+
+ transition: height .2s ease-out
37+
+}
38+
+
39+
+.splitpanes--dragging .splitpanes__pane {
40+
+ transition: none
41+
+}
42+
+
43+
+.splitpanes__splitter {
44+
+ touch-action: none
45+
+}
46+
+
47+
+.splitpanes--vertical>.splitpanes__splitter {
48+
+ min-width: 1px;
49+
+ cursor: col-resize
50+
+}
51+
+
52+
+.splitpanes--horizontal>.splitpanes__splitter {
53+
+ min-height: 1px;
54+
+ cursor: row-resize
55+
+}
56+
+
57+
+.splitpanes.default-theme .splitpanes__pane {
58+
+ background-color: #f2f2f2
59+
+}
60+
+
61+
+.splitpanes.default-theme .splitpanes__splitter {
62+
+ background-color: #fff;
63+
+ box-sizing: border-box;
64+
+ position: relative;
65+
+ flex-shrink: 0
66+
+}
67+
+
68+
+.splitpanes.default-theme .splitpanes__splitter:before,
69+
+.splitpanes.default-theme .splitpanes__splitter:after {
70+
+ content: "";
71+
+ position: absolute;
72+
+ top: 50%;
73+
+ left: 50%;
74+
+ background-color: #00000026;
75+
+ transition: background-color .3s
76+
+}
77+
+
78+
+.splitpanes.default-theme .splitpanes__splitter:hover:before,
79+
+.splitpanes.default-theme .splitpanes__splitter:hover:after {
80+
+ background-color: #00000040
81+
+}
82+
+
83+
+.splitpanes.default-theme .splitpanes__splitter:first-child {
84+
+ cursor: auto
85+
+}
86+
+
87+
+.default-theme.splitpanes .splitpanes .splitpanes__splitter {
88+
+ z-index: 1
89+
+}
90+
+
91+
+.default-theme.splitpanes--vertical>.splitpanes__splitter,
92+
+.default-theme .splitpanes--vertical>.splitpanes__splitter {
93+
+ width: 7px;
94+
+ border-left: 1px solid #eee;
95+
+ margin-left: -1px
96+
+}
97+
+
98+
+.default-theme.splitpanes--vertical>.splitpanes__splitter:before,
99+
+.default-theme.splitpanes--vertical>.splitpanes__splitter:after,
100+
+.default-theme .splitpanes--vertical>.splitpanes__splitter:before,
101+
+.default-theme .splitpanes--vertical>.splitpanes__splitter:after {
102+
+ transform: translateY(-50%);
103+
+ width: 1px;
104+
+ height: 30px
105+
+}
106+
+
107+
+.default-theme.splitpanes--vertical>.splitpanes__splitter:before,
108+
+.default-theme .splitpanes--vertical>.splitpanes__splitter:before {
109+
+ margin-left: -2px
110+
+}
111+
+
112+
+.default-theme.splitpanes--vertical>.splitpanes__splitter:after,
113+
+.default-theme .splitpanes--vertical>.splitpanes__splitter:after {
114+
+ margin-left: 1px
115+
+}
116+
+
117+
+.default-theme.splitpanes--horizontal>.splitpanes__splitter,
118+
+.default-theme .splitpanes--horizontal>.splitpanes__splitter {
119+
+ height: 7px;
120+
+ border-top: 1px solid #eee;
121+
+ margin-top: -1px
122+
+}
123+
+
124+
+.default-theme.splitpanes--horizontal>.splitpanes__splitter:before,
125+
+.default-theme.splitpanes--horizontal>.splitpanes__splitter:after,
126+
+.default-theme .splitpanes--horizontal>.splitpanes__splitter:before,
127+
+.default-theme .splitpanes--horizontal>.splitpanes__splitter:after {
128+
+ transform: translate(-50%);
129+
+ width: 30px;
130+
+ height: 1px
131+
+}
132+
+
133+
+.default-theme.splitpanes--horizontal>.splitpanes__splitter:before,
134+
+.default-theme .splitpanes--horizontal>.splitpanes__splitter:before {
135+
+ margin-top: -2px
136+
+}
137+
+
138+
+.default-theme.splitpanes--horizontal>.splitpanes__splitter:after,
139+
+.default-theme .splitpanes--horizontal>.splitpanes__splitter:after {
140+
+ margin-top: 1px
141+
+}

pnpm-lock.yaml

Lines changed: 8 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

stores/playground.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { WebContainer, WebContainerProcess } from '@webcontainer/api'
33
import { dirname } from 'pathe'
44
import { VirtualFile } from '../structures/VirtualFile'
55
import type { ClientInfo } from '~/types/rpc'
6-
import type { GuideMeta } from '~/types/guides'
6+
import type { GuideMeta, PlaygroundFeatures } from '~/types/guides'
77

88
export const PlaygroundStatusOrder = [
99
'init',
@@ -20,6 +20,8 @@ export type PlaygroundStatus = typeof PlaygroundStatusOrder[number] | 'error'
2020
const NUXT_PORT = 4000
2121

2222
export const usePlaygroundStore = defineStore('playground', () => {
23+
const ui = useUiState()
24+
2325
const status = ref<PlaygroundStatus>('init')
2426
const error = shallowRef<{ message: string }>()
2527
const currentProcess = shallowRef<Raw<WebContainerProcess | undefined>>()
@@ -28,6 +30,7 @@ export const usePlaygroundStore = defineStore('playground', () => {
2830
const clientInfo = ref<ClientInfo>()
2931
const fileSelected = shallowRef<Raw<VirtualFile>>()
3032
const mountedGuide = shallowRef<Raw<GuideMeta>>()
33+
const features = ref<PlaygroundFeatures>({})
3134

3235
const previewLocation = ref({
3336
origin: '',
@@ -98,6 +101,21 @@ export const usePlaygroundStore = defineStore('playground', () => {
98101
mountPromise = mount()
99102
}
100103

104+
watch(features, () => {
105+
if (features.value.fileTree === true) {
106+
if (ui.panelFileTree <= 0)
107+
ui.panelFileTree = 20
108+
}
109+
else if (features.value.fileTree === false) {
110+
ui.panelFileTree = 0
111+
}
112+
113+
if (features.value.terminal === true)
114+
ui.showTerminal = true
115+
else if (features.value.terminal === false)
116+
ui.showTerminal = false
117+
})
118+
101119
let abortController: AbortController | undefined
102120

103121
function killPreviousProcess() {
@@ -248,6 +266,11 @@ export const usePlaygroundStore = defineStore('playground', () => {
248266
await updateOrCreateFile(filepath, content)
249267
}),
250268
)
269+
270+
features.value = guide?.features || {}
271+
}
272+
else {
273+
features.value = {}
251274
}
252275

253276
previewLocation.value.fullPath = guide?.startingUrl || '/'
@@ -293,6 +316,7 @@ export const usePlaygroundStore = defineStore('playground', () => {
293316
mountGuide,
294317
previewLocation,
295318
previewUrl,
319+
features,
296320
restartServer: startServer,
297321
status,
298322
updatePreviewUrl,

types/guides.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@ export interface GuideMeta {
22
features?: PlaygroundFeatures
33
startingFile?: string
44
startingUrl?: string
5-
packageJsonOverrides?: any // TODO:
5+
// TODO:
6+
packageJsonOverrides?: any
67
/**
78
* When not provided, this will be loaded from './files' directory
89
*/
910
files?: Record<string, string>
11+
// TODO:
12+
solutions?: Record<string, string>
1013
}
1114

1215
export interface PlaygroundFeatures {
1316
terminal?: boolean
14-
showNodeJsFiles?: boolean
15-
filesSideBar?: boolean
17+
fileTree?: boolean
18+
download?: boolean
1619
}

0 commit comments

Comments
 (0)