305 lines
11 KiB
C#
305 lines
11 KiB
C#
|
/*
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
}
|