Skip to content

Commit

Permalink
feat(admin): admin improvements (#524)
Browse files Browse the repository at this point in the history
* wip: components playground

* fix: switch to nuxt module for components preview

* refactor(admin): modulize preview

* refactor(admin): folder structure

* chore: typo

* feat(admin): embed windicss-analysis

* chore: update notes

* feat: config tab

* feat: preview navigate to editor

* fix(admin): reuse utils instance for windi analysis

* chore: update deps

* feat(admin): entry for  windi analyzer

* chore: clean up

* Apply suggestions from code review

Co-authored-by: Sébastien Chopin <seb@nuxtjs.com>

* 🚨 (lint) no-console fix

* chore: cleanup vite fix

Co-authored-by: Sébastien Chopin <seb@nuxtjs.com>
Co-authored-by: Yaël GUILLOUX <yael.guilloux@gmail.com>
  • Loading branch information
3 people authored Jul 12, 2021
1 parent 98c3603 commit dc0caa0
Show file tree
Hide file tree
Showing 22 changed files with 575 additions and 99 deletions.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"defu": "^5.0.0",
"detab": "^3.0.0",
"directory-tree": "^2.2.9",
"fast-glob": "^3.2.6",
"flat": "^5.0.2",
"graceful-fs": "^4.2.6",
"gray-matter": "^4.0.3",
Expand All @@ -90,7 +91,7 @@
"nuxt-extend": "^0.1.0",
"nuxt-i18n": "^6.27.2",
"nuxt-vite": "^0.1.1",
"nuxt-windicss": "1.1.3",
"nuxt-windicss": "^1.1.3",
"ohmyfetch": "^0.1.8",
"plausible-tracker": "^0.3.1",
"prism-theme-vars": "^0.2.2",
Expand Down Expand Up @@ -122,7 +123,8 @@
"vue-docgen-api": "^4.40.0",
"vue-plausible": "^1.1.4",
"vue3": "npm:vue@next",
"vue3-router": "npm:vue-router@next"
"vue3-router": "npm:vue-router@next",
"windicss-analysis": "^0.3.4"
},
"devDependencies": {
"@iconify/json": "^1.1.372",
Expand Down
65 changes: 65 additions & 0 deletions src/admin/api/functions/components.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { promises as fs } from 'fs'
import { join, extname } from 'path'
import { createError, Middleware, useBody } from 'h3'
import dirTree from 'directory-tree'
import { FileData, File } from '../../type'
import { normalizeFiles, r } from '../utils'

export default <Middleware>async function componentsHandler(req) {
const url = req.url

if (req.method === 'GET') {
// List all files in components/
if (url === '/') {
const tree = dirTree(r('components'))
return normalizeFiles(tree.children, r('components'))
}
// Read a single content file
try {
const path = join(r('components'), url)
const file = await fs.readFile(path, 'utf-8')

return <File>{
path: path.replace(r('components'), ''),
extension: extname(path),
raw: file
}
} catch (err) {
return createError({
statusCode: 404,
statusMessage: 'File not found'
})
}
}

// Update changes
if (req.method === 'PUT') {
const { raw } = await useBody<FileData>(req)
if (raw == null) {
return createError({
statusCode: 400,
statusMessage: '"raw" key is required'
})
}

const path = join(r('components'), url)

try {
// @ts-ignore
// await fs.stat(path, 'utf-8')
await fs.writeFile(path, raw)

return { ok: true }
} catch (err) {
return createError({
statusCode: 404,
statusMessage: 'File not found'
})
}
}

return createError({
statusCode: 400,
statusMessage: 'Bad Request'
})
}
49 changes: 49 additions & 0 deletions src/admin/api/functions/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { parse } from 'path'
import fs from 'fs-extra'
import { createError, Middleware, useBody } from 'h3'
import { FileData } from '../../type'
import { r } from '../utils'

export default <Middleware>async function configHandler(req) {
const root = r()
let path = [r('nuxt.config.ts'), r('nuxt.config.js')].find(i => fs.existsSync(i))
const exist = Boolean(path)
path = path || r('nuxt.config.ts')

if (req.method === 'GET') {
// Get config file
return {
path: path.replace(root, ''),
exist,
extension: parse(path).ext,
raw: exist ? await fs.readFile(path, 'utf-8') : ''
}
}

// Update config
if (req.method === 'PUT') {
const { raw } = await useBody<FileData>(req)
if (raw == null) {
return createError({
statusCode: 400,
statusMessage: '"raw" key is required'
})
}

try {
await fs.writeFile(path, raw)

return { ok: true }
} catch (err) {
return createError({
statusCode: 404,
statusMessage: 'File not found'
})
}
}

return createError({
statusCode: 400,
statusMessage: 'Bad Request'
})
}
4 changes: 4 additions & 0 deletions src/admin/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ import { createApp } from 'h3'
import contentHandler from './functions/content'
import previewHandler from './functions/preview'
import staticHandler from './functions/static'
import componentsHandler from './functions/components'
import configHandler from './functions/config'

const app = createApp()

app.useAsync('/content', contentHandler)
app.useAsync('/preview', previewHandler)
app.useAsync('/static', staticHandler)
app.useAsync('/components', componentsHandler)
app.useAsync('/config', configHandler)

export default app._handle
26 changes: 25 additions & 1 deletion src/admin/app/components/AppHeaderNav.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,35 @@
<RouterLink
to="/static"
class="relative flex-none px-4 py-1 text-sm font-medium leading-5 d-border border rounded-md"
:class="[$route.path.includes('static') ? 'router-link-active' : '']"
:class="[$route.path.startsWith('/static') ? 'router-link-active' : '']"
>
Static
</RouterLink>

<RouterLink
to="/components"
class="relative flex-none px-4 py-1 text-sm font-medium leading-5 d-border border rounded-md"
:class="[$route.path.startsWith('/components') ? 'router-link-active' : '']"
>
Components
</RouterLink>

<RouterLink
to="/config"
class="relative flex-none px-4 py-1 text-sm font-medium leading-5 d-border border rounded-md"
:class="[$route.path.startsWith('/config') ? 'router-link-active' : '']"
>
Config
</RouterLink>

<RouterLink
to="/windicss"
class="relative flex-none px-4 py-1 text-sm font-medium leading-5 d-border border rounded-md"
:class="[$route.path.startsWith('/windicss') ? 'router-link-active' : '']"
>
Windi Analyzer
</RouterLink>

<div class="flex-auto"></div>

<button class="p-1 opacity-50 hover:opacity-100 !outline-none" @click="toggleDark">
Expand Down
17 changes: 14 additions & 3 deletions src/admin/app/components/Editor.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<template>
<Monaco :value="raw" language="markdown" @change="update" />
<Monaco :value="raw" :language="language" @change="update" />
</template>

<script setup lang="ts">
import type { PropType } from 'vue3'
import { ref, watch, defineProps } from 'vue3'
import { ref, watch, defineProps, computed } from 'vue3'
import type { File } from '../../type'
import { useApi } from '../plugins/api'
import Monaco from './Monaco.vue'
Expand All @@ -13,12 +13,23 @@ const props = defineProps({
file: {
type: Object as PropType<File>,
required: true
},
apiEntry: {
type: String,
default: '/content'
}
})
const api = useApi()
const raw = ref(props.file.raw)
const language = computed(() => {
if (props.file.extension === '.vue') return 'html'
if (props.file.extension === '.ts') return 'javascript'
if (props.file.extension === '.js') return 'javascript'
return 'markdown'
})
// Sync local data when file changes
watch(
() => props.file,
Expand All @@ -28,7 +39,7 @@ watch(
)
function update(content) {
api.put(`/content${props.file.path}`, {
api.put(props.apiEntry + props.file.path, {
raw: content
})
}
Expand Down
18 changes: 16 additions & 2 deletions src/admin/app/components/Preview.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { onBeforeMount, ref, watch } from 'vue3'
import { onPreviewNavigated } from '../composables/content'
import { fetchPreviewOrigin, previewOrigin, previewUrl, previewPath } from '../composables/preview'
const iframe = ref<HTMLIFrameElement>()
Expand All @@ -18,9 +19,10 @@ watch(
function updateIframe(url: string) {
if (!iframe.value) return
if (!url.startsWith(previewOrigin.value)) return updateIframeHard(url)
if (!url.startsWith(previewOrigin.value) || previewPath.value.startsWith('/admin')) return updateIframeHard(url)
try {
// use nuxt router
iframe.value.contentWindow.$nuxt.$router.push(previewPath.value)
} catch (e) {
// fallback to hard refresh when working with cross-origin
Expand All @@ -41,6 +43,18 @@ function onUrlInput() {
updateIframe(url.value)
}
function onIframeLoad() {
try {
iframe.value.contentWindow.$nuxt.$router.afterEach(to => {
previewPath.value = to.path
onPreviewNavigated(to.path)
})
} catch (e) {
// eslint-disable-next-line no-console
console.warn(e)
}
}
</script>

<template>
Expand All @@ -58,6 +72,6 @@ function onUrlInput() {
<heroicons-outline:external-link class="m-auto" />
</a>
</div>
<iframe ref="iframe" :src="previewOrigin" class="w-full h-full" />
<iframe ref="iframe" :src="previewOrigin" class="w-full h-full" @load="onIframeLoad" />
</div>
</template>
37 changes: 37 additions & 0 deletions src/admin/app/composables/content.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ref } from 'vue3'
import { useApi } from '../plugins/api'
import { File } from '../../type'
import { getRoutePath, navigateToFile } from './preview'

const api = useApi()

export const files = ref<File[]>([])
export const currentFile = ref(null)

export const openFile = async (file: File) => {
if (currentFile.value?.path === file.path) return

navigateToFile(file.path)
currentFile.value = await api.get(`/content${file.path}`)
}

export async function fetchFiles() {
files.value = await api.get('/content')
}

export function findFileFromRoute(routePath: string, fileList: File[] = files.value) {
for (const file of fileList) {
if (getRoutePath(file.path) === routePath) {
return file
}
if (file.children) {
const result = findFileFromRoute(routePath, file.children)
if (result) return result
}
}
}

export function onPreviewNavigated(routePath: string) {
const file = findFileFromRoute(routePath)
if (file) openFile(file)
}
5 changes: 2 additions & 3 deletions src/admin/app/composables/monaco.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ const setupMonaco = createSingletonPromise(async () => {
getWorker(_: any, label: string) {
if (label === 'json') return new JsonWorker()
if (label === 'css' || label === 'scss' || label === 'less') return new CssWorker()
if (label === 'html' || label === 'handlebars' || label === 'razor') return new HtmlWorker()
if (label === 'html' || label === 'handlebars' || label === 'razor' || label === 'vue')
return new HtmlWorker()
if (label === 'typescript' || label === 'javascript') return new TsWorker()
return new EditorWorker()
}
Expand Down Expand Up @@ -110,10 +111,8 @@ export function useMonaco(

isSetup.value = true

// const plugins = editorPlugins.filter(({ language }) => language === options.language)
editor.getModel()?.onDidChangeContent(() => {
options.onChanged?.(editor.getValue())
// plugins.forEach(({ onContentChanged }) => onContentChanged(editor))
})
},
{
Expand Down
10 changes: 7 additions & 3 deletions src/admin/app/composables/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@ export async function fetchPreviewOrigin() {
previewOrigin.value = url
}

export function navigateToFile(filepath: string) {
previewPath.value = filepath
.replace(/\/\d+\./g, '/')
export function getRoutePath(filepath: string) {
return filepath
.replace(/\/\d+\./g, '/') // remove digit index
.replace(/\.md$/, '')
.replace(/\/index$/, '/')
}

export function navigateToFile(filepath: string) {
previewPath.value = getRoutePath(filepath)
}
Loading

0 comments on commit dc0caa0

Please sign in to comment.