Skip to content

Commit

Permalink
feat(logger): support infinite scroll (without fixed top)
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Jun 15, 2024
1 parent 258d854 commit 3dbe46a
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 70 deletions.
8 changes: 0 additions & 8 deletions packages/client/client/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ declare const CLIENT_CONFIG: ClientConfig
export const global = CLIENT_CONFIG

export const socket = ref<WebSocket>()
const listeners: Record<string, (data: any) => void> = {}

export function send<T extends keyof Events>(type: T, ...args: Parameters<Events[T]>): Promisify<ReturnType<Events[T]>>
export async function send(type: string, ...args: any[]) {
Expand All @@ -35,10 +34,6 @@ export async function send(type: string, ...args: any[]) {
return result
}

export function receive<T = any>(event: string, listener: (data: T) => void) {
listeners[event] = listener
}

export function connect(ctx: Context, callback: () => WebSocket) {
const value = callback()

Expand Down Expand Up @@ -74,9 +69,6 @@ export function connect(ctx: Context, callback: () => WebSocket) {
if (data.type !== 'pong') {
console.debug('↓%c', 'color:purple', data.type, data.body)
}
if (data.type in listeners) {
listeners[data.type](data.body)
}
ctx.emit(data.type, data.body)
})

Expand Down
11 changes: 5 additions & 6 deletions packages/client/client/plugins/loader.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Ref, ref, shallowReactive } from 'vue'
import { Context } from '../context'
import { Service } from '../utils'
import { receive } from '../data'
import { ForkScope } from 'cordis'
import { defineProperty, Dict } from 'cosmokit'
import { Entry } from '@cordisjs/plugin-webui'
Expand Down Expand Up @@ -72,22 +71,22 @@ export default class LoaderService extends Service {
constructor(ctx: Context) {
super(ctx, '$loader', true)

receive('entry:refresh', ({ id, data }) => {
ctx.on('entry:refresh', ({ id, data }) => {
const entry = this.entries[id]
if (!entry) return
entry.data.value = data
})

receive('entry:patch', ({ id, data, key }) => {
ctx.on('entry:patch', ({ id, data, key }) => {
const entry = this.entries[id]
if (!entry) return
let node = entry.data.value
const parts: string[] = key ? key.split('.') : []
while (parts.length) {
const part = parts.shift()!
node = node[part]
node = node[part] ?? (parts.length || !Array.isArray(data) ? {} : [])
}
if (Array.isArray(node)) {
if (Array.isArray(data)) {
node.push(...data)
} else {
Object.assign(node, data)
Expand All @@ -96,7 +95,7 @@ export default class LoaderService extends Service {
}

initTask = new Promise<void>((resolve) => {
receive('entry:init', async (value) => {
this.ctx.on('entry:init', async (value) => {
const { _id, ...rest } = value as Dict<Entry.Data> & { _id?: string }
if (this.id && _id && this.id !== _id as unknown) {
return window.location.reload()
Expand Down
21 changes: 13 additions & 8 deletions packages/components/client/virtual/list.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

<script lang="ts" setup>
import { ref, computed, watch, nextTick, onActivated, onMounted, PropType } from 'vue'
import { ref, computed, watch, nextTick, onActivated, onMounted, onUpdated, PropType } from 'vue'
import type { ElScrollbar } from 'element-plus'
import Virtual from './virtual'
import VirtualItem from './item'
Expand Down Expand Up @@ -91,6 +91,7 @@ onMounted(() => {
} else {
scrollToBottom()
}
checkBoundary()
})
function scrollToOffset(offset: number, smooth = false) {
Expand Down Expand Up @@ -140,22 +141,26 @@ function onScroll(ev: MouseEvent) {
const clientLength = Math.ceil(root.value.wrapRef.clientHeight)
const scrollLength = Math.ceil(root.value.wrapRef.scrollHeight)
// iOS scroll-spring-back behavior will make direction mistake
if (offset < 0 || (offset + clientLength > scrollLength + 1) || !scrollLength) {
return
}
virtual.handleScroll(offset)
emitEvent(offset, clientLength, scrollLength, ev)
}
function emitEvent(offset: number, clientLength: number, scrollLength: number, ev: MouseEvent) {
emit('scroll', ev, virtual.range)
if (virtual.direction < 0 && !!props.data.length && (offset - props.threshold <= 0)) {
checkBoundary(true)
}
function checkBoundary(directed = false) {
const offset = Math.ceil(root.value.wrapRef.scrollTop)
const clientLength = Math.ceil(root.value.wrapRef.clientHeight)
const scrollLength = Math.ceil(root.value.wrapRef.scrollHeight)
if ((!directed || virtual.direction < 0 && !!props.data.length) && (offset - props.threshold <= 0)) {
emit('top')
} else if (virtual.direction > 0 && (offset + clientLength + props.threshold >= scrollLength)) {
} else if ((!directed || virtual.direction > 0) && (offset + clientLength + props.threshold >= scrollLength)) {
emit('bottom')
}
}
onUpdated(checkBoundary)
</script>
27 changes: 18 additions & 9 deletions plugins/logger/client/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Context } from '@cordisjs/client'
import type { Logger } from 'cordis'
import {} from '../src'
import Logs from './index.vue'
import Settings from './settings.vue'
Expand All @@ -9,7 +9,7 @@ export const inject = {
manager: false,
}

export function apply(ctx: Context) {
export function apply(ctx: Context, data: Ref<Dict<Logger.Record[]>>) {
ctx.page({
path: '/logs',
name: '日志',
Expand All @@ -24,12 +24,21 @@ export function apply(ctx: Context) {
order: -800,
})

// this.subroute({
// path: 'logs',
// title: '日志',
// component: ServicesPage,
// hidden: (entry) => {
// return !this.data.value.packages[entry.name]?.runtime
// },
// ctx.inject(['manager'], (ctx) => {
// ctx.manager.subroute({
// path: 'logs',
// title: '日志',
// component: Settings,
// hidden: (entry) => {
// let last = Infinity
// for (let index = data.value.length - 1; index > 0; --index) {
// if (data.value[index].id >= last) break
// last = data.value[index].id
// if (!data.value[index].meta.paths?.includes(entry.id)) continue
// return false
// }
// return true
// },
// })
// })
}
7 changes: 4 additions & 3 deletions plugins/logger/client/index.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
<template>
<k-layout>
<logs class="layout-logger" :logs="data" show-link></logs>
<logs class="layout-logger" :logs="flattenRecords(data)" show-link></logs>
</k-layout>
</template>

<script lang="ts" setup>
import { useRpc } from '@cordisjs/client'
import { useRpc, Dict } from '@cordisjs/client'
import type { Logger } from 'cordis'
import Logs from './logs.vue'
import { flattenRecords } from './utils'
const data = useRpc<Logger.Record[]>()
const data = useRpc<Dict<Logger.Record[]>>()
</script>
25 changes: 19 additions & 6 deletions plugins/logger/client/logs.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
<template>
<virtual-list class="log-list k-text-selectable" :data="logs" :count="300" :max-height="maxHeight">
<virtual-list
class="log-list k-text-selectable"
:data="logs" :count="300"
:max-height="maxHeight"
@top="onTop"
>
<template #="record">
<div :class="{ line: true, start: isStart(record) }">
<code v-html="renderLine(record)"></code>
<!-- FIXME: ctx.manager is not reactive -->
<router-link
class="log-link inline-flex items-center justify-center absolute w-20px h-20px bottom-0 right-0"
class="log-link inline-flex items-center justify-center absolute w-20px h-20px bottom-0 right-1"
v-if="showLink && ctx.manager && record.meta?.paths?.length"
:to="'/plugins/' + record.meta.paths[0].replace(/\./, '/')"
>
Expand All @@ -18,7 +22,7 @@

<script lang="ts" setup>
import { Time, VirtualList, useContext } from '@cordisjs/client'
import { Dict, Time, VirtualList, useContext, useRpc, send } from '@cordisjs/client'
import {} from '@cordisjs/plugin-manager/client'
import Logger from 'reggol'
import AnsiUp from 'ansi_up'
Expand All @@ -30,6 +34,7 @@ const props = defineProps<{
}>()
const ctx = useContext()
const data = useRpc<Dict<Logger.Record[] | null>>()
const converter = new AnsiUp()
Expand All @@ -40,7 +45,7 @@ function renderColor(code: number, value: any, decoration = '') {
const showTime = 'yyyy-MM-dd hh:mm:ss'
function isStart(record: Logger.Record & { index: number }) {
return record.index && props.logs[record.index - 1].id > record.id && record.name === 'app'
return record.index && props.logs[record.index - 1].id > record.id
}
function renderLine(record: Logger.Record) {
Expand All @@ -57,6 +62,14 @@ function renderLine(record: Logger.Record) {
return converter.ansi_to_html(output)
}
async function onTop() {
const keys = Object.keys(data.value).filter(key => !data.value[key]).sort((a, b) => {
return a.slice(0, 11).localeCompare(b.slice(0, 11)) || +a.slice(11) - +b.slice(11)
}).reverse()
if (!keys.length) return
data.value[keys[0]] = await send('log.read', { name: keys[0] })
}
</script>

<style lang="scss" scoped>
Expand Down Expand Up @@ -92,7 +105,7 @@ function renderLine(record: Logger.Record) {
.line {
padding: 0 0.5rem;
border-radius: 2px;
border-radius: 4px;
font-size: 14px;
line-height: 20px;
white-space: pre-wrap;
Expand Down
18 changes: 6 additions & 12 deletions plugins/logger/client/settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,20 @@

<script setup lang="ts">
import { useRpc, useContext } from '@cordisjs/client'
import { Dict, useRpc, useContext } from '@cordisjs/client'
import { computed } from 'vue'
import {} from '@cordisjs/plugin-manager/client'
import type { Logger } from 'cordis'
import { flattenRecords } from './utils'
import Logs from './logs.vue'
const data = useRpc<Logger.Record[]>()
const data = useRpc<Dict<Logger.Record[] | null>>()
const ctx = useContext()
const logs = computed(() => {
if (!data.value) return []
const results: Logger.Record[] = []
let last = Infinity
for (let index = data.value.length - 1; index > 0; --index) {
if (data.value[index].id >= last) break
last = data.value[index].id
if (!data.value[index].meta?.paths?.includes(ctx.manager?.currentEntry?.id)) continue
results.unshift(data.value[index])
}
return results
return flattenRecords(data.value).filter(record => {
return record.meta.paths?.includes(ctx.manager?.currentEntry?.id!)
})
})
</script>
Expand Down
8 changes: 8 additions & 0 deletions plugins/logger/client/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Dict } from '@cordisjs/client'

export function flattenRecords<T>(data: Dict<T[] | null>) {
const keys = Object.keys(data).sort((a, b) => {
return a.slice(0, 11).localeCompare(b.slice(0, 11)) || +a.slice(11) - +b.slice(11)
})
return keys.flatMap(key => data[key] ?? [])
}
10 changes: 5 additions & 5 deletions plugins/logger/src/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import { FileHandle, open } from 'fs/promises'
import { Logger } from 'cordis'
import { Buffer } from 'buffer'

export class FileWriter {
export class LogFile {
public data: Logger.Record[] = []
public task: Promise<FileHandle>
public size: number = 0

private temp: Logger.Record[] = []

constructor(public date: string, public path: string) {
constructor(public date: string, public name: string, public path: string) {
this.task = open(path, 'a+').then(async (handle) => {
const buffer = await handle.readFile()
this.data = this.parse(new TextDecoder().decode(buffer))
this.data = LogFile.parse(new TextDecoder().decode(buffer))
this.size = buffer.byteLength
return handle
})
Expand All @@ -31,8 +31,8 @@ export class FileWriter {
})
}

parse(text: string) {
return text.split('\n').map((line) => {
static parse(text: string) {
return text.split('\n').map<Logger.Record>((line) => {
try {
return JSON.parse(line)
} catch {}
Expand Down
Loading

0 comments on commit 3dbe46a

Please sign in to comment.