Files
test-repo/docs/.vitepress/theme/components/appendix/terminal-intro/InputVisualizer.vue
T
sanbuphy d35211071a style: update border-radius and padding values across components
- standardize border-radius from 8px to 6px for consistent styling
- adjust padding values from 1rem to 0.75rem for better visual hierarchy
- remove redundant overflow-y properties for cleaner code
2026-02-14 20:23:34 +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: 6px;
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: 6px;
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>