simplenotesdesktop/SimpleNotes/MainForm.cs

305 lines
11 KiB
C#
Executable File

/*
SimpleNotes, A server backed note taking app
Copyright (C) 2023 Mike Nolan
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Eto.Forms;
using Eto.Drawing;
using System.Net.Http;
using Newtonsoft.Json;
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Net;
using System.Text;
namespace SimpleNotes
{
public class _NotesResponse
{
[JsonProperty("success")]
public bool Success {get;set;}
[JsonProperty("notes")]
public List<Note> Notes {get;set;}=new List<Note>();
}
public class _NoteResponse
{
[JsonProperty("success")]
public bool Success {get;set;}
[JsonProperty("note")]
public Note Note {get;set;}=new Note();
}
public class Note
{
[JsonProperty("id")]
public long Id {get;set;}
[JsonProperty("title")]
public string Title {get;set;}="";
[JsonProperty("body")]
public string Body {get;set;}="";
}
public partial class MainForm : Form
{
public static HttpClient Client = new HttpClient();
public MainForm()
{
Icon = Icon.FromResource("SimpleNotes.icon.ico",this.GetType().Assembly);
Title = "SimpleNotes";
MinimumSize = new Size(320,240);
Size=new Size(640,480);
StackLayout layout=new StackLayout();
layout.Orientation = Orientation.Vertical;
this.Content = layout;
async Task UpdateAsync()
{
await Application.Instance.InvokeAsync(()=>{layout.Items.Clear();});
var pref=PreferenceFile.GetPreferences();
if(!pref.HasValue) return;
var request = new HttpRequestMessage(HttpMethod.Get,$"{pref.ServerUrl.TrimEnd('/')}/api/note");
request.Headers.Add("Authorization",$"Basic {Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes($"{pref.Username}:{pref.Password}"))}");
var response=await Client.SendAsync(request);
if(response.IsSuccessStatusCode)
{
var notes = JsonConvert.DeserializeObject<_NotesResponse>(await response.Content.ReadAsStringAsync());
if(notes != null && notes.Success)
{
await Application.Instance.InvokeAsync(()=>{
foreach(var note in notes.Notes)
{
DynamicLayout layout1=new DynamicLayout();
layout1.BeginVertical();
layout1.BeginHorizontal();
LinkButton item = new LinkButton();
bool down=false;
item.MouseDown += (sender,e)=>{
down=true;
};
item.MouseUp += (sender,e)=>{
down = false;
};
item.MouseMove += (sender,e)=>{
if(down)
{
down=false;
DataObject o=new DataObject();
if(string.IsNullOrWhiteSpace(note.Body)) return;
o.SetData(Encoding.UTF8.GetBytes(note.Body),"text/plain");
DoDragDrop(o,DragEffects.Copy);
}
};
item.Click += (sender,e)=>{
using(var noteEditor=new NoteEditor(note.Title,note.Id))
noteEditor.ShowModal(this);
Task.Run(UpdateAsync).ConfigureAwait(false);
};
item.Text = note.Title;
layout1.Add(item,true);
layout1.Add(new Button((sender,e)=>{
if(MessageBox.Show($"Delete Note {note.Title}?", MessageBoxButtons.YesNo,MessageBoxType.Question,MessageBoxDefaultButton.No) == DialogResult.Yes)
{
Task.Run(async()=>{
var request2 = new HttpRequestMessage(HttpMethod.Delete,$"{pref.ServerUrl.TrimEnd('/')}/api/note?id={note.Id}");
request2.Headers.Add("Authorization",$"Basic {Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes($"{pref.Username}:{pref.Password}"))}");
var response2=await Client.SendAsync(request2);
response2.Dispose();
await UpdateAsync();
}).ConfigureAwait(false);
}
}){Text="Delete"});
layout1.EndHorizontal();
layout1.EndVertical();
var s=new StackLayoutItem();
s.HorizontalAlignment = HorizontalAlignment.Stretch;
s.Control = layout1;
layout.Items.Add(s);
}
});
}
}
}
// create a few commands that can be used for the menu and toolbar
var createNote = new Command((sender,e)=>{
using(var noteDlg = new NoteEditor())
noteDlg.ShowModal(this);
Task.Run(UpdateAsync).ConfigureAwait(false);
}) { MenuText = "Create Note", ToolBarText = "Create Note" };
var refreshNotes = new Command((sender,e)=>{Task.Run(UpdateAsync).ConfigureAwait(false);}) {MenuText="Refresh", ToolBarText="Refresh", Shortcut = Keys.F5};
var quitCommand = new Command { MenuText = "Quit", Shortcut = Application.Instance.CommonModifier | Keys.Q };
quitCommand.Executed += (sender, e) => Application.Instance.Quit();
var aboutCommand = new Command { MenuText = "About..." };
aboutCommand.Executed += (sender, e) => new AboutDialog(this.GetType().Assembly){ Logo=Icon, ProgramDescription="A server backed note taking app\nServer Code: https://gitlab.tesses.net/tesses50/simplenotesserver", Website = new Uri("https://simplenotes.tesses.net/")}.ShowDialog(this);
var preferences= new ButtonMenuItem { Text = "&Preferences..."};
preferences.Click += (sender,e)=>{
using(var pref = new PreferenceDialog())
{
if(pref.ShowModal(this))
{
Task.Run(UpdateAsync).ConfigureAwait(false);
}
}
};
// create menu
Menu = new MenuBar
{
Items =
{
// File submenu
new SubMenuItem { Text = "&File", Items = { createNote, refreshNotes } },
// new SubMenuItem { Text = "&Edit", Items = { /* commands/items */ } },
// new SubMenuItem { Text = "&View", Items = { /* commands/items */ } },
},
ApplicationItems =
{
// application (OS X) or file menu (others)
preferences
},
QuitItem = quitCommand,
AboutItem = aboutCommand
};
// create toolbar
ToolBar = new ToolBar { Items = { createNote, refreshNotes } };
Task.Run(UpdateAsync).ConfigureAwait(false);
}
}
internal class NoteEditor : Dialog
{
public NoteEditor(string noteName="",long id=0)
{
Resizable=true;
Title = string.IsNullOrWhiteSpace(noteName) ? "Create Note" : noteName;
Size = new Size(640,480);
DynamicLayout layout=new DynamicLayout();
layout.BeginVertical();
layout.BeginHorizontal();
TextBox noteTitle = new TextBox(){Text=noteName, PlaceholderText="Note Title"};
noteTitle.TextChanged += (sender,e)=>{
Title=string.IsNullOrWhiteSpace(noteTitle.Text) ? "Create Note" : noteTitle.Text;
};
TextArea noteBody = new TextArea(){Text=""};
layout.Add(noteTitle,true);
layout.EndBeginHorizontal();
layout.Add(noteBody,true,true);
layout.EndHorizontal();
layout.EndVertical();
Content = layout;
this.Shown += (sender,e)=>{
Task.Run(async()=>{
var pref = PreferenceFile.GetPreferences();
if(!pref.HasValue) return;
var request = new HttpRequestMessage(HttpMethod.Get,$"{pref.ServerUrl.TrimEnd('/')}/api/note?id={id}");
request.Headers.Add("Authorization",$"Basic {Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes($"{pref.Username}:{pref.Password}"))}");
var response=await MainForm.Client.SendAsync(request);
var note = response.StatusCode == HttpStatusCode.OK ? JsonConvert.DeserializeObject<_NoteResponse>(await response.Content.ReadAsStringAsync())?.Note : null;
await Application.Instance.InvokeAsync(()=>{
noteBody.Text = note.Body;
});
}).ConfigureAwait(false);
};
ToolBar = new ToolBar(){Items = {
new Command((sender,e)=>{
Note note=new Note();
note.Id = id;
note.Title = noteTitle.Text;
note.Body = noteBody.Text;
var pref = PreferenceFile.GetPreferences();
if(pref.HasValue)
{
Task.Run(async()=>{
var request = new HttpRequestMessage(HttpMethod.Post,$"{pref.ServerUrl}/api/note");
request.Headers.Add("Authorization",$"Basic {Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes($"{pref.Username}:{pref.Password}"))}");
request.Content =new System.Net.Http.StringContent(JsonConvert.SerializeObject(note),Encoding.UTF8,"application/json");
var result=await MainForm.Client.SendAsync(request);
if(result.StatusCode == HttpStatusCode.OK)
{
var res2=JsonConvert.DeserializeObject<_NoteResponse>(await result.Content.ReadAsStringAsync());
id=res2?.Note?.Id ?? 0;
await Application.Instance.InvokeAsync(()=>{MessageBox.Show("Saved note");});
}
}).ConfigureAwait(false);
}
}){ToolBarText="Save"}
}};
}
}
internal class PreferenceDialog : Dialog<bool>
{
public PreferenceDialog()
{
this.Shown += (sender, e) =>
{
this.Size = new Size(320, Size.Height);
};
Title="Preferences";
this.Result=false;
var pref=PreferenceFile.GetPreferences();
DynamicLayout layout=new DynamicLayout();
TextBox url = new TextBox(){PlaceholderText = "Server Url", Text=pref.ServerUrl};
TextBox username = new TextBox(){PlaceholderText = "Username", Text=pref.Username};
PasswordBox password = new PasswordBox(){Text=pref.Password};
Button save=new Button((sender,e)=>{
pref.ServerUrl = url.Text;
pref.Username = username.Text;
pref.Password = password.Text;
pref.Save();
Close(true);
}){Text = "Save"};
layout.BeginVertical();
layout.BeginHorizontal();
layout.Add(new Label(){Text = "Server Url: "});
layout.Add(url,true);
layout.EndBeginHorizontal();
layout.Add(new Label(){Text="Username: "});
layout.Add(username,true);
layout.EndBeginHorizontal();
layout.Add(new Label(){Text="Password: "});
layout.Add(password,true);
layout.EndHorizontal();
layout.EndBeginVertical();
layout.BeginHorizontal();
layout.Add(save);
layout.EndBeginHorizontal();
layout.EndHorizontal();
layout.EndVertical();
Content = layout;
}
}
}