Files
test-repo/docs/.vitepress/theme/components/appendix/terminal-intro/InputVisualizer.vue
T
sanbuphy 73f4788d7e feat: comprehensive documentation and demo updates
- Update READMEs and docs across multiple languages
- Enhance interactive demos for Agent, LLM, VLM, Audio, Image Gen, Terminal, and Web Basics
- Add new appendix sections for Database and IDE intros
- Update VitePress config, theme, and utility scripts
- Clean up unused assets and components
2026-01-16 19:10:51 +08:00

307 lines
6.7 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
InputVisualizer.vue
输入可视化组件
用途
展示键盘输入在底层是如何被转换为字节流发送给终端的
纠正按键直接上屏的误区强调按键 -> 编码 -> 发送的过程
交互功能
- 键盘监听捕获用户的真实按键
- 数据展示同时显示按键名16进制字节码和转义序列如方向键
- 历史记录记录最近几次按键的编码流
-->
<template>
<div
class="input-visualizer"
tabindex="0"
@keydown="handleKeydown"
@blur="handleBlur"
>
<div class="focus-overlay" v-if="!isFocused" @click="focus">
<div class="focus-btn">
<span class="icon"></span>
<span>Click to Type</span>
</div>
</div>
<div class="main-display" :class="{ 'blur-content': !isFocused }">
<div class="key-name">{{ currentKey.name || 'Press any key' }}</div>
<div class="info-grid">
<div class="info-box">
<div class="label">BYTES (HEX)</div>
<div class="value highlight">{{ currentKey.bytes || '-' }}</div>
</div>
<div class="info-box">
<div class="label">SEQUENCE</div>
<div class="value code">{{ currentKey.sequence || '-' }}</div>
</div>
</div>
<div class="char-display">
Character:
<span class="char-val">{{ currentKey.charDisplay || '-' }}</span>
</div>
</div>
<div class="history-strip">
<div v-for="(item, i) in history" :key="i" class="history-item">
<span class="h-name">{{ item.name }}</span>
<span class="arrow"></span>
<span class="h-bytes">{{ item.bytes }}</span>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const isFocused = ref(false)
const currentKey = ref({ name: '', bytes: '', sequence: '', charDisplay: '' })
const history = ref([])
const focus = (e) => {
// Find the parent .input-visualizer and focus it
const container = e.currentTarget.closest('.input-visualizer')
if (container) {
container.focus()
isFocused.value = true
}
}
const handleBlur = () => {
isFocused.value = false
}
const handleKeydown = (e) => {
e.preventDefault()
let name = e.key
let bytes = ''
let sequence = ''
let charDisplay = e.key
// Map special keys
const keyMap = {
' ': { name: 'Space', bytes: '20', char: ' ' },
Enter: { name: 'Enter', bytes: '0a', char: '\\n' },
Tab: { name: 'Tab', bytes: '09', char: '\\t' },
Escape: { name: 'Esc', bytes: '1b', char: '\\e' },
Backspace: { name: 'Backspace', bytes: '7f', char: '\\b' },
Delete: { name: 'Del', bytes: '1b 5b 33 7e', sequence: '^[[3~' },
ArrowUp: { name: 'Arrow Up', bytes: '1b 5b 41', sequence: '^[[A' },
ArrowDown: { name: 'Arrow Down', bytes: '1b 5b 42', sequence: '^[[B' },
ArrowRight: { name: 'Arrow Right', bytes: '1b 5b 43', sequence: '^[[C' },
ArrowLeft: { name: 'Arrow Left', bytes: '1b 5b 44', sequence: '^[[D' }
}
if (keyMap[e.key]) {
const map = keyMap[e.key]
name = map.name
bytes = map.bytes
sequence = map.sequence || ''
charDisplay = map.char || map.name
} else if (e.key.length === 1) {
// Printable characters
const code = e.key.charCodeAt(0)
bytes = code.toString(16).toLowerCase().padStart(2, '0')
if (e.ctrlKey) {
// Ctrl + Letter
name = `Ctrl+${e.key.toUpperCase()}`
const ctrlCode = code >= 97 && code <= 122 ? code - 96 : code
bytes = ctrlCode.toString(16).toLowerCase().padStart(2, '0')
sequence = '^' + e.key.toUpperCase()
charDisplay = sequence
}
} else {
// Other special keys
name = e.key
charDisplay = e.key
}
const keyData = { name, bytes, sequence, charDisplay }
currentKey.value = keyData
history.value.unshift(keyData)
if (history.value.length > 5) history.value.pop()
}
</script>
<style scoped>
.input-visualizer {
position: relative;
background: #09090b; /* Slightly lighter than pure black */
border: 1px solid #27272a;
border-radius: 12px;
padding: 30px 20px;
text-align: center;
font-family: 'JetBrains Mono', 'Menlo', monospace;
outline: none;
min-height: 320px;
display: flex;
flex-direction: column;
justify-content: space-between;
overflow: hidden;
transition:
border-color 0.2s,
box-shadow 0.2s;
}
.input-visualizer:focus {
border-color: #10b981; /* Emerald 500 */
box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.2);
}
.focus-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(2px);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 10;
transition: all 0.2s;
}
.focus-overlay:hover {
background: rgba(0, 0, 0, 0.3);
}
.focus-btn {
background: #10b981;
color: #fff;
padding: 12px 24px;
border-radius: 8px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
transition: transform 0.1s;
}
.focus-btn:hover {
transform: translateY(-1px);
}
.focus-btn:active {
transform: translateY(1px);
}
.main-display {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
transition:
opacity 0.2s,
filter 0.2s;
}
.blur-content {
opacity: 0.5;
filter: blur(1px);
}
.key-name {
font-size: 36px;
font-weight: 700;
color: #e4e4e7; /* Zinc 200 */
margin-bottom: 30px;
height: 50px;
line-height: 50px;
}
.info-grid {
display: flex;
justify-content: center;
gap: 24px;
margin-bottom: 30px;
width: 100%;
}
.info-box {
background: #18181b; /* Zinc 900 */
padding: 16px 20px;
border-radius: 8px;
min-width: 140px;
border: 1px solid #27272a;
}
.label {
color: #71717a; /* Zinc 500 */
font-size: 11px;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 8px;
}
.value {
font-size: 24px;
font-weight: 500;
font-variant-numeric: tabular-nums;
}
.highlight {
color: #facc15; /* Yellow 400 */
}
.code {
color: #22d3ee; /* Cyan 400 */
}
.char-display {
color: #a1a1aa; /* Zinc 400 */
font-size: 14px;
}
.char-val {
color: #fff;
font-weight: bold;
background: #27272a;
padding: 2px 6px;
border-radius: 4px;
margin-left: 5px;
}
.history-strip {
display: flex;
gap: 12px;
justify-content: center;
border-top: 1px solid #27272a;
padding-top: 20px;
margin-top: 20px;
flex-wrap: wrap;
}
.history-item {
display: flex;
align-items: center;
background: #18181b;
padding: 6px 12px;
border-radius: 6px;
font-size: 12px;
color: #a1a1aa;
border: 1px solid #27272a;
}
.arrow {
color: #71717a; /* Lighter grey for better visibility */
margin: 0 8px;
}
.h-name {
font-weight: 500;
color: #e4e4e7;
}
.h-bytes {
color: #facc15;
font-family: monospace;
}
</style>