Refine service information and simplify device controls

This commit is contained in:
2026-10-12 18:26:01 +09:00
parent 4f0231da3d
commit b73f929c77
5 changed files with 101 additions and 21 deletions

View File

@@ -23,7 +23,6 @@ import {
ShieldCheck,
ChevronDown,
LoaderCircle,
RefreshCw,
ArrowDownLeft,
CircleHelp,
File as FileIcon,
@@ -33,8 +32,11 @@ import {
EllipsisVertical,
Ellipsis,
Info,
MonitorSmartphone,
Globe,
} from '@lucide/vue';
import QRCode from 'qrcode';
import { version as appVersion } from '../package.json';
import AccountPanel from './AccountPanel.vue';
import SendComposer from './SendComposer.vue';
import InstallButton from './InstallButton.vue';
@@ -800,9 +802,6 @@ async function spaceAction(space, action, memberId) {
{{ spaces.length ? '방 안의 기기' : '주변 기기' }}
</h2>
</div>
<button class="icon-button" @click="act(refresh)" aria-label="기기 목록 새로고침">
<RefreshCw :size="17" />
</button>
</div>
<div v-if="!connectionCards.length" class="empty-devices">
<Monitor :size="30" stroke-width="1.4" />
@@ -846,10 +845,10 @@ async function spaceAction(space, action, memberId) {
>
<span class="peer-top"
><component :is="iconFor(card.peer.name)" :size="30" /><span
v-if="card.pending"
class="peer-live"
:class="{ hasMenu: managedSpaces(card.peer.id).length }"
><template v-if="card.pending">승인 대기</template
><template v-else><i></i>온라인</template></span
>승인 대기</span
></span
><strong>{{ deviceLabel(card.peer) }}</strong
><span v-if="deviceDetail(card.peer)" class="peer-model">{{
@@ -1130,13 +1129,38 @@ async function spaceAction(space, action, memberId) {
</template>
<template v-else-if="modal === 'about'">
<div class="service-info">
<strong>hop.</strong>
<p>기기 사이를 가볍게.</p>
<p>
파일과 링크를 다른 기기로 보낼 수 있어요.<br />보관함에 저장한 자료는 최대 7일 후
삭제됩니다.
</p>
<small>by noname.company</small>
<strong class="service-brand">hop.</strong>
<p class="service-tagline">기기 사이를 가볍게.</p>
<div class="service-intro">
<p>Android, iOS, macOS, Windows.</p>
<p>서로 다른 운영체제에서도 파일과 링크를 자유롭게 주고받으세요.</p>
</div>
<ul class="service-features">
<li>
<MonitorSmartphone :size="20" aria-hidden="true" />
<div>
<strong>기기 구분 없이</strong>
<p>스마트폰과 태블릿, PC를 자유롭게 연결</p>
</div>
</li>
<li>
<Globe :size="20" aria-hidden="true" />
<div>
<strong>설치 없이 간편하게</strong>
<p>회원가입 없이 브라우저에서 바로 전송</p>
</div>
</li>
<li>
<QrCode :size="20" aria-hidden="true" />
<div>
<strong>네트워크가 달라도</strong>
<p>QR 또는 초대 코드로 다른 기기와 연결</p>
</div>
</li>
</ul>
<footer class="service-info-footer">
<small>by noname.company</small><small>v{{ appVersion }}</small>
</footer>
</div>
</template>
<template v-else

View File

@@ -164,7 +164,7 @@ button:active:not(:disabled) {
}
.header-actions {
display: flex;
gap: 20px;
gap: 8px;
align-items: center;
margin-left: auto;
}
@@ -3252,7 +3252,12 @@ a.button {
.modal .create-space {
display: flex;
flex-direction: column;
gap: 18px;
margin-top: 18px;
gap: 14px;
}
.modal .join-form .modal-subtitle,
.modal .create-space .modal-subtitle {
margin: 0;
}
.modal .join-form input {
width: 100%;
@@ -3440,16 +3445,58 @@ a.button {
.service-info {
padding: 24px 0 4px;
font-size: 13px;
line-height: 1.8;
}
.service-info strong {
.service-brand {
font-size: 30px;
letter-spacing: -0.07em;
}
.service-info p {
margin: 12px 0;
color: var(--muted);
margin: 0;
}
.service-info small {
.service-info .service-tagline {
color: var(--muted);
margin-top: 2px;
}
.service-intro {
margin: 24px 0;
}
.service-intro p + p {
margin-top: 8px;
}
.service-features {
display: flex;
flex-direction: column;
gap: 20px;
list-style: none;
padding: 0;
margin: 0;
}
.service-features li {
display: flex;
gap: 14px;
align-items: flex-start;
}
.service-features svg {
color: var(--muted);
flex-shrink: 0;
margin-top: 3px;
}
.service-features strong {
font-size: 14px;
font-weight: 500;
}
.service-features p {
font-size: 12px;
color: var(--muted);
margin-top: 5px;
}
.service-info-footer {
border-top: 1px solid var(--line);
padding-top: 18px;
margin-top: 28px;
display: flex;
justify-content: space-between;
gap: 16px;
color: var(--muted);
font-size: 12px;
}

View File

@@ -109,3 +109,7 @@
- 최신 Docker 이미지 빌드 및 재시작 후 DB·파일·세션·미완료 분할 업로드 복구 검증 통과. NAS 원격 배포는 수행하지 않았다.
- 헤더 보조 기능은 로그인/보관함 뒤 더보기로 모았다. 메뉴의 앱 설치·테마·서비스 정보, 바깥 클릭/Escape 닫힘과 방향키 이동, 모바일 메뉴 경계를 검증했다. 메뉴가 닫혀도 설치 이벤트 수신을 유지해 PWA 설치 기능이 사라지지 않도록 했다.
- 서비스 정보는 hop. 제목을 유지하고 운영체제 설명과 아이콘 목록으로 정리했다. 제작사명은 왼쪽, 웹 패키지 버전(v0.1.0)은 오른쪽에 표시한다. PC·모바일 화면을 확인했다. 연결 참여/만들기 폼의 설명 여백과 간격을 줄이고 기기 카드의 온라인 표시는 제거했으며 승인 대기 표시는 유지했다.
- 기기 목록의 수동 새로고침 버튼을 제거했다. 접속 이벤트에 따른 목록 자동 갱신은 유지한다. 사용자가 수정한 헤더 버튼 간격과 서비스 정보 줄 간격도 함께 반영했다.

View File

@@ -67,7 +67,7 @@ try {
assert.equal(await guestCard.locator('.peer-card').isDisabled(), true);
await guestCard.getByRole('button', { name: '외부 기기 기기 더보기' }).click();
await guestCard.getByRole('menuitem', { name: '참여 승인', exact: true }).click();
await guestCard.getByText('온라인', { exact: true }).waitFor();
await guestCard.getByText('승인 대기', { exact: true }).waitFor({ state: 'hidden' });
assert.equal(await guestCard.locator('.peer-card').isEnabled(), true);
const hostCard = guest.locator('.peer-entry').filter({ hasText: '방장 Mac' });
await hostCard.locator('.host-badge').waitFor();

View File

@@ -65,6 +65,10 @@ try {
await page.getByRole('menuitem', { name: '서비스 정보', exact: true }).click();
await page.getByRole('dialog', { name: '서비스 정보', exact: true }).waitFor();
assert.equal(await more.getAttribute('aria-expanded'), 'false');
await page.screenshot({ path: 'tests/e2e/service-info-desktop.png' });
await page.setViewportSize({ width: 390, height: 844 });
await page.screenshot({ path: 'tests/e2e/service-info-mobile.png' });
await page.setViewportSize({ width: 1280, height: 1000 });
await page.getByRole('button', { name: '대화상자 닫기', exact: true }).click();
assert.equal(await page.getByText('어디로 보낼까요?', { exact: true }).count(), 0);
@@ -288,6 +292,7 @@ try {
await page.getByRole('button', { name: '연결 공간 만들기', exact: true }).click();
await page.getByRole('checkbox', { name: '참여할 때 내 승인 받기' }).waitFor();
assert.equal(await page.getByLabel('연결 코드', { exact: true }).count(), 0);
await page.screenshot({ path: 'tests/e2e/connection-create-mobile.png' });
await page.getByRole('button', { name: '만들기', exact: true }).click();
assert.match(await page.locator('.invite-code').innerText(), /^[A-HJ-NP-Z2-9]{4}$/);
assert.equal(await page.locator('.local-qr-note,.invite-display .spin').count(), 0);