Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Peek][PreviewPane] Fix missing Copy menu-item #33845

Merged
merged 7 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 42 additions & 27 deletions src/common/FilePreviewCommon/Assets/Monaco/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// `theme` can be "vs" for light theme or "vs-dark" for dark theme
// `lang` is the language of the file
// `wrap` if the editor is wrapping or not

var theme = ("[[PT_THEME]]" == "dark") ? "vs-dark" : "vs";
var lang = "[[PT_LANG]]";
var wrap = ([[PT_WRAP]] == 1) ? true : false;
Expand All @@ -19,11 +19,29 @@
var stickyScroll = ([[PT_STICKY_SCROLL]] == 1) ? true : false;

var fontSize = [[PT_FONT_SIZE]];

var contextMenu = ([[PT_CONTEXTMENU]] == 1) ? true : false;

var editor;

// Code taken from https://stackoverflow.com/a/30106551/14774889
var code = decodeURIComponent(atob(base64code).split('').map(function(c) {
var code = decodeURIComponent(atob(base64code).split('').map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));

function runToggleTextWrapCommand() {
if (wrap) {
editor.updateOptions({ wordWrap: 'off' })
} else {
editor.updateOptions({ wordWrap: 'on' })
}
wrap = !wrap;
}

function runCopyCommand() {
editor.focus();
document.execCommand('copy');
}

</script>
<!-- Set browser to Edge-->
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
Expand All @@ -33,32 +51,33 @@
<title>Previewer for developer Files</title>
<style>
/* Fits content to window size */
html, body{
padding:0;
html, body {
padding: 0;
}
#container,.monaco-editor {
position:fixed;
height:100%;
left:0;
top:0;
right:0;
bottom:0;

#container, .monaco-editor {
position: fixed;
height: 100%;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.overflowingContentWidgets{

.overflowingContentWidgets {
/*Hides alert box */
display:none!important
}
display: none !important
}
</style>
</head>

<body oncontextmenu="onContextMenu()">
<body>
<!-- Container for the editor -->
<div id="container"></div>
<!-- Script -->
<script src="http://[[PT_URL]]/monacoSRC/min/vs/loader.js"></script>
<script src="http://[[PT_URL]]/monacoSpecialLanguages.js" type="module"></script>
<script type="module">
var editor;
<script type="module">
import { registerAdditionalLanguages } from 'http://[[PT_URL]]/monacoSpecialLanguages.js';
import { customTokenColors } from 'http://[[PT_URL]]/customTokenColors.js';
require.config({ paths: { vs: 'http://[[PT_URL]]/monacoSRC/min/vs' } });
Expand All @@ -80,8 +99,9 @@
language: lang, // Sets language of the code
readOnly: true, // Sets to readonly
theme: 'theme', // Sets editor theme
minimap: {enabled: false}, // Disables minimap
minimap: { enabled: false }, // Disables minimap
lineNumbersMinChars: '3', // Width of the line numbers
contextmenu: contextMenu,
scrollbar: {
// Deactivate shadows
shadows: false,
Expand All @@ -90,7 +110,7 @@
vertical: 'auto',
horizontal: 'auto',
},
stickyScroll: {enabled: stickyScroll},
stickyScroll: { enabled: stickyScroll },
fontSize: fontSize,
wordWrap: (wrap ? 'on' : 'off') // Word wraps
});
Expand All @@ -117,12 +137,7 @@
// Method that will be executed when the action is triggered.
// @param editor The editor instance is passed in as a convenience
run: function (ed) {
if (wrap) {
editor.updateOptions({ wordWrap: 'off' })
} else {
editor.updateOptions({ wordWrap: 'on' })
}
wrap = !wrap;
runToggleTextWrapCommand();
}
});

Expand Down Expand Up @@ -151,4 +166,4 @@
}
</script>
</body>
</html>
</html>
116 changes: 112 additions & 4 deletions src/modules/peek/Peek.FilePreviewer/Controls/BrowserControl.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,26 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Controls;
using ManagedCommon;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Web.WebView2.Core;
using Peek.Common.Constants;
using Peek.Common.Helpers;
using Windows.ApplicationModel.DataTransfer;
using Windows.System;
using Windows.UI;

using Control = System.Windows.Controls.Control;

namespace Peek.FilePreviewer.Controls
{
public sealed partial class BrowserControl : UserControl, IDisposable
public sealed partial class BrowserControl : Microsoft.UI.Xaml.Controls.UserControl, IDisposable
{
/// <summary>
/// Helper private Uri where we cache the last navigated page
Expand Down Expand Up @@ -67,6 +73,25 @@ public bool IsDevFilePreview
}
}

