WIP 실시간 동기화 화면 (관리자 지시 10/20, 28 지적): 경고 글 안의 옵션 (--filter=H a.txt) 을 줄바꿈 없이 묶음 · 좁은 화면에서는 카드의 "rsync · 복사로 맞춤 · 지우기 포함" 칩을 경로 아래 한 줄로 (잘리던 것) (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>review_d2dc267
parent
b2c400a0fb
commit
0bd1466212
|
|
@ -21,7 +21,7 @@ import 'path_label.dart';
|
||||||
import 'rsync_setup.dart';
|
import 'rsync_setup.dart';
|
||||||
import 'schedule_editor.dart';
|
import 'schedule_editor.dart';
|
||||||
import '../core/sync_preview.dart' show optionsRemoveSource;
|
import '../core/sync_preview.dart' show optionsRemoveSource;
|
||||||
import 'sync_preview_view.dart' show FilterLimitNote, confirmDeletingRun, effectiveRunText;
|
import 'sync_preview_view.dart' show FilterLimitNote, confirmDeletingRun, effectiveRunText, unbreakableOption;
|
||||||
import 'theme.dart';
|
import 'theme.dart';
|
||||||
|
|
||||||
/// 모니터링 (파일 탐색기 > 모니터링): 복사 · rsync 와 lsync (실시간 동기화) 를 따로 보여 준다.
|
/// 모니터링 (파일 탐색기 > 모니터링): 복사 · rsync 와 lsync (실시간 동기화) 를 따로 보여 준다.
|
||||||
|
|
@ -969,6 +969,18 @@ class _LiveTabState extends State<_LiveTab> {
|
||||||
final active = LiveSync.activeNow(x);
|
final active = LiveSync.activeNow(x);
|
||||||
final running = live?.isRunning(x) ?? false;
|
final running = live?.isRunning(x) ?? false;
|
||||||
final held = live?.isPaused(x) ?? false;
|
final held = live?.isPaused(x) ?? false;
|
||||||
|
// 257: 칩은 길게 누르기 · 마우스로 전체를 본다. 관리자 지시 (10/20, 28 지적): 좁은 화면 (폰 세로) 에서는 경로 줄에 끼면
|
||||||
|
// "rsync · 복…" 으로 잘려 경로 아래 따로 한 줄로 (넓으면 예전처럼 경로 옆)
|
||||||
|
final narrow = MediaQuery.sizeOf(context).width < 600;
|
||||||
|
final chipText = [tr(CopyMethod.of(x.method).label), tr('복사로 맞춤'), if (x.delete) tr('지우기 포함')].join(' · ');
|
||||||
|
final chip = Tooltip(
|
||||||
|
message: chipText,
|
||||||
|
child: Chip(
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
// 243: 실시간 동기화는 늘 복사로 맞춘다 (원본은 지우지 않음 - 원본을 비우려면 [최종 정리])
|
||||||
|
label: Text(chipText, maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||||
|
),
|
||||||
|
);
|
||||||
return Card(
|
return Card(
|
||||||
color: JjColors.panel,
|
color: JjColors.panel,
|
||||||
margin: const EdgeInsets.only(bottom: 10),
|
margin: const EdgeInsets.only(bottom: 10),
|
||||||
|
|
@ -1006,17 +1018,7 @@ class _LiveTabState extends State<_LiveTab> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// 257: 폰 세로에서 이 줄이 넘쳤다 - 칩은 남는 폭만큼 (넘치면 … 로 줄임, 전체는 길게 누르기 · 마우스)
|
// 257: 폰 세로에서 이 줄이 넘쳤다 - 칩은 남는 폭만큼 (넘치면 … 로 줄임, 전체는 길게 누르기 · 마우스)
|
||||||
Flexible(
|
if (!narrow) Flexible(child: chip),
|
||||||
child: Tooltip(
|
|
||||||
message: [tr(CopyMethod.of(x.method).label), tr('복사로 맞춤'), if (x.delete) tr('지우기 포함')].join(' · '),
|
|
||||||
child: Chip(
|
|
||||||
visualDensity: VisualDensity.compact,
|
|
||||||
// 243: 실시간 동기화는 늘 복사로 맞춘다 (원본은 지우지 않음 - 원본을 비우려면 [최종 정리])
|
|
||||||
label: Text([tr(CopyMethod.of(x.method).label), tr('복사로 맞춤'), if (x.delete) tr('지우기 포함')].join(' · '),
|
|
||||||
maxLines: 1, overflow: TextOverflow.ellipsis),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// 이번 실행 동안 멈춤 / 시작 (설정의 켜기 · 끄기는 오른쪽 스위치)
|
// 이번 실행 동안 멈춤 / 시작 (설정의 켜기 · 끄기는 오른쪽 스위치)
|
||||||
if (live != null && x.enabled)
|
if (live != null && x.enabled)
|
||||||
IconButton(
|
IconButton(
|
||||||
|
|
@ -1026,6 +1028,7 @@ class _LiveTabState extends State<_LiveTab> {
|
||||||
),
|
),
|
||||||
Switch(value: x.enabled, onChanged: (v) => _replace(x, x.copyWith(enabled: v))),
|
Switch(value: x.enabled, onChanged: (v) => _replace(x, x.copyWith(enabled: v))),
|
||||||
]),
|
]),
|
||||||
|
if (narrow) Padding(padding: const EdgeInsets.only(left: 32, top: 2), child: chip),
|
||||||
if (held)
|
if (held)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 32, top: 2),
|
padding: const EdgeInsets.only(left: 32, top: 2),
|
||||||
|
|
@ -1054,7 +1057,7 @@ class _LiveTabState extends State<_LiveTab> {
|
||||||
const Icon(Icons.block, size: 16, color: Colors.redAccent),
|
const Icon(Icons.block, size: 16, color: Colors.redAccent),
|
||||||
Text(tr('이 옵션의 일부 거르기 규칙은 미리 볼 수 없어 자동으로 지우지 않습니다'),
|
Text(tr('이 옵션의 일부 거르기 규칙은 미리 볼 수 없어 자동으로 지우지 않습니다'),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.redAccent, fontWeight: FontWeight.w600)),
|
style: const TextStyle(fontSize: 12, color: Colors.redAccent, fontWeight: FontWeight.w600)),
|
||||||
Text('(${live.noAutoDelete[k]!.join(' · ')})', style: const TextStyle(fontSize: 12, color: Colors.redAccent)),
|
Text('(${live.noAutoDelete[k]!.map(unbreakableOption).join(' · ')})', style: const TextStyle(fontSize: 12, color: Colors.redAccent)),
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onPressed: () => confirmLiveDeleteNow(context, live, x),
|
onPressed: () => confirmLiveDeleteNow(context, live, x),
|
||||||
child: Text(tr('지금 지우기 (확인)')),
|
child: Text(tr('지금 지우기 (확인)')),
|
||||||
|
|
|
||||||
|
|
@ -307,6 +307,10 @@ Future<bool> confirmDeletingRun(BuildContext context, CopyTask t, {bool includeM
|
||||||
|
|
||||||
/// 15 검토 F2 (177): rsync 옵션에 미리 볼 수 없는 거르기 규칙 (고치개 · H · P · merge · -F · -C · 읽지 못한 파일 등) 이 있으면
|
/// 15 검토 F2 (177): rsync 옵션에 미리 볼 수 없는 거르기 규칙 (고치개 · H · P · merge · -F · -C · 읽지 못한 파일 등) 이 있으면
|
||||||
/// 지울 목록이 실제보다 적게 보일 수 있다 - 빨갛게 알리고, 지우기 (--delete) 와 함께면 체크해야 [실행] 이 켜진다 (72 · 104 와 같게)
|
/// 지울 목록이 실제보다 적게 보일 수 있다 - 빨갛게 알리고, 지우기 (--delete) 와 함께면 체크해야 [실행] 이 켜진다 (72 · 104 와 같게)
|
||||||
|
/// 옵션 글 (예: --filter=H a.txt) 이 좁은 창에서 "--" 와 "filter=…" 로 갈리지 않게 줄바꿈 없이 묶는다 (관리자 지시 10/20, 28 지적):
|
||||||
|
/// 빈칸은 줄바꿈 없는 빈칸, - · / 뒤에는 줄바꿈을 막는 글자 (U+2060, 보이지 않음)
|
||||||
|
String unbreakableOption(String s) => s.replaceAll(' ', ' ').replaceAllMapped(RegExp(r'[-/]'), (m) => '${m[0]}');
|
||||||
|
|
||||||
class FilterLimitNote extends StatelessWidget {
|
class FilterLimitNote extends StatelessWidget {
|
||||||
final List<String> unsupported;
|
final List<String> unsupported;
|
||||||
final bool deletes;
|
final bool deletes;
|
||||||
|
|
@ -316,7 +320,7 @@ class FilterLimitNote extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
Widget build(BuildContext context) => Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
Text(trf('이 옵션의 일부 거르기 규칙은 미리 볼 수 없습니다 - 실제로 더 지워질 수 있습니다: {0}', [unsupported.join(' · ')]),
|
Text(trf('이 옵션의 일부 거르기 규칙은 미리 볼 수 없습니다 - 실제로 더 지워질 수 있습니다: {0}', [unsupported.map(unbreakableOption).join(' · ')]),
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.redAccent, fontWeight: FontWeight.w600)),
|
style: const TextStyle(fontSize: 12, color: Colors.redAccent, fontWeight: FontWeight.w600)),
|
||||||
if (deletes)
|
if (deletes)
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:jj_mkvmaker/ui/sync_preview_view.dart';
|
||||||
|
|
||||||
|
/// 관리자 지시 (10/20, 28 지적): 경고 글 안의 옵션 (--filter=H a.txt) 이 좁은 창에서 "--" 와 "filter=…" 로 갈리던 것
|
||||||
|
void main() {
|
||||||
|
double widest(String s, {bool min = true}) {
|
||||||
|
final tp = TextPainter(text: TextSpan(text: s, style: const TextStyle(fontSize: 12)), textDirection: TextDirection.ltr)..layout();
|
||||||
|
final w = min ? tp.minIntrinsicWidth : tp.maxIntrinsicWidth;
|
||||||
|
tp.dispose();
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
test('옵션 글은 줄바꿈 자리가 없다 (가장 좁게 접어도 한 줄 폭)', () {
|
||||||
|
for (final o in ['--filter=H a.txt', '-f"- *.log"', '--files-from=list/a b.txt', '-C']) {
|
||||||
|
final u = unbreakableOption(o);
|
||||||
|
expect(widest(u), closeTo(widest(u, min: false), 0.01), reason: o);
|
||||||
|
expect(u.replaceAll('', '').replaceAll(' ', ' '), o, reason: '보이는 글은 그대로');
|
||||||
|
}
|
||||||
|
// 묶지 않은 글은 줄이 갈릴 수 있다 (시험이 실제로 무언가를 보는지)
|
||||||
|
expect(widest('--filter=H a.txt'), lessThan(widest('--filter=H a.txt', min: false)));
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue