/* 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 . */ 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 Notes {get;set;}=new List(); } 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 { 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; } } }