public static readonly DependencyProperty CustomContextMenuProperty = DependencyProperty.Register(
nameof(CustomContextMenu),
typeof(bool),
typeof(BrowserControl),
null);

public bool CustomContextMenu
{
get
{
return (bool)GetValue(CustomContextMenuProperty);
}

set
{
SetValue(CustomContextMenuProperty, value);
}
}

public BrowserControl()
{
this.InitializeComponent();
Expand All @@ -78,6 +103,7 @@ public void Dispose()
if (PreviewBrowser.CoreWebView2 != null)
{
PreviewBrowser.CoreWebView2.DOMContentLoaded -= CoreWebView2_DOMContentLoaded;
PreviewBrowser.CoreWebView2.ContextMenuRequested -= CoreWebView2_ContextMenuRequested;
}
}

Expand Down Expand Up @@ -145,7 +171,7 @@ private async void PreviewWV2_Loaded(object sender, RoutedEventArgs e)
PreviewBrowser.DefaultBackgroundColor = Color.FromArgb(0, 0, 0, 0);

PreviewBrowser.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = false;
PreviewBrowser.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false;
PreviewBrowser.CoreWebView2.Settings.AreDefaultContextMenusEnabled = true;
PreviewBrowser.CoreWebView2.Settings.AreDevToolsEnabled = false;
PreviewBrowser.CoreWebView2.Settings.AreHostObjectsAllowed = false;
PreviewBrowser.CoreWebView2.Settings.IsGeneralAutofillEnabled = false;
Expand All @@ -164,6 +190,7 @@ private async void PreviewWV2_Loaded(object sender, RoutedEventArgs e)

PreviewBrowser.CoreWebView2.DOMContentLoaded += CoreWebView2_DOMContentLoaded;
PreviewBrowser.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested;
PreviewBrowser.CoreWebView2.ContextMenuRequested += CoreWebView2_ContextMenuRequested;
}
catch (Exception ex)
{
Expand All @@ -173,6 +200,87 @@ private async void PreviewWV2_Loaded(object sender, RoutedEventArgs e)
Navigate();
}

private List<Control> GetContextMenuItems(CoreWebView2 sender, CoreWebView2ContextMenuRequestedEventArgs args)
{
var menuItems = args.MenuItems;

if (menuItems.IsReadOnly)
{
return [];
}

if (CustomContextMenu)
{
MenuItem CreateCommandMenuItem(string resourceId, string commandName)
{
MenuItem commandMenuItem = new()
{
Header = ResourceLoaderInstance.ResourceLoader.GetString(resourceId),
IsEnabled = true,
};

commandMenuItem.Click += async (s, ex) =>
{
await sender.ExecuteScriptAsync($"{commandName}()");
};

return commandMenuItem;
}

// When using Monaco, we show menu items that call the appropriate JS functions -
// WebView2 isn't able to show a "Copy" menu item of its own.
return [
CreateCommandMenuItem("ContextMenu_Copy", "runCopyCommand"),
new Separator(),
CreateCommandMenuItem("ContextMenu_ToggleTextWrapping", "runToggleTextWrapCommand"),
];
}
else
{
MenuItem CreateMenuItemFromWebViewMenuItem(CoreWebView2ContextMenuItem webViewMenuItem)
{
MenuItem menuItem = new()
{
Header = webViewMenuItem.Label.Replace('&', '_'), // replace with '_' so it is underlined in the label
IsEnabled = webViewMenuItem.IsEnabled,
InputGestureText = webViewMenuItem.ShortcutKeyDescription,
};

menuItem.Click += (_, _) =>
{
args.SelectedCommandId = webViewMenuItem.CommandId;
};

return menuItem;
}

// When not using Monaco, we keep the "Copy" menu item from WebView2's default context menu.
return menuItems.Where(menuItem => menuItem.Name == "copy")
.Select(CreateMenuItemFromWebViewMenuItem)
.ToList<Control>();
}
}

private void CoreWebView2_ContextMenuRequested(CoreWebView2 sender, CoreWebView2ContextMenuRequestedEventArgs args)
{
var deferral = args.GetDeferral();
args.Handled = true;

var menuItems = GetContextMenuItems(sender, args);

if (menuItems.Count != 0)
{
var contextMenu = new ContextMenu();
contextMenu.Closed += (_, _) => deferral.Complete();
contextMenu.IsOpen = true;

foreach (var menuItem in menuItems)
{
contextMenu.Items.Add(menuItem);
}
}
}

