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

feat: create new window when dragging tab outside the app #465

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions src/Notepads/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ sealed partial class App : Application

public static bool IsFirstInstance = false;
public static bool IsGameBarWidget = false;
public static string PassedEditorData = string.Empty;

private const string AppCenterSecret = null;

Expand Down
2 changes: 2 additions & 0 deletions src/Notepads/Controls/TextEditor/ITextEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ void GetLineColumnSelection(

Task SaveContentToFileAndUpdateEditorState(StorageFile file);

void UpdateEditingFile(StorageFile file);

string GetContentForSharing();

void TypeText(string text);
Expand Down
6 changes: 6 additions & 0 deletions src/Notepads/Controls/TextEditor/TextEditor.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,12 @@ private async Task<TextFile> SaveContentToFile(StorageFile file)
return new TextFile(text, encoding, lineEnding, newFileModifiedTime);
}

public void UpdateEditingFile(StorageFile file)
{
EditingFile = file;
StartCheckingFileStatusPeriodically();
}

public string GetContentForSharing()
{
return TextEditorCore.Document.Selection.StartPosition == TextEditorCore.Document.Selection.EndPosition ?
Expand Down
5 changes: 5 additions & 0 deletions src/Notepads/Core/ISessionManager.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
namespace Notepads.Core
{
using Notepads.Controls.TextEditor;
using Notepads.Core.SessionDataModels;
using System;
using System.Threading.Tasks;
using Windows.Storage;

internal interface ISessionManager
{
Expand All @@ -16,5 +19,7 @@ internal interface ISessionManager
void StopSessionBackup();

Task ClearSessionDataAsync();

Task<ITextEditor> RecoverTextEditorAsync(TextEditorSessionDataV1 editorSessionData, StorageFile file = null);
}
}
110 changes: 92 additions & 18 deletions src/Notepads/Core/NotepadsCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Microsoft.AppCenter.Analytics;
using Notepads.Core.SessionDataModels;

public class NotepadsCore : INotepadsCore
{
Expand Down Expand Up @@ -56,13 +57,16 @@ public class NotepadsCore : INotepadsCore

private readonly CoreDispatcher _dispatcher;

public const string NotepadsShouldHandleFileDrop = "NotepadsShouldHandleFileDrop";
private const string SetDragAndDropActionStatus = "SetDragAndDropActionStatus";
private const string NotepadsTextEditorMetaData = "NotepadsTextEditorMetaData";
private const string NotepadsTextEditorGuid = "NotepadsTextEditorGuid";
private const string NotepadsInstanceId = "NotepadsInstanceId";
private const string NotepadsTextEditorLastSavedContent = "NotepadsTextEditorLastSavedContent";
private const string NotepadsTextEditorPendingContent = "NotepadsTextEditorPendingContent";
private const string NotepadsTextEditorEditingFilePath = "NotepadsTextEditorEditingFilePath";
private const string NotepadsTextEditorEditingFile = "NotepadsTextEditorEditingFile";
private const string NotepadsTextEditorTempFolderPath = "Temp";

public NotepadsCore(SetsView sets,
INotepadsExtensionProvider extensionProvider,
Expand Down Expand Up @@ -596,7 +600,8 @@ private async void Sets_DragOver(object sender, DragEventArgs args)
}
}

if (!canHandle && args.DataView.Contains(StandardDataFormats.StorageItems))
if (!canHandle && args.DataView.Contains(StandardDataFormats.StorageItems) &&
!args.DataView.Properties.TryGetValue(NotepadsShouldHandleFileDrop, out object handleFileDrop))
{
try
{
Expand Down Expand Up @@ -659,9 +664,8 @@ private void Sets_DragItemsStarting(object sender, DragItemsStartingEventArgs ar
// Add Editing File
if (editor.EditingFile != null)
{
args.Data.Properties.FileTypes.Add(StandardDataFormats.StorageItems);
args.Data.Properties.Add(NotepadsTextEditorEditingFilePath, editor.EditingFilePath);
args.Data.SetStorageItems(new List<IStorageItem>() { editor.EditingFile }, readOnly: false);
args.Data.Properties.Add(NotepadsTextEditorEditingFile, editor.EditingFile);
}

args.Data.Properties.Add(NotepadsTextEditorMetaData, data);
Expand All @@ -688,7 +692,8 @@ private async void Sets_Drop(object sender, DragEventArgs args)
if (string.IsNullOrEmpty(args.DataView?.Properties?.ApplicationName) ||
!string.Equals(args.DataView?.Properties?.ApplicationName, App.ApplicationName))
{
if (args.DataView == null || !args.DataView.Contains(StandardDataFormats.StorageItems)) return;
if (args.DataView == null || !args.DataView.Contains(StandardDataFormats.StorageItems) ||
args.DataView.Properties.TryGetValue(NotepadsShouldHandleFileDrop, out object handleFileDrop)) return;
var fileDropDeferral = args.GetDeferral();
var storageItems = await args.DataView.GetStorageItemsAsync();
StorageItemsDropped?.Invoke(this, storageItems);
Expand Down Expand Up @@ -719,18 +724,9 @@ private async void Sets_Drop(object sender, DragEventArgs args)
}

StorageFile editingFile = null;

if (metaData.HasEditingFile && args.DataView.Contains(StandardDataFormats.StorageItems))
if (metaData.HasEditingFile && args.DataView.Properties.TryGetValue(NotepadsTextEditorEditingFile, out object editingFileObj))
{
var storageItems = await args.DataView.GetStorageItemsAsync();
if (storageItems.Count == 1 && storageItems[0] is StorageFile file)
{
editingFile = file;
}
else
{
throw new Exception("Failed to read storage file from dropped set: Expecting only one storage file.");
}
editingFile = (StorageFile)editingFileObj;
}

string lastSavedText = null;
Expand Down Expand Up @@ -816,13 +812,91 @@ private async void Sets_SetDraggedOutside(object sender, SetDraggedOutsideEventA
{
if (Sets.Items?.Count > 1 && e.Set?.Content is ITextEditor textEditor)
{
// Only allow untitled empty document to be dragged outside for now
if (!textEditor.IsModified && textEditor.EditingFile == null)
var index = Sets.SelectedIndex;
var message = new ValueSet();
var textEditorData = JsonConvert.SerializeObject(await BuildTextEditorSessionData(textEditor), Formatting.Indented);
if (textEditor.FileModificationState != FileModificationState.RenamedMovedOrDeleted)
{
message.Add("EditorData", textEditorData);
DeleteTextEditor(textEditor);
soumyamahunt marked this conversation as resolved.
Show resolved Hide resolved
if (!await NotepadsProtocolService.LaunchProtocolAsync(NotepadsOperationProtocol.OpenNewInstance, message))
{
OpenTextEditor(textEditor, index);
}
}
else
{
ApplicationSettingsStore.Write("EditorData", textEditorData);
DeleteTextEditor(textEditor);
await NotepadsProtocolService.LaunchProtocolAsync(NotepadsOperationProtocol.OpenNewInstance);
if (!await NotepadsProtocolService.LaunchProtocolAsync(NotepadsOperationProtocol.OpenNewInstance, textEditor.EditingFile))
{
OpenTextEditor(textEditor, index);
}
}
}
}

private async Task<TextEditorSessionDataV1> BuildTextEditorSessionData(ITextEditor textEditor)
{
TextEditorSessionDataV1 textEditorData = new TextEditorSessionDataV1
{
Id = textEditor.Id,
};

if (textEditor.EditingFile != null)
{
// Add the opened file to FutureAccessList so we can access it next launch
var futureAccessToken = textEditor.Id.ToString("N");
await FutureAccessListUtility.TryAddOrReplaceTokenInFutureAccessList(futureAccessToken, textEditor.EditingFile);
textEditorData.EditingFileFutureAccessToken = futureAccessToken;
textEditorData.EditingFileName = textEditor.EditingFileName;
textEditorData.EditingFilePath = textEditor.EditingFilePath;
}

if (textEditor.IsModified || textEditor.FileModificationState != FileModificationState.Untouched)
{
if (textEditor.EditingFile != null)
{
// Persist the last save known to the app, which might not be up-to-date (if the file was modified outside the app)
var lastSavedBackupFile = await SessionUtility.CreateNewFileInBackupFolderAsync(
textEditor.Id.ToString("N") + "-LastSaved",
CreationCollisionOption.ReplaceExisting,
NotepadsTextEditorTempFolderPath);

if (!await SessionManager.BackupTextAsync(textEditor.LastSavedSnapshot.Content,
textEditor.LastSavedSnapshot.Encoding,
textEditor.LastSavedSnapshot.LineEnding,
lastSavedBackupFile))
{
return null; // Error: Failed to write backup text to file
}

textEditorData.LastSavedBackupFilePath = lastSavedBackupFile.Path;
}

if (textEditor.IsModified)
{
// Persist pending changes relative to the last save
var pendingBackupFile = await SessionUtility.CreateNewFileInBackupFolderAsync(
textEditor.Id.ToString("N") + "-Pending",
CreationCollisionOption.ReplaceExisting,
NotepadsTextEditorTempFolderPath);

if (!await SessionManager.BackupTextAsync(textEditor.GetText(),
textEditor.LastSavedSnapshot.Encoding,
textEditor.LastSavedSnapshot.LineEnding,
pendingBackupFile))
{
return null; // Error: Failed to write backup text to file
}

textEditorData.PendingBackupFilePath = pendingBackupFile.Path;
}
}

textEditorData.StateMetaData = textEditor.GetTextEditorStateMetaData();

return textEditorData;
}

#endregion
Expand Down
9 changes: 7 additions & 2 deletions src/Notepads/Core/SessionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ private void UnbindEditorContentStateChangeEvent(object sender, ITextEditor text
textEditor.FileReloaded -= RemoveTextEditorSessionData;
}

private async Task<ITextEditor> RecoverTextEditorAsync(TextEditorSessionDataV1 editorSessionData)
public async Task<ITextEditor> RecoverTextEditorAsync(TextEditorSessionDataV1 editorSessionData, StorageFile file = null)
{
StorageFile editingFile = null;

Expand Down Expand Up @@ -446,10 +446,15 @@ private async Task<ITextEditor> RecoverTextEditorAsync(TextEditorSessionDataV1 e
textEditor.ResetEditorState(editorSessionData.StateMetaData, pendingText);
}

if (file != null)
{
textEditor.UpdateEditingFile(file);
}

return textEditor;
}

private static async Task<bool> BackupTextAsync(string text, Encoding encoding, LineEnding lineEnding, StorageFile file)
public static async Task<bool> BackupTextAsync(string text, Encoding encoding, LineEnding lineEnding, StorageFile file)
{
try
{
Expand Down
79 changes: 40 additions & 39 deletions src/Notepads/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ static void Main(string[] args)
Task.Run(LoggingService.InitializeFileSystemLoggingAsync);
#endif

IActivatedEventArgs activatedArgs = AppInstance.GetActivatedEventArgs();

//if (activatedArgs == null)
//{
// // No activated event args, so this is not an activation via the multi-instance ID
Expand All @@ -37,55 +35,58 @@ static void Main(string[] args)
ApplicationSettingsStore.Write(SettingsKey.ActiveInstanceIdStr, null);
}

if (activatedArgs is FileActivatedEventArgs)
{
RedirectOrCreateNewInstance();
}
else if (activatedArgs is CommandLineActivatedEventArgs)
{
RedirectOrCreateNewInstance();
}
else if (activatedArgs is ProtocolActivatedEventArgs protocolActivatedEventArgs)
switch (AppInstance.GetActivatedEventArgs())
{
LoggingService.LogInfo($"[{nameof(Main)}] [ProtocolActivated] Protocol: {protocolActivatedEventArgs.Uri}");
var protocol = NotepadsProtocolService.GetOperationProtocol(protocolActivatedEventArgs.Uri, out _);
if (protocol == NotepadsOperationProtocol.OpenNewInstance)
{
OpenNewInstance();
}
else
{
RedirectOrCreateNewInstance();
}
}
else if (activatedArgs is LaunchActivatedEventArgs launchActivatedEventArgs)
{
bool handled = false;
case LaunchActivatedEventArgs launchActivatedEventArgs:
bool handled = false;

if (!string.IsNullOrEmpty(launchActivatedEventArgs.Arguments))
{
var protocol = NotepadsProtocolService.GetOperationProtocol(new Uri(launchActivatedEventArgs.Arguments), out _);
if (protocol == NotepadsOperationProtocol.OpenNewInstance)
if (!string.IsNullOrEmpty(launchActivatedEventArgs.Arguments))
{
handled = true;
OpenNewInstance();
var protocol = NotepadsProtocolService.GetOperationProtocol(new Uri(launchActivatedEventArgs.Arguments), out _);
if (protocol == NotepadsOperationProtocol.OpenNewInstance)
{
handled = true;
OpenNewInstance();
}
}
}

if (!handled)
{
if (!handled)
{
RedirectOrCreateNewInstance();
}
break;
case FileActivatedEventArgs fileActivatedEventArgs:
if (fileActivatedEventArgs.CallerPackageFamilyName == Package.Current.Id.FamilyName)
{
OpenNewInstance();
}
else
{
RedirectOrCreateNewInstance();
}
break;
case ProtocolActivatedEventArgs protocolActivatedEventArgs:
LoggingService.LogInfo($"[{nameof(Main)}] [ProtocolActivated] Protocol: {protocolActivatedEventArgs.Uri}");
if (NotepadsProtocolService.GetOperationProtocol(protocolActivatedEventArgs.Uri, out _) == NotepadsOperationProtocol.OpenNewInstance)
{
OpenNewInstance();
}
else
{
RedirectOrCreateNewInstance();
}
break;
default:
RedirectOrCreateNewInstance();
}
}
else
{
RedirectOrCreateNewInstance();
break;
}
}

private static void OpenNewInstance()
{
AppInstance.FindOrRegisterInstanceForKey(App.Id.ToString());
App.PassedEditorData = (string)ApplicationSettingsStore.Read("EditorData");
ApplicationSettingsStore.Remove("EditorData");
App.IsFirstInstance = IsFirstInstance;
Windows.UI.Xaml.Application.Start(p => new App());
IsFirstInstance = false;
Expand Down
Loading