Fixed som graphics routines
Added support for on-the-fly textview contents updates Added iterative view removal to ConsoleController Added dumy layouts to Common Added support for account removal Fixed command management (now supports leading, padding and trailing spaces) Various smaller changes
This commit is contained in:
parent
35d61d9d91
commit
939f6c910b
@ -132,8 +132,9 @@ namespace Client
|
||||
bool b = !p.Value.StartsWith("ERROR");
|
||||
if (b) // Set proper state before notifying listener
|
||||
{
|
||||
RefreshTimeout();
|
||||
sessionID = p.Value;
|
||||
RefreshTimeout();
|
||||
SetAutoRefresh(true);
|
||||
}
|
||||
PostPromise(p.handler, b);
|
||||
return false;
|
||||
@ -203,7 +204,7 @@ namespace Client
|
||||
RefreshTimeout();
|
||||
return RegisterEventPromise(pID, p =>
|
||||
{
|
||||
p.handler.Value = p.Value.StartsWith("ERROR").ToString();
|
||||
PostPromise(p.handler, !p.Value.StartsWith("ERROR"));
|
||||
return false;
|
||||
});
|
||||
}
|
||||
@ -350,8 +351,7 @@ namespace Client
|
||||
|
||||
protected void SetAutoRefresh(bool doAR)
|
||||
{
|
||||
if (RefreshSessions == doAR) return;
|
||||
if (RefreshSessions = doAR)
|
||||
if (RefreshSessions = doAR && (sessionChecker==null || sessionChecker.Status!=TaskStatus.Running))
|
||||
{
|
||||
triggerRefreshCancel = false;
|
||||
sessionChecker = new Task(DoRefresh);
|
||||
@ -367,7 +367,14 @@ namespace Client
|
||||
return;
|
||||
}
|
||||
// Refresher calls refresh 1500ms before expiry (or asap if less time is available)
|
||||
Task.Delay((int)((Math.Min(0, loginTimeout - DateTime.Now.Ticks - 1500)) / TimeSpan.TicksPerMillisecond));
|
||||
try
|
||||
{
|
||||
Task.Delay((int)Math.Max(1, ((loginTimeout - DateTime.Now.Ticks) / TimeSpan.TicksPerMillisecond) - 1500)).Wait();
|
||||
}
|
||||
catch
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("OOF");
|
||||
}
|
||||
if (triggerRefreshCancel)
|
||||
{
|
||||
triggerRefreshCancel = false;
|
||||
@ -377,18 +384,21 @@ namespace Client
|
||||
{
|
||||
try
|
||||
{
|
||||
Refresh();
|
||||
Promise p = Promise.AwaitPromise(Refresh());
|
||||
p.Subscribe = refreshResult =>
|
||||
{
|
||||
if (RefreshSessions && bool.Parse(refreshResult.Value))
|
||||
{
|
||||
sessionChecker = new Task(DoRefresh);
|
||||
sessionChecker.Start();
|
||||
}
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Session probably died
|
||||
return;
|
||||
}
|
||||
if (RefreshSessions)
|
||||
{
|
||||
sessionChecker = new Task(DoRefresh);
|
||||
sessionChecker.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,8 @@
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
|
@ -78,6 +78,12 @@ namespace Client.ConsoleForms
|
||||
Draw(false);
|
||||
}
|
||||
|
||||
public void CloseIf(Predicate<View> p)
|
||||
{
|
||||
for(int i = renderQueue.Count - 1; i>=0; --i)
|
||||
if (p(renderQueue[i].Item1))
|
||||
CloseView(i);
|
||||
}
|
||||
public void CloseTop() => CloseView(renderQueue[renderQueue.Count - 1].Item1);
|
||||
public void CloseView(int idx) => CloseView(renderQueue[idx].Item1);
|
||||
public void CloseView(View v, bool redraw = true, int maxCloses = -1)
|
||||
|
@ -13,6 +13,7 @@ namespace Client.ConsoleForms.Graphics
|
||||
public delegate void SelectListener(DialogView view, int selectionIndex, string selection);
|
||||
|
||||
protected readonly ViewData[] options;
|
||||
protected readonly int optionsWidth;
|
||||
protected int select;
|
||||
protected SelectListener listener;
|
||||
|
||||
@ -21,6 +22,18 @@ namespace Client.ConsoleForms.Graphics
|
||||
get => select;
|
||||
set => select = value < 0 ? 0 : value >= options.Length ? options.Length - 1 : value;
|
||||
}
|
||||
|
||||
public override string Text
|
||||
{
|
||||
get => base.Text;
|
||||
set
|
||||
{
|
||||
base.Text = value;
|
||||
// Since setting the text triggers a rendering recomputation for TextView, we have to recompute rendering for options too
|
||||
if (optionsWidth > ContentWidth) ContentWidth = optionsWidth;
|
||||
ContentHeight += 2;
|
||||
}
|
||||
}
|
||||
/*
|
||||
public override Region Occlusion => new Region(
|
||||
new Rectangle(
|
||||
@ -47,27 +60,26 @@ namespace Client.ConsoleForms.Graphics
|
||||
ViewData optionsData = parameters.Get("Options");
|
||||
this.options = optionsData.nestedData.Filter(p => p.Name.Equals("Option")).ToArray();
|
||||
this.select = parameters.AttribueAsInt("select");
|
||||
ContentHeight += 2;
|
||||
//ContentHeight += 2;
|
||||
select = select < 0 ? 0 : select >= options.Length ? 0 : select;
|
||||
SelectColor = (ConsoleColor)parameters.AttribueAsInt("select_color", (int)ConsoleColor.Gray);
|
||||
NotSelectColor = (ConsoleColor)parameters.AttribueAsInt("unselect_color", (int)ConsoleColor.White);
|
||||
optionsWidth = ComputeLength(parameters.Get("Options")?.CollectSub("Option") ?? new Tuple<string, string>[0]);
|
||||
if (optionsWidth > ContentWidth) ContentWidth = optionsWidth;
|
||||
}
|
||||
|
||||
protected override void _Draw(int left, ref int top)
|
||||
{
|
||||
//DrawEmptyPadding(left, ref top, padding.Top());
|
||||
base.DrawContent(left, ref top);
|
||||
DrawEmptyPadding(left, ref top, 1);
|
||||
DrawOptions(left, ref top);
|
||||
//DrawEmptyPadding(left, ref top, padding.Bottom());
|
||||
}
|
||||
|
||||
protected virtual void DrawOptions(int left, ref int top)
|
||||
{
|
||||
int pl = padding.Left(), pr = padding.Right();
|
||||
Console.SetCursorPosition(left, top++);
|
||||
|
||||
int pad = ContentWidth - options.CollectiveLength() - options.Length;// + pl + pr;
|
||||
int pad = ContentWidth - options.CollectiveLength() - options.Length;
|
||||
int lpad = (int)(pad / 2f);
|
||||
Console.BackgroundColor = BackgroundColor;
|
||||
Console.Write(Filler(' ', lpad));
|
||||
|
@ -13,12 +13,13 @@ namespace Client.ConsoleForms.Graphics
|
||||
protected string[] text;
|
||||
protected string[] text_render;
|
||||
protected int maxWidth, maxHeight;
|
||||
protected readonly bool enforceWidth;
|
||||
|
||||
private string _text;
|
||||
public string Text
|
||||
public virtual string Text
|
||||
{
|
||||
get => _text;
|
||||
protected set
|
||||
set
|
||||
{
|
||||
_text = value;
|
||||
text = _text.Split(' ');
|
||||
@ -27,7 +28,7 @@ namespace Client.ConsoleForms.Graphics
|
||||
text_render = ComputeTextDimensions(this.text);
|
||||
int actualWidth = 0;
|
||||
foreach (var t in text_render) if (actualWidth < t.Length) actualWidth = t.Length;
|
||||
ContentWidth = maxWidth;// + padding.Left() + padding.Right();
|
||||
ContentWidth = enforceWidth ? maxWidth : actualWidth;// + padding.Left() + padding.Right();
|
||||
ContentHeight = text_render.Length;// + padding.Top() + padding.Bottom();
|
||||
Dirty = true;
|
||||
}
|
||||
@ -42,6 +43,8 @@ namespace Client.ConsoleForms.Graphics
|
||||
)
|
||||
);
|
||||
|
||||
//TODO: Add contentocclusion
|
||||
|
||||
//public char Border { get; set; }
|
||||
//public ConsoleColor BorderColor { get; set; }
|
||||
|
||||
@ -52,7 +55,8 @@ namespace Client.ConsoleForms.Graphics
|
||||
foreach (var t in parameters.NestedText("Text").Split('\n'))
|
||||
if (t.Length > widest)
|
||||
widest = t.Length;
|
||||
this.maxWidth = parameters.AttribueAsInt("width") < 1 ? widest : parameters.AttribueAsInt("width");
|
||||
enforceWidth = parameters.AttribueAsInt("width") > 0;
|
||||
this.maxWidth = enforceWidth ? parameters.AttribueAsInt("width") : widest;
|
||||
this.maxHeight = parameters.AttribueAsInt("height", -1);
|
||||
|
||||
this.text = (Text = parameters.NestedText("Text")).Split(' ');
|
||||
|
@ -28,11 +28,12 @@ namespace Client.ConsoleForms.Graphics
|
||||
public ConsoleColor TextColor { get; set; }
|
||||
public int ContentWidth { get; protected set; }
|
||||
public int ContentHeight { get; protected set; }
|
||||
public abstract Region Occlusion { get; }
|
||||
public bool Dirty { get; set; }
|
||||
public LangManager I18n { get; private set; }
|
||||
public ViewEvent OnBackEvent { get; set; }
|
||||
public ViewEvent OnClose { get; set; }
|
||||
public abstract Region Occlusion { get; } // Reports dimensions of entire view
|
||||
public Region ContentOcclusion { get => Occlusion; }// Reports dimensions of contents (SHOULD for most applications be smaller than or equal in size to Occlusion)
|
||||
public bool Dirty { get; set; } // Flag for whether or not view requires re-rendering
|
||||
public LangManager I18n { get; private set; } // Translation
|
||||
public ViewEvent OnBackEvent { get; set; } // Callback for [ESC] key
|
||||
public ViewEvent OnClose { get; set; } // Callback called immediately before controller removes view from render queue
|
||||
|
||||
public View(ViewData parameters, LangManager lang)
|
||||
{
|
||||
|
@ -248,7 +248,7 @@ namespace Client
|
||||
accountsGetter.Unsubscribe();
|
||||
Hide("data_fetch");
|
||||
|
||||
Show(GenerateList(p.Value.Split('&').ForEach(Support.FromBase64String), SubmitListener));
|
||||
Show(GenerateList(p.Value.Split('&').ForEach(Support.FromBase64String), ViewAccountListener));
|
||||
};
|
||||
});
|
||||
|
||||
@ -518,7 +518,7 @@ namespace Client
|
||||
}
|
||||
}
|
||||
|
||||
private void SubmitListener(View listener)
|
||||
private void ViewAccountListener(View listener)
|
||||
{
|
||||
ButtonView view = listener as ButtonView;
|
||||
|
||||
@ -536,7 +536,7 @@ namespace Client
|
||||
.SetAttribute("border", (int)ConsoleColor.DarkGreen)
|
||||
|
||||
// Option buttons
|
||||
.AddNested(new ViewData("Options").AddNestedSimple("Option", GetIntlString("GENERIC_dismiss")))
|
||||
.AddNested(new ViewData("Options").AddNestedSimple("Option", GetIntlString("GENERIC_dismiss")).AddNestedSimple("Option", GetIntlString("SE_account_delete")))
|
||||
|
||||
// Message
|
||||
.AddNestedSimple("Text",
|
||||
@ -548,7 +548,40 @@ namespace Client
|
||||
// No translation (it's already handled)
|
||||
LangManager.NO_LANG);
|
||||
|
||||
show.RegisterSelectListener((_, s, l) => Hide(show));
|
||||
show.RegisterSelectListener((_, s, l) =>
|
||||
{
|
||||
if(s==0) Hide(show);
|
||||
else
|
||||
{
|
||||
var ynDialog = GetView<DialogView>("yn");
|
||||
ynDialog.Text = GetIntlString("SE_account_delete_warn");
|
||||
ynDialog.RegisterSelectListener((v, i, str) =>
|
||||
{
|
||||
var stall = GetView<TextView>("stall");
|
||||
stall.Text = GetIntlString("SE_account_delete_stall");
|
||||
Show(stall);
|
||||
if (i == 1)
|
||||
{
|
||||
Promise p = Promise.AwaitPromise(interactor.CloseAccount(name));
|
||||
p.Subscribe = deleteAwait =>
|
||||
{
|
||||
if (bool.Parse(deleteAwait.Value))
|
||||
{
|
||||
accountChange = true;
|
||||
controller.Popup(GetIntlString("SE_account_delete_success"), 1500, ConsoleColor.Green, () => {
|
||||
bool closed = false;
|
||||
controller.CloseIf(predV => closed = !closed && predV is ListView);
|
||||
Hide(show);
|
||||
});
|
||||
}
|
||||
else controller.Popup(GetIntlString("SE_account_delete_fail"), 2000, ConsoleColor.Red);
|
||||
Hide(stall);
|
||||
};
|
||||
}
|
||||
});
|
||||
Show(ynDialog);
|
||||
}
|
||||
});
|
||||
Show(show);
|
||||
}
|
||||
|
||||
|
@ -52,4 +52,23 @@
|
||||
<Text>@string/NC_quit</Text>
|
||||
</DialogView>
|
||||
|
||||
<DialogView id="yn"
|
||||
padding_left="2"
|
||||
padding_right="2"
|
||||
padding_top="1"
|
||||
padding_bottom="1"
|
||||
border="4">
|
||||
<Options>
|
||||
<Option close="true">@string/GENERIC_negative</Option>
|
||||
<Option close="true">@string/GENERIC_positive</Option>
|
||||
</Options>
|
||||
<Text></Text>
|
||||
</DialogView>
|
||||
|
||||
<TextView id="stall"
|
||||
padding_left="2"
|
||||
padding_right="2"
|
||||
padding_top="1"
|
||||
padding_bottom="1">
|
||||
</TextView>
|
||||
</Resources>
|
@ -125,6 +125,11 @@ Balance: $2 SEK</Entry>
|
||||
<Entry name="SE_account_stall">Creating account...</Entry>
|
||||
<Entry name="SE_account_exists">Account "$0" already exists!</Entry>
|
||||
<Entry name="SE_account_success">Account successfully created!</Entry>
|
||||
<Entry name="SE_account_delete">Delete account</Entry>
|
||||
<Entry name="SE_account_delete_warn">Are you sure you want to delete this account?</Entry>
|
||||
<Entry name="SE_account_delete_success">Account successfully deleted!</Entry>
|
||||
<Entry name="SE_account_delete_fail">Account couldn't be deleted!</Entry>
|
||||
<Entry name="SE_account_delete_stall">Deleting...</Entry>
|
||||
<Entry name="SE_exit_prompt">Are you sure you would like log out and exit?</Entry>
|
||||
|
||||
<Entry name="GENERIC_fetch">Fetching data...</Entry>
|
||||
|
@ -124,6 +124,11 @@ Balance: $1 SEK</Entry>
|
||||
<Entry name="SE_account_stall">Creating account...</Entry>
|
||||
<Entry name="SE_account_exists">Account "$0" already exists!</Entry>
|
||||
<Entry name="SE_account_success">Account successfully created!</Entry>
|
||||
<Entry name="SE_account_delete">Delete account</Entry>
|
||||
<Entry name="SE_account_delete_warn">Are you sure you want to delete this account?</Entry>
|
||||
<Entry name="SE_account_delete_success">Account successfully deleted!</Entry>
|
||||
<Entry name="SE_account_delete_fail">Account couldn't be deleted!</Entry>
|
||||
<Entry name="SE_account_delete_stall">Deleting...</Entry>
|
||||
<Entry name="SE_exit_prompt">Are you sure you would like log out and exit?</Entry>
|
||||
|
||||
<Entry name="GENERIC_fetch">Fetching data...</Entry>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Strings label="Svenska">
|
||||
<Entry name="WS_welcome">Hej och välkommen till ConsoleForms bankprojektet!
|
||||
Om du är ovan vid ConsoleForms-gränssnittet och vill
|
||||
@ -62,6 +62,7 @@ programmet, tryck [ESC].</Entry>
|
||||
<Entry name="NC_identity">Verifierar serverns identitet...</Entry>
|
||||
<Entry name="NC_verified">Serveridentitet verifierad!</Entry>
|
||||
<Entry name="NC_verror">Serveridentitet kunde inte verifieras!</Entry>
|
||||
<Entry name="NC_quit">Vill du avsluta programmet?</Entry>
|
||||
|
||||
<Entry name="SU_welcome">Välkommen till Tofvessons banksystem!
|
||||
För att fortsätta, tryck [ENTER]
|
||||
@ -129,6 +130,11 @@ Kontobalans: $1 SEK</Entry>
|
||||
<Entry name="SE_account_stall">Skapar konto...</Entry>
|
||||
<Entry name="SE_account_exists">Kontot "$0" finns redan!</Entry>
|
||||
<Entry name="SE_account_success">Konto skapat!</Entry>
|
||||
<Entry name="SE_account_delete">Radera konto</Entry>
|
||||
<Entry name="SE_account_delete_warn">Är du säker på att du vill radera kontot?</Entry>
|
||||
<Entry name="SE_account_delete_success">Kontot raderat</Entry>
|
||||
<Entry name="SE_account_delete_fail">Konto kunde inte raderas!</Entry>
|
||||
<Entry name="SE_account_delete_stall">Raderar...</Entry>
|
||||
<Entry name="SE_exit_prompt">Är du säker på att du vill logga ut och stänga?</Entry>
|
||||
|
||||
<Entry name="GENERIC_fetch">Hämtar data...</Entry>
|
||||
|
@ -74,6 +74,7 @@ namespace Server
|
||||
bool wasFlag = true;
|
||||
for (int i = 1; i<parts.Length; ++i)
|
||||
{
|
||||
if (parts[i].Length == 0) continue;
|
||||
if (parts[i].StartsWith("-") && parts[i].Length==2)
|
||||
{
|
||||
if (reconstruct.Length != 0)
|
||||
|
@ -32,6 +32,32 @@ namespace Server
|
||||
|
||||
public bool HandleCommand(string cmd)
|
||||
{
|
||||
|
||||
// Find leading and trailing spaces
|
||||
int pre = 0, post = cmd.Length;
|
||||
bool preS = false, postS = false;
|
||||
for(int i = 0; i<cmd.Length; ++i)
|
||||
{
|
||||
if(cmd[i]!=' ')
|
||||
{
|
||||
pre = i;
|
||||
if (postS) break;
|
||||
else preS = true;
|
||||
}
|
||||
if(cmd[cmd.Length - 1 - i]!=' ')
|
||||
{
|
||||
post = cmd.Length - i;
|
||||
if (preS) break;
|
||||
else postS = true;
|
||||
}
|
||||
}
|
||||
|
||||
// The entire command is just blank spaces
|
||||
if (post < 2 || pre>post) return false;
|
||||
|
||||
// Trim leading and trailing spaces
|
||||
cmd = cmd.Substring(pre, post - pre);
|
||||
|
||||
foreach (var command in commands)
|
||||
if (command.Item1.Invoke(cmd))
|
||||
return true;
|
||||
|
@ -471,21 +471,37 @@ Use command 'help' to get a list of available commands";
|
||||
}), "Get or set verbosity level: DEBUG, INFO, FATAL (alternatively enter 0, 1 or 2 respectively)")
|
||||
.Append(new Command("sess") // Display active sessions
|
||||
.WithParameter("sessionID", 'r', Parameter.ParamType.STRING, true)
|
||||
.WithParameter("username", 'u', Parameter.ParamType.STRING, true)
|
||||
.SetAction(
|
||||
(c, l) => {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
manager.Update(); // Ensure that we don't show expired sessions (artifacts exist until it is necessary to remove them)
|
||||
foreach (var session in manager.Sessions)
|
||||
builder
|
||||
.Append(session.user.Name)
|
||||
.Append(" : ")
|
||||
.Append(session.sessionID)
|
||||
.Append(" : ")
|
||||
.Append((session.expiry-DateTime.Now.Ticks) /TimeSpan.TicksPerMillisecond)
|
||||
.Append('\n');
|
||||
if (builder.Length == 0) builder.Append("There are no active sessions at the moment");
|
||||
else --builder.Insert(0, "Active sessions:\n").Length;
|
||||
Output.Raw(builder);
|
||||
bool r = l.HasFlag('r'), u = l.HasFlag('u');
|
||||
if(r && u)
|
||||
{
|
||||
Output.Error("Cannot refresh session by username AND SessionID!");
|
||||
return;
|
||||
}
|
||||
if((r||u) && !manager.HasSession(l[0].Item1, u))
|
||||
{
|
||||
Output.Error("Session could not be found!");
|
||||
return;
|
||||
}
|
||||
if (r||u) Output.Raw($"Session refreshed: {manager.Refresh(l[0].Item1, u)}");
|
||||
else
|
||||
{
|
||||
foreach (var session in manager.Sessions)
|
||||
builder
|
||||
.Append(session.user.Name)
|
||||
.Append(" : ")
|
||||
.Append(session.sessionID)
|
||||
.Append(" : ")
|
||||
.Append((session.expiry - DateTime.Now.Ticks) / TimeSpan.TicksPerMillisecond)
|
||||
.Append('\n');
|
||||
if (builder.Length == 0) builder.Append("There are no active sessions at the moment");
|
||||
else --builder.Insert(0, "Active sessions:\n").Length;
|
||||
Output.Raw(builder);
|
||||
}
|
||||
}), "List or refresh active client sessions")
|
||||
.Append(new Command("list").WithParameter(Parameter.Flag('a')).SetAction( // Display users
|
||||
(c, l) => {
|
||||
|
@ -19,6 +19,14 @@ namespace Server
|
||||
this.sidLength = sidLength < 10 ? 10 : sidLength;
|
||||
}
|
||||
|
||||
public bool HasSession(string sess, bool byUserName = false)
|
||||
{
|
||||
foreach (var session in Sessions)
|
||||
if ((byUserName && session.user.Name.Equals(sess)) || (!byUserName && session.sessionID.Equals(sess)))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public string GetSession(Database.User user, string invalidSID)
|
||||
{
|
||||
Update();
|
||||
@ -70,11 +78,11 @@ namespace Server
|
||||
return;
|
||||
}
|
||||
|
||||
public bool Refresh(string sid)
|
||||
public bool Refresh(string sid, bool asUser = false)
|
||||
{
|
||||
Update();
|
||||
for (int i = sessions.Count - 1; i >= 0; --i)
|
||||
if (sessions[i].sessionID.Equals(sid))
|
||||
if ((asUser && sessions[i].user.Name.Equals(sid)) || (!asUser && sessions[i].sessionID.Equals(sid)))
|
||||
{
|
||||
Session s = sessions[i];
|
||||
s.expiry = DateTime.Now.Ticks + timeout;
|
||||
|
Loading…
x
Reference in New Issue
Block a user