private void CoreWebView2_DOMContentLoaded(CoreWebView2 sender, CoreWebView2DOMContentLoadedEventArgs args)
{
// If the file being previewed is HTML or HTM, reset the background color to its original state.
Expand Down Expand Up @@ -202,7 +310,7 @@ private async void CoreWebView2_NewWindowRequested(CoreWebView2 sender, CoreWebV
}
}

private async void PreviewBrowser_NavigationStarting(WebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationStartingEventArgs args)
private async void PreviewBrowser_NavigationStarting(WebView2 sender, CoreWebView2NavigationStartingEventArgs args)
{
if (_navigatedUri == null)
{
Expand All @@ -218,7 +326,7 @@ private async void PreviewBrowser_NavigationStarting(WebView2 sender, Microsoft.
}
}

private void PreviewWV2_NavigationCompleted(WebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationCompletedEventArgs args)
private void PreviewWV2_NavigationCompleted(WebView2 sender, CoreWebView2NavigationCompletedEventArgs args)
{
if (args.IsSuccess)
{
Expand Down
1 change: 1 addition & 0 deletions src/modules/peek/Peek.FilePreviewer/FilePreview.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
<controls:BrowserControl
x:Name="BrowserPreview"
x:Load="True"
CustomContextMenu="{x:Bind BrowserPreviewer.CustomContextMenu, Mode=OneWay}"
DOMContentLoaded="BrowserPreview_DOMContentLoaded"
FlowDirection="LeftToRight"
IsDevFilePreview="{x:Bind BrowserPreviewer.IsDevFilePreview, Mode=OneWay}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ public interface IBrowserPreviewer : IPreviewer, IPreviewTarget
public Uri? Preview { get; }

public bool IsDevFilePreview { get; }

public bool CustomContextMenu { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ private static string InitializeIndexFileAndSelectedFile(string fileContent, str

html = html.Replace("[[PT_LANG]]", vsCodeLangSet, StringComparison.InvariantCulture);
html = html.Replace("[[PT_WRAP]]", wrapText ? "1" : "0", StringComparison.InvariantCulture);
html = html.Replace("[[PT_CONTEXTMENU]]", "0", StringComparison.InvariantCulture);
html = html.Replace("[[PT_STICKY_SCROLL]]", stickyScroll ? "1" : "0", StringComparison.InvariantCulture);
html = html.Replace("[[PT_THEME]]", theme, StringComparison.InvariantCulture);
html = html.Replace("[[PT_FONT_SIZE]]", fontSize.ToString(CultureInfo.InvariantCulture), StringComparison.InvariantCulture);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public partial class WebBrowserPreviewer : ObservableObject, IBrowserPreviewer,
[ObservableProperty]
private bool isDevFilePreview;

[ObservableProperty]
private bool customContextMenu;

private bool disposed;

public WebBrowserPreviewer(IFileSystemItem file, IPreviewSettings previewSettings)
Expand Down Expand Up @@ -107,9 +110,14 @@ await Dispatcher.RunOnUiThread(async () =>
{
bool isHtml = File.Extension == ".html" || File.Extension == ".htm";
bool isMarkdown = File.Extension == ".md";
IsDevFilePreview = MonacoHelper.SupportedMonacoFileTypes.Contains(File.Extension);

if (IsDevFilePreview && !isHtml && !isMarkdown)
bool supportedByMonaco = MonacoHelper.SupportedMonacoFileTypes.Contains(File.Extension);
bool useMonaco = supportedByMonaco && !isHtml && !isMarkdown;

IsDevFilePreview = supportedByMonaco;
CustomContextMenu = useMonaco;

if (useMonaco)
{
var raw = await ReadHelper.Read(File.Path.ToString());
Preview = new Uri(MonacoHelper.PreviewTempFile(raw, File.Extension, TempFolderPath.Path, _previewSettings.SourceCodeTryFormat, _previewSettings.SourceCodeWrapText, _previewSettings.SourceCodeStickyScroll, _previewSettings.SourceCodeFontSize));
Expand Down
8 changes: 8 additions & 0 deletions src/modules/peek/Peek.UI/Strings/en-us/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -318,4 +318,12 @@
<value>Length: {0}</value>
<comment>{0} is the duration of the audio read from file metadata</comment>
</data>
<data name="ContextMenu_Copy" xml:space="preserve">
<value>Copy</value>
<comment>Copy selected text to clipboard</comment>
</data>
<data name="ContextMenu_ToggleTextWrapping" xml:space="preserve">
<value>Toggle text wrapping</value>
<comment>Toggle whether text in pane is word-wrapped</comment>
</data>
</root>
Loading
Loading