Skip to content

Commit 14d0b7f

Browse files
committed
Web view show/hide
1 parent 76708f5 commit 14d0b7f

File tree

1 file changed

+244
-64
lines changed

1 file changed

+244
-64
lines changed

CodeEdit/Features/Editor/Views/EditorAreaFileView.swift

Lines changed: 244 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// swiftlint:disable line_length
2+
// swiftLint:disable file_length
3+
// swiftlint:disable type_body_length
14
//
25
// EditorAreaFileView.swift
36
// CodeEdit
@@ -329,6 +332,9 @@ struct EditorAreaFileView: View {
329332
@State private var cancellables = Set<AnyCancellable>()
330333
@State private var renderWorkItem: DispatchWorkItem?
331334

335+
// NEW: preview visibility toggle
336+
@State private var showPreviewPane: Bool = true
337+
332338
@State private var webViewAllowJS: Bool = false
333339
@State private var previewSource: PreviewSource = .localHTML
334340

@@ -433,7 +439,7 @@ struct EditorAreaFileView: View {
433439
}
434440
}
435441

436-
// MARK: - FS Watch helpers
442+
// MARK: - FS Watch helpers
437443
private func startFileWatchIfNeeded() {
438444
guard let fileURL = codeFile.fileURL else { return }
439445

@@ -501,46 +507,15 @@ struct EditorAreaFileView: View {
501507
CodeFileView(editorInstance: editorInstance, codeFile: codeFile)
502508

503509
case .preview:
504-
VStack(spacing: 0) {
505-
ZStack {
506-
WebView(
507-
html: renderedHTMLState.isEmpty
508-
? "<!doctype html><html><body style='background:#fff'><h1>Preview Ready</h1></body></html>"
509-
: renderedHTMLState,
510-
baseURL: codeFile.fileURL?.deletingLastPathComponent(),
511-
onCrash: { /* WebKit always on; show message if needed */ },
512-
allowJavaScript: webViewAllowJS
513-
)
514-
.id(webViewRefreshToken)
515-
.frame(maxWidth: .infinity, minHeight: FIXED_PREVIEW_HEIGHT, maxHeight: FIXED_PREVIEW_HEIGHT)
516-
.background(Color.white)
517-
518-
VStack(spacing: 0) {
519-
Spacer()
520-
PreviewBottomBar(
521-
refresh: { refreshPreview() },
522-
reloadIgnoreCache: { webViewRefreshToken = UUID() },
523-
enableJS: $webViewAllowJS,
524-
previewSource: $previewSource,
525-
serverErrorMessage: serverErrorMessage
526-
)
527-
}
528-
}
529-
}
530-
.frame(maxWidth: .infinity, maxHeight: .infinity)
531-
532-
case .split:
533-
HStack(spacing: 0) {
534-
CodeFileView(editorInstance: editorInstance, codeFile: codeFile)
535-
510+
if showPreviewPane {
536511
VStack(spacing: 0) {
537512
ZStack {
538513
WebView(
539514
html: renderedHTMLState.isEmpty
540515
? "<!doctype html><html><body style='background:#fff'><h1>Preview Ready</h1></body></html>"
541516
: renderedHTMLState,
542517
baseURL: codeFile.fileURL?.deletingLastPathComponent(),
543-
onCrash: { /* WebKit always on */ },
518+
onCrash: { /* WebKit always on; show message if needed */ },
544519
allowJavaScript: webViewAllowJS
545520
)
546521
.id(webViewRefreshToken)
@@ -558,46 +533,144 @@ struct EditorAreaFileView: View {
558533
)
559534
}
560535
}
536+
// Ensure overlay respects safe area and is not clipped
537+
.overlay(alignment: .topTrailing) {
538+
Button {
539+
withAnimation(.easeInOut(duration: 0.18)) {
540+
showPreviewPane = false
541+
}
542+
} label: {
543+
Image(systemName: "eye.slash")
544+
.font(.system(size: 14, weight: .medium))
545+
.padding(6)
546+
.background(Color.black.opacity(0.12))
547+
.clipShape(Circle())
548+
}
549+
.buttonStyle(.plain)
550+
.help("Hide Preview")
551+
.padding(.top, edgeInsets.top + 8)
552+
.padding(.trailing, 8)
553+
.zIndex(10)
554+
}
561555
}
562-
.frame(width: FIXED_PREVIEW_WIDTH)
563-
.frame(maxHeight: .infinity)
564-
.background(Color.white)
556+
.frame(maxWidth: .infinity, maxHeight: .infinity)
557+
} else {
558+
// Preview hidden in Preview mode — show a center "show" button
559+
VStack {
560+
Spacer()
561+
Button {
562+
withAnimation(.easeInOut(duration: 0.18)) {
563+
showPreviewPane = true
564+
}
565+
} label: {
566+
Image(systemName: "eye")
567+
.font(.system(size: 20, weight: .semibold))
568+
.padding(10)
569+
.background(Color.black.opacity(0.08))
570+
.clipShape(Circle())
571+
}
572+
.buttonStyle(.plain)
573+
.help("Show Preview")
574+
Spacer()
575+
}
576+
.frame(maxWidth: .infinity, maxHeight: .infinity)
577+
.background(Color(NSColor.windowBackgroundColor))
565578
}
566-
}
567-
} else if isMarkdown {
568-
switch displayMode {
569-
case .code:
570-
CodeFileView(editorInstance: editorInstance, codeFile: codeFile)
571579

572-
case .preview:
573-
VStack(spacing: 0) {
580+
case .split:
581+
HStack(spacing: 0) {
582+
// Wrap code view so we can show the "show preview" button when preview is hidden
574583
ZStack {
575-
MarkdownView(source: contentString)
576-
.frame(maxWidth: .infinity, minHeight: FIXED_PREVIEW_HEIGHT, maxHeight: FIXED_PREVIEW_HEIGHT)
577-
.background(Color.white)
584+
CodeFileView(editorInstance: editorInstance, codeFile: codeFile)
585+
586+
if !showPreviewPane {
587+
// small overlay button at top-right of code area to restore preview
588+
Button {
589+
withAnimation(.easeInOut(duration: 0.18)) {
590+
showPreviewPane = true
591+
}
592+
} label: {
593+
Image(systemName: "eye")
594+
.font(.system(size: 14, weight: .medium))
595+
.padding(6)
596+
.background(Color.black.opacity(0.12))
597+
.clipShape(Circle())
598+
}
599+
.buttonStyle(.plain)
600+
.help("Show Preview")
601+
.padding(.top, edgeInsets.top + 8)
602+
.padding(.trailing, 8)
603+
.zIndex(10)
604+
}
605+
}
606+
.frame(minWidth: 200)
578607

608+
if showPreviewPane {
579609
VStack(spacing: 0) {
580-
Spacer()
581-
PreviewBottomBar(
582-
refresh: { refreshPreview() },
583-
reloadIgnoreCache: { webViewRefreshToken = UUID() },
584-
enableJS: $webViewAllowJS,
585-
previewSource: $previewSource,
586-
serverErrorMessage: serverErrorMessage
587-
)
610+
ZStack {
611+
WebView(
612+
html: renderedHTMLState.isEmpty
613+
? "<!doctype html><html><body style='background:#fff'><h1>Preview Ready</h1></body></html>"
614+
: renderedHTMLState,
615+
baseURL: codeFile.fileURL?.deletingLastPathComponent(),
616+
onCrash: { /* WebKit always on */ },
617+
allowJavaScript: webViewAllowJS
618+
)
619+
.id(webViewRefreshToken)
620+
.frame(maxWidth: .infinity, minHeight: FIXED_PREVIEW_HEIGHT, maxHeight: FIXED_PREVIEW_HEIGHT)
621+
.background(Color.white)
622+
623+
VStack(spacing: 0) {
624+
Spacer()
625+
PreviewBottomBar(
626+
refresh: { refreshPreview() },
627+
reloadIgnoreCache: { webViewRefreshToken = UUID() },
628+
enableJS: $webViewAllowJS,
629+
previewSource: $previewSource,
630+
serverErrorMessage: serverErrorMessage
631+
)
632+
}
633+
634+
// drag / divider area (kept minimal here, you can replace with more advanced drag logic)
635+
}
636+
.overlay(alignment: .topTrailing) {
637+
Button {
638+
withAnimation(.easeInOut(duration: 0.18)) {
639+
showPreviewPane = false
640+
}
641+
} label: {
642+
Image(systemName: "eye.slash")
643+
.font(.system(size: 14, weight: .medium))
644+
.padding(6)
645+
.background(Color.black.opacity(0.12))
646+
.clipShape(Circle())
647+
}
648+
.buttonStyle(.plain)
649+
.help("Hide Preview")
650+
.padding(.top, edgeInsets.top + 8)
651+
.padding(.trailing, 8)
652+
.zIndex(10)
653+
}
588654
}
655+
.frame(width: FIXED_PREVIEW_WIDTH)
656+
.frame(maxHeight: .infinity)
657+
.background(Color.white)
589658
}
590659
}
591-
.frame(maxWidth: .infinity, maxHeight: .infinity)
660+
}
661+
} else if isMarkdown {
662+
switch displayMode {
663+
case .code:
664+
CodeFileView(editorInstance: editorInstance, codeFile: codeFile)
592665

593-
case .split:
594-
HStack(spacing: 0) {
595-
CodeFileView(editorInstance: editorInstance, codeFile: codeFile)
666+
case .preview:
667+
if showPreviewPane {
596668
VStack(spacing: 0) {
597669
ZStack {
598670
MarkdownView(source: contentString)
599671
.frame(maxWidth: .infinity, minHeight: FIXED_PREVIEW_HEIGHT, maxHeight: FIXED_PREVIEW_HEIGHT)
600672
.background(Color.white)
673+
601674
VStack(spacing: 0) {
602675
Spacer()
603676
PreviewBottomBar(
@@ -609,12 +682,119 @@ struct EditorAreaFileView: View {
609682
)
610683
}
611684
}
685+
.overlay(alignment: .topTrailing) {
686+
Button {
687+
withAnimation(.easeInOut(duration: 0.18)) {
688+
showPreviewPane = false
689+
}
690+
} label: {
691+
Image(systemName: "eye.slash")
692+
.font(.system(size: 14, weight: .medium))
693+
.padding(6)
694+
.background(Color.black.opacity(0.12))
695+
.clipShape(Circle())
696+
}
697+
.buttonStyle(.plain)
698+
.help("Hide Preview")
699+
.padding(.top, edgeInsets.top + 8)
700+
.padding(.trailing, 8)
701+
.zIndex(10)
702+
}
703+
}
704+
.frame(maxWidth: .infinity, maxHeight: .infinity)
705+
} else {
706+
// Preview hidden in Preview mode — show a center "show" button
707+
VStack {
708+
Spacer()
709+
Button {
710+
withAnimation(.easeInOut(duration: 0.18)) {
711+
showPreviewPane = true
712+
}
713+
} label: {
714+
Image(systemName: "eye")
715+
.font(.system(size: 20, weight: .semibold))
716+
.padding(10)
717+
.background(Color.black.opacity(0.08))
718+
.clipShape(Circle())
719+
}
720+
.buttonStyle(.plain)
721+
.help("Show Preview")
722+
Spacer()
723+
}
724+
.frame(maxWidth: .infinity, maxHeight: .infinity)
725+
.background(Color(NSColor.windowBackgroundColor))
726+
}
727+
728+
case .split:
729+
HStack(spacing: 0) {
730+
// Wrap code view so we can show the "show preview" button when preview is hidden
731+
ZStack {
732+
CodeFileView(editorInstance: editorInstance, codeFile: codeFile)
733+
734+
if !showPreviewPane {
735+
Button {
736+
withAnimation(.easeInOut(duration: 0.18)) {
737+
showPreviewPane = true
738+
}
739+
} label: {
740+
Image(systemName: "eye")
741+
.font(.system(size: 14, weight: .medium))
742+
.padding(6)
743+
.background(Color.black.opacity(0.12))
744+
.clipShape(Circle())
745+
}
746+
.buttonStyle(.plain)
747+
.help("Show Preview")
748+
.padding(.top, edgeInsets.top + 8)
749+
.padding(.trailing, 8)
750+
.zIndex(10)
751+
}
752+
}
753+
.frame(minWidth: 200)
754+
755+
if showPreviewPane {
756+
VStack(spacing: 0) {
757+
ZStack {
758+
MarkdownView(source: contentString)
759+
.frame(maxWidth: .infinity, minHeight: FIXED_PREVIEW_HEIGHT, maxHeight: FIXED_PREVIEW_HEIGHT)
760+
.background(Color.white)
761+
762+
VStack(spacing: 0) {
763+
Spacer()
764+
PreviewBottomBar(
765+
refresh: { refreshPreview() },
766+
reloadIgnoreCache: { webViewRefreshToken = UUID() },
767+
enableJS: $webViewAllowJS,
768+
previewSource: $previewSource,
769+
serverErrorMessage: serverErrorMessage
770+
)
771+
}
772+
}
773+
.overlay(alignment: .topTrailing) {
774+
Button {
775+
withAnimation(.easeInOut(duration: 0.18)) {
776+
showPreviewPane = false
777+
}
778+
} label: {
779+
Image(systemName: "eye.slash")
780+
.font(.system(size: 14, weight: .medium))
781+
.padding(6)
782+
.background(Color.black.opacity(0.12))
783+
.clipShape(Circle())
784+
}
785+
.buttonStyle(.plain)
786+
.help("Hide Preview")
787+
.padding(.top, edgeInsets.top + 8)
788+
.padding(.trailing, 8)
789+
.zIndex(10)
790+
}
791+
}
792+
.frame(minWidth: FIXED_PREVIEW_WIDTH,
793+
idealWidth: FIXED_PREVIEW_WIDTH,
794+
maxWidth: FIXED_PREVIEW_WIDTH,
795+
maxHeight: .infinity, alignment: .center)
796+
.background(Color.white)
612797
}
613-
.frame(minWidth: FIXED_PREVIEW_WIDTH,
614-
idealWidth: FIXED_PREVIEW_WIDTH,
615-
maxWidth: FIXED_PREVIEW_WIDTH,
616-
maxHeight: .infinity, alignment: .center)
617-
.background(Color.white)
618798
}
619799
}
620800
} else if let utType = codeFile.utType, utType.conforms(to: .text) {

0 commit comments

Comments
 (0)