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