Commit 95158366 by Mac Stephens

WIP building new client and adding item_num system

parent ba563f22
...@@ -16,3 +16,7 @@ emT3XDataServer/Win32/ ...@@ -16,3 +16,7 @@ emT3XDataServer/Win32/
*.txt *.txt
emT3Web/Win32/Debug/ emT3Web/Win32/Debug/
emT3Web/__recovery/
emT3WebApp/__history/
...@@ -110,6 +110,6 @@ String TfMain::BuildLaunchUrl(const String &baseUrl, const String &userId, const ...@@ -110,6 +110,6 @@ String TfMain::BuildLaunchUrl(const String &baseUrl, const String &userId, const
String qTaskId = TNetEncoding::URL->Encode(taskId); String qTaskId = TNetEncoding::URL->Encode(taskId);
String qCode = TNetEncoding::URL->Encode(code); String qCode = TNetEncoding::URL->Encode(code);
return cleanBaseUrl + sep + "user_id=" + qUserId + "&task_id=" + qTaskId + "&code=" + qCode; return cleanBaseUrl + sep + "user_id=" + qUserId + "&task_id=" + qTaskId + "&url_code=" + qCode;
} }
...@@ -80,7 +80,7 @@ object fMain: TfMain ...@@ -80,7 +80,7 @@ object fMain: TfMain
Width = 337 Width = 337
Height = 23 Height = 23
TabOrder = 4 TabOrder = 4
Text = 'http://127.0.0.1:8000/emT3webClient/index.html' Text = 'http://localhost:8000/emT3webApp/index.html'
end end
object edtExpSeconds: TEdit object edtExpSeconds: TEdit
Left = 500 Left = 500
......
...@@ -120,10 +120,6 @@ end; ...@@ -120,10 +120,6 @@ end;
procedure TAuthService.Logout; procedure TAuthService.Logout;
begin begin
DeleteToken; DeleteToken;
window.localStorage.removeItem('EMT3_USER_ID');
window.localStorage.removeItem('EMT3_TASK_ID');
window.localStorage.removeItem('EMT3_CODE');
end; end;
procedure TAuthService.SetToken(AToken: string); procedure TAuthService.SetToken(AToken: string);
......
...@@ -17,9 +17,6 @@ type ...@@ -17,9 +17,6 @@ type
procedure AuthConnectionError(Error: TXDataWebConnectionError); procedure AuthConnectionError(Error: TXDataWebConnectionError);
private private
FUnauthorizedAccessProc: TUnauthorizedAccessProc; FUnauthorizedAccessProc: TUnauthorizedAccessProc;
FUserIdParam: string;
FTaskIdParam: string;
FCodeParam: string;
public public
const clientVersion = '0.0.1'; const clientVersion = '0.0.1';
......
object FHome: TFHome
Width = 640
Height = 480
CSSLibrary = cssBootstrap
ElementFont = efCSS
object edtCode: TWebEdit
Left = 380
Top = 198
Width = 121
Height = 22
TabStop = False
ElementClassName = 'form-control'
ElementID = 'edt_code'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
ShowFocus = False
WidthPercent = 100.000000000000000000
end
object edtTaskId: TWebEdit
Left = 104
Top = 198
Width = 121
Height = 22
TabStop = False
ChildOrder = 1
ElementClassName = 'form-control'
ElementID = 'edt_task_id'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
ShowFocus = False
WidthPercent = 100.000000000000000000
end
object edtUserId: TWebEdit
Left = 240
Top = 198
Width = 121
Height = 22
TabStop = False
ChildOrder = 2
ElementClassName = 'form-control'
ElementID = 'edt_user_id'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
ShowFocus = False
WidthPercent = 100.000000000000000000
end
end
<div class="container-fluid py-3">
<div class="card shadow-sm">
<div class="card-body">
<h4 class="mb-3">Home Form</h4>
<div class="mb-3">
<label for="edt_task_id" class="form-label">Task Id</label>
<input id="edt_task_id" type="text" class="form-control">
</div>
<div class="mb-3">
<label for="edt_user_id" class="form-label">User Id</label>
<input id="edt_user_id" type="text" class="form-control">
</div>
<div class="mb-3">
<label for="edt_code" class="form-label">Code</label>
<input id="edt_code" type="text" class="form-control">
</div>
</div>
</div>
</div>
unit View.Home;
interface
uses
System.SysUtils, System.Classes, JS, Web, WEBLib.Graphics, WEBLib.Controls,
WEBLib.Forms, WEBLib.Dialogs, Vcl.Controls, Vcl.StdCtrls, WEBLib.StdCtrls;
type
TFHome = class(TWebForm)
edtCode: TWebEdit;
edtTaskId: TWebEdit;
edtUserId: TWebEdit;
private
FTaskId: string;
FUserId: string;
FCode: string;
public
class function CreateForm(AElementID, ATaskId, AUserId, ACode: string): TWebForm;
procedure InitializeForm;
end;
var
FHome: TFHome;
implementation
{$R *.dfm}
class function TFHome.CreateForm(AElementID, ATaskId, AUserId, ACode: string): TWebForm;
procedure AfterCreate(AForm: TObject);
begin
TFHome(AForm).FTaskId := ATaskId;
TFHome(AForm).FUserId := AUserId;
TFHome(AForm).FCode := ACode;
TFHome(AForm).InitializeForm;
end;
begin
Application.CreateForm(TFHome, AElementID, Result, @AfterCreate);
end;
procedure TFHome.InitializeForm;
begin
console.log('TFHome.InitializeForm fired');
console.log('TaskId=' + FTaskId);
console.log('UserId=' + FUserId);
console.log('Code=' + FCode);
edtTaskId.Text := FTaskId;
edtUserId.Text := FUserId;
edtCode.Text := FCode;
end;
end.
...@@ -113,6 +113,19 @@ object FViewMain: TFViewMain ...@@ -113,6 +113,19 @@ object FViewMain: TFViewMain
'Close') 'Close')
Opacity = 0.200000000000000000 Opacity = 0.200000000000000000
end end
object edtTaskIdMain: TWebEdit
Left = 220
Top = 170
Width = 121
Height = 22
ChildOrder = 6
ElementClassName = 'form-control'
ElementID = 'edt_task_id_main'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object XDataWebClient: TXDataWebClient object XDataWebClient: TXDataWebClient
Connection = DMConnection.ApiConnection Connection = DMConnection.ApiConnection
Left = 44 Left = 44
......
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
<a id="view.main.apptitle" class="navbar-brand" href="index.html">emT3web</a> <a id="view.main.apptitle" class="navbar-brand" href="index.html">emT3web</a>
<span id="view.main.version" class="small text-muted ms-2"></span> <span id="view.main.version" class="small text-muted ms-2"></span>
</div> </div>
<li class="nav-item ms-2 me-2 d-flex align-items-center">
<input id="edt_task_id_main" type="text" class="form-control form-control-sm" placeholder="Task Id">
</li>
<div class="collapse navbar-collapse show" id="navbarNavDropdown"> <div class="collapse navbar-collapse show" id="navbarNavDropdown">
<ul class="navbar-nav ms-auto"> <ul class="navbar-nav ms-auto">
......
...@@ -6,7 +6,7 @@ uses ...@@ -6,7 +6,7 @@ uses
System.SysUtils, System.Classes, JS, Web, WEBLib.Graphics, WEBLib.Controls, System.SysUtils, System.Classes, JS, Web, WEBLib.Graphics, WEBLib.Controls,
WEBLib.Forms, WEBLib.Dialogs, WEBLib.ExtCtrls, Vcl.Controls, Vcl.StdCtrls, WEBLib.Forms, WEBLib.Dialogs, WEBLib.ExtCtrls, Vcl.Controls, Vcl.StdCtrls,
WEBLib.StdCtrls, Data.DB, XData.Web.JsonDataset, XData.Web.Dataset, WEBLib.StdCtrls, Data.DB, XData.Web.JsonDataset, XData.Web.Dataset,
App.Types, ConnectionModule, XData.Web.Client, WEBLib.Menus, Utils; App.Types, ConnectionModule, XData.Web.Client, WEBLib.Menus, Utils, View.Home;
type type
TFViewMain = class(TWebForm) TFViewMain = class(TWebForm)
...@@ -17,25 +17,23 @@ type ...@@ -17,25 +17,23 @@ type
XDataWebClient: TXDataWebClient; XDataWebClient: TXDataWebClient;
lblVersion: TWebLabel; lblVersion: TWebLabel;
lblAppTitle: TWebLabel; lblAppTitle: TWebLabel;
edtTaskIdMain: TWebEdit;
procedure WebFormCreate(Sender: TObject); procedure WebFormCreate(Sender: TObject);
procedure mnuLogoutClick(Sender: TObject); procedure mnuLogoutClick(Sender: TObject);
procedure wllblLogoutClick(Sender: TObject); procedure wllblLogoutClick(Sender: TObject);
private private
{ Private declarations } { Private declarations }
FUserInfo: string; FTasksHtmlForm: TWebForm;
FSearchSettings: string;
FChildForm: TWebForm;
FLogoutProc: TLogoutProc; FLogoutProc: TLogoutProc;
FSearchProc: TSearchProc;
procedure ShowCrudForm( AFormClass: TWebFormClass );
procedure ConfirmLogout; procedure ConfirmLogout;
procedure LoadTasksHtmlForm;
procedure LoadHomeForm;
public public
{ Public declarations } { Public declarations }
class procedure Display(LogoutProc: TLogoutProc); FUserId: string;
procedure ShowForm( AFormClass: TWebFormClass ); FTaskId: string;
var FCode: string;
search: string; class procedure Display(LogoutProc: TLogoutProc; const AUserId, ATaskId, ACode: string);
change: boolean;
end; end;
var var
...@@ -45,30 +43,40 @@ implementation ...@@ -45,30 +43,40 @@ implementation
uses uses
Auth.Service, Auth.Service,
View.Tasks, View.TasksHTML;
View.TasksHTML,
View.TasksDataGrid,
View.TasksDBGrid;
{$R *.dfm} {$R *.dfm}
class procedure TFViewMain.Display(LogoutProc: TLogoutProc); class procedure TFViewMain.Display(LogoutProc: TLogoutProc; const AUserId, ATaskId, ACode: string);
begin begin
if Assigned(FViewMain) then if Assigned(FViewMain) then
FViewMain.Free; FViewMain.Free;
FViewMain := TFViewMain.CreateNew; FViewMain := TFViewMain.CreateNew;
FViewMain.FLogoutProc := LogoutProc; FViewMain.FLogoutProc := LogoutProc;
FViewMain.FUserId := AUserId;
FViewMain.FTaskId := ATaskId;
FViewMain.FCode := ACode;
console.log('Main form values assigned after create');
console.log('UserId=' + FViewMain.FUserId);
console.log('TaskId=' + FViewMain.FTaskId);
console.log('Code=' + FViewMain.FCode);
end; end;
procedure TFViewMain.WebFormCreate(Sender: TObject); procedure TFViewMain.WebFormCreate(Sender: TObject);
begin begin
console.log('TFViewMain.WebFormCreate fired'); console.log('TFViewMain.WebFormCreate fired');
// FChildForm := nil;
console.log('About to ShowForm(TFTasksHTML), host=' + WebPanel1.ElementID);
ShowForm(TFTasksHTML);
lblAppTitle.Caption := 'emT3web'; lblAppTitle.Caption := 'emT3web';
lblVersion.Caption := 'v' + DMConnection.clientVersion; lblVersion.Caption := 'v' + DMConnection.clientVersion;
console.log('Main form values assigned in webformcreate');
console.log('UserId=' + FViewMain.FUserId);
console.log('TaskId=' + FViewMain.FTaskId);
console.log('Code=' + FViewMain.FCode);
LoadHomeForm;
// LoadTasksHtmlForm;
end; end;
...@@ -99,26 +107,28 @@ begin ...@@ -99,26 +107,28 @@ begin
); );
end; end;
procedure TFViewMain.LoadTasksHtmlForm;
procedure TFViewMain.ShowCrudForm(AFormClass: TWebFormClass);
begin begin
ShowForm(AFormClass); if Assigned(FTasksHtmlForm) then
end; FTasksHtmlForm.Free;
console.log('About to create TFTasksHTML, host=' + WebPanel1.ElementID);
console.log('Main form task id is: ' + FTaskId);
procedure TFViewMain.ShowForm(AFormClass: TWebFormClass); FTasksHtmlForm := TFTasksHTML.CreateForm(WebPanel1.ElementID, FTaskId);
begin
if Assigned(FChildForm) then
FChildForm.Free;
Application.CreateForm(AFormClass, WebPanel1.ElementID, FChildForm);
console.log('CreateForm called, FChildForm assigned: ' + BoolToStr(Assigned(FChildForm)));
end; end;
//procedure TFViewMain.ShowTasksForm(Info: string); procedure TFViewMain.LoadHomeForm;
//begin begin
// if Assigned(FChildForm) then if Assigned(FTasksHtmlForm) then
// FChildForm.Free; FTasksHtmlForm.Free;
// FChildForm := TFViewUsers.CreateForm(WebPanel1.ElementID, Info);
//end; console.log('About to create TFHome, host=' + WebPanel1.ElementID);
console.log('Main form task id is: ' + FTaskId);
console.log('Main form user id is: ' + FUserId);
console.log('Main form code is: ' + FCode);
FTasksHtmlForm := TFHome.CreateForm(WebPanel1.ElementID, FTaskId, FUserId, FCode);
end;
end. end.
object FTasks: TFTasks
Width = 1416
Height = 846
CSSLibrary = cssBootstrap
ElementFont = efCSS
FormContainer = 'tasks.root'
OnCreate = WebFormCreate
object pnlGrid: TWebPanel
Left = 0
Top = 0
Width = 1408
Height = 841
ElementID = 'pnl_grid'
ChildOrder = 1
ElementFont = efCSS
TabOrder = 0
object grdTasks: TTMSFNCDataGrid
Left = 12
Top = 14
Width = 1373
Height = 799
ParentDoubleBuffered = False
DoubleBuffered = True
TabOrder = 0
ShowAcceleratorChar = False
Footer.Bar.Buttons = <>
Header.VisualGrouping.Layout.Font.Charset = DEFAULT_CHARSET
Header.VisualGrouping.Layout.Font.Color = clWindowText
Header.VisualGrouping.Layout.Font.Height = -12
Header.VisualGrouping.Layout.Font.Name = 'Segoe UI'
Header.VisualGrouping.Layout.Font.Style = []
Header.Bar.Buttons = <>
CellAppearance.FilterMatchLayout.Font.Charset = DEFAULT_CHARSET
CellAppearance.FilterMatchLayout.Font.Color = clWindowText
CellAppearance.FilterMatchLayout.Font.Height = -12
CellAppearance.FilterMatchLayout.Font.Name = 'Segoe UI'
CellAppearance.FilterMatchLayout.Font.Style = []
CellAppearance.FilterMatchLayout.WordWrapping = True
CellAppearance.FilterInverseMatchLayout.Font.Charset = DEFAULT_CHARSET
CellAppearance.FilterInverseMatchLayout.Font.Color = clWindowText
CellAppearance.FilterInverseMatchLayout.Font.Height = -12
CellAppearance.FilterInverseMatchLayout.Font.Name = 'Segoe UI'
CellAppearance.FilterInverseMatchLayout.Font.Style = []
CellAppearance.FilterInverseMatchLayout.WordWrapping = True
CellAppearance.BandLayout.Font.Charset = DEFAULT_CHARSET
CellAppearance.BandLayout.Font.Color = clBlack
CellAppearance.BandLayout.Font.Height = -12
CellAppearance.BandLayout.Font.Name = 'Segoe UI'
CellAppearance.BandLayout.Font.Style = []
CellAppearance.BandLayout.WordWrapping = True
CellAppearance.FixedLayout.Font.Charset = DEFAULT_CHARSET
CellAppearance.FixedLayout.Font.Color = clBlack
CellAppearance.FixedLayout.Font.Height = -12
CellAppearance.FixedLayout.Font.Name = 'Segoe UI'
CellAppearance.FixedLayout.Font.Style = []
CellAppearance.FixedLayout.WordWrapping = True
CellAppearance.FixedSelectedLayout.Font.Charset = DEFAULT_CHARSET
CellAppearance.FixedSelectedLayout.Font.Color = clBlack
CellAppearance.FixedSelectedLayout.Font.Height = -12
CellAppearance.FixedSelectedLayout.Font.Name = 'Segoe UI'
CellAppearance.FixedSelectedLayout.Font.Style = []
CellAppearance.FixedSelectedLayout.WordWrapping = True
CellAppearance.FocusedLayout.Font.Charset = DEFAULT_CHARSET
CellAppearance.FocusedLayout.Font.Color = clBlack
CellAppearance.FocusedLayout.Font.Height = -12
CellAppearance.FocusedLayout.Font.Name = 'Segoe UI'
CellAppearance.FocusedLayout.Font.Style = []
CellAppearance.FocusedLayout.WordWrapping = True
CellAppearance.GroupLayout.Font.Charset = DEFAULT_CHARSET
CellAppearance.GroupLayout.Font.Color = clBlack
CellAppearance.GroupLayout.Font.Height = -12
CellAppearance.GroupLayout.Font.Name = 'Segoe UI'
CellAppearance.GroupLayout.Font.Style = []
CellAppearance.GroupLayout.WordWrapping = True
CellAppearance.NormalLayout.Font.Charset = DEFAULT_CHARSET
CellAppearance.NormalLayout.Font.Color = clBlack
CellAppearance.NormalLayout.Font.Height = -12
CellAppearance.NormalLayout.Font.Name = 'Segoe UI'
CellAppearance.NormalLayout.Font.Style = []
CellAppearance.NormalLayout.WordWrapping = True
CellAppearance.SelectedLayout.Font.Charset = DEFAULT_CHARSET
CellAppearance.SelectedLayout.Font.Color = clBlack
CellAppearance.SelectedLayout.Font.Height = -12
CellAppearance.SelectedLayout.Font.Name = 'Segoe UI'
CellAppearance.SelectedLayout.Font.Style = []
CellAppearance.SummaryLayout.Font.Charset = DEFAULT_CHARSET
CellAppearance.SummaryLayout.Font.Color = clBlack
CellAppearance.SummaryLayout.Font.Height = -12
CellAppearance.SummaryLayout.Font.Name = 'Segoe UI'
CellAppearance.SummaryLayout.Font.Style = []
CellAppearance.SummaryLayout.WordWrapping = True
ColumnCount = 13
Columns = <
item
Appearance.FilterMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterMatchLayout.Font.Color = clWindowText
Appearance.FilterMatchLayout.Font.Height = -12
Appearance.FilterMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterMatchLayout.Font.Style = []
Appearance.FilterInverseMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterInverseMatchLayout.Font.Color = clWindowText
Appearance.FilterInverseMatchLayout.Font.Height = -12
Appearance.FilterInverseMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterInverseMatchLayout.Font.Style = []
Appearance.BandLayout.Font.Charset = DEFAULT_CHARSET
Appearance.BandLayout.Font.Color = clWindowText
Appearance.BandLayout.Font.Height = -12
Appearance.BandLayout.Font.Name = 'Segoe UI'
Appearance.BandLayout.Font.Style = []
Appearance.FixedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedLayout.Font.Color = clWindowText
Appearance.FixedLayout.Font.Height = -12
Appearance.FixedLayout.Font.Name = 'Segoe UI'
Appearance.FixedLayout.Font.Style = []
Appearance.FixedSelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedSelectedLayout.Font.Color = clWindowText
Appearance.FixedSelectedLayout.Font.Height = -12
Appearance.FixedSelectedLayout.Font.Name = 'Segoe UI'
Appearance.FixedSelectedLayout.Font.Style = []
Appearance.FocusedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FocusedLayout.Font.Color = clWindowText
Appearance.FocusedLayout.Font.Height = -12
Appearance.FocusedLayout.Font.Name = 'Segoe UI'
Appearance.FocusedLayout.Font.Style = []
Appearance.GroupLayout.Font.Charset = DEFAULT_CHARSET
Appearance.GroupLayout.Font.Color = clWindowText
Appearance.GroupLayout.Font.Height = -12
Appearance.GroupLayout.Font.Name = 'Segoe UI'
Appearance.GroupLayout.Font.Style = []
Appearance.NormalLayout.Font.Charset = DEFAULT_CHARSET
Appearance.NormalLayout.Font.Color = clWindowText
Appearance.NormalLayout.Font.Height = -12
Appearance.NormalLayout.Font.Name = 'Segoe UI'
Appearance.NormalLayout.Font.Style = []
Appearance.SelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SelectedLayout.Font.Color = clWindowText
Appearance.SelectedLayout.Font.Height = -12
Appearance.SelectedLayout.Font.Name = 'Segoe UI'
Appearance.SelectedLayout.Font.Style = []
Appearance.SummaryLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SummaryLayout.Font.Color = clWindowText
Appearance.SummaryLayout.Font.Height = -12
Appearance.SummaryLayout.Font.Name = 'Segoe UI'
Appearance.SummaryLayout.Font.Style = []
Header = 'Task Item ID'
Name = 'COL_TASK_ITEM_ID'
Width = 70.000000000000000000
end
item
Appearance.FilterMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterMatchLayout.Font.Color = clWindowText
Appearance.FilterMatchLayout.Font.Height = -12
Appearance.FilterMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterMatchLayout.Font.Style = []
Appearance.FilterInverseMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterInverseMatchLayout.Font.Color = clWindowText
Appearance.FilterInverseMatchLayout.Font.Height = -12
Appearance.FilterInverseMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterInverseMatchLayout.Font.Style = []
Appearance.BandLayout.Font.Charset = DEFAULT_CHARSET
Appearance.BandLayout.Font.Color = clWindowText
Appearance.BandLayout.Font.Height = -12
Appearance.BandLayout.Font.Name = 'Segoe UI'
Appearance.BandLayout.Font.Style = []
Appearance.FixedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedLayout.Font.Color = clWindowText
Appearance.FixedLayout.Font.Height = -12
Appearance.FixedLayout.Font.Name = 'Segoe UI'
Appearance.FixedLayout.Font.Style = []
Appearance.FixedSelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedSelectedLayout.Font.Color = clWindowText
Appearance.FixedSelectedLayout.Font.Height = -12
Appearance.FixedSelectedLayout.Font.Name = 'Segoe UI'
Appearance.FixedSelectedLayout.Font.Style = []
Appearance.FocusedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FocusedLayout.Font.Color = clWindowText
Appearance.FocusedLayout.Font.Height = -12
Appearance.FocusedLayout.Font.Name = 'Segoe UI'
Appearance.FocusedLayout.Font.Style = []
Appearance.GroupLayout.Font.Charset = DEFAULT_CHARSET
Appearance.GroupLayout.Font.Color = clWindowText
Appearance.GroupLayout.Font.Height = -12
Appearance.GroupLayout.Font.Name = 'Segoe UI'
Appearance.GroupLayout.Font.Style = []
Appearance.NormalLayout.Font.Charset = DEFAULT_CHARSET
Appearance.NormalLayout.Font.Color = clWindowText
Appearance.NormalLayout.Font.Height = -12
Appearance.NormalLayout.Font.Name = 'Segoe UI'
Appearance.NormalLayout.Font.Style = []
Appearance.SelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SelectedLayout.Font.Color = clWindowText
Appearance.SelectedLayout.Font.Height = -12
Appearance.SelectedLayout.Font.Name = 'Segoe UI'
Appearance.SelectedLayout.Font.Style = []
Appearance.SummaryLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SummaryLayout.Font.Color = clWindowText
Appearance.SummaryLayout.Font.Height = -12
Appearance.SummaryLayout.Font.Name = 'Segoe UI'
Appearance.SummaryLayout.Font.Style = []
Header = 'Task ID'
Name = 'COL_TASK_ID'
Width = 250.000000000000000000
end
item
Appearance.FilterMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterMatchLayout.Font.Color = clWindowText
Appearance.FilterMatchLayout.Font.Height = -12
Appearance.FilterMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterMatchLayout.Font.Style = []
Appearance.FilterInverseMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterInverseMatchLayout.Font.Color = clWindowText
Appearance.FilterInverseMatchLayout.Font.Height = -12
Appearance.FilterInverseMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterInverseMatchLayout.Font.Style = []
Appearance.BandLayout.Font.Charset = DEFAULT_CHARSET
Appearance.BandLayout.Font.Color = clWindowText
Appearance.BandLayout.Font.Height = -12
Appearance.BandLayout.Font.Name = 'Segoe UI'
Appearance.BandLayout.Font.Style = []
Appearance.FixedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedLayout.Font.Color = clWindowText
Appearance.FixedLayout.Font.Height = -12
Appearance.FixedLayout.Font.Name = 'Segoe UI'
Appearance.FixedLayout.Font.Style = []
Appearance.FixedSelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedSelectedLayout.Font.Color = clWindowText
Appearance.FixedSelectedLayout.Font.Height = -12
Appearance.FixedSelectedLayout.Font.Name = 'Segoe UI'
Appearance.FixedSelectedLayout.Font.Style = []
Appearance.FocusedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FocusedLayout.Font.Color = clWindowText
Appearance.FocusedLayout.Font.Height = -12
Appearance.FocusedLayout.Font.Name = 'Segoe UI'
Appearance.FocusedLayout.Font.Style = []
Appearance.GroupLayout.Font.Charset = DEFAULT_CHARSET
Appearance.GroupLayout.Font.Color = clWindowText
Appearance.GroupLayout.Font.Height = -12
Appearance.GroupLayout.Font.Name = 'Segoe UI'
Appearance.GroupLayout.Font.Style = []
Appearance.NormalLayout.Font.Charset = DEFAULT_CHARSET
Appearance.NormalLayout.Font.Color = clWindowText
Appearance.NormalLayout.Font.Height = -12
Appearance.NormalLayout.Font.Name = 'Segoe UI'
Appearance.NormalLayout.Font.Style = []
Appearance.SelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SelectedLayout.Font.Color = clWindowText
Appearance.SelectedLayout.Font.Height = -12
Appearance.SelectedLayout.Font.Name = 'Segoe UI'
Appearance.SelectedLayout.Font.Style = []
Appearance.SummaryLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SummaryLayout.Font.Color = clWindowText
Appearance.SummaryLayout.Font.Height = -12
Appearance.SummaryLayout.Font.Name = 'Segoe UI'
Appearance.SummaryLayout.Font.Style = []
Header = 'Application'
Name = 'COL_APP'
Width = 100.000000000000000000
end
item
Appearance.FilterMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterMatchLayout.Font.Color = clWindowText
Appearance.FilterMatchLayout.Font.Height = -12
Appearance.FilterMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterMatchLayout.Font.Style = []
Appearance.FilterInverseMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterInverseMatchLayout.Font.Color = clWindowText
Appearance.FilterInverseMatchLayout.Font.Height = -12
Appearance.FilterInverseMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterInverseMatchLayout.Font.Style = []
Appearance.BandLayout.Font.Charset = DEFAULT_CHARSET
Appearance.BandLayout.Font.Color = clWindowText
Appearance.BandLayout.Font.Height = -12
Appearance.BandLayout.Font.Name = 'Segoe UI'
Appearance.BandLayout.Font.Style = []
Appearance.FixedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedLayout.Font.Color = clWindowText
Appearance.FixedLayout.Font.Height = -12
Appearance.FixedLayout.Font.Name = 'Segoe UI'
Appearance.FixedLayout.Font.Style = []
Appearance.FixedSelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedSelectedLayout.Font.Color = clWindowText
Appearance.FixedSelectedLayout.Font.Height = -12
Appearance.FixedSelectedLayout.Font.Name = 'Segoe UI'
Appearance.FixedSelectedLayout.Font.Style = []
Appearance.FocusedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FocusedLayout.Font.Color = clWindowText
Appearance.FocusedLayout.Font.Height = -12
Appearance.FocusedLayout.Font.Name = 'Segoe UI'
Appearance.FocusedLayout.Font.Style = []
Appearance.GroupLayout.Font.Charset = DEFAULT_CHARSET
Appearance.GroupLayout.Font.Color = clWindowText
Appearance.GroupLayout.Font.Height = -12
Appearance.GroupLayout.Font.Name = 'Segoe UI'
Appearance.GroupLayout.Font.Style = []
Appearance.NormalLayout.Font.Charset = DEFAULT_CHARSET
Appearance.NormalLayout.Font.Color = clWindowText
Appearance.NormalLayout.Font.Height = -12
Appearance.NormalLayout.Font.Name = 'Segoe UI'
Appearance.NormalLayout.Font.Style = []
Appearance.SelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SelectedLayout.Font.Color = clWindowText
Appearance.SelectedLayout.Font.Height = -12
Appearance.SelectedLayout.Font.Name = 'Segoe UI'
Appearance.SelectedLayout.Font.Style = []
Appearance.SummaryLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SummaryLayout.Font.Color = clWindowText
Appearance.SummaryLayout.Font.Height = -12
Appearance.SummaryLayout.Font.Name = 'Segoe UI'
Appearance.SummaryLayout.Font.Style = []
Header = 'Version'
Name = 'COL_VERSION'
Width = 100.000000000000000000
end
item
Appearance.FilterMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterMatchLayout.Font.Color = clWindowText
Appearance.FilterMatchLayout.Font.Height = -12
Appearance.FilterMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterMatchLayout.Font.Style = []
Appearance.FilterInverseMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterInverseMatchLayout.Font.Color = clWindowText
Appearance.FilterInverseMatchLayout.Font.Height = -12
Appearance.FilterInverseMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterInverseMatchLayout.Font.Style = []
Appearance.BandLayout.Font.Charset = DEFAULT_CHARSET
Appearance.BandLayout.Font.Color = clWindowText
Appearance.BandLayout.Font.Height = -12
Appearance.BandLayout.Font.Name = 'Segoe UI'
Appearance.BandLayout.Font.Style = []
Appearance.FixedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedLayout.Font.Color = clWindowText
Appearance.FixedLayout.Font.Height = -12
Appearance.FixedLayout.Font.Name = 'Segoe UI'
Appearance.FixedLayout.Font.Style = []
Appearance.FixedSelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedSelectedLayout.Font.Color = clWindowText
Appearance.FixedSelectedLayout.Font.Height = -12
Appearance.FixedSelectedLayout.Font.Name = 'Segoe UI'
Appearance.FixedSelectedLayout.Font.Style = []
Appearance.FocusedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FocusedLayout.Font.Color = clWindowText
Appearance.FocusedLayout.Font.Height = -12
Appearance.FocusedLayout.Font.Name = 'Segoe UI'
Appearance.FocusedLayout.Font.Style = []
Appearance.GroupLayout.Font.Charset = DEFAULT_CHARSET
Appearance.GroupLayout.Font.Color = clWindowText
Appearance.GroupLayout.Font.Height = -12
Appearance.GroupLayout.Font.Name = 'Segoe UI'
Appearance.GroupLayout.Font.Style = []
Appearance.NormalLayout.Font.Charset = DEFAULT_CHARSET
Appearance.NormalLayout.Font.Color = clWindowText
Appearance.NormalLayout.Font.Height = -12
Appearance.NormalLayout.Font.Name = 'Segoe UI'
Appearance.NormalLayout.Font.Style = []
Appearance.SelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SelectedLayout.Font.Color = clWindowText
Appearance.SelectedLayout.Font.Height = -12
Appearance.SelectedLayout.Font.Name = 'Segoe UI'
Appearance.SelectedLayout.Font.Style = []
Appearance.SummaryLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SummaryLayout.Font.Color = clWindowText
Appearance.SummaryLayout.Font.Height = -12
Appearance.SummaryLayout.Font.Name = 'Segoe UI'
Appearance.SummaryLayout.Font.Style = []
Header = 'Date'
Name = 'COL_TASK_DATE'
end
item
Appearance.FilterMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterMatchLayout.Font.Color = clWindowText
Appearance.FilterMatchLayout.Font.Height = -12
Appearance.FilterMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterMatchLayout.Font.Style = []
Appearance.FilterInverseMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterInverseMatchLayout.Font.Color = clWindowText
Appearance.FilterInverseMatchLayout.Font.Height = -12
Appearance.FilterInverseMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterInverseMatchLayout.Font.Style = []
Appearance.BandLayout.Font.Charset = DEFAULT_CHARSET
Appearance.BandLayout.Font.Color = clWindowText
Appearance.BandLayout.Font.Height = -12
Appearance.BandLayout.Font.Name = 'Segoe UI'
Appearance.BandLayout.Font.Style = []
Appearance.FixedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedLayout.Font.Color = clWindowText
Appearance.FixedLayout.Font.Height = -12
Appearance.FixedLayout.Font.Name = 'Segoe UI'
Appearance.FixedLayout.Font.Style = []
Appearance.FixedSelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedSelectedLayout.Font.Color = clWindowText
Appearance.FixedSelectedLayout.Font.Height = -12
Appearance.FixedSelectedLayout.Font.Name = 'Segoe UI'
Appearance.FixedSelectedLayout.Font.Style = []
Appearance.FocusedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FocusedLayout.Font.Color = clWindowText
Appearance.FocusedLayout.Font.Height = -12
Appearance.FocusedLayout.Font.Name = 'Segoe UI'
Appearance.FocusedLayout.Font.Style = []
Appearance.GroupLayout.Font.Charset = DEFAULT_CHARSET
Appearance.GroupLayout.Font.Color = clWindowText
Appearance.GroupLayout.Font.Height = -12
Appearance.GroupLayout.Font.Name = 'Segoe UI'
Appearance.GroupLayout.Font.Style = []
Appearance.NormalLayout.Font.Charset = DEFAULT_CHARSET
Appearance.NormalLayout.Font.Color = clWindowText
Appearance.NormalLayout.Font.Height = -12
Appearance.NormalLayout.Font.Name = 'Segoe UI'
Appearance.NormalLayout.Font.Style = []
Appearance.SelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SelectedLayout.Font.Color = clWindowText
Appearance.SelectedLayout.Font.Height = -12
Appearance.SelectedLayout.Font.Name = 'Segoe UI'
Appearance.SelectedLayout.Font.Style = []
Appearance.SummaryLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SummaryLayout.Font.Color = clWindowText
Appearance.SummaryLayout.Font.Height = -12
Appearance.SummaryLayout.Font.Name = 'Segoe UI'
Appearance.SummaryLayout.Font.Style = []
Header = 'Reported By'
Name = 'COL_REPORTED_BY'
end
item
Appearance.FilterMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterMatchLayout.Font.Color = clWindowText
Appearance.FilterMatchLayout.Font.Height = -12
Appearance.FilterMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterMatchLayout.Font.Style = []
Appearance.FilterInverseMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterInverseMatchLayout.Font.Color = clWindowText
Appearance.FilterInverseMatchLayout.Font.Height = -12
Appearance.FilterInverseMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterInverseMatchLayout.Font.Style = []
Appearance.BandLayout.Font.Charset = DEFAULT_CHARSET
Appearance.BandLayout.Font.Color = clWindowText
Appearance.BandLayout.Font.Height = -12
Appearance.BandLayout.Font.Name = 'Segoe UI'
Appearance.BandLayout.Font.Style = []
Appearance.FixedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedLayout.Font.Color = clWindowText
Appearance.FixedLayout.Font.Height = -12
Appearance.FixedLayout.Font.Name = 'Segoe UI'
Appearance.FixedLayout.Font.Style = []
Appearance.FixedSelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedSelectedLayout.Font.Color = clWindowText
Appearance.FixedSelectedLayout.Font.Height = -12
Appearance.FixedSelectedLayout.Font.Name = 'Segoe UI'
Appearance.FixedSelectedLayout.Font.Style = []
Appearance.FocusedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FocusedLayout.Font.Color = clWindowText
Appearance.FocusedLayout.Font.Height = -12
Appearance.FocusedLayout.Font.Name = 'Segoe UI'
Appearance.FocusedLayout.Font.Style = []
Appearance.GroupLayout.Font.Charset = DEFAULT_CHARSET
Appearance.GroupLayout.Font.Color = clWindowText
Appearance.GroupLayout.Font.Height = -12
Appearance.GroupLayout.Font.Name = 'Segoe UI'
Appearance.GroupLayout.Font.Style = []
Appearance.NormalLayout.Font.Charset = DEFAULT_CHARSET
Appearance.NormalLayout.Font.Color = clWindowText
Appearance.NormalLayout.Font.Height = -12
Appearance.NormalLayout.Font.Name = 'Segoe UI'
Appearance.NormalLayout.Font.Style = []
Appearance.SelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SelectedLayout.Font.Color = clWindowText
Appearance.SelectedLayout.Font.Height = -12
Appearance.SelectedLayout.Font.Name = 'Segoe UI'
Appearance.SelectedLayout.Font.Style = []
Appearance.SummaryLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SummaryLayout.Font.Color = clWindowText
Appearance.SummaryLayout.Font.Height = -12
Appearance.SummaryLayout.Font.Name = 'Segoe UI'
Appearance.SummaryLayout.Font.Style = []
Header = 'Assigned To'
Name = 'COL_ASSIGNED_TO'
end
item
Appearance.FilterMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterMatchLayout.Font.Color = clWindowText
Appearance.FilterMatchLayout.Font.Height = -12
Appearance.FilterMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterMatchLayout.Font.Style = []
Appearance.FilterInverseMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterInverseMatchLayout.Font.Color = clWindowText
Appearance.FilterInverseMatchLayout.Font.Height = -12
Appearance.FilterInverseMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterInverseMatchLayout.Font.Style = []
Appearance.BandLayout.Font.Charset = DEFAULT_CHARSET
Appearance.BandLayout.Font.Color = clWindowText
Appearance.BandLayout.Font.Height = -12
Appearance.BandLayout.Font.Name = 'Segoe UI'
Appearance.BandLayout.Font.Style = []
Appearance.FixedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedLayout.Font.Color = clWindowText
Appearance.FixedLayout.Font.Height = -12
Appearance.FixedLayout.Font.Name = 'Segoe UI'
Appearance.FixedLayout.Font.Style = []
Appearance.FixedSelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedSelectedLayout.Font.Color = clWindowText
Appearance.FixedSelectedLayout.Font.Height = -12
Appearance.FixedSelectedLayout.Font.Name = 'Segoe UI'
Appearance.FixedSelectedLayout.Font.Style = []
Appearance.FocusedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FocusedLayout.Font.Color = clWindowText
Appearance.FocusedLayout.Font.Height = -12
Appearance.FocusedLayout.Font.Name = 'Segoe UI'
Appearance.FocusedLayout.Font.Style = []
Appearance.GroupLayout.Font.Charset = DEFAULT_CHARSET
Appearance.GroupLayout.Font.Color = clWindowText
Appearance.GroupLayout.Font.Height = -12
Appearance.GroupLayout.Font.Name = 'Segoe UI'
Appearance.GroupLayout.Font.Style = []
Appearance.NormalLayout.Font.Charset = DEFAULT_CHARSET
Appearance.NormalLayout.Font.Color = clWindowText
Appearance.NormalLayout.Font.Height = -12
Appearance.NormalLayout.Font.Name = 'Segoe UI'
Appearance.NormalLayout.Font.Style = []
Appearance.SelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SelectedLayout.Font.Color = clWindowText
Appearance.SelectedLayout.Font.Height = -12
Appearance.SelectedLayout.Font.Name = 'Segoe UI'
Appearance.SelectedLayout.Font.Style = []
Appearance.SummaryLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SummaryLayout.Font.Color = clWindowText
Appearance.SummaryLayout.Font.Height = -12
Appearance.SummaryLayout.Font.Name = 'Segoe UI'
Appearance.SummaryLayout.Font.Style = []
Header = 'Status'
Name = 'COL_STATUS'
end
item
Appearance.FilterMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterMatchLayout.Font.Color = clWindowText
Appearance.FilterMatchLayout.Font.Height = -12
Appearance.FilterMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterMatchLayout.Font.Style = []
Appearance.FilterInverseMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterInverseMatchLayout.Font.Color = clWindowText
Appearance.FilterInverseMatchLayout.Font.Height = -12
Appearance.FilterInverseMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterInverseMatchLayout.Font.Style = []
Appearance.BandLayout.Font.Charset = DEFAULT_CHARSET
Appearance.BandLayout.Font.Color = clWindowText
Appearance.BandLayout.Font.Height = -12
Appearance.BandLayout.Font.Name = 'Segoe UI'
Appearance.BandLayout.Font.Style = []
Appearance.FixedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedLayout.Font.Color = clWindowText
Appearance.FixedLayout.Font.Height = -12
Appearance.FixedLayout.Font.Name = 'Segoe UI'
Appearance.FixedLayout.Font.Style = []
Appearance.FixedSelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedSelectedLayout.Font.Color = clWindowText
Appearance.FixedSelectedLayout.Font.Height = -12
Appearance.FixedSelectedLayout.Font.Name = 'Segoe UI'
Appearance.FixedSelectedLayout.Font.Style = []
Appearance.FocusedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FocusedLayout.Font.Color = clWindowText
Appearance.FocusedLayout.Font.Height = -12
Appearance.FocusedLayout.Font.Name = 'Segoe UI'
Appearance.FocusedLayout.Font.Style = []
Appearance.GroupLayout.Font.Charset = DEFAULT_CHARSET
Appearance.GroupLayout.Font.Color = clWindowText
Appearance.GroupLayout.Font.Height = -12
Appearance.GroupLayout.Font.Name = 'Segoe UI'
Appearance.GroupLayout.Font.Style = []
Appearance.NormalLayout.Font.Charset = DEFAULT_CHARSET
Appearance.NormalLayout.Font.Color = clWindowText
Appearance.NormalLayout.Font.Height = -12
Appearance.NormalLayout.Font.Name = 'Segoe UI'
Appearance.NormalLayout.Font.Style = []
Appearance.SelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SelectedLayout.Font.Color = clWindowText
Appearance.SelectedLayout.Font.Height = -12
Appearance.SelectedLayout.Font.Name = 'Segoe UI'
Appearance.SelectedLayout.Font.Style = []
Appearance.SummaryLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SummaryLayout.Font.Color = clWindowText
Appearance.SummaryLayout.Font.Height = -12
Appearance.SummaryLayout.Font.Name = 'Segoe UI'
Appearance.SummaryLayout.Font.Style = []
Header = 'Status Date'
Name = 'COL_STATUS_DATE'
end
item
Appearance.FilterMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterMatchLayout.Font.Color = clWindowText
Appearance.FilterMatchLayout.Font.Height = -12
Appearance.FilterMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterMatchLayout.Font.Style = []
Appearance.FilterInverseMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterInverseMatchLayout.Font.Color = clWindowText
Appearance.FilterInverseMatchLayout.Font.Height = -12
Appearance.FilterInverseMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterInverseMatchLayout.Font.Style = []
Appearance.BandLayout.Font.Charset = DEFAULT_CHARSET
Appearance.BandLayout.Font.Color = clWindowText
Appearance.BandLayout.Font.Height = -12
Appearance.BandLayout.Font.Name = 'Segoe UI'
Appearance.BandLayout.Font.Style = []
Appearance.FixedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedLayout.Font.Color = clWindowText
Appearance.FixedLayout.Font.Height = -12
Appearance.FixedLayout.Font.Name = 'Segoe UI'
Appearance.FixedLayout.Font.Style = []
Appearance.FixedSelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedSelectedLayout.Font.Color = clWindowText
Appearance.FixedSelectedLayout.Font.Height = -12
Appearance.FixedSelectedLayout.Font.Name = 'Segoe UI'
Appearance.FixedSelectedLayout.Font.Style = []
Appearance.FocusedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FocusedLayout.Font.Color = clWindowText
Appearance.FocusedLayout.Font.Height = -12
Appearance.FocusedLayout.Font.Name = 'Segoe UI'
Appearance.FocusedLayout.Font.Style = []
Appearance.GroupLayout.Font.Charset = DEFAULT_CHARSET
Appearance.GroupLayout.Font.Color = clWindowText
Appearance.GroupLayout.Font.Height = -12
Appearance.GroupLayout.Font.Name = 'Segoe UI'
Appearance.GroupLayout.Font.Style = []
Appearance.NormalLayout.Font.Charset = DEFAULT_CHARSET
Appearance.NormalLayout.Font.Color = clWindowText
Appearance.NormalLayout.Font.Height = -12
Appearance.NormalLayout.Font.Name = 'Segoe UI'
Appearance.NormalLayout.Font.Style = []
Appearance.SelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SelectedLayout.Font.Color = clWindowText
Appearance.SelectedLayout.Font.Height = -12
Appearance.SelectedLayout.Font.Name = 'Segoe UI'
Appearance.SelectedLayout.Font.Style = []
Appearance.SummaryLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SummaryLayout.Font.Color = clWindowText
Appearance.SummaryLayout.Font.Height = -12
Appearance.SummaryLayout.Font.Name = 'Segoe UI'
Appearance.SummaryLayout.Font.Style = []
Header = 'Fixed Version'
Name = 'COL_FIXED_VER'
end
item
Appearance.FilterMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterMatchLayout.Font.Color = clWindowText
Appearance.FilterMatchLayout.Font.Height = -12
Appearance.FilterMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterMatchLayout.Font.Style = []
Appearance.FilterInverseMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterInverseMatchLayout.Font.Color = clWindowText
Appearance.FilterInverseMatchLayout.Font.Height = -12
Appearance.FilterInverseMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterInverseMatchLayout.Font.Style = []
Appearance.BandLayout.Font.Charset = DEFAULT_CHARSET
Appearance.BandLayout.Font.Color = clWindowText
Appearance.BandLayout.Font.Height = -12
Appearance.BandLayout.Font.Name = 'Segoe UI'
Appearance.BandLayout.Font.Style = []
Appearance.FixedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedLayout.Font.Color = clWindowText
Appearance.FixedLayout.Font.Height = -12
Appearance.FixedLayout.Font.Name = 'Segoe UI'
Appearance.FixedLayout.Font.Style = []
Appearance.FixedSelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedSelectedLayout.Font.Color = clWindowText
Appearance.FixedSelectedLayout.Font.Height = -12
Appearance.FixedSelectedLayout.Font.Name = 'Segoe UI'
Appearance.FixedSelectedLayout.Font.Style = []
Appearance.FocusedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FocusedLayout.Font.Color = clWindowText
Appearance.FocusedLayout.Font.Height = -12
Appearance.FocusedLayout.Font.Name = 'Segoe UI'
Appearance.FocusedLayout.Font.Style = []
Appearance.GroupLayout.Font.Charset = DEFAULT_CHARSET
Appearance.GroupLayout.Font.Color = clWindowText
Appearance.GroupLayout.Font.Height = -12
Appearance.GroupLayout.Font.Name = 'Segoe UI'
Appearance.GroupLayout.Font.Style = []
Appearance.NormalLayout.Font.Charset = DEFAULT_CHARSET
Appearance.NormalLayout.Font.Color = clWindowText
Appearance.NormalLayout.Font.Height = -12
Appearance.NormalLayout.Font.Name = 'Segoe UI'
Appearance.NormalLayout.Font.Style = []
Appearance.SelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SelectedLayout.Font.Color = clWindowText
Appearance.SelectedLayout.Font.Height = -12
Appearance.SelectedLayout.Font.Name = 'Segoe UI'
Appearance.SelectedLayout.Font.Style = []
Appearance.SummaryLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SummaryLayout.Font.Color = clWindowText
Appearance.SummaryLayout.Font.Height = -12
Appearance.SummaryLayout.Font.Name = 'Segoe UI'
Appearance.SummaryLayout.Font.Style = []
Header = 'Form (Section)'
Name = 'COL_FORM_SECTION'
Settings = [gcsEditor]
end
item
Appearance.FilterMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterMatchLayout.Font.Color = clWindowText
Appearance.FilterMatchLayout.Font.Height = -12
Appearance.FilterMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterMatchLayout.Font.Style = []
Appearance.FilterInverseMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterInverseMatchLayout.Font.Color = clWindowText
Appearance.FilterInverseMatchLayout.Font.Height = -12
Appearance.FilterInverseMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterInverseMatchLayout.Font.Style = []
Appearance.BandLayout.Font.Charset = DEFAULT_CHARSET
Appearance.BandLayout.Font.Color = clWindowText
Appearance.BandLayout.Font.Height = -12
Appearance.BandLayout.Font.Name = 'Segoe UI'
Appearance.BandLayout.Font.Style = []
Appearance.FixedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedLayout.Font.Color = clWindowText
Appearance.FixedLayout.Font.Height = -12
Appearance.FixedLayout.Font.Name = 'Segoe UI'
Appearance.FixedLayout.Font.Style = []
Appearance.FixedSelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedSelectedLayout.Font.Color = clWindowText
Appearance.FixedSelectedLayout.Font.Height = -12
Appearance.FixedSelectedLayout.Font.Name = 'Segoe UI'
Appearance.FixedSelectedLayout.Font.Style = []
Appearance.FocusedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FocusedLayout.Font.Color = clWindowText
Appearance.FocusedLayout.Font.Height = -12
Appearance.FocusedLayout.Font.Name = 'Segoe UI'
Appearance.FocusedLayout.Font.Style = []
Appearance.GroupLayout.Font.Charset = DEFAULT_CHARSET
Appearance.GroupLayout.Font.Color = clWindowText
Appearance.GroupLayout.Font.Height = -12
Appearance.GroupLayout.Font.Name = 'Segoe UI'
Appearance.GroupLayout.Font.Style = []
Appearance.NormalLayout.Font.Charset = DEFAULT_CHARSET
Appearance.NormalLayout.Font.Color = clWindowText
Appearance.NormalLayout.Font.Height = -12
Appearance.NormalLayout.Font.Name = 'Segoe UI'
Appearance.NormalLayout.Font.Style = []
Appearance.SelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SelectedLayout.Font.Color = clWindowText
Appearance.SelectedLayout.Font.Height = -12
Appearance.SelectedLayout.Font.Name = 'Segoe UI'
Appearance.SelectedLayout.Font.Style = []
Appearance.SummaryLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SummaryLayout.Font.Color = clWindowText
Appearance.SummaryLayout.Font.Height = -12
Appearance.SummaryLayout.Font.Name = 'Segoe UI'
Appearance.SummaryLayout.Font.Style = []
Editor = getMemo
Header = 'Issue'
Name = 'COL_ISSUE'
Settings = [gcsEditor]
Width = 300.000000000000000000
end
item
Appearance.FilterMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterMatchLayout.Font.Color = clWindowText
Appearance.FilterMatchLayout.Font.Height = -12
Appearance.FilterMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterMatchLayout.Font.Style = []
Appearance.FilterInverseMatchLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FilterInverseMatchLayout.Font.Color = clWindowText
Appearance.FilterInverseMatchLayout.Font.Height = -12
Appearance.FilterInverseMatchLayout.Font.Name = 'Segoe UI'
Appearance.FilterInverseMatchLayout.Font.Style = []
Appearance.BandLayout.Font.Charset = DEFAULT_CHARSET
Appearance.BandLayout.Font.Color = clWindowText
Appearance.BandLayout.Font.Height = -12
Appearance.BandLayout.Font.Name = 'Segoe UI'
Appearance.BandLayout.Font.Style = []
Appearance.FixedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedLayout.Font.Color = clWindowText
Appearance.FixedLayout.Font.Height = -12
Appearance.FixedLayout.Font.Name = 'Segoe UI'
Appearance.FixedLayout.Font.Style = []
Appearance.FixedSelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FixedSelectedLayout.Font.Color = clWindowText
Appearance.FixedSelectedLayout.Font.Height = -12
Appearance.FixedSelectedLayout.Font.Name = 'Segoe UI'
Appearance.FixedSelectedLayout.Font.Style = []
Appearance.FocusedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.FocusedLayout.Font.Color = clWindowText
Appearance.FocusedLayout.Font.Height = -12
Appearance.FocusedLayout.Font.Name = 'Segoe UI'
Appearance.FocusedLayout.Font.Style = []
Appearance.GroupLayout.Font.Charset = DEFAULT_CHARSET
Appearance.GroupLayout.Font.Color = clWindowText
Appearance.GroupLayout.Font.Height = -12
Appearance.GroupLayout.Font.Name = 'Segoe UI'
Appearance.GroupLayout.Font.Style = []
Appearance.NormalLayout.Font.Charset = DEFAULT_CHARSET
Appearance.NormalLayout.Font.Color = clWindowText
Appearance.NormalLayout.Font.Height = -12
Appearance.NormalLayout.Font.Name = 'Segoe UI'
Appearance.NormalLayout.Font.Style = []
Appearance.SelectedLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SelectedLayout.Font.Color = clWindowText
Appearance.SelectedLayout.Font.Height = -12
Appearance.SelectedLayout.Font.Name = 'Segoe UI'
Appearance.SelectedLayout.Font.Style = []
Appearance.SummaryLayout.Font.Charset = DEFAULT_CHARSET
Appearance.SummaryLayout.Font.Color = clWindowText
Appearance.SummaryLayout.Font.Height = -12
Appearance.SummaryLayout.Font.Name = 'Segoe UI'
Appearance.SummaryLayout.Font.Style = []
Editor = getMemo
Header = 'Notes'
Name = 'COL_NOTES'
Settings = [gcsEditor]
Width = 300.000000000000000000
end>
Designer = False
FilterActions = <>
FilterAppearance.Font.Charset = DEFAULT_CHARSET
FilterAppearance.Font.Color = clBlack
FilterAppearance.Font.Height = -12
FilterAppearance.Font.Name = 'Segoe UI'
FilterAppearance.Font.Style = []
Icons.ExpandIcon.Data = {
1054544D53464E435356474269746D6170080200003C73766720786D6C6E733D
22687474703A2F2F7777772E77332E6F72672F323030302F7376672220776964
74683D22333222206865696768743D223332222076696577426F783D22302030
203332200D0A3332223E3C646566733E3C7374796C653E2E636C732D317B6669
6C6C3A2330303030303B7D3C2F7374796C653E3C2F646566733E3C7469746C65
3E506C75733C2F7469746C653E203C67200D0A69643D2249636F6E223E3C7265
637420636C6173733D22636C732D312220783D22372220793D22313522207769
6474683D22313822206865696768743D2231222F3E3C72656374200D0A636C61
73733D22636C732D312220783D2231352220793D2237222077696474683D2231
22206865696768743D223138222F3E3C7265637420636C6173733D22636C732D
312220783D223122200D0A793D2231222077696474683D223122206865696768
743D223330222F3E3C7265637420636C6173733D22636C732D312220783D2231
2220793D2231222077696474683D22333022200D0A6865696768743D2231222F
3E3C7265637420636C6173733D22636C732D312220783D2233302220793D2231
222077696474683D223122206865696768743D223330222F3E3C72656374200D
0A636C6173733D22636C732D312220783D22312220793D223330222077696474
683D22333022206865696768743D2231222F3E3C2F673E3C2F7376673E}
Icons.CollapseIcon.Data = {
1054544D53464E435356474269746D6170CE0100003C73766720786D6C6E733D
22687474703A2F2F7777772E77332E6F72672F323030302F7376672220776964
74683D22333222206865696768743D223332222076696577426F783D22302030
203332200D0A3332223E3C646566733E3C7374796C653E2E636C732D317B6669
6C6C3A2330303030303B7D3C2F7374796C653E3C2F646566733E3C7469746C65
3E506C75733C2F7469746C653E203C67200D0A69643D2249636F6E223E3C7265
637420636C6173733D22636C732D312220783D22372220793D22313522207769
6474683D22313822206865696768743D2231222F3E3C7265637420636C617373
3D22636C732D312220783D223122200D0A793D2231222077696474683D223122
206865696768743D223330222F3E3C7265637420636C6173733D22636C732D31
2220783D22312220793D2231222077696474683D22333022200D0A6865696768
743D2231222F3E3C7265637420636C6173733D22636C732D312220783D223330
2220793D2231222077696474683D223122206865696768743D223330222F3E3C
72656374200D0A636C6173733D22636C732D312220783D22312220793D223330
222077696474683D22333022206865696768743D2231222F3E3C2F673E3C2F73
76673E}
Icons.FilterIcon.Data = {
1054544D53464E435356474269746D6170E30200003C73766720786D6C6E733D
22687474703A2F2F7777772E77332E6F72672F323030302F7376672220786D6C
6E733A786C696E6B3D22687474703A2F2F7777772E77332E6F72672F31393939
2F786C696E6B222077696474683D22333222206865696768743D223332222076
696577426F783D22302030203332203332223E3C646566733E3C7374796C653E
2E636C732D317B66696C6C3A6E6F6E653B7D2E636C732D327B66696C6C3A2366
61666166613B7D2E636C732D337B66696C6C3A75726C28234E65775F50617474
65726E5F5377617463685F32293B7D2E636C732D347B66696C6C3A2333613361
33383B7D3C2F7374796C653E3C7061747465726E2069643D224E65775F506174
7465726E5F5377617463685F322220646174612D6E616D653D224E6577205061
747465726E205377617463682032222077696474683D22363822206865696768
743D22363822207061747465726E556E6974733D227573657253706163654F6E
557365222076696577426F783D22302030203638203638223E3C726563742063
6C6173733D22636C732D31222077696474683D22363822206865696768743D22
3638222F3E3C7265637420636C6173733D22636C732D32222077696474683D22
363822206865696768743D223638222F3E3C2F7061747465726E3E3C2F646566
733E3C7469746C653E46696C7465723C2F7469746C653E3C672069643D224963
6F6E223E3C706F6C79676F6E20636C6173733D22636C732D332220706F696E74
733D22312E3520362031322E352031352031322E352033302E352031382E3520
33302E352031382E352031352032392E3520362032392E3520312E3520312E35
20312E3520312E352036222F3E3C7061746820636C6173733D22636C732D3422
20643D224D31392C33314831325631352E32346C2D31312D3956314833305636
2E32346C2D31312C395A6D2D362D3168355631342E37366C31312D3956324832
56352E37366C31312C395A222F3E3C2F673E3C2F7376673E}
Icons.FilterActiveIcon.Data = {
1054544D53464E435356474269746D6170E30200003C73766720786D6C6E733D
22687474703A2F2F7777772E77332E6F72672F323030302F7376672220786D6C
6E733A786C696E6B3D22687474703A2F2F7777772E77332E6F72672F31393939
2F786C696E6B222077696474683D22333222206865696768743D223332222076
696577426F783D22302030203332203332223E3C646566733E3C7374796C653E
2E636C732D317B66696C6C3A6E6F6E653B7D2E636C732D327B66696C6C3A2334
36383242343B7D2E636C732D337B66696C6C3A75726C28234E65775F50617474
65726E5F5377617463685F32293B7D2E636C732D347B66696C6C3A2333613361
33383B7D3C2F7374796C653E3C7061747465726E2069643D224E65775F506174
7465726E5F5377617463685F322220646174612D6E616D653D224E6577205061
747465726E205377617463682032222077696474683D22363822206865696768
743D22363822207061747465726E556E6974733D227573657253706163654F6E
557365222076696577426F783D22302030203638203638223E3C726563742063
6C6173733D22636C732D31222077696474683D22363822206865696768743D22
3638222F3E3C7265637420636C6173733D22636C732D32222077696474683D22
363822206865696768743D223638222F3E3C2F7061747465726E3E3C2F646566
733E3C7469746C653E46696C7465723C2F7469746C653E3C672069643D224963
6F6E223E3C706F6C79676F6E20636C6173733D22636C732D332220706F696E74
733D22312E3520362031322E352031352031322E352033302E352031382E3520
33302E352031382E352031352032392E3520362032392E3520312E3520312E35
20312E3520312E352036222F3E3C7061746820636C6173733D22636C732D3422
20643D224D31392C33314831325631352E32346C2D31312D3956314833305636
2E32346C2D31312C395A6D2D362D3168355631342E37366C31312D3956324832
56352E37366C31312C395A222F3E3C2F673E3C2F7376673E}
Icons.FilterClearIcon.Data = {
1054544D53464E435356474269746D61709D0400003C73766720786D6C6E733D
22687474703A2F2F7777772E77332E6F72672F323030302F7376672220786D6C
6E733A786C696E6B3D22687474703A2F2F7777772E77332E6F72672F31393939
2F786C696E6B222077696474683D22333222206865696768743D223332222076
696577426F783D22302030203332203332223E3C646566733E3C7374796C653E
2E636C732D317B66696C6C3A6E6F6E653B7D2E636C732D327B66696C6C3A2361
37346162303B7D2E636C732D337B66696C6C3A236433393464363B7D2E636C73
2D347B66696C6C3A236661666166613B7D2E636C732D357B66696C6C3A75726C
28234E65775F5061747465726E5F5377617463685F38293B7D2E636C732D367B
66696C6C3A75726C28234E65775F5061747465726E5F5377617463685F37293B
7D3C2F7374796C653E3C7061747465726E2069643D224E65775F506174746572
6E5F5377617463685F382220646174612D6E616D653D224E6577205061747465
726E205377617463682038222077696474683D22363822206865696768743D22
363822207061747465726E556E6974733D227573657253706163654F6E557365
222076696577426F783D22302030203638203638223E3C7265637420636C6173
733D22636C732D31222077696474683D22363822206865696768743D22363822
2F3E3C7265637420636C6173733D22636C732D33222077696474683D22363822
206865696768743D223638222F3E3C2F7061747465726E3E3C7061747465726E
2069643D224E65775F5061747465726E5F5377617463685F372220646174612D
6E616D653D224E6577205061747465726E205377617463682037222077696474
683D22363822206865696768743D22363822207061747465726E556E6974733D
227573657253706163654F6E557365222076696577426F783D22302030203638
203638223E3C7265637420636C6173733D22636C732D31222077696474683D22
363822206865696768743D223638222F3E3C7265637420636C6173733D22636C
732D32222077696474683D22363822206865696768743D223638222F3E3C2F70
61747465726E3E3C2F646566733E3C7469746C653E436C6561723C2F7469746C
653E3C672069643D2249636F6E223E3C7265637420636C6173733D22636C732D
342220783D22312E38362220793D2231302E3334222077696474683D2232382E
323822206865696768743D2231312E333122207472616E73666F726D3D227472
616E736C617465282D362E36332031362920726F74617465282D343529222F3E
3C706F6C79676F6E20636C6173733D22636C732D352220706F696E74733D2232
2E37312032322031302032392E32392031352E37392032332E3520382E352031
362E323120322E3731203232222F3E3C7061746820636C6173733D22636C732D
362220643D224D31302E37312C33306C32302D32304C32322C312E32392C312E
32392C32322C31302C33302E37315633314833315633305A4D32322C322E3731
2C32392E32392C31302C31362E352C32322E37392C392E32312C31352E355A4D
322E37312C32322C382E352C31362E32316C372E32392C372E32394C31302C32
392E32395A222F3E3C2F673E3C2F7376673E}
Icons.FilterTypeIcon.Data = {
1054544D53464E435356474269746D6170CB0700003C73766720786D6C6E733D
22687474703A2F2F7777772E77332E6F72672F323030302F7376672220786D6C
6E733A786C696E6B3D22687474703A2F2F7777772E77332E6F72672F31393939
2F786C696E6B222077696474683D22333222206865696768743D223332222076
696577426F783D22302030203332203332223E3C646566733E3C7374796C653E
2E636C732D317B66696C6C3A6E6F6E653B7D2E636C732D327B66696C6C3A2337
39373737343B7D2E636C732D337B66696C6C3A236661666166613B7D2E636C73
2D347B66696C6C3A233361336133383B7D2E636C732D357B66696C6C3A75726C
28234E65775F5061747465726E5F5377617463685F34293B7D3C2F7374796C65
3E3C7061747465726E2069643D224E65775F5061747465726E5F537761746368
5F342220646174612D6E616D653D224E6577205061747465726E205377617463
682034222077696474683D22363822206865696768743D223638222070617474
65726E556E6974733D227573657253706163654F6E557365222076696577426F
783D22302030203638203638223E3C7265637420636C6173733D22636C732D31
222077696474683D22363822206865696768743D223638222F3E3C7265637420
636C6173733D22636C732D32222077696474683D22363822206865696768743D
223638222F3E3C2F7061747465726E3E3C2F646566733E3C7469746C653E5465
78742D46696C7465723C2F7469746C653E3C672069643D224D61736B223E3C70
6F6C79676F6E20636C6173733D22636C732D332220706F696E74733D2232352E
35203920313920322E3520313920392032352E352039222F3E3C706174682063
6C6173733D22636C732D332220643D224D31382C313056324836563330483231
5632362E31384C31392E38342C323548385632344831382E38356C2D322D3248
3856323168385631394838563138683856313648385631354832347631683256
31305A4D382C396838763148385A6D302C3456313248323476315A222F3E3C70
6F6C79676F6E20636C6173733D22636C732D342220706F696E74733D22352031
2035203331203620333120323120333120323120333020362033302036203220
313820322031382E352032203139203220313920322E352032352E3520392032
36203920323620392E3520323620313020323620313620323720313620323720
392E372032372039203139203120352031222F3E3C706F6C79676F6E20636C61
73733D22636C732D342220706F696E74733D22323620313020323620392E3520
323620392032352E352039203139203920313920322E3520313920322031382E
3520322031382032203138203130203236203130222F3E3C7265637420636C61
73733D22636C732D332220783D22382220793D2239222077696474683D223822
206865696768743D2231222F3E3C7265637420636C6173733D22636C732D3522
20783D22382220793D2239222077696474683D223822206865696768743D2231
222F3E3C7265637420636C6173733D22636C732D332220783D22382220793D22
3132222077696474683D22313622206865696768743D2231222F3E3C72656374
20636C6173733D22636C732D352220783D22382220793D223132222077696474
683D22313622206865696768743D2231222F3E3C706F6C79676F6E20636C6173
733D22636C732D332220706F696E74733D223820313520382031362031362031
362032342031362032342031352038203135222F3E3C706F6C79676F6E20636C
6173733D22636C732D352220706F696E74733D22382031352038203136203136
2031362032342031362032342031352038203135222F3E3C7265637420636C61
73733D22636C732D332220783D22382220793D223138222077696474683D2238
22206865696768743D2231222F3E3C7265637420636C6173733D22636C732D35
2220783D22382220793D223138222077696474683D223822206865696768743D
2231222F3E3C706F6C79676F6E20636C6173733D22636C732D332220706F696E
74733D22382032322031362E38372032322031362032312E3132203136203231
20382032312038203232222F3E3C706F6C79676F6E20636C6173733D22636C73
2D352220706F696E74733D22382032322031362E38372032322031362032312E
313220313620323120382032312038203232222F3E3C706F6C79676F6E20636C
6173733D22636C732D332220706F696E74733D22382032352031392E38342032
352031382E383520323420382032342038203235222F3E3C706F6C79676F6E20
636C6173733D22636C732D352220706F696E74733D22382032352031392E3834
2032352031382E383520323420382032342038203235222F3E3C2F673E3C6720
69643D224F7665726C6179223E3C706F6C79676F6E20636C6173733D22636C73
2D332220706F696E74733D2233312E352031372E352031372E352031372E3520
31372E352032302E352032322E352032352E35362032322E352033312E352032
362E352033312E352032362E352032352E35362033312E352032302E35203331
2E352031372E35222F3E3C7061746820636C6173733D22636C732D342220643D
224D32372C33324832325632352E37376C2D352D352E30365631374833327633
2E37316C2D352C352E30365A6D2D342D3168335632352E33366C352D352E3037
56313848313876322E32396C352C352E30375A222F3E3C2F673E3C2F7376673E}
Icons.SortAscendingIcon.Data = {
1054544D53464E435356474269746D6170990300003C73766720786D6C6E733D
22687474703A2F2F7777772E77332E6F72672F323030302F7376672220786D6C
6E733A786C696E6B3D22687474703A2F2F7777772E77332E6F72672F31393939
2F786C696E6B222077696474683D22333222206865696768743D223332222076
696577426F783D22302030203332203332223E3C646566733E3C7374796C653E
2E636C732D317B66696C6C3A6E6F6E653B7D2E636C732D327B66696C6C3A2361
37346162303B7D2E636C732D337B66696C6C3A75726C28234E65775F50617474
65726E5F5377617463685F37293B7D2E636C732D347B66696C6C3A2331653862
63643B7D2E636C732D357B66696C6C3A233361336133383B7D3C2F7374796C65
3E3C7061747465726E2069643D224E65775F5061747465726E5F537761746368
5F372220646174612D6E616D653D224E6577205061747465726E205377617463
682037222077696474683D22363822206865696768743D223638222070617474
65726E556E6974733D227573657253706163654F6E557365222076696577426F
783D22302030203638203638223E3C7265637420636C6173733D22636C732D31
222077696474683D22363822206865696768743D223638222F3E3C7265637420
636C6173733D22636C732D32222077696474683D22363822206865696768743D
223638222F3E3C2F7061747465726E3E3C2F646566733E3C7469746C653E536F
72742D415A3C2F7469746C653E3C672069643D2249636F6E223E3C7061746820
636C6173733D22636C732D332220643D224D322C32392E36346C372D31305632
304833563138683976312E34344C352C32392E33365632396837763248325A22
2F3E3C7061746820636C6173733D22636C732D342220643D224D352E372C3131
2C342E35322C313548312E39334C362E33332C3148392E35344C31342C313568
2D322E376C2D312E32342D345A4D392E36342C392E31312C382E35362C352E36
36632D2E32372D2E38352D2E34392D312E382D2E36392D322E36316830632D2E
322E38312D2E342C312E37382D2E36352C322E36314C362E31312C392E31315A
222F3E3C706F6C79676F6E20636C6173733D22636C732D352220706F696E7473
3D2232332032352E3132203233203620323220362032322032352E3132203136
2E34342031392E35362031352E35362032302E34342032322E352032372E3338
2032392E34342032302E34342032382E35362031392E35362032332032352E31
32222F3E3C2F673E3C2F7376673E}
Icons.SortDescendingIcon.Data = {
1054544D53464E435356474269746D6170990300003C73766720786D6C6E733D
22687474703A2F2F7777772E77332E6F72672F323030302F7376672220786D6C
6E733A786C696E6B3D22687474703A2F2F7777772E77332E6F72672F31393939
2F786C696E6B222077696474683D22333222206865696768743D223332222076
696577426F783D22302030203332203332223E3C646566733E3C7374796C653E
2E636C732D317B66696C6C3A6E6F6E653B7D2E636C732D327B66696C6C3A2361
37346162303B7D2E636C732D337B66696C6C3A75726C28234E65775F50617474
65726E5F5377617463685F37293B7D2E636C732D347B66696C6C3A2331653862
63643B7D2E636C732D357B66696C6C3A233361336133383B7D3C2F7374796C65
3E3C7061747465726E2069643D224E65775F5061747465726E5F537761746368
5F372220646174612D6E616D653D224E6577205061747465726E205377617463
682037222077696474683D22363822206865696768743D223638222070617474
65726E556E6974733D227573657253706163654F6E557365222076696577426F
783D22302030203638203638223E3C7265637420636C6173733D22636C732D31
222077696474683D22363822206865696768743D223638222F3E3C7265637420
636C6173733D22636C732D32222077696474683D22363822206865696768743D
223638222F3E3C2F7061747465726E3E3C2F646566733E3C7469746C653E536F
72742D5A413C2F7469746C653E3C672069643D2249636F6E223E3C7061746820
636C6173733D22636C732D332220643D224D322C31322E36346C372D31305633
48335631683956322E34344C352C31322E33365631326837763248325A222F3E
3C7061746820636C6173733D22636C732D342220643D224D352E372C32372C34
2E35322C333148312E39336C342E342D313448392E35344C31342C3331682D32
2E376C2D312E32342D345A6D332E39342D312E39334C382E35362C32312E3636
632D2E32372D2E38352D2E34392D312E382D2E36392D322E36316830632D2E32
2E38312D2E342C312E37382D2E36352C322E36314C362E31312C32352E31315A
222F3E3C706F6C79676F6E20636C6173733D22636C732D352220706F696E7473
3D2232332032352E3132203233203620323220362032322032352E3132203136
2E34342031392E35362031352E35362032302E34342032322E352032372E3338
2032392E34342032302E34342032382E35362031392E35362032332032352E31
32222F3E3C2F673E3C2F7376673E}
Icons.CloseIcon.Data = {
1054544D53464E435356474269746D6170180400003C21444F43545950452073
7667205055424C494320222D2F2F5733432F2F4454442053564720312E312F2F
454E222022687474703A2F2F7777772E77332E6F72672F47726170686963732F
5356472F312E312F4454442F73766731312E647464223E0A0D3C212D2D205570
6C6F6164656420746F3A20535647205265706F2C207777772E7376677265706F
2E636F6D2C205472616E73666F726D65642062793A20535647205265706F204D
6978657220546F6F6C73202D2D3E0A3C7376672077696474683D223634707822
206865696768743D2236347078222076696577426F783D223020302032342032
34222066696C6C3D226E6F6E652220786D6C6E733D22687474703A2F2F777777
2E77332E6F72672F323030302F73766722207374726F6B653D22233030303030
30223E0A0D3C672069643D225356475265706F5F626743617272696572222073
74726F6B652D77696474683D2230222F3E0A0D3C672069643D22535647526570
6F5F7472616365724361727269657222207374726F6B652D6C696E656361703D
22726F756E6422207374726F6B652D6C696E656A6F696E3D22726F756E64222F
3E0A0D3C672069643D225356475265706F5F69636F6E43617272696572223E20
3C706174682066696C6C2D72756C653D226576656E6F64642220636C69702D72
756C653D226576656E6F64642220643D224D352E323932383920352E32393238
3943352E363833343220342E393032333720362E333136353820342E39303233
3720362E373037313120352E32393238394C31322031302E353835384C31372E
3239323920352E32393238394331372E3638333420342E39303233372031382E
3331363620342E39303233372031382E3730373120352E32393238394331392E
3039373620352E36383334322031392E3039373620362E33313635382031382E
3730373120362E37303731314C31332E343134322031324C31382E3730373120
31372E323932394331392E303937362031372E363833342031392E3039373620
31382E333136362031382E373037312031382E373037314331382E3331363620
31392E303937362031372E363833342031392E303937362031372E3239323920
31382E373037314C31322031332E343134324C362E37303731312031382E3730
373143362E33313635382031392E3039373620352E36383334322031392E3039
373620352E32393238392031382E3730373143342E39303233372031382E3331
363620342E39303233372031372E3638333420352E32393238392031372E3239
32394C31302E353835382031324C352E323932383920362E373037313143342E
393032333720362E333136353820342E393032333720352E363833343220352E
323932383920352E32393238395A222066696C6C3D2223304631373239222F3E
203C2F673E0A0D3C2F7376673E}
Icons.FirstPageIcon.Data = {
1054544D53464E435356474269746D6170800300003C3F786D6C207665727369
6F6E3D22312E3022203F3E3C21444F43545950452073766720205055424C4943
20272D2F2F5733432F2F4454442053564720312E312F2F454E27202027687474
703A2F2F7777772E77332E6F72672F47726170686963732F5356472F312E312F
4454442F73766731312E647464273E3C73766720656E61626C652D6261636B67
726F756E643D226E65772030203020333220333222206865696768743D223332
7078222069643D22D0A1D0BBD0BED0B95F31222076657273696F6E3D22312E31
222076696577426F783D22302030203332203332222077696474683D22333270
782220786D6C3A73706163653D2270726573657276652220786D6C6E733D2268
7474703A2F2F7777772E77332E6F72672F323030302F7376672220786D6C6E73
3A786C696E6B3D22687474703A2F2F7777772E77332E6F72672F313939392F78
6C696E6B223E3C672069643D22446F75626C655F43686576726F6E5F4C656674
223E3C7061746820643D224D31302E3435362C31366C362E3139362D362E3238
3563302E3339312D302E3339342C302E3339312D312E3033342C302D312E3432
38632D302E3339312D302E3339342D312E3032342D302E3339342D312E343134
2C306C2D362E3839392C362E393939202020632D302E3337352C302E3337392D
302E3337372C312E3034382C302C312E3432396C362E392C362E39393963302E
33392C302E3339342C312E3032342C302E3339342C312E3431342C3063302E33
39312D302E3339342C302E3339312D312E3033342C302D312E3432384C31302E
3435362C31367A222066696C6C3D2223313231333133222F3E3C706174682064
3D224D31372E3435362C31366C362E3139362D362E32383563302E3339312D30
2E3339342C302E3339312D312E3033342C302D312E343238632D302E3339312D
302E3339342D312E3032342D302E3339342D312E3431342C306C2D362E383939
2C362E393939202020632D302E3337352C302E3337392D302E3337372C312E30
34382C302C312E3432396C362E3839392C362E39393963302E3339312C302E33
39342C312E3032342C302E3339342C312E3431342C3063302E3339312D302E33
39342C302E3339312D312E3033342C302D312E3432384C31372E3435362C3136
7A222066696C6C3D2223313231333133222F3E3C2F673E3C672F3E3C672F3E3C
672F3E3C672F3E3C672F3E3C672F3E3C2F7376673E}
Icons.LastPageIcon.Data = {
1054544D53464E435356474269746D6170A60300003C3F786D6C207665727369
6F6E3D22312E3022203F3E3C21444F43545950452073766720205055424C4943
20272D2F2F5733432F2F4454442053564720312E312F2F454E27202027687474
703A2F2F7777772E77332E6F72672F47726170686963732F5356472F312E312F
4454442F73766731312E647464273E3C73766720656E61626C652D6261636B67
726F756E643D226E65772030203020333220333222206865696768743D223332
7078222069643D22D0A1D0BBD0BED0B95F31222076657273696F6E3D22312E31
222076696577426F783D22302030203332203332222077696474683D22333270
782220786D6C3A73706163653D2270726573657276652220786D6C6E733D2268
7474703A2F2F7777772E77332E6F72672F323030302F7376672220786D6C6E73
3A786C696E6B3D22687474703A2F2F7777772E77332E6F72672F313939392F78
6C696E6B223E3C672069643D22446F75626C655F43686576726F6E5F52696768
74223E3C7061746820643D224D32332E3636322C31352E3238366C2D362E392D
362E393939632D302E33392D302E3339342D312E3032342D302E3339342D312E
3431342C30632D302E3339312C302E3339342D302E3339312C312E3033342C30
2C312E3432384C32312E3534342C31362020206C2D362E3139362C362E323835
632D302E3339312C302E3339342D302E3339312C312E3033342C302C312E3432
3863302E3339312C302E3339342C312E3032342C302E3339342C312E3431342C
306C362E3839392D362E3939392020204332342E3033382C31362E3333352C32
342E3033392C31352E3636362C32332E3636322C31352E3238367A222066696C
6C3D2223313231333133222F3E3C7061746820643D224D31362E3636322C3135
2E3238364C392E3736332C382E323837632D302E3339312D302E3339342D312E
3032342D302E3339342D312E3431342C30632D302E3339312C302E3339342D30
2E3339312C312E3033342C302C312E3432384C31342E3534342C31362020206C
2D362E3139362C362E323835632D302E3339312C302E3339342D302E3339312C
312E3033342C302C312E34323863302E3339312C302E3339342C312E3032342C
302E3339342C312E3431342C306C362E3839392D362E3939392020204331372E
3033382C31362E3333352C31372E3033392C31352E3636362C31362E3636322C
31352E3238367A222066696C6C3D2223313231333133222F3E3C2F673E3C672F
3E3C672F3E3C672F3E3C672F3E3C672F3E3C672F3E3C2F7376673E}
Icons.NextPageIcon.Data = {
1054544D53464E435356474269746D6170B50200003C3F786D6C207665727369
6F6E3D22312E3022203F3E3C21444F43545950452073766720205055424C4943
20272D2F2F5733432F2F4454442053564720312E312F2F454E27202027687474
703A2F2F7777772E77332E6F72672F47726170686963732F5356472F312E312F
4454442F73766731312E647464273E3C73766720656E61626C652D6261636B67
726F756E643D226E65772030203020333220333222206865696768743D223332
7078222069643D22D0A1D0BBD0BED0B95F31222076657273696F6E3D22312E31
222076696577426F783D22302030203332203332222077696474683D22333270
782220786D6C3A73706163653D2270726573657276652220786D6C6E733D2268
7474703A2F2F7777772E77332E6F72672F323030302F7376672220786D6C6E73
3A786C696E6B3D22687474703A2F2F7777772E77332E6F72672F313939392F78
6C696E6B223E3C7061746820636C69702D72756C653D226576656E6F64642220
643D224D32312E3639382C31352E3238366C2D392E3030322D382E3939392020
632D302E3339352D302E3339342D312E3033352D302E3339342D312E3433312C
30632D302E3339352C302E3339342D302E3339352C312E3033342C302C312E34
32384C31392E3535332C31366C2D382E3238372C382E323835632D302E333935
2C302E3339342D302E3339352C312E3033342C302C312E343239202063302E33
39352C302E3339342C312E3033362C302E3339342C312E3433312C306C392E30
30322D382E3939394332322E3038382C31362E3332352C32322E3038382C3135
2E3637352C32312E3639382C31352E3238367A222066696C6C3D222331323133
3133222066696C6C2D72756C653D226576656E6F6464222069643D2243686576
726F6E5F5269676874222F3E3C672F3E3C672F3E3C672F3E3C672F3E3C672F3E
3C672F3E3C2F7376673E}
Icons.PreviousPageIcon.Data = {
1054544D53464E435356474269746D6170B20200003C3F786D6C207665727369
6F6E3D22312E3022203F3E3C21444F43545950452073766720205055424C4943
20272D2F2F5733432F2F4454442053564720312E312F2F454E27202027687474
703A2F2F7777772E77332E6F72672F47726170686963732F5356472F312E312F
4454442F73766731312E647464273E3C73766720656E61626C652D6261636B67
726F756E643D226E65772030203020333220333222206865696768743D223332
7078222069643D22D0A1D0BBD0BED0B95F31222076657273696F6E3D22312E31
222076696577426F783D22302030203332203332222077696474683D22333270
782220786D6C3A73706163653D2270726573657276652220786D6C6E733D2268
7474703A2F2F7777772E77332E6F72672F323030302F7376672220786D6C6E73
3A786C696E6B3D22687474703A2F2F7777772E77332E6F72672F313939392F78
6C696E6B223E3C7061746820636C69702D72756C653D226576656E6F64642220
643D224D31312E3236322C31362E3731346C392E3030322C382E393939202063
302E3339352C302E3339342C312E3033352C302E3339342C312E3433312C3063
302E3339352D302E3339342C302E3339352D312E3033342C302D312E3432384C
31332E3430372C31366C382E3238372D382E32383563302E3339352D302E3339
342C302E3339352D312E3033342C302D312E3432392020632D302E3339352D30
2E3339342D312E3033362D302E3339342D312E3433312C306C2D392E3030322C
382E3939394331302E3837322C31352E3637352C31302E3837322C31362E3332
352C31312E3236322C31362E3731347A222066696C6C3D222331323133313322
2066696C6C2D72756C653D226576656E6F6464222069643D2243686576726F6E
5F5269676874222F3E3C672F3E3C672F3E3C672F3E3C672F3E3C672F3E3C672F
3E3C2F7376673E}
end
end
object xdwcTasks: TXDataWebClient
Connection = DMConnection.ApiConnection
Left = 690
Top = 246
end
end
<div id="pnl_grid" class="h-100"></div>
unit View.Tasks;
interface
uses
System.SysUtils, System.Classes,
JS, Web, WEBLib.Graphics, WEBLib.Controls, WEBLib.Forms, WEBLib.Dialogs,
VCL.TMSFNCTypes, VCL.TMSFNCUtils, VCL.TMSFNCGraphics, VCL.TMSFNCGraphicsTypes,
VCL.TMSFNCDataGridCell, VCL.TMSFNCDataGridData, VCL.TMSFNCDataGridBase,
VCL.TMSFNCDataGridCore, VCL.TMSFNCDataGridRenderer, VCL.TMSFNCCustomControl,
VCL.TMSFNCDataGrid,
XData.Web.Client,
Utils, WEBLib.ExtCtrls, System.Rtti, Vcl.Controls;
type
TFTasks = class(TWebForm)
xdwcTasks: TXDataWebClient;
pnlGrid: TWebPanel;
grdTasks: TTMSFNCDataGrid;
procedure WebFormCreate(Sender: TObject);
private
[async] procedure LoadTasks(const AProjectId: string);
public
end;
var
FTasks: TFTasks;
implementation
uses
ConnectionModule;
{$R *.dfm}
const
COL_TASK_ITEM_ID = 0;
COL_TASK_ID = 1;
COL_APP = 2;
COL_VERSION = 3;
COL_TASK_DATE = 4;
COL_REPORTED_BY = 5;
COL_ASSIGNED_TO = 6;
COL_STATUS = 7;
COL_STATUS_DATE = 8;
COL_FIXED_VER = 9;
COL_FORM_SECTION = 10;
COL_ISSUE = 11;
COL_NOTES = 12;
procedure TFTasks.WebFormCreate(Sender: TObject);
begin
xdwcTasks.Connection := DMConnection.ApiConnection;
LoadTasks('WPR0001');
end;
[async] procedure TFTasks.LoadTasks(const AProjectId: string);
var
response: TXDataClientResponse;
resultObj: TJSObject;
tasksArray: TJSArray;
taskObj: TJSObject;
itemsArray: TJSArray;
itemObj: TJSObject;
taskIndex, itemIndex: Integer;
gridRow: Integer;
statusDateVal: JSValue;
begin
Utils.ShowSpinner('spinner');
try
try
response := await(xdwcTasks.RawInvokeAsync(
'IApiService.GetProjectTasks', [AProjectId]
));
if not Assigned(response.Result) then
Exit;
resultObj := TJSObject(response.Result);
tasksArray := TJSArray(resultObj['data']);
grdTasks.BeginUpdate;
try
grdTasks.RowCount := 1;
gridRow := 1;
for taskIndex := 0 to tasksArray.Length - 1 do
begin
taskObj := TJSObject(tasksArray[taskIndex]);
itemsArray := TJSArray(taskObj['items']);
for itemIndex := 0 to itemsArray.Length - 1 do
begin
itemObj := TJSObject(itemsArray[itemIndex]);
grdTasks.RowCount := gridRow + 1;
grdTasks.Cells[COL_TASK_ITEM_ID, gridRow] := string(itemObj['taskItemId']);
grdTasks.Cells[COL_TASK_ID, gridRow] := string(itemObj['taskId']);
grdTasks.Cells[COL_APP, gridRow] := string(itemObj['application']);
grdTasks.Cells[COL_VERSION, gridRow] := string(itemObj['version']);
grdTasks.Cells[COL_TASK_DATE, gridRow] := string(itemObj['taskDate']);
grdTasks.Cells[COL_REPORTED_BY, gridRow] := string(itemObj['reportedBy']);
grdTasks.Cells[COL_ASSIGNED_TO, gridRow] := string(itemObj['assignedTo']);
grdTasks.Cells[COL_STATUS, gridRow] := string(itemObj['status']);
statusDateVal := itemObj['statusDate'];
if JS.isNull(statusDateVal) or JS.isUndefined(statusDateVal) then
grdTasks.Cells[COL_STATUS_DATE, gridRow] := ''
else
grdTasks.Cells[COL_STATUS_DATE, gridRow] := string(statusDateVal);
grdTasks.Cells[COL_FIXED_VER, gridRow] := string(itemObj['fixedVersion']);
grdTasks.Cells[COL_FORM_SECTION, gridRow] := string(itemObj['formSection']);
grdTasks.Cells[COL_ISSUE, gridRow] := string(itemObj['issue']);
grdTasks.Cells[COL_NOTES, gridRow] := string(itemObj['notes']);
Inc(gridRow);
end;
end;
finally
grdTasks.EndUpdate;
end;
except
on E: EXDataClientRequestException do
Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
end;
finally
Utils.HideSpinner('spinner');
end;
end;
end.
object FTasksDBGrid: TFTasksDBGrid
Width = 1161
Height = 725
CSSLibrary = cssBootstrap
ElementFont = efCSS
OnCreate = WebFormCreate
object dbGridTasks: TWebDBGrid
Left = 0
Top = 0
Width = 1161
Height = 725
TabStop = False
Align = alClient
Columns = <
item
ElementClassName = 'text-nowrap tasks-nowrap'
DataField = 'taskId'
Title = 'Task Id'
Width = 95
end
item
DataField = 'application'
Editor = geCombo
Title = 'Application'
Width = 150
end
item
ElementClassName = 'text-nowrap tasks-nowrap'
DataField = 'version'
Title = 'Version'
Width = 95
end
item
DataField = 'taskDate'
Editor = geDate
Title = 'Date'
Width = 120
end
item
ComboBoxItems.Strings = (
#11
''
'Elias'
'Mark'
'Mac')
DataField = 'reportedBy'
Editor = geCombo
Title = 'Reported By'
Width = 95
end
item
ComboBoxItems.Strings = (
''
'Elias'
'Mark'
'Mac')
DataField = 'assignedTo'
Editor = geCombo
Title = 'Assigned To'
Width = 95
end
item
ElementClassName = 'text-nowrap'
DataField = 'status'
Title = 'Status'
Width = 120
end
item
DataField = 'statusDate'
Editor = geDate
Title = 'Status Date'
Width = 120
end
item
ElementClassName = 'text-nowrap tasks-nowrap'
DataField = 'formSection'
Title = 'Form/Section'
Width = 150
end
item
ElementClassName = 'text-wrap'
DataField = 'issue'
Title = 'Issue'
Width = 350
end
item
ElementClassName = 'text-wrap'
DataField = 'notes'
Editor = geMemo
Title = 'Notes'
Width = 350
end>
DataSource = wdsTasks
ElementFont = efCSS
ElementId = 'db_grid_tasks'
FixedFont.Charset = DEFAULT_CHARSET
FixedFont.Color = clWindowText
FixedFont.Height = -12
FixedFont.Name = 'Segoe UI'
FixedFont.Style = []
FixedCols = 0
Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goRangeSelect, goColSizing, goRowMoving, goEditing, goFixedRowDefAlign]
TabOrder = 0
HeightPercent = 100.000000000000000000
WidthStyle = ssAuto
WidthPercent = 100.000000000000000000
ColWidths = (
95
150
95
120
95
95
120
120
150
350
350)
end
object btnReload: TWebButton
Left = 864
Top = 259
Width = 96
Height = 25
Caption = 'Reload'
ChildOrder = 1
ElementID = 'btn_reload'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnReloadClick
end
object btnAddRow: TWebButton
Left = 980
Top = 259
Width = 96
Height = 25
Caption = 'Add Row'
ChildOrder = 1
ElementID = 'btn_add_row'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnAddRowClick
end
object xdwcTasks: TXDataWebClient
Connection = DMConnection.ApiConnection
Left = 1010
Top = 430
end
object wdsTasks: TWebDataSource
DataSet = xdwdsTasks
Left = 1014
Top = 574
end
object xdwdsTasks: TXDataWebDataSet
Left = 1010
Top = 504
object xdwdsTaskstaskID: TStringField
FieldName = 'taskId'
end
object xdwdsTasksapplication: TStringField
FieldName = 'application'
end
object xdwdsTasksversion: TStringField
FieldName = 'version'
end
object xdwdsTaskstaskDate: TStringField
FieldName = 'taskDate'
end
object xdwdsTasksreportedBy: TStringField
FieldName = 'reportedBy'
end
object xdwdsTasksassignedTo: TStringField
FieldName = 'assignedTo'
end
object xdwdsTasksstatus: TStringField
FieldName = 'status'
end
object xdwdsTasksstatusDate: TStringField
FieldName = 'statusDate'
end
object xdwdsTasksformSection: TStringField
FieldName = 'formSection'
end
object xdwdsTasksissue: TStringField
FieldName = 'issue'
end
object xdwdsTasksnotes: TStringField
FieldName = 'notes'
end
end
end
unit View.TasksDBGrid;
interface
uses
System.SysUtils, System.Classes, JS, Web, WEBLib.Graphics, WEBLib.Controls,
WEBLib.Forms, WEBLib.Dialogs, WEBLib.Grids, Vcl.Controls, Vcl.Grids,
WEBLib.DBCtrls, Data.DB, WEBLib.DB, XData.Web.JsonDataset, XData.Web.Dataset,
XData.Web.Client, ConnectionModule, Vcl.StdCtrls, WEBLib.StdCtrls;
type
TFTasksDBGrid = class(TWebForm)
xdwcTasks: TXDataWebClient;
wdsTasks: TWebDataSource;
xdwdsTasks: TXDataWebDataSet;
xdwdsTaskstaskID: TStringField;
xdwdsTasksapplication: TStringField;
xdwdsTasksversion: TStringField;
xdwdsTaskstaskDate: TStringField;
xdwdsTasksreportedBy: TStringField;
xdwdsTasksassignedTo: TStringField;
xdwdsTasksstatus: TStringField;
xdwdsTasksstatusDate: TStringField;
xdwdsTasksformSection: TStringField;
xdwdsTasksissue: TStringField;
xdwdsTasksnotes: TStringField;
btnReload: TWebButton;
btnAddRow: TWebButton;
dbGridTasks: TWebDBGrid;
procedure btnAddRowClick(Sender: TObject);
procedure btnReloadClick(Sender: TObject);
procedure WebFormCreate(Sender: TObject);
private
FProjectId: string;
[async] procedure LoadTasks(const AProjectId: string);
procedure SetProjectTitle(const AProjectId: string);
public
{ Public declarations }
end;
var
FTasksDBGrid: TFTasksDBGrid;
implementation
{$R *.dfm}
procedure TFTasksDBGrid.WebFormCreate(Sender: TObject);
begin
wdsTasks.DataSet := xdwdsTasks;
dbGridTasks.DataSource := wdsTasks;
FProjectId := 'WPR0001';
SetProjectTitle(FProjectId);
LoadTasks(FProjectId);
end;
procedure TFTasksDBGrid.btnAddRowClick(Sender: TObject);
begin
xdwdsTasks.Append;
xdwdsTaskstaskID.AsString := FProjectId;
xdwdsTasks.Post;
end;
procedure TFTasksDBGrid.btnReloadClick(Sender: TObject);
begin
LoadTasks(FProjectId);
end;
procedure TFTasksDBgrid.SetProjectTitle(const AProjectId: string);
begin
TJSHTMLElement(document.getElementById('lbl_project_name')).innerText := AProjectId;
end;
[async] procedure TFTasksDBgrid.LoadTasks(const AProjectId: string);
var
response: TXDataClientResponse;
resultObj, taskObj: TJSObject;
tasksArray, itemsArray, flatItems: TJSArray;
taskIndex, itemIndex: Integer;
begin
response := await(xdwcTasks.RawInvokeAsync('IApiService.GetProjectTasks', [AProjectId]));
resultObj := TJSObject(response.Result);
tasksArray := TJSArray(resultObj['data']);
flatItems := TJSArray.new;
for taskIndex := 0 to tasksArray.Length - 1 do
begin
taskObj := TJSObject(tasksArray[taskIndex]);
itemsArray := TJSArray(taskObj['items']);
for itemIndex := 0 to itemsArray.Length - 1 do
flatItems.push(itemsArray[itemIndex]);
end;
xdwdsTasks.Close;
xdwdsTasks.SetJsonData(flatItems);
xdwdsTasks.Open;
end;
end.
\ No newline at end of file
object FTasksDataGrid: TFTasksDataGrid
Width = 1274
Height = 792
CSSLibrary = cssBootstrap
ElementFont = efCSS
OnCreate = WebFormCreate
object dataGridTasks: TWebDataGrid
Left = 22
Top = 96
Width = 1223
Height = 357
ElementID = 'data_grid_tasks'
Banding.Enabled = False
Banding.OddRowsColor = 16777215
Banding.EvenRowsColor = 16777215
MaxBlocksInCache = 0
TabOrder = 0
RowMultiSelectWithClick = False
EnableClickSelection = True
BidiMode = bdLeftToRight
SuppressMoveWhenColumnDragging = False
MultilevelHeaders = <>
ColumnDefs = <
item
Field = 'taskId'
CellDataType = cdtText
HeaderName = 'Task'
Editable = True
CheckboxSelection = False
Resizable = True
Width = 95
LockPinned = True
SelectOptions = <>
end
item
Field = 'application'
CellDataType = cdtText
HeaderName = 'App'
EditModeType = cetCombobox
Editable = True
CheckboxSelection = False
LockPinned = True
SelectOptions = <
item
Value = 'null'
end
item
Text = 'WebPoliceReport - Client'
Value = 'WebPoliceReport - Client'
end
item
Text = 'WebPoliceReport - Server'
Value = 'WebPoliceReport - Server'
end>
end
item
Field = 'version'
CellDataType = cdtText
HeaderName = 'Version'
Editable = True
CheckboxSelection = False
Width = 95
LockPinned = True
SelectOptions = <>
end
item
Field = 'taskDate'
CellDataType = cdtDateString
HeaderName = 'Date'
EditModeType = cetDate
Editable = True
CheckboxSelection = False
Width = 110
LockPinned = True
SelectOptions = <>
end
item
Field = 'reportedBy'
CellDataType = cdtText
HeaderName = 'Reported By'
EditModeType = cetCombobox
Editable = True
CheckboxSelection = False
Width = 150
LockPinned = True
SelectOptions = <
item
Value = 'Null'
end
item
Text = 'Elias'
Value = 'Elias'
end
item
Text = 'Mark'
Value = 'Mark'
end
item
Text = 'Mac'
Value = 'Mac'
end>
end
item
Field = 'assignedTo'
CellDataType = cdtText
HeaderName = 'Assigned To'
EditModeType = cetCombobox
Editable = True
CheckboxSelection = False
Width = 150
LockPinned = True
SelectOptions = <
item
Value = 'Null'
end
item
Text = 'Elias'
Value = 'Elias'
end
item
Text = 'Mark'
Value = 'Mark'
end
item
Text = 'Mac'
Value = 'Mac'
end>
end
item
Field = 'status'
CellDataType = cdtText
HeaderName = 'Status'
Editable = True
CheckboxSelection = False
Width = 95
LockPinned = True
SelectOptions = <
item
Value = 'Null'
end
item
Text = 'Fixed'
Value = 'Fixed'
end
item
Text = 'Fixed - Verified'
Value = 'Fixed - Verified'
end
item
Text = 'Not Fixed'
Value = 'Not Fixed'
end>
end
item
Field = 'statusDate'
CellDataType = cdtDateString
HeaderName = 'Status Date'
EditModeType = cetDate
Editable = True
CheckboxSelection = False
Width = 110
LockPinned = True
SelectOptions = <>
end
item
Field = 'formSection'
CellDataType = cdtText
HeaderName = 'Form/Section'
Editable = True
CheckboxSelection = False
LockPinned = True
SelectOptions = <>
end
item
Field = 'issue'
CellDataType = cdtText
HeaderName = 'Issue'
EditModeType = cetMemo
Editable = True
CheckboxSelection = False
Width = 300
WrapText = True
AutoHeight = True
LockPinned = True
SelectOptions = <>
end
item
Field = 'notes'
CellDataType = cdtText
HeaderName = 'Notes'
EditModeType = cetMemo
Editable = True
CheckboxSelection = False
Width = 300
WrapText = True
AutoHeight = True
LockPinned = True
SelectOptions = <>
end>
end
object btnReload: TWebButton
Left = 356
Top = 30
Width = 96
Height = 25
Caption = 'Reload'
ChildOrder = 1
ElementID = 'btn_reload'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnAddRow: TWebButton
Left = 356
Top = 61
Width = 96
Height = 25
Caption = 'Add Row'
ChildOrder = 1
ElementID = 'btn_add_row'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object xdwcTasks: TXDataWebClient
Connection = DMConnection.ApiConnection
Left = 150
Top = 534
end
end
unit View.TasksDataGrid;
interface
uses
System.SysUtils, System.Classes,
JS, Web, WEBLib.Forms, WEBLib.Dialogs, WEBLib.StdCtrls,
XData.Web.Client,
WEBLib.DataGrid,
Utils, WEBLib.DataGrid.Common, Vcl.StdCtrls, Vcl.Controls, Vcl.Grids,
WEBLib.DataGrid.DataAdapter.Base, WEBLib.DataGrid.DataAdapter.XData,
libdatagrid;
type
TFTasksDataGrid = class(TWebForm)
xdwcTasks: TXDataWebClient;
dataGridTasks: TWebDataGrid;
btnReload: TWebButton;
btnAddRow: TWebButton;
procedure WebFormCreate(Sender: TObject);
procedure btnReloadClick(Sender: TObject);
procedure btnAddRowClick(Sender: TObject);
private
FProjectId: string;
procedure SetProjectTitle(const AProjectId: string);
[async] procedure LoadTasks(const AProjectId: string);
public
end;
var
FTasksDataGrid: TFTasksDataGrid;
implementation
uses
ConnectionModule;
{$R *.dfm}
procedure TFTasksDataGrid.WebFormCreate(Sender: TObject);
begin
xdwcTasks.Connection := DMConnection.ApiConnection;
FProjectId := 'WPR0001';
SetProjectTitle(FProjectId);
LoadTasks(FProjectId);
end;
procedure TFTasksDataGrid.SetProjectTitle(const AProjectId: string);
begin
TJSHTMLElement(document.getElementById('lbl_project_name')).innerText := AProjectId;
end;
procedure TFTasksDataGrid.btnReloadClick(Sender: TObject);
begin
LoadTasks(FProjectId);
end;
procedure TFTasksDataGrid.btnAddRowClick(Sender: TObject);
begin
dataGridTasks.InsertNewRow;
dataGridTasks.EnsureLastRowVisible;
end;
[async] procedure TFTasksDataGrid.LoadTasks(const AProjectId: string);
var
response: TXDataClientResponse;
resultObj, taskObj: TJSObject;
tasksArray, itemsArray, flatItems: TJSArray;
taskIndex, itemIndex: Integer;
begin
response := await(xdwcTasks.RawInvokeAsync('IApiService.GetProjectTasks', [AProjectId]));
resultObj := TJSObject(response.Result);
tasksArray := TJSArray(resultObj['data']);
flatItems := TJSArray.new;
for taskIndex := 0 to tasksArray.Length - 1 do
begin
taskObj := TJSObject(tasksArray[taskIndex]);
itemsArray := TJSArray(taskObj['items']);
for itemIndex := 0 to itemsArray.Length - 1 do
flatItems.push(itemsArray[itemIndex]);
end;
dataGridTasks.LoadFromJSON(TJSObject(flatItems));
end;
end.
...@@ -3,7 +3,7 @@ object FTasksHTML: TFTasksHTML ...@@ -3,7 +3,7 @@ object FTasksHTML: TFTasksHTML
Height = 480 Height = 480
CSSLibrary = cssBootstrap CSSLibrary = cssBootstrap
ElementFont = efCSS ElementFont = efCSS
OnShow = WebFormShow OnCreate = WebFormCreate
object btnReload: TWebButton object btnReload: TWebButton
Left = 78 Left = 78
Top = 88 Top = 88
......
...@@ -30,7 +30,7 @@ type ...@@ -30,7 +30,7 @@ type
xdwdsTaskstaskItemId: TStringField; xdwdsTaskstaskItemId: TStringField;
procedure btnAddRowClick(Sender: TObject); procedure btnAddRowClick(Sender: TObject);
procedure btnReloadClick(Sender: TObject); procedure btnReloadClick(Sender: TObject);
procedure WebFormShow(Sender: TObject); procedure WebFormCreate(Sender: TObject);
private private
FTaskId: string; FTaskId: string;
[async] procedure LoadTasks(const ATaskId: string); [async] procedure LoadTasks(const ATaskId: string);
...@@ -49,7 +49,10 @@ type ...@@ -49,7 +49,10 @@ type
[async] procedure SaveRow(AIndex: Integer); [async] procedure SaveRow(AIndex: Integer);
procedure EditorBlur(Event: TJSEvent); procedure EditorBlur(Event: TJSEvent);
procedure InitializeForm;
public public
class function CreateForm(AElementID, ATaskId: string): TWebForm;
end; end;
var var
...@@ -62,11 +65,29 @@ uses ...@@ -62,11 +65,29 @@ uses
{$R *.dfm} {$R *.dfm}
class function TFTasksHTML.CreateForm(AElementID, ATaskId: string): TWebForm;
begin
console.log('TFTasksHTML.CreateForm called, host=' + AElementID + ', taskId=' + ATaskId);
Application.CreateForm(TFTasksHTML, AElementID, Result,
procedure(AForm: TObject)
begin
console.log('TFTasksHTML.CreateForm callback fired, assigned=' + BoolToStr(Assigned(AForm), True));
procedure TFTasksHTML.WebFormShow(Sender: TObject); if Assigned(AForm) then
begin
TFTasksHTML(AForm).FTaskId := ATaskId;
TFTasksHTML(AForm).InitializeForm;
end
else
Utils.ShowErrorModal('TFTasksHTML form callback returned nil.');
end
);
end;
procedure TFTasksHTML.InitializeForm;
begin begin
console.log('TFTasksHTML.WebFormShow fired'); console.log('TFTasksHTML.InitializeForm fired');
FTaskId := window.localStorage.getItem('EMT3_TASK_ID');
console.log('The task id is: ' + FTaskId); console.log('The task id is: ' + FTaskId);
if FTaskId = '' then if FTaskId = '' then
...@@ -79,12 +100,12 @@ begin ...@@ -79,12 +100,12 @@ begin
if not DMConnection.ApiConnection.Connected then if not DMConnection.ApiConnection.Connected then
begin begin
DMConnection.ApiConnection.Open( DMConnection.ApiConnection.Open(
procedure procedure
begin begin
LoadTasks(FTaskID); LoadTasks(FTaskId);
end end
); );
end end
else else
LoadTasks(FTaskId); LoadTasks(FTaskId);
...@@ -255,6 +276,11 @@ begin ...@@ -255,6 +276,11 @@ begin
end; end;
procedure TFTasksHTML.WebFormCreate(Sender: TObject);
begin
console.log('TFTasksHTML.WebFormCreate fired');
end;
[async] procedure TFTasksHTML.LoadTasks(const ATaskId: string); [async] procedure TFTasksHTML.LoadTasks(const ATaskId: string);
var var
response: TXDataClientResponse; response: TXDataClientResponse;
......
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>TMS Web Project</title>
<style>
</style>
</head>
<body>
</body>
</html>
\ No newline at end of file
unit View.TasksTabulator;
interface
uses
System.SysUtils, System.Classes, JS, Web, WEBLib.Graphics, WEBLib.Controls,
WEBLib.Forms, WEBLib.Dialogs, Data.DB, XData.Web.JsonDataset,
XData.Web.Dataset, XData.Web.Client, ConnectionModule;
type
TFTasksTabulator = class(TWebForm)
xdwcTasks: TXDataWebClient;
xdwdsTasks: TXDataWebDataSet;
xdwdsTaskstaskID: TStringField;
xdwdsTasksapplication: TStringField;
xdwdsTasksversion: TStringField;
xdwdsTaskstaskDate: TStringField;
xdwdsTasksreportedBy: TStringField;
xdwdsTasksassignedTo: TStringField;
xdwdsTasksstatus: TStringField;
xdwdsTasksstatusDate: TStringField;
xdwdsTasksformSection: TStringField;
xdwdsTasksissue: TStringField;
xdwdsTasksnotes: TStringField;
private
{ Private declarations }
public
{ Public declarations }
end;
var
FTasksTabulator: TFTasksTabulator;
implementation
{$R *.dfm}
end.
\ No newline at end of file
program emT3web; program emT3web;
{$R *.dres}
uses uses
Vcl.Forms, Vcl.Forms,
System.SysUtils, System.SysUtils,
...@@ -14,11 +16,8 @@ uses ...@@ -14,11 +16,8 @@ uses
App.Config in 'App.Config.pas', App.Config in 'App.Config.pas',
View.Main in 'View.Main.pas' {FViewMain: TWebForm} {*.html}, View.Main in 'View.Main.pas' {FViewMain: TWebForm} {*.html},
Utils in 'Utils.pas', Utils in 'Utils.pas',
View.Tasks in 'View.Tasks.pas' {FTasks: TWebForm} {*.html},
View.TasksHTML in 'View.TasksHTML.pas' {FTasksHTML: TWebForm} {*.html}, View.TasksHTML in 'View.TasksHTML.pas' {FTasksHTML: TWebForm} {*.html},
View.TasksDataGrid in 'View.TasksDataGrid.pas' {FTasksDataGrid: TWebForm} {*.html}, View.Home in 'View.Home.pas' {FHome: TWebForm} {*.html};
View.TasksTabulator in 'View.TasksTabulator.pas' {FTasksTabulator: TWebForm} {*.html},
View.TasksDBGrid in 'View.TasksDBGrid.pas' {FTasksDBGrid: TWebForm} {*.html};
{$R *.res} {$R *.res}
...@@ -74,11 +73,9 @@ begin ...@@ -74,11 +73,9 @@ begin
DisplayAccessDeniedModal(AMessage); DisplayAccessDeniedModal(AMessage);
end; end;
procedure DisplayMainView; procedure DisplayMainView(const AUserId, ATaskId, ACode: string);
begin begin
if Assigned(FViewLogin) then TFViewMain.Display(@DisplayLoginView, AUserId, ATaskId, ACode);
FViewLogin.Free;
TFViewMain.Display(@DisplayLoginView);
end; end;
procedure UnauthorizedAccessProc(AMessage: string); procedure UnauthorizedAccessProc(AMessage: string);
...@@ -86,65 +83,49 @@ begin ...@@ -86,65 +83,49 @@ begin
DisplayLoginView(AMessage); DisplayLoginView(AMessage);
end; end;
procedure SaveUrlParamsToStorage(const userId, taskId, code: string);
begin
if userId <> '' then
window.localStorage.setItem('EMT3_USER_ID', userId);
if taskId <> '' then
window.localStorage.setItem('EMT3_TASK_ID', taskId);
if code <> '' then
window.localStorage.setItem('EMT3_CODE', code);
end;
procedure StartApplication; procedure StartApplication;
var var
UserIdParam: string; userIdParam: string;
TaskIdParam: string; taskIdParam: string;
CodeParam: string; codeParam: string;
begin begin
UserIdParam := Application.Parameters.Values['user_id']; userIdParam := Application.Parameters.Values['user_id'];
TaskIdParam := Application.Parameters.Values['task_id']; taskIdParam := Application.Parameters.Values['task_id'];
CodeParam := Application.Parameters.Values['code']; codeParam := Application.Parameters.Values['code'];
SaveUrlParamsToStorage(UserIdParam, TaskIdParam, CodeParam); DMConnection.SetClientConfig(
procedure(Success: Boolean; ErrorMessage: string)
DMConnection.InitApp(
procedure
begin begin
DMConnection.SetClientConfig( if not Success then
procedure(Success: Boolean; ErrorMessage: string) begin
begin DisplayAccessDeniedModal(ErrorMessage);
if Success then Exit;
end;
if AuthService.Authenticated and (not AuthService.TokenExpired) then
begin
DisplayMainView(userIdParam, taskIdParam, codeParam);
Exit;
end;
if (userIdParam <> '') and (taskIdParam <> '') and (codeParam <> '') then
begin
AuthService.Login(
userIdParam, taskIdParam, codeParam,
procedure
begin begin
if (UserIdParam <> '') and (TaskIdParam <> '') and (CodeParam <> '') then DisplayMainView(userIdParam, taskIdParam, codeParam);
begin end,
AuthService.Login( procedure(LoginError: string)
UserIdParam, TaskIdParam, CodeParam,
procedure
begin
DisplayMainView;
end,
procedure(LoginError: string)
begin
DisplayLoginView('Invalid or expired link.' + sLineBreak + LoginError);
end
);
Exit;
end;
if AuthService.Authenticated and (not AuthService.TokenExpired) then
DisplayMainView
else
DisplayLoginView;
end
else
begin begin
DisplayAccessDeniedModal(ErrorMessage); DisplayLoginView('Invalid or expired link.' + sLineBreak + LoginError);
end; end
end); );
end, Exit;
@UnauthorizedAccessProc end;
DisplayLoginView;
end
); );
end; end;
...@@ -152,6 +133,6 @@ begin ...@@ -152,6 +133,6 @@ begin
Application.Initialize; Application.Initialize;
Application.MainFormOnTaskbar := True; Application.MainFormOnTaskbar := True;
Application.CreateForm(TDMConnection, DMConnection); Application.CreateForm(TDMConnection, DMConnection);
StartApplication; DMConnection.InitApp(@StartApplication, @UnauthorizedAccessProc);
Application.Run; Application.Run;
end. end.
...@@ -99,11 +99,10 @@ ...@@ -99,11 +99,10 @@
<VerInfo_MajorVer>0</VerInfo_MajorVer> <VerInfo_MajorVer>0</VerInfo_MajorVer>
<VerInfo_MinorVer>9</VerInfo_MinorVer> <VerInfo_MinorVer>9</VerInfo_MinorVer>
<VerInfo_Release>8</VerInfo_Release> <VerInfo_Release>8</VerInfo_Release>
<TMSUseJSDebugger>2</TMSUseJSDebugger>
<TMSWebBrowser>1</TMSWebBrowser> <TMSWebBrowser>1</TMSWebBrowser>
<TMSWebOutputPath>..\kgOrdersServer\bin\static</TMSWebOutputPath>
<TMSWebSingleInstance>1</TMSWebSingleInstance> <TMSWebSingleInstance>1</TMSWebSingleInstance>
<TMSURLParams>?user_id=1019&amp;task_id=4245&amp;code=749358</TMSURLParams> <TMSWebOutputPath>..\emT3XDataServer\bin</TMSWebOutputPath>
<TMSUseJSDebugger>2</TMSUseJSDebugger>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2)'!=''"> <PropertyGroup Condition="'$(Cfg_2)'!=''">
<DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols> <DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols>
...@@ -140,24 +139,13 @@ ...@@ -140,24 +139,13 @@
<DesignClass>TWebForm</DesignClass> <DesignClass>TWebForm</DesignClass>
</DCCReference> </DCCReference>
<DCCReference Include="Utils.pas"/> <DCCReference Include="Utils.pas"/>
<DCCReference Include="View.Tasks.pas">
<Form>FTasks</Form>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<DCCReference Include="View.TasksHTML.pas"> <DCCReference Include="View.TasksHTML.pas">
<Form>FTasksHTML</Form> <Form>FTasksHTML</Form>
<DesignClass>TWebForm</DesignClass> <DesignClass>TWebForm</DesignClass>
</DCCReference> </DCCReference>
<DCCReference Include="View.TasksDataGrid.pas"> <DCCReference Include="View.Home.pas">
<Form>FTasksDataGrid</Form> <Form>FHome</Form>
<DesignClass>TWebForm</DesignClass> <FormType>dfm</FormType>
</DCCReference>
<DCCReference Include="View.TasksTabulator.pas">
<Form>FTasksTabulator</Form>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<DCCReference Include="View.TasksDBGrid.pas">
<Form>FTasksDBGrid</Form>
<DesignClass>TWebForm</DesignClass> <DesignClass>TWebForm</DesignClass>
</DCCReference> </DCCReference>
<None Include="index.html"/> <None Include="index.html"/>
......
unit App.Config;
interface
uses
JS,
XData.Web.Connection,
XData.Web.Request,
XData.Web.Response;
type
TAppConfig = class
private
FAuthUrl: string;
FApiUrl: string;
FAppUrl: string;
public
constructor Create;
property AuthUrl: string read FAuthUrl write FAuthUrl;
property ApiUrl: string read FApiUrl write FApiUrl;
property AppUrl: string read FAppUrl write FAppUrl;
end;
TConfigLoadedProc = reference to procedure(Config: TAppConfig);
procedure LoadConfig(LoadProc: TConfigLoadedProc);
implementation
procedure LoadConfig(LoadProc: TConfigLoadedProc);
procedure OnSuccess(Response: IHttpResponse);
var
Obj: TJSObject;
Config: TAppConfig;
begin
Config := TAppConfig.Create;
try
if Response.StatusCode = 200 then
begin
Obj := TJSObject(TJSJSON.parse(Response.ContentAsText));
if JS.toString(Obj['AuthUrl']) <> '' then
Config.AuthUrl := JS.toString(Obj['AuthUrl']);
if JS.toString(Obj['ApiUrl']) <> '' then
Config.ApiUrl := JS.toString(Obj['ApiUrl']);
end;
finally
LoadProc(Config);
Config.Free;
end;
end;
procedure OnError;
var
Config: TAppConfig;
begin
Config := TAppConfig.Create;
try
LoadProc(Config);
finally
Config.Free;
end;
end;
var
Conn: TXDataWebConnection;
begin
Conn := TXDataWebConnection.Create(nil);
try
Conn.SendRequest(THttpRequest.Create('config/config.json'), @OnSuccess, @OnError);
finally
Conn.Free;
end;
end;
{ TAppConfig }
constructor TAppConfig.Create;
begin
FAuthUrl := '';
FApiUrl := '';
FAppUrl := '';
end;
end.
unit App.Types;
interface
uses
Bcl.Rtti.Common;
type
TProc = reference to procedure;
TSuccessProc = reference to procedure;
TLogoutProc = reference to procedure(AMessage: string = '');
TUnauthorizedAccessProc = reference to procedure(AMessage: string);
TVersionCheckCallback = reference to procedure(Success: Boolean; ErrorMessage: string);
TListProc = reference to procedure;
TSelectProc = reference to procedure(AParam: string);
TSelectProc2 = reference to procedure(AParam: string; BParam: string);
TSelectProc3 = reference to procedure(AParam: string; BParam: string; CParam: Boolean);
TSelectProc4 = reference to procedure(AParam: string; BParam: string; CParam: string; DParam: Boolean);
TSearchProc = reference to procedure(AParam: string; BParam: string; CParam: Integer; DParam: Boolean);
TReportProc = reference to procedure(AParam: string);
implementation
end.
unit Auth.Service;
interface
uses
SysUtils, Web, JS,
XData.Web.Client;
const
TOKEN_NAME = 'EMT3_WEB_TOKEN';
type
TOnLoginSuccess = reference to procedure;
TOnLoginError = reference to procedure(AMsg: string);
TOnProfileSuccess = reference to procedure;
TOnProfileError = reference to procedure(AMsg: string);
TAuthService = class
private
FClient: TXDataWebClient;
procedure SetToken(AToken: string);
procedure DeleteToken;
public
constructor Create; reintroduce;
destructor Destroy; override;
procedure Login(AUser, APassword, AClientVersion: string; ASuccess: TOnLoginSuccess;
AError: TOnLoginError);
procedure Logout;
function GetToken: string;
function Authenticated: Boolean;
function TokenExpirationDate: TDateTime;
function TokenExpired: Boolean;
function TokenPayload: JS.TJSObject;
end;
TJwtHelper = class
private
class function HasExpirationDate(AToken: string): Boolean;
public
class function TokenExpirationDate(AToken: string): TJSDate;
class function TokenExpired(AToken: string): Boolean;
class function DecodePayload(AToken: string): string;
end;
function AuthService: TAuthService;
implementation
uses
ConnectionModule;
var
_AuthService: TAuthService;
function AuthService: TAuthService;
begin
if not Assigned(_AuthService) then
begin
_AuthService := TAuthService.Create;
end;
Result := _AuthService;
end;
{ TAuthService }
function TAuthService.Authenticated: Boolean;
begin
Result := not isNull(window.localStorage.getItem(TOKEN_NAME)) and
(window.localStorage.getItem(TOKEN_NAME) <> '');
end;
constructor TAuthService.Create;
begin
FClient := TXDataWebClient.Create(nil);
FClient.Connection := DMConnection.AuthConnection;
end;
procedure TAuthService.DeleteToken;
begin
window.localStorage.removeItem(TOKEN_NAME);
end;
destructor TAuthService.Destroy;
begin
FClient.Free;
inherited;
end;
function TAuthService.GetToken: string;
begin
Result := window.localStorage.getItem(TOKEN_NAME);
end;
procedure TAuthService.Login(AUser, APassword, AClientVersion: string; ASuccess: TOnLoginSuccess;
AError: TOnLoginError);
procedure OnLoad(Response: TXDataClientResponse);
var
Token: JS.TJSObject;
begin
Token := JS.TJSObject(Response.Result);
SetToken(JS.toString(Token.Properties['value']));
ASuccess;
end;
procedure OnError(Error: TXDataClientError);
begin
AError(Format('%s: %s', [Error.ErrorCode, Error.ErrorMessage]));
end;
begin
if (AUser = '') or (APassword = '') then
begin
AError('Please enter a username and a password');
Exit;
end;
FClient.RawInvoke(
'IAuthService.Login', [AUser, APassword, AClientVersion],
@OnLoad, @OnError
);
end;
procedure TAuthService.Logout;
begin
DeleteToken;
end;
procedure TAuthService.SetToken(AToken: string);
begin
window.localStorage.setItem(TOKEN_NAME, AToken);
end;
function TAuthService.TokenExpirationDate: TDateTime;
var
ExpirationDate: TJSDate;
begin
if not Authenticated then
Exit(Now);
ExpirationDate := TJwtHelper.TokenExpirationDate(GetToken);
Result := EncodeDate(
ExpirationDate.FullYear,
ExpirationDate.Month + 1,
ExpirationDate.Date
) +
EncodeTime(
ExpirationDate.Hours,
ExpirationDate.Minutes,
ExpirationDate.Seconds,
0
);
end;
function TAuthService.TokenExpired: Boolean;
begin
if not Authenticated then
Exit(False);
Result := TJwtHelper.TokenExpired(GetToken);
end;
function TAuthService.TokenPayload: JS.TJSObject;
begin
if not Authenticated then
Exit(nil);
Result := TJSObject(TJSJSON.parse(TJwtHelper.DecodePayload(GetToken)));
end;
{ TJwtHelper }
class function TJwtHelper.DecodePayload(AToken: string): string;
begin
if Trim(AToken) = '' then
Exit('');
Result := '';
asm
const parts = AToken.split('.');
if (parts.length === 3) { // <- strict compare
// JWTs use url-safe base64; convert before atob
Result = atob(parts[1].replace(/-/g,'+').replace(/_/g,'/'));
}
end;
end;
class function TJwtHelper.HasExpirationDate(AToken: string): Boolean;
var
Payload: string;
Obj: TJSObject;
begin
Payload := DecodePayload(AToken);
Obj := TJSObject(TJSJSON.parse(Payload));
Result := Obj.hasOwnProperty('exp');
end;
class function TJwtHelper.TokenExpirationDate(AToken: string): TJSDate;
var
Payload: string;
Obj: TJSObject;
Epoch: NativeInt;
begin
if not HasExpirationDate(AToken) then
raise Exception.Create('Token has no expiration date');
Payload := DecodePayload(AToken);
Obj := TJSObject(TJSJSON.parse(Payload));
Epoch := toInteger(Obj.Properties['exp']);
Result := TJSDate.New(Epoch * 1000);
end;
class function TJwtHelper.TokenExpired(AToken: string): Boolean;
begin
if not HasExpirationDate(AToken) then
Exit(False);
Result := TJSDate.now > toInteger(TokenExpirationDate(AToken).valueOf);
end;
end.
object DMConnection: TDMConnection
Height = 264
Width = 395
object ApiConnection: TXDataWebConnection
URL = 'http://localhost:2004/emsys/emt3/api'
OnError = ApiConnectionError
OnRequest = ApiConnectionRequest
OnResponse = ApiConnectionResponse
Left = 48
Top = 80
end
object AuthConnection: TXDataWebConnection
URL = 'http://localhost:2004/emsys/emt3/auth'
OnError = AuthConnectionError
Left = 48
Top = 16
end
object XDataWebClient1: TXDataWebClient
Connection = AuthConnection
Left = 269
Top = 164
end
end
unit ConnectionModule;
interface
uses
System.SysUtils, System.Classes, WEBLib.Modules, XData.Web.Connection,
App.Types, App.Config, XData.Web.Client, WEBLib.Dialogs;
type
TDMConnection = class(TWebDataModule)
ApiConnection: TXDataWebConnection;
AuthConnection: TXDataWebConnection;
XDataWebClient1: TXDataWebClient;
procedure ApiConnectionError(Error: TXDataWebConnectionError);
procedure ApiConnectionRequest(Args: TXDataWebConnectionRequest);
procedure ApiConnectionResponse(Args: TXDataWebConnectionResponse);
procedure AuthConnectionError(Error: TXDataWebConnectionError);
private
FUnauthorizedAccessProc: TUnauthorizedAccessProc;
public
const clientVersion = '0.7.1';
procedure InitApp(SuccessProc: TSuccessProc;
UnauthorizedAccessProc: TUnauthorizedAccessProc);
procedure SetClientConfig(Callback: TVersionCheckCallback);
end;
var
DMConnection: TDMConnection;
implementation
uses
JS, Web,
XData.Web.Request,
XData.Web.Response,
Auth.Service;
{%CLASSGROUP 'Vcl.Controls.TControl'}
{$R *.dfm}
procedure TDMConnection.ApiConnectionError(Error: TXDataWebConnectionError);
var
errorMsg: string;
begin
errorMsg := Error.ErrorMessage;
if errorMsg = '' then
errorMsg := 'Connection error';
if Assigned(FUnauthorizedAccessProc) then
FUnauthorizedAccessProc(errorMsg)
else
ShowMessage(errorMsg);
end;
procedure TDMConnection.ApiConnectionRequest(Args: TXDataWebConnectionRequest);
begin
if AuthService.Authenticated then
Args.Request.Headers.SetValue('Authorization', 'Bearer ' + AuthService.GetToken);
end;
procedure TDMConnection.ApiConnectionResponse(
Args: TXDataWebConnectionResponse);
begin
if Args.Response.StatusCode = 401 then
FUnauthorizedAccessProc(Format('%d: %s',[Args.Response.StatusCode, Args.Response.ContentAsText]));
end;
procedure TDMConnection.AuthConnectionError(Error: TXDataWebConnectionError);
var
errorMsg: string;
begin
errorMsg := Error.ErrorMessage;
if errorMsg = '' then
errorMsg := 'Connection error';
if Assigned(FUnauthorizedAccessProc) then
FUnauthorizedAccessProc(errorMsg)
else
ShowMessage(errorMsg);
end;
procedure TDMConnection.InitApp(SuccessProc: TSuccessProc;
UnauthorizedAccessProc: TUnauthorizedAccessProc);
procedure ConfigLoaded(Config: TAppConfig);
begin
if Config.AuthUrl <> '' then
AuthConnection.URL := Config.AuthUrl;
if Config.ApiUrl <> '' then
ApiConnection.URL := Config.ApiUrl;
AuthConnection.Open(SuccessProc);
end;
begin
FUnauthorizedAccessProc := UnauthorizedAccessProc;
LoadConfig(@ConfigLoaded);
end;
procedure TDMConnection.SetClientConfig(Callback: TVersionCheckCallback);
begin
XDataWebClient1.Connection := AuthConnection;
XDataWebClient1.RawInvoke('IAuthService.VerifyVersion', [clientVersion],
procedure(Response: TXDataClientResponse)
var
jsonResult: TJSObject;
error: string;
begin
jsonResult := TJSObject(Response.Result);
if jsonResult.HasOwnProperty('error') then
error := string(jsonResult['error'])
else
error := '';
if error <> '' then
Callback(False, error)
else
Callback(True, '');
end);
end;
end.
unit Utils;
interface
uses
System.Classes, SysUtils, JS, Web, WEBLib.Forms, WEBLib.Toast, DateUtils, WebLib.Dialogs;
procedure ShowStatusMessage(const AMessage, AClass: string; const AElementId: string);
procedure HideStatusMessage(const AElementId: string);
procedure ShowSpinner(SpinnerID: string);
procedure HideSpinner(SpinnerID: string);
procedure ShowErrorModal(msg: string);
function CalculateAge(DateOfBirth: TDateTime): Integer;
function FormatPhoneNumber(PhoneNumber: string): string;
procedure ApplyReportTitle(CurrentReportType: string);
procedure ShowToast(const MessageText: string; const ToastType: string = 'success');
procedure ShowConfirmationModal(msg, leftLabel, rightLabel: string; ConfirmProc: TProc<Boolean>);
procedure ShowNotificationModal(msg: string);
// function FormatDollarValue(ValueStr: string): string;
implementation
procedure ShowStatusMessage(const AMessage, AClass: string; const AElementId: string);
var
StatusMessage: TJSHTMLElement;
begin
StatusMessage := TJSHTMLElement(document.getElementById(AElementId));
if Assigned(StatusMessage) then
begin
if AMessage = '' then
begin
StatusMessage.style.setProperty('display', 'none');
StatusMessage.className := '';
StatusMessage.innerHTML := '';
end
else
begin
StatusMessage.innerHTML := AMessage;
StatusMessage.className := 'alert ' + AClass;
StatusMessage.style.setProperty('display', 'block');
end
end
else
console.log('Error: Status message element not found');
end;
procedure HideStatusMessage(const AElementId: string);
var
StatusMessage: TJSHTMLElement;
begin
StatusMessage := TJSHTMLElement(document.getElementById(AElementId));
if Assigned(StatusMessage) then
begin
StatusMessage.style.setProperty('display', 'none');
StatusMessage.className := '';
StatusMessage.innerHTML := '';
end
else
console.log('Error: Status message element not found');
end;
procedure ShowSpinner(SpinnerID: string);
var
SpinnerElement: TJSHTMLElement;
begin
SpinnerElement := TJSHTMLElement(document.getElementById(SpinnerID));
if Assigned(SpinnerElement) then
begin
// Move spinner to the <body> if it's not already there
asm
if (SpinnerElement.parentNode !== document.body) {
document.body.appendChild(SpinnerElement);
}
end;
SpinnerElement.classList.remove('d-none');
SpinnerElement.classList.add('d-block');
end;
end;
procedure HideSpinner(SpinnerID: string);
var
SpinnerElement: TJSHTMLElement;
begin
SpinnerElement := TJSHTMLElement(document.getElementById(SpinnerID));
if Assigned(SpinnerElement) then
begin
SpinnerElement.classList.remove('d-block');
SpinnerElement.classList.add('d-none');
end;
end;
procedure ShowErrorModal(msg: string);
begin
asm
var modal = document.getElementById('main_errormodal');
var label = document.getElementById('main_lblmodal_body');
var reloadBtn = document.getElementById('btn_modal_restart');
if (label) label.innerText = msg;
// Ensure modal is a direct child of <body>
if (modal && modal.parentNode !== document.body) {
document.body.appendChild(modal);
}
// Bind hard reload to button
if (reloadBtn) {
reloadBtn.onclick = function () {
window.location.reload(true); // hard reload, bypass cache
};
}
// Show the Bootstrap modal
var bsModal = new bootstrap.Modal(modal, { keyboard: false });
bsModal.show();
end;
end;
procedure ShowNotificationModal(msg: string);
begin
asm
var modal = document.getElementById('main_notification_modal');
var label = document.getElementById('main_notification_modal_body');
var closeBtn = document.getElementById('btn_modal_close');
if (label) label.innerText = msg;
// Ensure modal is a direct child of <body>
if (modal && modal.parentNode !== document.body) {
document.body.appendChild(modal);
}
// Button simply closes the modal
if (closeBtn) {
closeBtn.onclick = function () {
var existing = bootstrap.Modal.getInstance(modal);
if (existing) {
existing.hide();
}
};
}
// Show the Bootstrap modal
var bsModal = new bootstrap.Modal(modal, { keyboard: false });
bsModal.show();
end;
end;
// ShowConfirmationModal displays a two-button modal with custom labels.
// Params:
// - messageText: text shown in the modal body
// - leftButtonText: label for the left button (e.g., "Cancel")
// - rightButtonText: label for the right button (e.g., "Delete")
// - callback: procedure(confirmed: Boolean); confirmed = True if right button clicked
//
// Example:
// ShowConfirmationModal('Delete this?', 'Cancel', 'Delete',
// procedure(confirmed: Boolean)
// begin
// if confirmed then DeleteOrder();
// end);
// function ShowConfirmationModal(msg, leftLabel, rightLabel: string;): Boolean;
// if ShowConfirmationModal then
// doThing()
// else
// doOtherThing();
procedure ShowConfirmationModal(msg, leftLabel, rightLabel: string; ConfirmProc: TProc<Boolean>);
begin
asm
var modal = document.getElementById('main_confirmation_modal');
var body = document.getElementById('main_modal_body');
var btnLeft = document.getElementById('btn_confirm_left');
var btnRight = document.getElementById('btn_confirm_right');
var bsModal;
if (body) body.innerText = msg;
if (btnLeft) btnLeft.innerText = leftLabel;
if (btnRight) btnRight.innerText = rightLabel;
if (modal && modal.parentNode !== document.body) {
document.body.appendChild(modal);
}
btnLeft.onclick = null;
btnRight.onclick = null;
btnLeft.onclick = function () {
bsModal.hide();
ConfirmProc(true); // user confirmed
};
btnRight.onclick = function () {
bsModal.hide();
ConfirmProc(false); // user canceled
};
bsModal = new bootstrap.Modal(modal, { keyboard: false });
bsModal.show();
end;
end;
function CalculateAge(DateOfBirth: TDateTime): Integer;
var
Today, BirthDate: TJSDate;
Year, Month, Day, BirthYear, BirthMonth, BirthDay: NativeInt;
DOBString: string;
begin
Today := TJSDate.New;
Year := Today.FullYear;
Month := Today.Month + 1;
Day := Today.Date;
// Formats the DateOfBirth as an ISO 8601 date string
DOBString := FormatDateTime('yyyy-mm-dd', DateOfBirth);
BirthDate := TJSDate.New(DOBString);
if BirthDate = nil then
begin
Exit(0); // Exit the function with an age of 0 if the date creation fails
end;
BirthYear := BirthDate.FullYear;
BirthMonth := BirthDate.Month + 1;
BirthDay := BirthDate.Date;
Result := Year - BirthYear;
if (Month < BirthMonth) or ((Month = BirthMonth) and (Day < BirthDay)) then
Dec(Result);
end;
function FormatPhoneNumber(PhoneNumber: string): string;
var
Digits: string;
begin
Digits := PhoneNumber.Replace('(', '').Replace(')', '').Replace('-', '').Replace(' ', '');
case Length(Digits) of
7: Result := Format('%s-%s', [Copy(Digits, 1, 3), Copy(Digits, 4, 4)]);
10: Result := Format('(%s) %s-%s', [Copy(Digits, 1, 3), Copy(Digits, 4, 3), Copy(Digits, 7, 4)]);
else
// If the number does not have 7 or 10 digits, whatever they typed is returned
Result := PhoneNumber;
end;
end;
procedure ShowToast(const MessageText: string; const ToastType: string = 'success');
var
ParsedText, ToastKind, MsgPrefix: string;
Parts: TArray<string>;
begin
ParsedText := MessageText.Trim;
ToastKind := ToastType.ToLower;
// Check for "Success:" or "Failure:" at the start of message
if ParsedText.Contains(':') then
begin
Parts := ParsedText.Split([':'], 2);
MsgPrefix := Parts[0].Trim.ToLower;
if (MsgPrefix = 'success') or (MsgPrefix = 'failure') then
begin
ParsedText := Parts[1].Trim;
if MsgPrefix = 'success' then
ToastKind := 'success'
else
ToastKind := 'danger';
end;
end;
asm
var toastEl = document.getElementById('bootstrapToast');
var toastBody = document.getElementById('bootstrapToastBody');
if (!toastEl || !toastBody) return;
toastBody.innerText = ParsedText;
toastEl.classList.remove('bg-success', 'bg-danger', 'bg-warning', 'bg-primary');
toastEl.classList.remove('slide-in');
switch (ToastKind) {
case 'danger':
toastEl.classList.add('bg-danger');
break;
case 'warning':
toastEl.classList.add('bg-warning');
break;
case 'info':
toastEl.classList.add('bg-primary');
break;
default:
toastEl.classList.add('bg-success');
}
// Add slide-in animation
toastEl.classList.add('slide-in');
var toast = new bootstrap.Toast(toastEl, { delay: 2500 });
toast.show();
// Remove animation class after it's done (so it can be reapplied)
setTimeout(function() {
toastEl.classList.remove('slide-in');
}, 500);
end;
end;
procedure ApplyReportTitle(CurrentReportType: string);
var
CrimeTitleElement: TJSHTMLElement;
begin
CrimeTitleElement := TJSHTMLElement(document.getElementById('crime_title'));
if Assigned(CrimeTitleElement) then
CrimeTitleElement.innerText := CurrentReportType
else
Console.Log('Element with ID "crime_title" not found.');
end;
// Used html number input type to restrict the input instead of this function
// function FormatDollarValue(ValueStr: string): string;
// var
// i: Integer;
// begin
// Result := ''; // Initialize the result
// // Filter out any characters that are not digits or decimal point
// for i := 1 to Length(ValueStr) do
// begin
// if (Pos(ValueStr[i], '0123456789.') > 0) then
// begin
// Result := Result + ValueStr[i];
// end;
// end;
// end;
end.
object FViewMain: TFViewMain
Width = 1322
Height = 764
CSSLibrary = cssBootstrap
ElementFont = efCSS
Font.Charset = ANSI_CHARSET
Font.Color = clBlack
Font.Height = -11
Font.Name = 'Arial'
Font.Style = []
ParentFont = False
OnCreate = WebFormCreate
object lblUsername: TWebLabel
Left = 536
Top = 4
Width = 49
Height = 14
Caption = 'Username'
ElementID = 'lbl_username'
ElementPosition = epRelative
HeightPercent = 100.000000000000000000
Transparent = False
WidthPercent = 100.000000000000000000
end
object lblUserProfile: TWebLinkLabel
Left = 529
Top = 21
Width = 59
Height = 14
ElementID = 'lbl_user_profile'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
Caption = ' User Profile'
end
object lblLogout: TWebLinkLabel
Left = 547
Top = 55
Width = 36
Height = 14
ElementID = 'lbl_logout'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = lblLogoutClick
Caption = ' Logout'
end
object lblHome: TWebLinkLabel
Left = 556
Top = 38
Width = 27
Height = 14
ElementID = 'lbl_home'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
Caption = 'Home'
end
object lblAppTitle: TWebLabel
Left = 57
Top = 33
Width = 42
Height = 14
Caption = 'App Title'
ElementID = 'lbl_app_title'
ElementPosition = epRelative
HeightPercent = 100.000000000000000000
Transparent = False
WidthPercent = 100.000000000000000000
end
object lblVersion: TWebLabel
Left = 536
Top = 71
Width = 47
Height = 14
Caption = 'lblVersion'
ElementID = 'lbl_version'
ElementFont = efCSS
ElementPosition = epRelative
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object pnlMain: TWebPanel
Left = 62
Top = 92
Width = 393
Height = 219
ElementID = 'pnl_main'
HeightStyle = ssAuto
WidthStyle = ssAuto
ChildOrder = 3
ElementFont = efCSS
ElementPosition = epIgnore
Font.Charset = ANSI_CHARSET
Font.Color = clBlack
Font.Height = -11
Font.Name = 'Arial'
Font.Style = []
ParentFont = False
Role = 'null'
TabOrder = 0
end
object memoDebug: TWebMemo
Left = 45
Top = 361
Width = 471
Height = 83
ElementID = 'memo_debug'
ElementPosition = epRelative
Enabled = False
HeightPercent = 100.000000000000000000
Lines.Strings = (
'WebMemo1')
Role = 'null'
SelLength = 0
SelStart = 0
ShowFocus = False
Visible = False
WidthPercent = 100.000000000000000000
end
object xdwcMain: TXDataWebClient
Connection = DMConnection.ApiConnection
Left = 80
Top = 476
end
end
<div id="div_wrapper">
<nav class="navbar navbar-expand-lg bg-body-tertiary border-bottom shadow-sm">
<div class="container-fluid">
<div class="d-flex align-items-center gap-2">
<a id="lbl_app_title" class="navbar-brand fw-semibold" href="index.html">Koehler-Gibson Orders</a>
<span id="lbl_version" class="badge text-bg-light border text-muted fw-normal"></span>
</div>
<div class="collapse navbar-collapse show" id="pnl_navbar_nav_dropdown">
<ul class="navbar-nav ms-auto">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle d-flex align-items-center gap-2" id="lnk_navbar_dropdown_menu_link"
role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa fa-user fa-fw"></i>
<span id="lbl_username" class="fw-semibold">Username</span>
</a>
<ul class="dropdown-menu dropdown-menu-end shadow-sm" aria-labelledby="lnk_navbar_dropdown_menu_link">
<li>
<a class="dropdown-item d-flex align-items-center gap-2" id="lbl_home" href="#">
<i class="fa fa-home fa-fw"></i><span>Home</span>
</a>
</li>
<li>
<a class="dropdown-item d-flex align-items-center gap-2" id="lbl_user_profile" href="#">
<i class="fa fa-user fa-fw"></i><span>User Profile</span>
</a>
</li>
<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item d-flex align-items-center gap-2 text-danger" id="lbl_logout" href="#">
<i class="fa fa-sign-out fa-fw"></i><span>Logout</span>
</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<!-- Toast -->
<div id="pnl_toast_wrapper" class="position-fixed top-0 start-0 mt-5 ms-4"
style="z-index: 1080; min-width: 300px; max-width: 500px;">
<div id="toast_bootstrap" class="toast align-items-center text-white bg-success border-0 shadow" role="alert"
aria-live="assertive" aria-atomic="true">
<div class="d-flex">
<div class="toast-body" id="lbl_bootstrap_toast_body">
Success message
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"
aria-label="Close"></button>
</div>
</div>
</div>
<!-- Main Panel (where all forms display) -->
<div class="container-fluid py-3">
<div class="row">
<div id="pnl_main" class="col-12"></div>
</div>
<div class="row mt-3">
<div class="col-12">
<textarea class="form-control font-monospace" id="memo_debug" rows="4" placeholder="Debug output..."></textarea>
</div>
</div>
</div>
<!-- Spinner Modal -->
<div id="div_spinner" class="position-absolute top-50 start-50 translate-middle d-none">
<div class="lds-roller">
<div></div><div></div><div></div><div></div>
<div></div><div></div><div></div><div></div>
</div>
</div>
<!-- Error Modal -->
<div class="modal fade" id="mdl_error" tabindex="-1" aria-labelledby="lbl_modal_title" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content shadow-lg">
<div class="modal-header">
<h5 class="modal-title" id="lbl_modal_title">Error</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body fs-6 fw-bold" id="lbl_modal_body">
Please contact EMSystems to solve the issue.
</div>
<div class="modal-footer justify-content-center">
<button type="button" id="btn_modal_restart" class="btn btn-primary">Back to Orders</button>
</div>
</div>
</div>
</div>
<!-- Confirmation Modal -->
<div class="modal fade" id="mdl_confirmation" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content shadow-lg">
<div class="modal-header">
<h5 class="modal-title">Confirm</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body fw-bold" id="lbl_confirmation_body">
Placeholder text
</div>
<div class="modal-footer justify-content-center">
<button type="button" class="btn btn-primary me-3" id="btn_confirm_left">Cancel</button>
<button type="button" class="btn btn-secondary" id="btn_confirm_right">Confirm</button>
</div>
</div>
</div>
</div>
<!-- Notification Modal -->
<div class="modal fade" id="mdl_notification" tabindex="-1" aria-labelledby="lbl_notification_title"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content shadow-lg">
<div class="modal-header">
<h5 class="modal-title" id="lbl_notification_title">Info</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body fs-6 fw-bold" id="lbl_notification_body">
Please contact EMSystems to solve the issue.
</div>
<div class="modal-footer justify-content-center">
<button type="button" id="btn_modal_close" class="btn btn-primary">Close</button>
</div>
</div>
</div>
</div>
</div>
unit View.Main;
interface
uses
System.SysUtils, System.Classes, JS, Web,
WEBLib.Controls, WEBLib.Forms, WEBLib.ExtCtrls, WEBLib.StdCtrls,
App.Types, ConnectionModule, XData.Web.Client, WEBLib.Dialogs, Vcl.StdCtrls,
Vcl.Controls, Vcl.Graphics;
type
TFViewMain = class(TWebForm)
pnlMain: TWebPanel;
lblUsername: TWebLabel;
lblUserProfile: TWebLinkLabel;
lblHome: TWebLinkLabel;
lblLogout: TWebLinkLabel;
lblVersion: TWebLabel;
lblAppTitle: TWebLabel;
memoDebug: TWebMemo;
xdwcMain: TXDataWebClient;
procedure WebFormCreate(Sender: TObject);
procedure lblLogoutClick(Sender: TObject);
private
FChildForm: TWebForm;
FLogoutProc: TLogoutProc;
procedure ShowForm(aFormClass: TWebFormClass);
public
class procedure Display(logoutProc: TLogoutProc);
end;
var
FViewMain: TFViewMain;
implementation
uses
Auth.Service,
View.Test,
View.TasksHTML;
{$R *.dfm}
procedure TFViewMain.WebFormCreate(Sender: TObject);
var
userName: string;
begin
userName := JS.toString(AuthService.TokenPayload.Properties['user_name']);
lblUsername.Caption := userName;
lblVersion.Caption := 'v' + DMConnection.clientVersion;
ShowForm(TFTasksHTML);
end;
procedure TFViewMain.lblLogoutClick(Sender: TObject);
begin
if Assigned(FLogoutProc) then
FLogoutProc('');
end;
procedure TFViewMain.ShowForm(aFormClass: TWebFormClass);
begin
if Assigned(FChildForm) then
FChildForm.Free;
Application.CreateForm(aFormClass, pnlMain.ElementID, FChildForm);
end;
class procedure TFViewMain.Display(logoutProc: TLogoutProc);
begin
if Assigned(FViewMain) then
FViewMain.Free;
FViewMain := TFViewMain.CreateNew;
FViewMain.FLogoutProc := logoutProc;
end;
end.
object FTasksTabulator: TFTasksTabulator object FTasksHTML: TFTasksHTML
Width = 640 Width = 640
Height = 480 Height = 480
CSSLibrary = cssBootstrap
ElementFont = efCSS
OnCreate = WebFormCreate
object btnReload: TWebButton
Left = 78
Top = 88
Width = 96
Height = 25
Caption = 'Reload'
ElementID = 'btn_reload'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnReloadClick
end
object btnAddRow: TWebButton
Left = 78
Top = 119
Width = 96
Height = 25
Caption = 'Add Row'
ChildOrder = 1
ElementID = 'btn_add_row'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnAddRowClick
end
object xdwcTasks: TXDataWebClient object xdwcTasks: TXDataWebClient
Connection = DMConnection.ApiConnection Connection = DMConnection.ApiConnection
Left = 506 Left = 506
Top = 92 Top = 92
end end
object xdwdsTasks: TXDataWebDataSet object xdwdsTasks: TXDataWebDataSet
Left = 468 Left = 506
Top = 182 Top = 148
object xdwdsTaskstaskID: TStringField object xdwdsTaskstaskID: TStringField
FieldName = 'taskId' FieldName = 'taskId'
end end
object xdwdsTasksitemNum: TIntegerField
FieldName = 'itemNum'
end
object xdwdsTasksapplication: TStringField object xdwdsTasksapplication: TStringField
FieldName = 'application' FieldName = 'application'
end end
...@@ -42,5 +71,8 @@ object FTasksTabulator: TFTasksTabulator ...@@ -42,5 +71,8 @@ object FTasksTabulator: TFTasksTabulator
object xdwdsTasksnotes: TStringField object xdwdsTasksnotes: TStringField
FieldName = 'notes' FieldName = 'notes'
end end
object xdwdsTaskstaskItemId: TStringField
FieldName = 'taskItemId'
end
end end
end end
...@@ -7,7 +7,8 @@ ...@@ -7,7 +7,8 @@
</div> </div>
</div> </div>
<div id="data_grid_tasks" class="flex-grow-1 min-vh-0"></div> <div id="tasks_table_host" class="flex-grow-1 min-vh-0"></div>
</div> </div>
unit View.TasksHTML;
interface
uses
System.SysUtils, System.Classes,
JS, Web, WEBLib.Graphics, WEBLib.Controls, WEBLib.Forms, WEBLib.Dialogs,
WEBLib.ExtCtrls,
XData.Web.Client, XData.Web.Dataset,
Utils, Data.DB, XData.Web.JsonDataset, Vcl.Controls, Vcl.StdCtrls,
WEBLib.StdCtrls;
type
TFTasksHTML = class(TWebForm)
xdwcTasks: TXDataWebClient;
xdwdsTasks: TXDataWebDataSet;
xdwdsTaskstaskId: TStringField;
xdwdsTasksapplication: TStringField;
xdwdsTasksversion: TStringField;
xdwdsTaskstaskDate: TStringField;
xdwdsTasksreportedBy: TStringField;
xdwdsTasksassignedTo: TStringField;
xdwdsTasksstatus: TStringField;
xdwdsTasksstatusDate: TStringField;
xdwdsTasksformSection: TStringField;
xdwdsTasksissue: TStringField;
xdwdsTasksnotes: TStringField;
btnReload: TWebButton;
btnAddRow: TWebButton;
xdwdsTaskstaskItemId: TStringField;
xdwdsTasksitemNum: TIntegerField;
procedure btnAddRowClick(Sender: TObject);
procedure btnReloadClick(Sender: TObject);
procedure WebFormCreate(Sender: TObject);
private
FTaskId: string;
[async] procedure LoadTasks(const ATaskId: string);
procedure RenderTable;
procedure BindTableEditors;
procedure EditorInput(Event: TJSEvent);
procedure SelectChange(Event: TJSEvent);
procedure EnableColumnResize;
procedure EnableAutoGrowTextAreas;
procedure GotoRowIndex(AIndex: Integer);
function HtmlEncode(const s: string): string;
procedure SetTaskLabel(const ATitle: string);
[async] procedure SaveRow(AIndex: Integer);
procedure EditorBlur(Event: TJSEvent);
procedure InitializeForm;
public
class function CreateForm(AElementID, ATaskId: string): TWebForm;
end;
var
FTasksHTML: TFTasksHTML;
implementation
uses
ConnectionModule;
{$R *.dfm}
class function TFTasksHTML.CreateForm(AElementID, ATaskId: string): TWebForm;
begin
console.log('TFTasksHTML.CreateForm called, host=' + AElementID + ', taskId=' + ATaskId);
Application.CreateForm(TFTasksHTML, AElementID, Result,
procedure(AForm: TObject)
begin
console.log('TFTasksHTML.CreateForm callback fired, assigned=' + BoolToStr(Assigned(AForm), True));
if Assigned(AForm) then
begin
TFTasksHTML(AForm).FTaskId := ATaskId;
TFTasksHTML(AForm).InitializeForm;
end
else
Utils.ShowErrorModal('TFTasksHTML form callback returned nil.');
end
);
end;
procedure TFTasksHTML.InitializeForm;
begin
console.log('TFTasksHTML.InitializeForm fired');
console.log('The task id is: ' + FTaskId);
if FTaskId = '' then
begin
Utils.ShowErrorModal('Missing task_id. Please reopen from emt3.');
Exit;
end;
btnAddRow.Enabled := False;
if not DMConnection.ApiConnection.Connected then
begin
DMConnection.ApiConnection.Open(
procedure
begin
LoadTasks(FTaskId);
end
);
end
else
LoadTasks(FTaskId);
end;
function TFTasksHTML.HtmlEncode(const s: string): string;
begin
Result := s;
Result := StringReplace(Result, '&', '&amp;', [rfReplaceAll]);
Result := StringReplace(Result, '<', '&lt;', [rfReplaceAll]);
Result := StringReplace(Result, '>', '&gt;', [rfReplaceAll]);
Result := StringReplace(Result, '"', '&quot;', [rfReplaceAll]);
Result := StringReplace(Result, '''', '&#39;', [rfReplaceAll]);
end;
procedure TFTasksHTML.GotoRowIndex(AIndex: Integer);
var
i: Integer;
begin
if (AIndex < 0) or (not xdwdsTasks.Active) then
Exit;
xdwdsTasks.First;
i := 0;
while (i < AIndex) and (not xdwdsTasks.Eof) do
begin
xdwdsTasks.Next;
Inc(i);
end;
end;
procedure TFTasksHTML.EditorInput(Event: TJSEvent);
var
el: TJSHTMLElement;
idx: Integer;
idxStr, fieldName: string;
newVal: string;
begin
if not xdwdsTasks.Active then
Exit;
el := TJSHTMLElement(Event.target);
idxStr := string(el.getAttribute('data-idx'));
fieldName := string(el.getAttribute('data-field'));
idx := StrToIntDef(idxStr, -1);
if (idx < 0) or (fieldName = '') then
Exit;
GotoRowIndex(idx);
if xdwdsTasks.Eof then
Exit;
newVal := string(TJSObject(el)['value']);
console.log('EditorInput: idx=' + IntToStr(idx) + ' field=' + fieldName + ' val=' + newVal);
xdwdsTasks.Edit;
xdwdsTasks.FieldByName(fieldName).AsString := newVal;
xdwdsTasks.Post;
el.setAttribute('data-unsaved-data', '1');
end;
procedure TFTasksHTML.SelectChange(Event: TJSEvent);
var
el: TJSHTMLElement;
idx: Integer;
idxStr, fieldName: string;
sel: TJSHTMLSelectElement;
begin
if not xdwdsTasks.Active then
Exit;
el := TJSHTMLElement(Event.target);
idxStr := string(el.getAttribute('data-idx'));
fieldName := string(el.getAttribute('data-field'));
idx := StrToIntDef(idxStr, -1);
if (idx < 0) or (fieldName = '') then
Exit;
GotoRowIndex(idx);
if xdwdsTasks.Eof then
Exit;
sel := TJSHTMLSelectElement(el);
console.log('SelectChange: idx=' + IntToStr(idx) + ' field=' + fieldName + ' val=' + string(sel.value));
xdwdsTasks.Edit;
xdwdsTasks.FieldByName(fieldName).AsString := string(sel.value);
xdwdsTasks.Post;
el.setAttribute('data-unsaved-data', '1');
SaveRow(idx);
end;
procedure TFTasksHTML.BindTableEditors;
var
nodes: TJSNodeList;
i: Integer;
el: TJSHTMLElement;
begin
console.log('BindTableEditors: wiring handlers...');
nodes := document.querySelectorAll('.task-editor');
console.log('BindTableEditors: task-editor count=' + IntToStr(nodes.length));
for i := 0 to nodes.length - 1 do
begin
el := TJSHTMLElement(nodes.item(i));
el.addEventListener('input', TJSEventHandler(@EditorInput));
el.addEventListener('blur', TJSEventHandler(@EditorBlur));
end;
nodes := document.querySelectorAll('.task-select');
console.log('BindTableEditors: task-select count=' + IntToStr(nodes.length));
for i := 0 to nodes.length - 1 do
begin
el := TJSHTMLElement(nodes.item(i));
el.addEventListener('change', TJSEventHandler(@SelectChange));
end;
end;
procedure TFTasksHTML.btnAddRowClick(Sender: TObject);
begin
Utils.ShowErrorModal('Add row is not enabled yet.');
end;
procedure TFTasksHTML.btnReloadClick(Sender: TObject);
begin
if FTaskId = '' then
begin
Utils.ShowErrorModal('Missing Task Id. Update url params or resend from emT3.');
Exit;
end;
LoadTasks(FTaskId);
end;
procedure TFTasksHTML.EnableAutoGrowTextAreas;
begin
asm
(function(){
const host = document.getElementById('tasks_table_host');
if(!host) return;
host.querySelectorAll('textarea.cell-textarea').forEach(ta => {
const fit = () => { ta.style.height = 'auto'; ta.style.height = ta.scrollHeight + 'px'; };
fit();
ta.addEventListener('input', fit);
});
})();
end;
end;
procedure TFTasksHTML.SetTaskLabel(const ATitle: string);
var
el: TJSHTMLElement;
begin
el := TJSHTMLElement(document.getElementById('lbl_project_name'));
if Assigned(el) then
el.innerText := ATitle;
end;
procedure TFTasksHTML.WebFormCreate(Sender: TObject);
begin
console.log('TFTasksHTML.WebFormCreate fired');
FTaskId := Application.Parameters.Values['task_id'];
if FTaskId = '' then
begin
Utils.ShowErrorModal('Missing task_id. Please reopen from emt3.');
Exit;
end;
btnAddRow.Enabled := False;
if DMConnection.ApiConnection.Connected then
begin
LoadTasks(FTaskId);
Exit;
end;
DMConnection.ApiConnection.Open(
procedure
begin
LoadTasks(FTaskId);
end
);
end;
[async] procedure TFTasksHTML.LoadTasks(const ATaskId: string);
var
response: TXDataClientResponse;
resultObj, taskObj: TJSObject;
itemsArray: TJSArray;
titleText: string;
begin
console.log('IApiService.GetTaskItems called with task_id: ' + ATaskId);
console.log('Load Tasks Fired');
Utils.ShowSpinner('spinner');
try
try
response := await(xdwcTasks.RawInvokeAsync(
'IApiService.GetTaskItems', [ATaskId]
));
except
on E: EXDataClientRequestException do
begin
Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
Exit;
end;
end;
if not Assigned(response.Result) then
Exit;
resultObj := TJSObject(response.Result);
taskObj := TJSObject(resultObj['task']);
if Assigned(taskObj) then
titleText := string(taskObj['title'])
else
titleText := 'Task - ' + ATaskId;
SetTaskLabel(titleText);
itemsArray := TJSArray(resultObj['items']);
if not Assigned(itemsArray) then
itemsArray := TJSArray.new;
xdwdsTasks.Close;
xdwdsTasks.SetJsonData(itemsArray);
xdwdsTasks.Open;
RenderTable;
finally
Utils.HideSpinner('spinner');
end;
end;
procedure TFTasksHTML.RenderTable;
var
host: TJSHTMLElement;
html: string;
rowIdx: Integer;
function Th(const s: string): string;
begin
Result := '<th scope="col">' + s + '</th>';
end;
function ThBlank: string;
begin
Result := '<th scope="col">&nbsp;</th>';
end;
function TdNowrap(const s: string): string;
begin
Result := '<td class="align-top nowrap-cell">' + s + '</td>';
end;
function TdWrap(const s: string): string;
begin
Result := '<td class="align-top wrap-cell">' + s + '</td>';
end;
function TextInput(const FieldName, Value: string; const AIdx: Integer; const MinWidth: Integer = 0): string;
var
w: string;
begin
w := '';
if MinWidth > 0 then
w := ' style="min-width: ' + IntToStr(MinWidth) + 'px;"';
Result :=
'<input class="form-control form-control-sm cell-input task-editor w-100" ' +
'data-idx="' + IntToStr(AIdx) + '" data-field="' + FieldName + '" ' +
'value="' + HtmlEncode(Value) + '"' + w + '>';
end;
function TextArea(const FieldName, Value: string; const AIdx: Integer): string;
begin
Result :=
'<textarea class="form-control form-control-sm cell-textarea task-editor w-100" ' +
'data-idx="' + IntToStr(AIdx) + '" data-field="' + FieldName + '" ' +
'rows="2">' + HtmlEncode(Value) + '</textarea>';
end;
function RowNumCell(const Value: Integer): string;
begin
Result :=
'<div class="form-control form-control-sm text-center bg-light-subtle">' +
IntToStr(Value) +
'</div>';
end;
function DateInput(const FieldName, Value: string; const AIdx: Integer; const MinWidth: Integer = 0): string;
var
w: string;
begin
w := '';
if MinWidth > 0 then
w := ' style="min-width: ' + IntToStr(MinWidth) + 'px;"';
Result :=
'<input type="date" class="form-control form-control-sm cell-input task-editor w-100" ' +
'data-idx="' + IntToStr(AIdx) + '" data-field="' + FieldName + '" ' +
'value="' + HtmlEncode(Value) + '"' + w + '>';
end;
function SelectList(const FieldName, Current: string; const AIdx: Integer; const Items: array of string): string;
var
i: Integer;
sel: string;
begin
Result :=
'<select class="form-select form-select-sm task-select" ' +
'data-idx="' + IntToStr(AIdx) + '" data-field="' + FieldName + '">';
sel := '';
if Trim(Current) = '' then
sel := ' selected';
Result := Result + '<option value=""' + sel + '></option>';
for i := Low(Items) to High(Items) do
begin
sel := '';
if SameText(Current, Items[i]) then
sel := ' selected';
Result := Result + '<option value="' + HtmlEncode(Items[i]) + '"' + sel + '>' + HtmlEncode(Items[i]) + '</option>';
end;
Result := Result + '</select>';
end;
function StatusSelect(const Current: string; const AIdx: Integer): string;
const
Statuses: array[0..5] of string = ('Open', 'In Progress', 'Blocked', 'Testing', 'Done', 'Closed');
var
i: Integer;
sel: string;
begin
Result :=
'<select class="form-select form-select-sm task-select" data-idx="' + IntToStr(AIdx) + '" data-field="status">';
for i := Low(Statuses) to High(Statuses) do
begin
sel := '';
if SameText(Current, Statuses[i]) then
sel := ' selected';
Result := Result + '<option value="' + HtmlEncode(Statuses[i]) + '"' + sel + '>' + HtmlEncode(Statuses[i]) + '</option>';
end;
Result := Result + '</select>';
end;
begin
host := TJSHTMLElement(document.getElementById('tasks_table_host'));
if not Assigned(host) then
Exit;
html :=
'<div class="tasks-vscroll">' +
'<div class="tasks-hscroll">' +
'<table class="table table-sm table-bordered table-hover align-middle mb-0">' +
'<colgroup>' +
'<col style="width:40px">' +
'<col style="width:240px">' +
'<col style="width:90px">' +
'<col style="width:120px">' +
'<col style="width:120px">' +
'<col style="width:120px">' +
'<col style="width:140px">' +
'<col style="width:140px">' +
'<col style="width:160px">' +
'<col style="width:520px">' +
'<col style="width:520px">' +
'</colgroup>' +
'<thead><tr>' +
ThBlank +
Th('App') +
Th('Version') +
Th('Date') +
Th('Reported') +
Th('Assigned') +
Th('Status') +
Th('Status Date') +
Th('Form') +
Th('Issue') +
Th('Notes') +
'</tr></thead><tbody>';
rowIdx := 0;
xdwdsTasks.First;
while not xdwdsTasks.Eof do
begin
html := html +
'<tr>' +
TdNowrap(RowNumCell(xdwdsTasksitemNum.AsInteger)) +
TdNowrap(TextInput('application', xdwdsTasksapplication.AsString, rowIdx, 180)) +
TdNowrap(TextInput('version', xdwdsTasksversion.AsString, rowIdx, 80)) +
TdNowrap(DateInput('taskDate', xdwdsTaskstaskDate.AsString, rowIdx, 110)) +
TdNowrap(SelectList('reportedBy', xdwdsTasksreportedBy.AsString, rowIdx, ['Elias','Mac','Mark'])) +
TdNowrap(SelectList('assignedTo', xdwdsTasksassignedTo.AsString, rowIdx, ['Elias','Mac','Mark'])) +
TdNowrap(StatusSelect(xdwdsTasksstatus.AsString, rowIdx)) +
TdNowrap(DateInput('statusDate', xdwdsTasksstatusDate.AsString, rowIdx, 110)) +
TdNowrap(TextInput('formSection', xdwdsTasksformSection.AsString, rowIdx, 160)) +
TdWrap(TextArea('issue', xdwdsTasksissue.AsString, rowIdx)) +
TdWrap(TextArea('notes', xdwdsTasksnotes.AsString, rowIdx)) +
'</tr>';
xdwdsTasks.Next;
Inc(rowIdx);
end;
html := html + '</tbody></table></div></div>';
host.innerHTML := html;
BindTableEditors;
EnableAutoGrowTextAreas;
EnableColumnResize;
end;
procedure TFTasksHTML.EnableColumnResize;
begin
asm
(function(){
const host = document.getElementById('tasks_table_host');
if(!host) return;
const table = host.querySelector('table');
if(!table) return;
const ths = table.querySelectorAll('thead th');
ths.forEach(th => {
th.classList.add('th-resize');
if(th.querySelector('.th-resize-handle')) return;
const handle = document.createElement('div');
handle.className = 'th-resize-handle';
th.appendChild(handle);
handle.addEventListener('mousedown', function(e){
e.preventDefault();
const startX = e.clientX;
const startW = th.getBoundingClientRect().width;
function onMove(ev){
const w = Math.max(40, startW + (ev.clientX - startX));
th.style.width = w + 'px';
}
function onUp(){
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
}
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
});
});
})();
end;
end;
procedure TFTasksHTML.EditorBlur(Event: TJSEvent);
var
el: TJSHTMLElement;
idx: Integer;
idxStr, fieldName: string;
begin
el := TJSHTMLElement(Event.target);
idxStr := string(el.getAttribute('data-idx'));
fieldName := string(el.getAttribute('data-field'));
if string(el.getAttribute('data-unsaved-data')) <> '1' then
begin
console.log('EditorBlur: skip (not unsaved) idx=' + idxStr + ' field=' + fieldName);
Exit;
end;
el.removeAttribute('data-unsaved-data');
idx := StrToIntDef(idxStr, -1);
if idx < 0 then
Exit;
console.log('EditorBlur: SAVE idx=' + IntToStr(idx) + ' field=' + fieldName);
SaveRow(idx);
end;
[async] procedure TFTasksHTML.SaveRow(AIndex: Integer);
const
// Note: Use this to manipulate saving to the server or not for testing
ENABLE_SERVER_SAVE = True;
var
response: TXDataClientResponse;
payload: TJSObject;
payloadJson: string;
begin
if not xdwdsTasks.Active then
Exit;
GotoRowIndex(AIndex);
if xdwdsTasks.Eof then
Exit;
payload := TJSObject.new;
payload['taskItemId'] := xdwdsTaskstaskItemId.AsString;
payload['taskId'] := xdwdsTaskstaskId.AsString;
payload['application'] := xdwdsTasksapplication.AsString;
payload['version'] := xdwdsTasksversion.AsString;
payload['taskDate'] := xdwdsTaskstaskDate.AsString;
payload['reportedBy'] := xdwdsTasksreportedBy.AsString;
payload['assignedTo'] := xdwdsTasksassignedTo.AsString;
payload['status'] := xdwdsTasksstatus.AsString;
payload['statusDate'] := xdwdsTasksstatusDate.AsString;
payload['formSection'] := xdwdsTasksformSection.AsString;
payload['issue'] := xdwdsTasksissue.AsString;
payload['notes'] := xdwdsTasksnotes.AsString;
payloadJson := string(TJSJSON.stringify(payload));
console.log('SaveRow: idx=' + IntToStr(AIndex) + ' payload=' + payloadJson);
if not ENABLE_SERVER_SAVE then
Exit;
try
response := await(xdwcTasks.RawInvokeAsync('IApiService.SaveTaskRow', [payload]));
console.log('SaveRow: response=' + string(TJSJSON.stringify(response.Result)));
except
on E: EXDataClientRequestException do
begin
console.log('SaveRow ERROR: ' + E.ErrorResult.ErrorMessage);
Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
end;
end;
end;
end.
object FTest: TFTest
Width = 640
Height = 480
CSSLibrary = cssBootstrap
ElementFont = efCSS
object lblTest: TWebLabel
Left = 280
Top = 204
Width = 52
Height = 15
Caption = 'Test Form'
ElementID = 'lbl_test'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnTestApi: TWebButton
Left = 266
Top = 240
Width = 96
Height = 25
Caption = 'Test Api'
ChildOrder = 1
ElementID = 'btn_test_api'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnTestApiClick
end
object memoTestDebug: TWebMemo
Left = 224
Top = 286
Width = 185
Height = 89
ElementClassName = 'form-control'
ElementID = 'memo_test_debug'
ElementFont = efCSS
HeightPercent = 100.000000000000000000
Lines.Strings = (
'')
SelLength = 0
SelStart = 2
ShowHint = False
WidthPercent = 100.000000000000000000
OnChange = memoTestDebugChange
end
object xdwcTest: TXDataWebClient
Connection = DMConnection.ApiConnection
Left = 464
Top = 208
end
end
<div class="container py-4">
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-4">
<div>
<div id="lbl_test" class="h2 fw-semibold mb-1">Test Form</div>
<div class="text-muted">Quick API + JWT/version diagnostics</div>
</div>
<button id="btn_test_api" class="btn btn-primary">Test API</button>
</div>
<div class="card border-0 shadow-sm">
<div class="card-header bg-body-tertiary d-flex align-items-center justify-content-between">
<div class="fw-semibold">Debug Output</div>
</div>
<div class="card-body" style="min-height: 70vh;">
<textarea id="memo_test_debug"
class="form-control font-monospace h-100"
style="min-height: 65vh;"
rows="28"
placeholder="Click Test API to populate diagnostics..."></textarea>
</div>
</div>
</div>
unit View.Test;
interface
uses
System.SysUtils, System.Classes, JS, Web,
WEBLib.Graphics, WEBLib.Controls, WEBLib.Forms, WEBLib.StdCtrls,
XData.Web.Client,
App.Types, ConnectionModule, Vcl.StdCtrls, Vcl.Controls;
type
TFTest = class(TWebForm)
lblTest: TWebLabel;
btnTestApi: TWebButton;
memoTestDebug: TWebMemo;
xdwcTest: TXDataWebClient;
[async] procedure btnTestApiClick(Sender: TObject);
procedure memoTestDebugChange(Sender: TObject);
private
procedure AddLine(const s: string);
procedure DumpClaims;
public
end;
var
FTest: TFTest;
implementation
uses
Auth.Service;
procedure TFTest.AddLine(const s: string);
begin
memoTestDebug.Lines.Add(s);
end;
procedure TFTest.DumpClaims;
var
token: string;
payload: TJSObject;
begin
token := AuthService.GetToken;
AddLine('token:' + token);
//add null logic
AddLine('token present: ' + BoolToStr(token <> '', True));
if token = '' then
Exit;
AddLine('token expired: ' + BoolToStr(AuthService.TokenExpired, True));
AddLine('token exp (local): ' + DateTimeToStr(AuthService.TokenExpirationDate));
payload := TJSObject(AuthService.TokenPayload);
if not Assigned(payload) then
Exit;
asm
const p = payload;
const keys = Object.keys(p);
for (let i = 0; i < keys.length; i++) {
const k = keys[i];
const v = p[k];
this.AddLine('claim ' + k + ': ' + String(v));
}
end;
end;
[async] procedure TFTest.btnTestApiClick(Sender: TObject);
var
response: TXDataClientResponse;
resultObj: TJSObject;
messageText: string;
requiredVersion: string;
begin
memoTestDebug.Visible := True;
memoTestDebug.Lines.Clear;
AddLine('--- request ---');
AddLine('api url: ' + DMConnection.ApiConnection.URL);
AddLine('auth url: ' + DMConnection.AuthConnection.URL);
AddLine('clientVersion const: ' + DMConnection.clientVersion);
AddLine('');
AddLine('--- token / claims ---');
DumpClaims;
messageText := 'hello from TFTest @ ' + DateTimeToStr(Now);
AddLine('');
AddLine('calling IApiService.TestApi ...');
try
response := await(xdwcTest.RawInvokeAsync('IApiService.TestApi', [messageText]));
except
on E: Exception do
begin
AddLine('');
AddLine('--- api exception ---');
AddLine(E.Message);
Exit;
end;
end;
AddLine('');
AddLine('--- api response ---');
if not Assigned(response.Result) then
begin
AddLine('response.Result is nil');
Exit;
end;
resultObj := TJSObject(response.Result);
AddLine('messageEcho: ' + JS.toString(resultObj['messageEcho']));
AddLine('serverTime: ' + JS.toString(resultObj['serverTime']));
AddLine('requiredWebClientVersion: ' + JS.toString(resultObj['requiredWebClientVersion']));
AddLine('note: ' + JS.toString(resultObj['note']));
requiredVersion := JS.toString(resultObj['requiredWebClientVersion']);
AddLine('');
AddLine('--- version compare (client-side) ---');
AddLine('clientVersion const: ' + DMConnection.clientVersion);
AddLine('requiredWebClientVersion: ' + requiredVersion);
AddLine('match: ' + BoolToStr(DMConnection.clientVersion = requiredVersion, True));
end;
procedure TFTest.memoTestDebugChange(Sender: TObject);
begin
end;
end.
[Paths]
HtmlPath=C:\Projects\emT3web\emT3XDataServer\bin\static
HtmlFile=index.html
DefaultURL=http://localhost:8000/emT3WebApp
SingleInstance=0
Debug=0
DebugManager=C:\RADTools\TMS\Products\tms.webcore\Bin\Win32\TMSDBGManager.exe
URL=http://localhost:8000/$(ProjectName)
URLParams=?user_id=1019&task_id=3997&url_code=123456
Browser=1
BrowserBin=
BrowserParams=
Electron=0
ElectronBuild=0
JSDebugger=1
{
"AuthUrl" : "http://localhost:2001/emsys/template/auth/",
"ApiUrl" : "http://localhost:2001/emsys/template/api/"
}
{
"AuthUrl" : "http://localhost:2001/emsys/emt3/auth/",
"ApiUrl" : "http://localhost:2001/emsys/emt3/api/"
}
is-invalid .form-check-input {
border: 1px solid #dc3545 !important;
}
.is-invalid .form-check-label {
color: #dc3545 !important;
}
.btn-primary {
background-color: #286090 !important;
border-color: #286090 !important;
color: #fff !important;
}
.btn-primary:hover {
background-color: #204d74 !important;
border-color: #204d74 !important;
}
@keyframes slideInLeft {
from {
transform: translateX(-120%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.toast.slide-in {
animation: slideInLeft 0.4s ease-out forwards;
}
#spinner {
position: fixed !important;
z-index: 9999 !important;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.lds-roller {
display: inline-block;
position: relative;
width: 80px;
height: 80px;
}
.lds-roller div {
animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
transform-origin: 40px 40px;
}
.lds-roller div:after {
content: " ";
display: block;
position: absolute;
width: 10px;
height: 10px;
border-radius: 50%;
background: #204d74;
margin: -5px 0 0 -5px;
}
.lds-roller div:nth-child(1) {
animation-delay: -0.036s;
}
.lds-roller div:nth-child(1):after {
top: 63px;
left: 63px;
}
.lds-roller div:nth-child(2) {
animation-delay: -0.072s;
}
.lds-roller div:nth-child(2):after {
top: 68px;
left: 56px;
}
.lds-roller div:nth-child(3) {
animation-delay: -0.108s;
}
.lds-roller div:nth-child(3):after {
top: 71px;
left: 48px;
}
.lds-roller div:nth-child(4) {
animation-delay: -0.144s;
}
.lds-roller div:nth-child(4):after {
top: 72px;
left: 40px;
}
.lds-roller div:nth-child(5) {
animation-delay: -0.18s;
}
.lds-roller div:nth-child(5):after {
top: 71px;
left: 32px;
}
.lds-roller div:nth-child(6) {
animation-delay: -0.216s;
}
.lds-roller div:nth-child(6):after {
top: 68px;
left: 24px;
}
.lds-roller div:nth-child(7) {
animation-delay: -0.252s;
}
.lds-roller div:nth-child(7):after {
top: 63px;
left: 17px;
}
.lds-roller div:nth-child(8) {
animation-delay: -0.288s;
}
.lds-roller div:nth-child(8):after {
top: 56px;
left: 12px;
}
@keyframes lds-roller {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
program emT3WebApp;
uses
Vcl.Forms,
XData.Web.Connection,
WEBLib.Dialogs,
Auth.Service in 'Auth.Service.pas',
App.Types in 'App.Types.pas',
ConnectionModule in 'ConnectionModule.pas' {DMConnection: TWebDataModule},
App.Config in 'App.Config.pas',
View.Main in 'View.Main.pas' {FViewMain: TWebForm} {*.html},
Utils in 'Utils.pas',
View.Test in 'View.Test.pas' {FTest: TWebForm} {*.html},
View.TasksHTML in 'View.TasksHTML.pas' {FTasksHTML: TWebForm} {*.html};
{$R *.res}
procedure DoLogout(AMsg: string = ''); forward;
procedure DisplayMainView;
procedure ConnectProc;
begin
TFViewMain.Display(@DoLogout);
end;
begin
if not DMConnection.ApiConnection.Connected then
DMConnection.ApiConnection.Open(@ConnectProc)
else
ConnectProc;
end;
procedure Login(userId: string; taskId: string; urlCode: string);
procedure LoginSuccess;
begin
DisplayMainView;
end;
procedure LoginError(AMsg: string);
begin
ShowMessage('Login Error: ' + AMsg);
end;
begin
AuthService.Login( userId, taskId, urlCode,
@LoginSuccess,
@LoginError
);
end;
procedure DoLogin();
var
userIdParam: string;
taskIdParam: string;
codeParam: string;
begin
userIdParam := Application.Parameters.Values['user_id'];
taskIdParam := Application.Parameters.Values['task_id'];
codeParam := Application.Parameters.Values['url_code'];
AuthService.Logout;
DMConnection.ApiConnection.Connected := False;
if Assigned(FViewMain) then
FViewMain.Free;
Login( userIdParam, taskIdParam, codeParam );
end;
procedure DoLogout(AMsg: string);
begin
AuthService.Logout;
ShowMessage('Logout successful: ' + AMsg);
end;
procedure UnauthorizedAccessProc(AMessage: string);
begin
ShowMessage('UnauthorizedAccessProc: ' + AMessage);
end;
procedure StartApplication;
var
ClientVer: string;
begin
ClientVer := TDMConnection.clientVersion;
DMConnection.InitApp(
procedure
begin
DMConnection.SetClientConfig(
procedure(Success: Boolean; ErrorMessage: string)
begin
if Success then
begin
DoLogin();
end
else
begin
asm
var dlg = document.createElement("dialog");
dlg.classList.add("shadow", "rounded", "border", "p-4");
dlg.style.maxWidth = "500px";
dlg.style.width = "90%";
dlg.style.fontFamily = "system-ui, sans-serif";
dlg.innerHTML =
"<h5 class='fw-bold mb-3 text-danger'>kgOrders web app</h5>" +
"<p class='mb-3' style='white-space: pre-wrap;'>" + ErrorMessage + "</p>" +
"<div class='text-end'>" +
"<button id='refreshBtn' class='btn btn-primary'>Reload</button></div>";
document.body.appendChild(dlg);
dlg.showModal();
document.getElementById("refreshBtn").addEventListener("click", function () {
var base = location.origin + location.pathname;
location.replace(base + "?ver=" + ClientVer + "&r=" + Date.now() + location.hash);
});
end;
end;
end);
end,
@UnauthorizedAccessProc
);
end;
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TDMConnection, DMConnection);
StartApplication;
Application.Run;
end.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{DB6F5DBF-7E4B-45DA-AFFA-6C8DF15BA740}</ProjectGuid>
<ProjectVersion>20.3</ProjectVersion>
<FrameworkType>VCL</FrameworkType>
<MainSource>emT3WebApp.dpr</MainSource>
<Base>True</Base>
<Config Condition="'$(Config)'==''">Debug</Config>
<Platform Condition="'$(Platform)'==''">Win32</Platform>
<TargetedPlatforms>1</TargetedPlatforms>
<AppType>Application</AppType>
<ProjectName Condition="'$(ProjectName)'==''">emT3WebApp</ProjectName>
</PropertyGroup>
<PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''">
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Base)'=='true') or '$(Base_Win32)'!=''">
<Base_Win32>true</Base_Win32>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win64' and '$(Base)'=='true') or '$(Base_Win64)'!=''">
<Base_Win64>true</Base_Win64>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="'$(Config)'=='Debug' or '$(Cfg_1)'!=''">
<Cfg_1>true</Cfg_1>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_1)'=='true') or '$(Cfg_1_Win32)'!=''">
<Cfg_1_Win32>true</Cfg_1_Win32>
<CfgParent>Cfg_1</CfgParent>
<Cfg_1>true</Cfg_1>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="'$(Config)'=='Release' or '$(Cfg_2)'!=''">
<Cfg_2>true</Cfg_2>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_2)'=='true') or '$(Cfg_2_Win32)'!=''">
<Cfg_2_Win32>true</Cfg_2_Win32>
<CfgParent>Cfg_2</CfgParent>
<Cfg_2>true</Cfg_2>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="'$(Base)'!=''">
<DCC_DcuOutput>.\$(Platform)\$(Config)</DCC_DcuOutput>
<DCC_ExeOutput>.\$(Platform)\$(Config)</DCC_ExeOutput>
<DCC_E>false</DCC_E>
<DCC_N>false</DCC_N>
<DCC_S>false</DCC_S>
<DCC_F>false</DCC_F>
<DCC_K>false</DCC_K>
<DCC_UsePackage>RESTComponents;emsclientfiredac;DataSnapFireDAC;FireDACIBDriver;xdata;emsclient;FireDACCommon;RESTBackendComponents;soapserver;CloudService;FireDACCommonDriver;inet;FireDAC;FireDACSqliteDriver;soaprtl;soapmidas;aurelius;$(DCC_UsePackage)</DCC_UsePackage>
<DCC_Namespace>System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace)</DCC_Namespace>
<Icon_MainIcon>$(BDS)\bin\delphi_PROJECTICON.ico</Icon_MainIcon>
<Icns_MainIcns>$(BDS)\bin\delphi_PROJECTICNS.icns</Icns_MainIcns>
<SanitizedProjectName>emT3WebApp</SanitizedProjectName>
<VerInfo_Locale>1046</VerInfo_Locale>
<TMSWebProject>2</TMSWebProject>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.802;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;LastCompiledTime=2018/07/25 12:57:53</VerInfo_Keys>
<TMSWebHTMLFile>index.html</TMSWebHTMLFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_Win32)'!=''">
<DCC_UsePackage>DBXSqliteDriver;DataSnapServerMidas;DBXDb2Driver;DBXInterBaseDriver;vclactnband;vclFireDAC;svnui;tethering;FireDACADSDriver;DBXMSSQLDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;svn;Intraweb;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;fmxdae;vclib;FireDACDBXDriver;dbexpress;IndyCore;vclx;dsnap;DataSnapCommon;DataSnapConnectors;VCLRESTComponents;vclie;TMSWEBCorePkgDXE11;bindengine;DBXMySQLDriver;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;DataSnapClient;bindcompdbx;IndyIPCommon;vcl;DBXSybaseASEDriver;IndyIPServer;IndySystem;FireDACDb2Driver;TMSWEBCorePkgLibDXE11;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;TeeDB;emshosting;FireDACPgDriver;ibmonitor;FireDACASADriver;DBXOdbcDriver;FireDACTDataDriver;FMXTee;DbxCommonDriver;ibxpress;Tee;DataSnapServer;xmlrtl;DataSnapNativeClient;fmxobj;vclwinx;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;vcldsnap;bindcomp;appanalytics;DBXInformixDriver;IndyIPClient;bindcompvcl;TeeUI;dbxcds;VclSmp;adortl;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(DCC_UsePackage)</DCC_UsePackage>
<DCC_Namespace>Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)</DCC_Namespace>
<BT_BuildType>Debug</BT_BuildType>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=0.9.5.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=0.9.5.0;Comments=</VerInfo_Keys>
<VerInfo_Locale>1033</VerInfo_Locale>
<Manifest_File>$(BDS)\bin\default_app.manifest</Manifest_File>
<VerInfo_MajorVer>0</VerInfo_MajorVer>
<VerInfo_MinorVer>9</VerInfo_MinorVer>
<VerInfo_Release>5</VerInfo_Release>
<AppDPIAwarenessMode>none</AppDPIAwarenessMode>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_Win64)'!=''">
<DCC_UsePackage>DBXSqliteDriver;DataSnapServerMidas;DBXDb2Driver;DBXInterBaseDriver;vclactnband;vclFireDAC;tethering;FireDACADSDriver;DBXMSSQLDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;Intraweb;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;fmxdae;vclib;FireDACDBXDriver;dbexpress;IndyCore;vclx;dsnap;DataSnapCommon;DataSnapConnectors;VCLRESTComponents;vclie;bindengine;DBXMySQLDriver;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;DataSnapClient;bindcompdbx;IndyIPCommon;vcl;DBXSybaseASEDriver;IndyIPServer;IndySystem;FireDACDb2Driver;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;TeeDB;emshosting;FireDACPgDriver;ibmonitor;FireDACASADriver;DBXOdbcDriver;FireDACTDataDriver;FMXTee;DbxCommonDriver;ibxpress;Tee;DataSnapServer;xmlrtl;DataSnapNativeClient;fmxobj;vclwinx;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;vcldsnap;bindcomp;appanalytics;DBXInformixDriver;IndyIPClient;bindcompvcl;TeeUI;dbxcds;VclSmp;adortl;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(DCC_UsePackage)</DCC_UsePackage>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1)'!=''">
<DCC_Define>DEBUG;$(DCC_Define)</DCC_Define>
<DCC_DebugDCUs>true</DCC_DebugDCUs>
<DCC_Optimize>false</DCC_Optimize>
<DCC_GenerateStackFrames>true</DCC_GenerateStackFrames>
<DCC_DebugInfoInExe>true</DCC_DebugInfoInExe>
<DCC_RemoteDebug>true</DCC_RemoteDebug>
<TMSWebDebugInfo>2</TMSWebDebugInfo>
<TMSWebDefines>DEBUG</TMSWebDefines>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1_Win32)'!=''">
<DCC_RemoteDebug>false</DCC_RemoteDebug>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Locale>1033</VerInfo_Locale>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=0.9.8.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=0.9.8.0;Comments=;LastCompiledTime=2018/08/27 15:18:29</VerInfo_Keys>
<AppDPIAwarenessMode>PerMonitor</AppDPIAwarenessMode>
<VerInfo_MajorVer>0</VerInfo_MajorVer>
<VerInfo_MinorVer>9</VerInfo_MinorVer>
<VerInfo_Release>8</VerInfo_Release>
<TMSURLParams>?user_id=1019&amp;task_id=3997&amp;url_code=123456</TMSURLParams>
<TMSWebBrowser>1</TMSWebBrowser>
<TMSWebSingleInstance>1</TMSWebSingleInstance>
<TMSUseJSDebugger>2</TMSUseJSDebugger>
<TMSWebOutputPath>..\emT3XDataServer\bin\static</TMSWebOutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2)'!=''">
<DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols>
<DCC_Define>RELEASE;$(DCC_Define)</DCC_Define>
<DCC_SymbolReferenceInfo>0</DCC_SymbolReferenceInfo>
<DCC_DebugInformation>0</DCC_DebugInformation>
<TMSWebOptimization>2</TMSWebOptimization>
<TMSWebDefines>RELEASE</TMSWebDefines>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2_Win32)'!=''">
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;LastCompiledTime=2018/08/22 16:25:56</VerInfo_Keys>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Locale>1033</VerInfo_Locale>
<AppDPIAwarenessMode>PerMonitor</AppDPIAwarenessMode>
<TMSWebBrowser>1</TMSWebBrowser>
</PropertyGroup>
<ItemGroup>
<DelphiCompile Include="$(MainSource)">
<MainSource>MainSource</MainSource>
</DelphiCompile>
<DCCReference Include="Auth.Service.pas"/>
<DCCReference Include="App.Types.pas"/>
<DCCReference Include="ConnectionModule.pas">
<Form>DMConnection</Form>
<DesignClass>TWebDataModule</DesignClass>
</DCCReference>
<DCCReference Include="App.Config.pas"/>
<DCCReference Include="View.Main.pas">
<Form>FViewMain</Form>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<DCCReference Include="Utils.pas"/>
<DCCReference Include="View.Test.pas">
<Form>FTest</Form>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<DCCReference Include="View.TasksHTML.pas">
<Form>FTasksHTML</Form>
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<None Include="index.html"/>
<None Include="css\app.css"/>
<None Include="config\config.json"/>
<None Include="css\spinner.css"/>
<BuildConfiguration Include="Base">
<Key>Base</Key>
</BuildConfiguration>
<BuildConfiguration Include="Debug">
<Key>Cfg_1</Key>
<CfgParent>Base</CfgParent>
</BuildConfiguration>
<BuildConfiguration Include="Release">
<Key>Cfg_2</Key>
<CfgParent>Base</CfgParent>
</BuildConfiguration>
</ItemGroup>
<ProjectExtensions>
<Borland.Personality>Delphi.Personality.12</Borland.Personality>
<Borland.ProjectType>Application</Borland.ProjectType>
<BorlandProject>
<Delphi.Personality>
<Source>
<Source Name="MainSource">emT3WebApp.dpr</Source>
</Source>
<Excluded_Packages/>
</Delphi.Personality>
<Deployment Version="5">
<DeployFile LocalName="Win32\Debug\emT3WebApp.exe" Configuration="Debug" Class="ProjectOutput">
<Platform Name="Win32">
<RemoteName>emT3WebApp.exe</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="Win32\Debug\webCharms.exe" Configuration="Debug" Class="ProjectOutput"/>
<DeployFile LocalName="Win32\Debug\webKGOrders.exe" Configuration="Debug" Class="ProjectOutput"/>
<DeployFile LocalName="Win32\Release\emT3WebApp.exe" Configuration="Release" Class="ProjectOutput">
<Platform Name="Win32">
<RemoteName>emT3WebApp.exe</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="config\config.json" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="config\config.json" Configuration="Debug" Class="ProjectFile">
<Platform Name="Win32">
<RemoteDir>.\</RemoteDir>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="config\config.json" Configuration="Release" Class="ProjectFile">
<Platform Name="Win32">
<RemoteDir>.\</RemoteDir>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="css\app.css" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="css\app.css" Configuration="Debug" Class="ProjectFile">
<Platform Name="Win32">
<RemoteDir>.\</RemoteDir>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="css\app.css" Configuration="Release" Class="ProjectFile">
<Platform Name="Win32">
<RemoteDir>.\</RemoteDir>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="css\spinner.css" Configuration="Debug" Class="ProjectFile">
<Platform Name="Win32">
<RemoteDir>.\</RemoteDir>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="css\spinner.css" Configuration="Release" Class="ProjectFile">
<Platform Name="Win32">
<RemoteDir>.\</RemoteDir>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="index.html" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="index.html" Configuration="Debug" Class="ProjectFile">
<Platform Name="Win32">
<RemoteDir>.\</RemoteDir>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="index.html" Configuration="Release" Class="ProjectFile">
<Platform Name="Win32">
<RemoteDir>.\</RemoteDir>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="template\bootstrap\bootstrap.min.css" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="template\bootstrap\bootstrap.min.js" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="template\bootstrap\dataTables.bootstrap.css" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="template\css\emsys.css" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="template\css\metisMenu.min.css" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="template\css\morris.css" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="template\css\sb-admin-2.css" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="template\font-awesome\font-awesome.min.css" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="template\font-awesome\fonts\fontawesome-webfont.ttf" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="template\font-awesome\fonts\fontawesome-webfont.woff2" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="template\font-awesome\fonts\fontawesome-webfont.woff" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="template\jquery\jquery.min.js" Configuration="Debug" Class="ProjectFile"/>
<DeployClass Name="AdditionalDebugSymbols">
<Platform Name="iOSSimulator">
<Operation>1</Operation>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidFileProvider">
<Platform Name="Android">
<RemoteDir>res\xml</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\xml</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidLibnativeArmeabiFile">
<Platform Name="Android">
<RemoteDir>library\lib\armeabi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>library\lib\armeabi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidLibnativeArmeabiv7aFile">
<Platform Name="Android64">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidLibnativeMipsFile">
<Platform Name="Android">
<RemoteDir>library\lib\mips</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>library\lib\mips</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidServiceOutput">
<Platform Name="Android">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>library\lib\arm64-v8a</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidServiceOutput_Android32">
<Platform Name="Android64">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashImageDef">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashImageDefV21">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashStyles">
<Platform Name="Android">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashStylesV21">
<Platform Name="Android">
<RemoteDir>res\values-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashStylesV31">
<Platform Name="Android">
<RemoteDir>res\values-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashStylesV35">
<Platform Name="Android">
<RemoteDir>res\values-v35</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values-v35</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIcon">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v26</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v26</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIconBackground">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIconForeground">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIconMonochrome">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIconV33">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v33</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v33</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_Colors">
<Platform Name="Android">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_ColorsDark">
<Platform Name="Android">
<RemoteDir>res\values-night-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values-night-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_DefaultAppIcon">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon144">
<Platform Name="Android">
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon192">
<Platform Name="Android">
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon36">
<Platform Name="Android">
<RemoteDir>res\drawable-ldpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-ldpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon48">
<Platform Name="Android">
<RemoteDir>res\drawable-mdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-mdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon72">
<Platform Name="Android">
<RemoteDir>res\drawable-hdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-hdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon96">
<Platform Name="Android">
<RemoteDir>res\drawable-xhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon24">
<Platform Name="Android">
<RemoteDir>res\drawable-mdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-mdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon36">
<Platform Name="Android">
<RemoteDir>res\drawable-hdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-hdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon48">
<Platform Name="Android">
<RemoteDir>res\drawable-xhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon72">
<Platform Name="Android">
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon96">
<Platform Name="Android">
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_SplashImage426">
<Platform Name="Android">
<RemoteDir>res\drawable-small</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-small</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_SplashImage470">
<Platform Name="Android">
<RemoteDir>res\drawable-normal</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-normal</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_SplashImage640">
<Platform Name="Android">
<RemoteDir>res\drawable-large</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-large</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_SplashImage960">
<Platform Name="Android">
<RemoteDir>res\drawable-xlarge</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xlarge</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_Strings">
<Platform Name="Android">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedNotificationIcon">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v24</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v24</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedSplash">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedSplashDark">
<Platform Name="Android">
<RemoteDir>res\drawable-night-anydpi-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-night-anydpi-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedSplashV31">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedSplashV31Dark">
<Platform Name="Android">
<RemoteDir>res\drawable-night-anydpi-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-night-anydpi-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="DebugSymbols">
<Platform Name="iOSSimulator">
<Operation>1</Operation>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="DependencyFramework">
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.framework</Extensions>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.framework</Extensions>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.framework</Extensions>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="DependencyModule">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
<Extensions>.dll;.bpl</Extensions>
</Platform>
</DeployClass>
<DeployClass Required="true" Name="DependencyPackage">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
<Extensions>.bpl</Extensions>
</Platform>
</DeployClass>
<DeployClass Name="File">
<Platform Name="Android">
<Operation>0</Operation>
</Platform>
<Platform Name="Android64">
<Operation>0</Operation>
</Platform>
<Platform Name="iOSDevice32">
<Operation>0</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>0</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>0</Operation>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\Resources\StartUp\</RemoteDir>
<Operation>0</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\Resources\StartUp\</RemoteDir>
<Operation>0</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\Resources\StartUp\</RemoteDir>
<Operation>0</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectAndroidManifest">
<Platform Name="Android">
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXDebug">
<Platform Name="OSX64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXEntitlements">
<Platform Name="OSX32">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXInfoPList">
<Platform Name="OSX32">
<RemoteDir>Contents</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXResource">
<Platform Name="OSX32">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Required="true" Name="ProjectOutput">
<Platform Name="Android">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>library\lib\arm64-v8a</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice32">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
</Platform>
<Platform Name="Linux64">
<Operation>1</Operation>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOutput_Android32">
<Platform Name="Android64">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectUWPManifest">
<Platform Name="Win32">
<Operation>1</Operation>
</Platform>
<Platform Name="Win64">
<Operation>1</Operation>
</Platform>
<Platform Name="Win64x">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSDeviceDebug">
<Platform Name="iOSDevice32">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSEntitlements">
<Platform Name="iOSDevice32">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSInfoPList">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSLaunchScreen">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen</RemoteDir>
<Operation>64</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen</RemoteDir>
<Operation>64</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSResource">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="UWP_DelphiLogo150">
<Platform Name="Win32">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win64">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="UWP_DelphiLogo44">
<Platform Name="Win32">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win64">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iOS_AppStore1024">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_AppIcon152">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_AppIcon167">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_Launch2x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_LaunchDark2x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_Notification40">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_Setting58">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_SpotLight80">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_AppIcon120">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_AppIcon180">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Launch2x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Launch3x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_LaunchDark2x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_LaunchDark3x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Notification40">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Notification60">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Setting58">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Setting87">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Spotlight120">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Spotlight80">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<ProjectRoot Platform="Android" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Android64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="iOSDevice32" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="iOSDevice64" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="iOSSimARM64" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="iOSSimulator" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="Linux64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="OSX32" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="OSX64" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="OSXARM64" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="Win32" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Win64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Win64x" Name="$(PROJECTNAME)"/>
</Deployment>
<Platforms>
<Platform value="Win32">True</Platform>
<Platform value="Win64">False</Platform>
</Platforms>
<ModelSupport>False</ModelSupport>
</BorlandProject>
<ProjectFileVersion>12</ProjectFileVersion>
</ProjectExtensions>
<Import Project="$(BDS)\Bin\CodeGear.Delphi.Targets" Condition="Exists('$(BDS)\Bin\CodeGear.Delphi.Targets')"/>
<Import Project="$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj" Condition="Exists('$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj')"/>
<Import Project="$(MSBuildProjectName).deployproj" Condition="Exists('$(MSBuildProjectName).deployproj')"/>
</Project>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<link href="data:;base64,=" rel="icon"/>
<title>EM Systems Template App</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/2.3.1/css/flag-icon.min.css" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.0/css/all.min.css" rel="stylesheet"/>
<link href="css/app.css" rel="stylesheet"/>
<link href="css/spinner.css" rel="stylesheet"/>
<script crossorigin="anonymous" integrity="sha256-eKhayi8LEQwp4NKxN+CfCh+3qOVUtJn3QNZ0TciWLP4=" src="https://code.jquery.com/jquery-3.7.1.js"></script>
<link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
<script src="$(ProjectName).js"></script>
</head>
<body>
<noscript>Your browser does not support JavaScript!</noscript>
<script>rtl.run();</script>
</body>
</html>
...@@ -3,10 +3,11 @@ object ApiDatabase: TApiDatabase ...@@ -3,10 +3,11 @@ object ApiDatabase: TApiDatabase
Height = 358 Height = 358
Width = 519 Width = 519
object ucETaskApi: TUniConnection object ucETaskApi: TUniConnection
AutoCommit = False
ProviderName = 'MySQL' ProviderName = 'MySQL'
Database = 'eTask' Database = 'eTask'
Username = 'root' Username = 'root'
Server = '192.168.116.132' Server = '192.168.102.129'
LoginPrompt = False LoginPrompt = False
Left = 71 Left = 71
Top = 65 Top = 65
...@@ -44,7 +45,7 @@ object ApiDatabase: TApiDatabase ...@@ -44,7 +45,7 @@ object ApiDatabase: TApiDatabase
object uqSaveTaskRow: TUniQuery object uqSaveTaskRow: TUniQuery
Connection = ucETaskApi Connection = ucETaskApi
SQL.Strings = ( SQL.Strings = (
'UPDATE web_tasks' 'UPDATE task_items'
'SET' 'SET'
' APPLICATION = :APPLICATION,' ' APPLICATION = :APPLICATION,'
' APP_VERSION = :APP_VERSION,' ' APP_VERSION = :APP_VERSION,'
...@@ -222,9 +223,10 @@ object ApiDatabase: TApiDatabase ...@@ -222,9 +223,10 @@ object ApiDatabase: TApiDatabase
object uqEnsureBlankRow: TUniQuery object uqEnsureBlankRow: TUniQuery
Connection = ucETaskApi Connection = ucETaskApi
SQL.Strings = ( SQL.Strings = (
'insert ignore into web_tasks (' 'insert ignore into task_items ('
' TASK_ITEM_ID,' ' TASK_ITEM_ID,'
' TASK_ID,' ' TASK_ID,'
' ITEM_NUM,'
' PROJECT_ID,' ' PROJECT_ID,'
' APPLICATION,' ' APPLICATION,'
' APP_VERSION,' ' APP_VERSION,'
...@@ -265,4 +267,180 @@ object ApiDatabase: TApiDatabase ...@@ -265,4 +267,180 @@ object ApiDatabase: TApiDatabase
Value = nil Value = nil
end> end>
end end
object uqTaskHeader: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'select'
' t.TASK_ID,'
' t.PARENT_ID,'
' t.TASK_NUM_1,'
' t.TASK_NUM_2,'
' t.TASK_NUM_3,'
' t.TASK_NUM_4,'
' t.TASK_NUM_5,'
' t.TASK_NUM_6,'
' t.PROJECT_ID,'
' t.SUBJECT,'
' p.NAME as PROJECT_NAME'
'from tasks t'
'left join project p'
' on p.PROJECT_ID = t.PROJECT_ID'
'where t.TASK_ID = :TASK_ID')
Left = 164
Top = 248
ParamData = <
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end>
object uqTaskHeaderTASK_ID: TStringField
FieldName = 'TASK_ID'
Required = True
Size = 7
end
object uqTaskHeaderPARENT_ID: TStringField
FieldName = 'PARENT_ID'
Size = 7
end
object uqTaskHeaderTASK_NUM_1: TIntegerField
FieldName = 'TASK_NUM_1'
end
object uqTaskHeaderTASK_NUM_2: TIntegerField
FieldName = 'TASK_NUM_2'
end
object uqTaskHeaderTASK_NUM_3: TIntegerField
FieldName = 'TASK_NUM_3'
end
object uqTaskHeaderTASK_NUM_4: TIntegerField
FieldName = 'TASK_NUM_4'
end
object uqTaskHeaderTASK_NUM_5: TIntegerField
FieldName = 'TASK_NUM_5'
end
object uqTaskHeaderTASK_NUM_6: TIntegerField
FieldName = 'TASK_NUM_6'
end
object uqTaskHeaderPROJECT_ID: TStringField
FieldName = 'PROJECT_ID'
Size = 7
end
object uqTaskHeaderSUBJECT: TStringField
FieldName = 'SUBJECT'
Size = 80
end
object uqTaskHeaderPROJECT_NAME: TStringField
FieldName = 'PROJECT_NAME'
ReadOnly = True
Size = 30
end
end
object uqTaskItemsForParent: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'select'
' ti.TASK_ITEM_ID,'
' ti.TASK_ID,'
' ti.ITEM_NUM,'
' ti.APPLICATION,'
' ti.APP_VERSION,'
' ti.TASK_DATE,'
' ti.STATUS_DATE,'
' ti.REPORTED_BY,'
' ti.ASSIGNED_TO,'
' ti.STATUS,'
' ti.FIXED_VERSION,'
' ti.FORM_SECTION,'
' ti.ISSUE,'
' ti.NOTES'
'from task_items ti'
'join tasks t'
' on t.TASK_ID = ti.TASK_ID'
'where ti.TASK_ID = :TASK_ID'
' or t.PARENT_ID = :TASK_ID'
'order by'
' case when ti.TASK_ID = :TASK_ID then 0 else 1 end,'
' t.TASK_NUM_1,'
' t.TASK_NUM_2,'
' t.TASK_NUM_3,'
' t.TASK_NUM_4,'
' t.TASK_NUM_5,'
' t.TASK_NUM_6,'
' ti.ITEM_NUM')
Left = 164
Top = 182
ParamData = <
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end>
object uqTaskItemsForParentTASK_ITEM_ID: TStringField
FieldName = 'TASK_ITEM_ID'
Required = True
Size = 7
end
object uqTaskItemsForParentTASK_ID: TStringField
FieldName = 'TASK_ID'
Required = True
Size = 7
end
object uqTaskItemsForParentITEM_NUM: TSmallintField
FieldName = 'ITEM_NUM'
Required = True
end
object uqTaskItemsForParentAPPLICATION: TStringField
FieldName = 'APPLICATION'
Required = True
Size = 255
end
object uqTaskItemsForParentAPP_VERSION: TStringField
FieldName = 'APP_VERSION'
Required = True
Size = 50
end
object uqTaskItemsForParentTASK_DATE: TDateField
FieldName = 'TASK_DATE'
Required = True
end
object uqTaskItemsForParentSTATUS_DATE: TDateField
FieldName = 'STATUS_DATE'
Required = True
end
object uqTaskItemsForParentREPORTED_BY: TStringField
FieldName = 'REPORTED_BY'
Required = True
Size = 50
end
object uqTaskItemsForParentASSIGNED_TO: TStringField
FieldName = 'ASSIGNED_TO'
Required = True
Size = 50
end
object uqTaskItemsForParentSTATUS: TStringField
FieldName = 'STATUS'
Required = True
Size = 100
end
object uqTaskItemsForParentFIXED_VERSION: TStringField
FieldName = 'FIXED_VERSION'
Required = True
Size = 50
end
object uqTaskItemsForParentFORM_SECTION: TStringField
FieldName = 'FORM_SECTION'
Required = True
Size = 255
end
object uqTaskItemsForParentISSUE: TStringField
FieldName = 'ISSUE'
Required = True
Size = 1000
end
object uqTaskItemsForParentNOTES: TStringField
FieldName = 'NOTES'
Required = True
Size = 1000
end
end
end end
...@@ -33,6 +33,33 @@ type ...@@ -33,6 +33,33 @@ type
uqTaskItemsFORM_SECTION: TStringField; uqTaskItemsFORM_SECTION: TStringField;
uqTaskItemsISSUE: TStringField; uqTaskItemsISSUE: TStringField;
uqTaskItemsNOTES: TStringField; uqTaskItemsNOTES: TStringField;
uqTaskHeader: TUniQuery;
uqTaskHeaderTASK_ID: TStringField;
uqTaskHeaderPARENT_ID: TStringField;
uqTaskHeaderTASK_NUM_1: TIntegerField;
uqTaskHeaderTASK_NUM_2: TIntegerField;
uqTaskHeaderTASK_NUM_3: TIntegerField;
uqTaskHeaderTASK_NUM_4: TIntegerField;
uqTaskHeaderTASK_NUM_5: TIntegerField;
uqTaskHeaderTASK_NUM_6: TIntegerField;
uqTaskHeaderPROJECT_ID: TStringField;
uqTaskHeaderSUBJECT: TStringField;
uqTaskHeaderPROJECT_NAME: TStringField;
uqTaskItemsForParent: TUniQuery;
uqTaskItemsForParentTASK_ITEM_ID: TStringField;
uqTaskItemsForParentTASK_ID: TStringField;
uqTaskItemsForParentITEM_NUM: TSmallintField;
uqTaskItemsForParentAPPLICATION: TStringField;
uqTaskItemsForParentAPP_VERSION: TStringField;
uqTaskItemsForParentTASK_DATE: TDateField;
uqTaskItemsForParentSTATUS_DATE: TDateField;
uqTaskItemsForParentREPORTED_BY: TStringField;
uqTaskItemsForParentASSIGNED_TO: TStringField;
uqTaskItemsForParentSTATUS: TStringField;
uqTaskItemsForParentFIXED_VERSION: TStringField;
uqTaskItemsForParentFORM_SECTION: TStringField;
uqTaskItemsForParentISSUE: TStringField;
uqTaskItemsForParentNOTES: TStringField;
procedure DataModuleCreate(Sender: TObject); procedure DataModuleCreate(Sender: TObject);
procedure uqUsersCalcFields(DataSet: TDataSet); procedure uqUsersCalcFields(DataSet: TDataSet);
private private
......
...@@ -20,39 +20,38 @@ type ...@@ -20,39 +20,38 @@ type
taskItemId: string; taskItemId: string;
taskId: string; taskId: string;
itemNum: integer; itemNum: integer;
application: string; application: string;
version: string; version: string;
taskDate: TDateTime; taskDate: TDateTime;
reportedBy: string; reportedBy: string;
assignedTo: string; assignedTo: string;
status: string; status: string;
statusDate: Variant; statusDate: Variant;
fixedVersion: string; fixedVersion: string;
formSection: string; formSection: string;
issue: string; issue: string;
notes: string; notes: string;
end; end;
TTask = class type
public TTaskHeader = class
taskId: string; public
items: TList<TTaskItem>; taskId: string;
constructor Create; parentId: string;
destructor Destroy; override; taskNumber: string;
end; projectName: string;
subject: string;
TTasksList = class title: string;
public end;
count: integer;
data: TList<TTask>; TTaskItemsResponse = class
constructor Create; public
destructor Destroy; override; count: integer;
end; task: TTaskHeader;
items: TList<TTaskItem>;
constructor Create;
destructor Destroy; override;
end;
TTaskRowSave = class TTaskRowSave = class
public public
...@@ -76,39 +75,26 @@ type ...@@ -76,39 +75,26 @@ type
[ServiceContract, Model(API_MODEL)] [ServiceContract, Model(API_MODEL)]
IApiService = interface(IInvokable) IApiService = interface(IInvokable)
['{0EFB33D7-8C4C-4F3C-9BC3-8B4D444B5F69}'] ['{0EFB33D7-8C4C-4F3C-9BC3-8B4D444B5F69}']
function GetTaskItems(taskId: string): TTasksList; function GetTaskItems(taskId: string): TTaskItemsResponse;
[HttpPost] function SaveTaskRow(const Item: TTaskRowSave): Boolean; [HttpPost] function SaveTaskRow(Item: TTaskRowSave): Boolean;
function TestApi(messageText: string): TJSONObject;
end; end;
implementation implementation
constructor TTask.Create; constructor TTaskItemsResponse.Create;
begin begin
inherited; inherited;
items := TList<TTaskItem>.Create; items := TList<TTaskItem>.Create;
end; end;
destructor TTask.Destroy; destructor TTaskItemsResponse.Destroy;
begin begin
items.Free; items.Free;
inherited; inherited;
end; end;
constructor TTasksList.Create;
begin
inherited;
data := TList<TTask>.Create;
end;
destructor TTasksList.Destroy;
begin
data.Free;
inherited;
end;
initialization initialization
RegisterServiceType(TypeInfo(IApiService)); RegisterServiceType(TypeInfo(IApiService));
end. end.
...@@ -10,7 +10,7 @@ uses ...@@ -10,7 +10,7 @@ uses
Api.Service, Api.Service,
Api.Database, Api.Database,
Common.Logging, Common.Logging,
System.SysUtils; System.SysUtils, JSON, System.IniFiles;
type type
[ServiceImplementation] [ServiceImplementation]
...@@ -19,11 +19,15 @@ type ...@@ -19,11 +19,15 @@ type
apiDB: TApiDatabase; apiDB: TApiDatabase;
private private
procedure EnsureBlankWebTaskRow(const taskId: string); procedure EnsureBlankWebTaskRow(const taskId: string);
function SaveTaskRow(const Item: TTaskRowSave): Boolean; function BuildTaskNumber: string;
function BuildTaskTitle(const taskNumber, projectName, subject: string): string;
function SaveTaskRow(Item: TTaskRowSave): Boolean;
function TestApi(messageText: string): TJSONObject;
function GetWebClientVersion: string;
public public
procedure AfterConstruction; override; procedure AfterConstruction; override;
procedure BeforeDestruction; override; procedure BeforeDestruction; override;
function GetTaskItems(taskId: string): TTasksList; function GetTaskItems(taskId: string): TTaskItemsResponse;
end; end;
implementation implementation
...@@ -42,84 +46,155 @@ begin ...@@ -42,84 +46,155 @@ begin
inherited; inherited;
end; end;
function TApiService.GetTaskItems(taskId: string): TTasksList; function TApiService.GetTaskItems(taskId: string): TTaskItemsResponse;
var var
task: TTask; taskHeader: TTaskHeader;
item: TTaskItem; item: TTaskItem;
useParentView: Boolean;
begin begin
Logger.Log(4, Format('ApiService.GetTaskItems - TASK_ID="%s"', [taskId])); Logger.Log(4, Format('ApiService.GetTaskItems - TASK_ID="%s"', [taskId]));
Result := TTasksList.Create; Result := TTaskItemsResponse.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result); TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);
try try
apiDB.uqTaskItems.Close; apiDB.uqTaskHeader.Close;
apiDB.uqTaskItems.ParamByName('TASK_ID').AsString := taskId; apiDB.uqTaskHeader.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqTaskItems.Open; apiDB.uqTaskHeader.Open;
if apiDB.uqTaskItems.IsEmpty then if apiDB.uqTaskHeader.IsEmpty then
begin begin
Logger.Log(4, Format('ApiService.GetTaskItems - no rows for TASK_ID="%s", ensuring blank row', [taskId])); Logger.Log(2, Format('ApiService.GetTaskItems - no task header found for TASK_ID="%s"', [taskId]));
EnsureBlankWebTaskRow(taskId);
apiDB.uqTaskItems.Close;
apiDB.uqTaskItems.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqTaskItems.Open;
end;
if apiDB.uqTaskItems.IsEmpty then
begin
Logger.Log(2, Format('ApiService.GetTaskItems - still no rows after ensure blank for TASK_ID="%s"', [taskId]));
Result.count := 0; Result.count := 0;
Exit; Exit;
end; end;
task := TTask.Create; taskHeader := TTaskHeader.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(task); TXDataOperationContext.Current.Handler.ManagedObjects.Add(taskHeader);
task.taskId := taskId; taskHeader.taskId := apiDB.uqTaskHeaderTASK_ID.AsString;
taskHeader.parentId := apiDB.uqTaskHeaderPARENT_ID.AsString;
taskHeader.taskNumber := BuildTaskNumber;
taskHeader.projectName := apiDB.uqTaskHeaderPROJECT_NAME.AsString;
taskHeader.subject := apiDB.uqTaskHeaderSUBJECT.AsString;
taskHeader.title := BuildTaskTitle(
taskHeader.taskNumber,
taskHeader.projectName,
taskHeader.subject
);
Result.data.Add(task); Result.task := taskHeader;
while not apiDB.uqTaskItems.Eof do useParentView := Trim(Result.task.parentId) = '';
useParentView := useParentView or (Trim(Result.task.parentId) = '0');
if useParentView then
begin
apiDB.uqTaskItemsForParent.Close;
apiDB.uqTaskItemsForParent.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqTaskItemsForParent.Open;
if apiDB.uqTaskItemsForParent.IsEmpty then
begin
Logger.Log(4, Format('ApiService.GetTaskItems - no rows for TASK_ID="%s", ensuring blank row', [taskId]));
EnsureBlankWebTaskRow(taskId);
apiDB.uqTaskItemsForParent.Close;
apiDB.uqTaskItemsForParent.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqTaskItemsForParent.Open;
end;
while not apiDB.uqTaskItemsForParent.Eof do
begin
item := TTaskItem.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(item);
item.taskItemId := apiDB.uqTaskItemsForParentTASK_ITEM_ID.AsString;
item.taskId := apiDB.uqTaskItemsForParentTASK_ID.AsString;
item.itemNum := apiDB.uqTaskItemsForParentITEM_NUM.AsInteger;
item.application := apiDB.uqTaskItemsForParentAPPLICATION.AsString;
item.version := apiDB.uqTaskItemsForParentAPP_VERSION.AsString;
if apiDB.uqTaskItemsForParentTASK_DATE.IsNull then
item.taskDate := 0
else
item.taskDate := apiDB.uqTaskItemsForParentTASK_DATE.AsDateTime;
item.reportedBy := apiDB.uqTaskItemsForParentREPORTED_BY.AsString;
item.assignedTo := apiDB.uqTaskItemsForParentASSIGNED_TO.AsString;
item.status := apiDB.uqTaskItemsForParentSTATUS.AsString;
if apiDB.uqTaskItemsForParentSTATUS_DATE.IsNull then
item.statusDate := Null
else
item.statusDate := apiDB.uqTaskItemsForParentSTATUS_DATE.AsDateTime;
item.fixedVersion := apiDB.uqTaskItemsForParentFIXED_VERSION.AsString;
item.formSection := apiDB.uqTaskItemsForParentFORM_SECTION.AsString;
item.issue := apiDB.uqTaskItemsForParentISSUE.AsString;
item.notes := apiDB.uqTaskItemsForParentNOTES.AsString;
Result.items.Add(item);
apiDB.uqTaskItemsForParent.Next;
end;
end
else
begin begin
item := TTaskItem.Create; apiDB.uqTaskItems.Close;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(item); apiDB.uqTaskItems.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqTaskItems.Open;
item.taskItemId := apiDB.uqTaskItemsTASK_ITEM_ID.AsString; if apiDB.uqTaskItems.IsEmpty then
item.taskId := apiDB.uqTaskItemsTASK_ID.AsString; begin
item.itemNum := apiDB.uqTaskItemsITEM_NUM.AsInteger; Logger.Log(4, Format('ApiService.GetTaskItems - no rows for TASK_ID="%s", ensuring blank row', [taskId]));
EnsureBlankWebTaskRow(taskId);
item.application := apiDB.uqTaskItemsAPPLICATION.AsString; apiDB.uqTaskItems.Close;
item.version := apiDB.uqTaskItemsAPP_VERSION.AsString; apiDB.uqTaskItems.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqTaskItems.Open;
end;
if apiDB.uqTaskItemsTASK_DATE.IsNull then while not apiDB.uqTaskItems.Eof do
item.taskDate := 0 begin
else item := TTaskItem.Create;
item.taskDate := apiDB.uqTaskItemsTASK_DATE.AsDateTime; TXDataOperationContext.Current.Handler.ManagedObjects.Add(item);
item.reportedBy := apiDB.uqTaskItemsREPORTED_BY.AsString; item.taskItemId := apiDB.uqTaskItemsTASK_ITEM_ID.AsString;
item.assignedTo := apiDB.uqTaskItemsASSIGNED_TO.AsString; item.taskId := apiDB.uqTaskItemsTASK_ID.AsString;
item.itemNum := apiDB.uqTaskItemsITEM_NUM.AsInteger;
item.status := apiDB.uqTaskItemsSTATUS.AsString; item.application := apiDB.uqTaskItemsAPPLICATION.AsString;
item.version := apiDB.uqTaskItemsAPP_VERSION.AsString;
if apiDB.uqTaskItemsSTATUS_DATE.IsNull then if apiDB.uqTaskItemsTASK_DATE.IsNull then
item.statusDate := Null item.taskDate := 0
else else
item.statusDate := apiDB.uqTaskItemsSTATUS_DATE.AsDateTime; item.taskDate := apiDB.uqTaskItemsTASK_DATE.AsDateTime;
item.fixedVersion := apiDB.uqTaskItemsFIXED_VERSION.AsString; item.reportedBy := apiDB.uqTaskItemsREPORTED_BY.AsString;
item.formSection := apiDB.uqTaskItemsFORM_SECTION.AsString; item.assignedTo := apiDB.uqTaskItemsASSIGNED_TO.AsString;
item.issue := apiDB.uqTaskItemsISSUE.AsString; item.status := apiDB.uqTaskItemsSTATUS.AsString;
item.notes := apiDB.uqTaskItemsNOTES.AsString;
task.items.Add(item); if apiDB.uqTaskItemsSTATUS_DATE.IsNull then
item.statusDate := Null
else
item.statusDate := apiDB.uqTaskItemsSTATUS_DATE.AsDateTime;
apiDB.uqTaskItems.Next; item.fixedVersion := apiDB.uqTaskItemsFIXED_VERSION.AsString;
item.formSection := apiDB.uqTaskItemsFORM_SECTION.AsString;
item.issue := apiDB.uqTaskItemsISSUE.AsString;
item.notes := apiDB.uqTaskItemsNOTES.AsString;
Result.items.Add(item);
apiDB.uqTaskItems.Next;
end;
end; end;
Result.count := Result.data.Count; Result.count := Result.items.Count;
Logger.Log(4, Format('ApiService.GetTaskItems - returned %d task(s)', [Result.count])); Logger.Log(4, Format('ApiService.GetTaskItems - returned %d item(s)', [Result.count]));
except except
on E: Exception do on E: Exception do
begin begin
...@@ -129,6 +204,7 @@ begin ...@@ -129,6 +204,7 @@ begin
end; end;
end; end;
procedure TApiService.EnsureBlankWebTaskRow(const taskId: string); procedure TApiService.EnsureBlankWebTaskRow(const taskId: string);
begin begin
Logger.Log(4, Format('ApiService.EnsureBlankWebTaskRow - TASK_ID="%s"', [taskId])); Logger.Log(4, Format('ApiService.EnsureBlankWebTaskRow - TASK_ID="%s"', [taskId]));
...@@ -145,7 +221,51 @@ begin ...@@ -145,7 +221,51 @@ begin
end; end;
end; end;
function TApiService.SaveTaskRow(const Item: TTaskRowSave): Boolean;
function TApiService.BuildTaskNumber: string;
procedure AddPart(const value: string);
var
s: string;
begin
s := Trim(value);
if s = '' then
Exit;
if Result = '' then
Result := s
else
Result := Result + '.' + s;
end;
begin
Result := '';
AddPart(apiDB.uqTaskHeaderTASK_NUM_1.AsString);
AddPart(apiDB.uqTaskHeaderTASK_NUM_2.AsString);
AddPart(apiDB.uqTaskHeaderTASK_NUM_3.AsString);
AddPart(apiDB.uqTaskHeaderTASK_NUM_4.AsString);
AddPart(apiDB.uqTaskHeaderTASK_NUM_5.AsString);
AddPart(apiDB.uqTaskHeaderTASK_NUM_6.AsString);
end;
function TApiService.BuildTaskTitle(const taskNumber, projectName, subject: string): string;
begin
Result := 'Task';
if Trim(taskNumber) <> '' then
Result := Result + ' - ' + Trim(taskNumber);
if Trim(projectName) <> '' then
Result := Result + ' | ' + Trim(projectName);
if Trim(subject) <> '' then
Result := Result + ' | ' + Trim(subject);
end;
function TApiService.SaveTaskRow(Item: TTaskRowSave): Boolean;
function ParseDateOrZero(const S: string; out D: TDateTime): Boolean; function ParseDateOrZero(const S: string; out D: TDateTime): Boolean;
begin begin
...@@ -200,6 +320,37 @@ begin ...@@ -200,6 +320,37 @@ begin
end; end;
end; end;
function TApiService.TestApi(messageText: string): TJSONObject;
var
requiredVersion: string;
begin
Logger.Log(3, 'IApiService.TestApi called');
Result := TJSONObject.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);
requiredVersion := GetWebClientVersion;
Result.AddPair('messageEcho', messageText);
Result.AddPair('serverTime', DateTimeToStr(Now));
Result.AddPair('requiredWebClientVersion', requiredVersion);
Result.AddPair('note', 'If this endpoint is reachable, JWT auth passed. Version enforcement on every API call is a separate step (middleware).');
end;
function TApiService.GetWebClientVersion: string;
var
iniFile: TIniFile;
begin
iniFile := TIniFile.Create(ChangeFileExt(ParamStr(0), '.ini'));
try
Result := iniFile.ReadString('Settings', 'webClientVersion', '');
finally
iniFile.Free;
end;
end;
initialization initialization
RegisterServiceType(TypeInfo(IApiService)); RegisterServiceType(TypeInfo(IApiService));
RegisterServiceType(TApiService); RegisterServiceType(TApiService);
......
...@@ -147,7 +147,7 @@ var ...@@ -147,7 +147,7 @@ var
userState: Integer; userState: Integer;
jwt: TJWT; jwt: TJWT;
begin begin
Logger.Log(3, Format('AuthService.Login - UserID: "%s", TaskID: "%s"', [userId, taskId])); Logger.Log(3, Format('AuthService.Login - UserID: "%s", TaskID: "%s", Code: "%s"', [userId, taskId, urlCode]));
try try
userState := CheckUrlLogin(userId, taskId, urlCode); userState := CheckUrlLogin(userId, taskId, urlCode);
......
<div class="container-fluid py-3">
<div class="card shadow-sm">
<div class="card-body">
<h4 class="mb-3">Home Form</h4>
<div class="mb-3">
<label for="edt_task_id" class="form-label">Task Id</label>
<input id="edt_task_id" type="text" class="form-control">
</div>
<div class="mb-3">
<label for="edt_user_id" class="form-label">User Id</label>
<input id="edt_user_id" type="text" class="form-control">
</div>
<div class="mb-3">
<label for="edt_code" class="form-label">Code</label>
<input id="edt_code" type="text" class="form-control">
</div>
</div>
</div>
</div>
<nav class="navbar navbar-light bg-light login-navbar">
<div class="container-fluid">
<a class="navbar-brand" href="#">Koehler-Gibson Orders</a>
</div>
</nav>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-auto">
<img id="kgpicture" style="width: 250px; height: 250px;">
</div>
<div class="col-md-6 col-lg-4">
<div class="card login-card">
<div class="card-header">
<h3 id="view.login.title" class="fs-6 card-title">Please Sign In</h3>
</div>
<div class="card-body">
<div role="form">
<div id="view.login.message" class="alert alert-danger">
<button id="view.login.message.button" type="button" class="btn-close" aria-label="Close"></button>
<span id="view.login.message.label"></span>
</div>
<fieldset>
<div class="mb-3">
<input id="view.login.edtusername" class="form-control" type="text" autofocus placeholder="Username">
</div>
<div class="mb-3">
<input id="view.login.edtpassword" class="form-control" type="password" placeholder="Password">
</div>
<div class="mb-3">
<button id="view.login.btnlogin" class="btn btn-primary w-100">Login</button>
</div>
<div class="text-end text-muted small mt-1">
<span id="lbl_client_version"></span>
</div>
</fieldset>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="wrapper" class="d-flex flex-column vh-100">
<nav class="navbar navbar-expand navbar-light bg-light" style="margin-bottom: 0px">
<div class="container-fluid">
<div class="d-flex align-items-center">
<a id="view.main.apptitle" class="navbar-brand" href="index.html">emT3web</a>
<span id="view.main.version" class="small text-muted ms-2"></span>
</div>
<li class="nav-item ms-2 me-2 d-flex align-items-center">
<input id="edt_task_id_main" type="text" class="form-control form-control-sm" placeholder="Task Id">
</li>
<div class="collapse navbar-collapse show" id="navbarNavDropdown">
<ul class="navbar-nav ms-auto">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" id="navbarDropdownMenuLink" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa fa-user fa-fw"></i><span class="panel-title" id="view.main.username">Username</span>
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdownMenuLink">
<li>
<a class="dropdown-item" id="dropdown.menu.logout" href="#">
<i class="fa fa-sign-out fa-fw"></i><span> Logout</span>
</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<!-- Toast wrapper directly under navbar -->
<div id="toast-wrapper" class="position-fixed top-0 start-0 mt-5 ms-4" style="z-index: 1080; min-width: 300px; max-width: 500px">
<div id="bootstrapToast" class="toast align-items-center text-white bg-success border-0 shadow" role="alert" aria-live="assertive" aria-atomic="true">
<div class="d-flex">
<div class="toast-body" id="bootstrapToastBody">Success message</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>
</div>
<div class="container-fluid d-flex flex-column flex-grow-1" style="min-height: 0">
<div id="main.webpanel" class="flex-grow-1 d-flex flex-column" style="min-height: 0"></div>
</div>
</div>
<div id="spinner" class="position-absolute top-50 start-50 translate-middle d-none">
<div class="lds-roller">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
<div class="modal fade" id="main_errormodal" tabindex="-1" aria-labelledby="main_lblmodal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content shadow-lg">
<div class="modal-header">
<h5 class="modal-title" id="main_lblmodal">Error</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body fs-6 fw-bold" id="main_lblmodal_body">
Please contact EMSystems to solve the issue.
</div>
<div class="modal-footer justify-content-center">
<button type="button" id="btn_modal_restart" class="btn btn-primary">
Restart
</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="main_confirmation_modal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content shadow-lg">
<div class="modal-header">
<h5 class="modal-title">Confirm</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body fw-bold" id="main_modal_body">Placeholder text</div>
<div class="modal-footer justify-content-center">
<button type="button" class="btn btn-primary me-3" id="btn_confirm_left">
Cancel
</button>
<button type="button" class="btn btn-secondary" id="btn_confirm_right">
Confirm
</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="main_notification_modal" tabindex="-1" aria-labelledby="main_lblmodal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content shadow-lg">
<div class="modal-header">
<h5 class="modal-title" id="main_notification_modal">Error</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body fs-6 fw-bold" id="main_notification_modal_body">Please contact EMSystems to solve the issue.</div>
<div class="modal-footer justify-content-center">
<button type="button" id="btn_modal_close" class="btn btn-primary">
Close
</button>
</div>
</div>
</div>
</div>
<div class="container h-100 d-flex flex-column mt-0 py-0" style="max-width: 100%;"> <div class="container-fluid p-2 d-flex flex-column h-100">
<div class="d-flex align-items-center justify-content-between mb-2 flex-shrink-0"> <div class="d-flex align-items-center justify-content-between mb-2 flex-shrink-0">
<h5 class="mb-0" id="lbl_project_name"></h5> <h5 class="mb-0" id="lbl_project_name"></h5>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<button id="btn_add_row" class="btn btn-sm btn-outline-success">Add Row</button> <button id="btn_add_row" class="btn btn-sm btn-outline-success">Add Row</button>
<button id="btn_reload" class="btn btn-sm btn-outline-primary">Reload</button> <button id="btn_reload" class="btn btn-sm btn-outline-primary">Reload</button>
</div> </div>
</div> </div>
<div id="db_grid_tasks" class="flex-grow-1" style="min-height:0;"></div> <div id="tasks_table_host" class="flex-grow-1 min-vh-0"></div>
</div> </div>
{
"AuthUrl" : "http://localhost:2001/emsys/emt3/auth/",
"ApiUrl" : "http://localhost:2001/emsys/emt3/api/"
}
/* Note: Base layout */
html, body{
height:100%;
margin:0;
}
#wrapper{
height:100vh;
display:flex;
flex-direction:column;
min-height:0;
}
/* Note: Embedded forms must be allowed to shrink inside flex containers */
#main\.webpanel{
min-height:0;
flex:1 1 auto;
display:flex;
flex-direction:column;
}
#main\.webpanel > *{
min-height:0;
}
/* Note: Primary button color */
.btn-primary{
background-color:#286090 !important;
border-color:#286090 !important;
color:#fff !important;
}
.btn-primary:hover{
background-color:#204d74 !important;
border-color:#204d74 !important;
}
/* Note: Navbar tweaks */
#view\.main\.apptitle{
display:flex;
align-items:center;
}
.navbar-nav .nav-link.active{
color:#fff !important;
background-color:#004F84 !important;
font-weight:700;
}
.navbar-nav .nav-link:hover{
color:#fff !important;
background-color:#286090 !important;
}
.navbar-toggler{
display:none;
}
/* Note: Dropdown menu items */
.dropdown-menu a{
display:flex;
align-items:center;
width:100%;
padding:.5rem 1rem;
color:#000;
text-decoration:none;
}
.dropdown-menu a:hover{
background-color:#204d74;
color:#fff;
}
.dropdown-menu a span{
flex-grow:1;
}
/* Note: Login card (used on login view) */
.login-card{
display:inline-block;
width:300px;
padding:0;
border-radius:10px;
box-shadow:0 4px 8px rgba(0,0,0,.1);
background-color:#fff;
}
/* Note: Validation helpers */
.is-invalid .form-check-input{
border:1px solid #dc3545 !important;
}
.is-invalid .form-check-label{
color:#dc3545 !important;
}
/* Note: Toast animation */
@keyframes slideInLeft{
from{transform:translateX(-120%);opacity:0;}
to{transform:translateX(0);opacity:1;}
}
.toast.slide-in{
animation:slideInLeft .4s ease-out forwards;
}
/* Note: Spinner overlay */
#spinner{
position:fixed !important;
z-index:9999 !important;
top:50%;
left:50%;
transform:translate(-50%,-50%);
}
/* Note: TasksHTML (table experiment) */
#tasks_table_host{
height:100%;
min-height:0;
}
#tasks_table_host .tasks-vscroll{
height:100%;
overflow-y:auto;
overflow-x:hidden;
}
#tasks_table_host .tasks-hscroll{
overflow-x:auto;
}
#tasks_table_host .tasks-hscroll table{
width:max-content;
min-width:100%;
table-layout:fixed;
}
#tasks_table_host thead th{
position:sticky;
top:0;
z-index:2;
background:var(--bs-body-bg);
}
#tasks_table_host td,
#tasks_table_host th{
padding:.25rem;
}
#tasks_table_host .nowrap-cell{white-space:nowrap;}
#tasks_table_host .wrap-cell{white-space:normal;word-break:break-word;}
#tasks_table_host .cell-input,
#tasks_table_host .cell-textarea{
border:0;
background:transparent;
border-radius:0;
padding:0;
margin:0;
box-shadow:none;
}
#tasks_table_host .cell-input:focus,
#tasks_table_host .cell-textarea:focus{
outline:0;
box-shadow:inset 0 -2px 0 var(--bs-primary);
}
#tasks_table_host .cell-textarea{
resize:none;
overflow:hidden;
white-space:pre-wrap;
}
/* Note: TasksDataGrid (TWebDataGrid experiment) */
#data_grid_tasks{
height:100%;
min-height:0;
}
#data_grid_tasks .ag-cell{
line-height:1.25;
padding-top:4px;
padding-bottom:4px;
}
#data_grid_tasks .ag-cell-inline-editing textarea{
line-height:1.25;
padding:4px 6px;
resize:none;
height:100%;
box-sizing:border-box;
}
.lds-roller {
display: inline-block;
position: relative;
width: 80px;
height: 80px;
}
.lds-roller div {
animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
transform-origin: 40px 40px;
}
.lds-roller div:after {
content: " ";
display: block;
position: absolute;
width: 10px;
height: 10px;
border-radius: 50%;
background: #204d74;
margin: -5px 0 0 -5px;
}
.lds-roller div:nth-child(1) {
animation-delay: -0.036s;
}
.lds-roller div:nth-child(1):after {
top: 63px;
left: 63px;
}
.lds-roller div:nth-child(2) {
animation-delay: -0.072s;
}
.lds-roller div:nth-child(2):after {
top: 68px;
left: 56px;
}
.lds-roller div:nth-child(3) {
animation-delay: -0.108s;
}
.lds-roller div:nth-child(3):after {
top: 71px;
left: 48px;
}
.lds-roller div:nth-child(4) {
animation-delay: -0.144s;
}
.lds-roller div:nth-child(4):after {
top: 72px;
left: 40px;
}
.lds-roller div:nth-child(5) {
animation-delay: -0.18s;
}
.lds-roller div:nth-child(5):after {
top: 71px;
left: 32px;
}
.lds-roller div:nth-child(6) {
animation-delay: -0.216s;
}
.lds-roller div:nth-child(6):after {
top: 68px;
left: 24px;
}
.lds-roller div:nth-child(7) {
animation-delay: -0.252s;
}
.lds-roller div:nth-child(7):after {
top: 63px;
left: 17px;
}
.lds-roller div:nth-child(8) {
animation-delay: -0.288s;
}
.lds-roller div:nth-child(8):after {
top: 56px;
left: 12px;
}
@keyframes lds-roller {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
[Settings] [Settings]
MemoLogLevel=4 MemoLogLevel=4
FileLogLevel=4 FileLogLevel=4
webClientVersion=0.0.1 webClientVersion=0.7.1
LogFileNum=175 LogFileNum=175
[Database] [Database]
Server=192.168.116.128 --Server=192.168.116.128
--Server=192.168.102.129 Server=192.168.102.129
--Server=192.168.75.133 --Server=192.168.75.133
--Server=192.168.159.10 --Server=192.168.159.10
Database=eTask Database=eTask
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<noscript>Your browser does not support JavaScript!</noscript>
<link href="data:;base64,=" rel="icon"/>
<title>Em Systems - emT3 Web</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/2.3.1/css/flag-icon.min.css" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.0/css/all.min.css" rel="stylesheet"/>
<link href="css/app.css" rel="stylesheet" type="text/css"/>
<link href="css/spinner.css" rel="stylesheet" type="text/css"/>
<script crossorigin="anonymous" integrity="sha256-eKhayi8LEQwp4NKxN+CfCh+3qOVUtJn3QNZ0TciWLP4=" src="https://code.jquery.com/jquery-3.7.1.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js" type="text/javascript"></script>
<link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet"/>
<script src="emT3web.js" type="text/javascript"></script>
</head>
<body>
</body>
<script type="text/javascript">rtl.run();</script>
</html>
<div id="div_wrapper">
<nav class="navbar navbar-expand-lg bg-body-tertiary border-bottom shadow-sm">
<div class="container-fluid">
<div class="d-flex align-items-center gap-2">
<a id="lbl_app_title" class="navbar-brand fw-semibold" href="index.html">Koehler-Gibson Orders</a>
<span id="lbl_version" class="badge text-bg-light border text-muted fw-normal"></span>
</div>
<div class="collapse navbar-collapse show" id="pnl_navbar_nav_dropdown">
<ul class="navbar-nav ms-auto">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle d-flex align-items-center gap-2" id="lnk_navbar_dropdown_menu_link"
role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa fa-user fa-fw"></i>
<span id="lbl_username" class="fw-semibold">Username</span>
</a>
<ul class="dropdown-menu dropdown-menu-end shadow-sm" aria-labelledby="lnk_navbar_dropdown_menu_link">
<li>
<a class="dropdown-item d-flex align-items-center gap-2" id="lbl_home" href="#">
<i class="fa fa-home fa-fw"></i><span>Home</span>
</a>
</li>
<li>
<a class="dropdown-item d-flex align-items-center gap-2" id="lbl_user_profile" href="#">
<i class="fa fa-user fa-fw"></i><span>User Profile</span>
</a>
</li>
<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item d-flex align-items-center gap-2 text-danger" id="lbl_logout" href="#">
<i class="fa fa-sign-out fa-fw"></i><span>Logout</span>
</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<!-- Toast -->
<div id="pnl_toast_wrapper" class="position-fixed top-0 start-0 mt-5 ms-4"
style="z-index: 1080; min-width: 300px; max-width: 500px;">
<div id="toast_bootstrap" class="toast align-items-center text-white bg-success border-0 shadow" role="alert"
aria-live="assertive" aria-atomic="true">
<div class="d-flex">
<div class="toast-body" id="lbl_bootstrap_toast_body">
Success message
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"
aria-label="Close"></button>
</div>
</div>
</div>
<!-- Main Panel (where all forms display) -->
<div class="container-fluid py-3">
<div class="row">
<div id="pnl_main" class="col-12"></div>
</div>
<div class="row mt-3">
<div class="col-12">
<textarea class="form-control font-monospace" id="memo_debug" rows="4" placeholder="Debug output..."></textarea>
</div>
</div>
</div>
<!-- Spinner Modal -->
<div id="div_spinner" class="position-absolute top-50 start-50 translate-middle d-none">
<div class="lds-roller">
<div></div><div></div><div></div><div></div>
<div></div><div></div><div></div><div></div>
</div>
</div>
<!-- Error Modal -->
<div class="modal fade" id="mdl_error" tabindex="-1" aria-labelledby="lbl_modal_title" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content shadow-lg">
<div class="modal-header">
<h5 class="modal-title" id="lbl_modal_title">Error</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body fs-6 fw-bold" id="lbl_modal_body">
Please contact EMSystems to solve the issue.
</div>
<div class="modal-footer justify-content-center">
<button type="button" id="btn_modal_restart" class="btn btn-primary">Back to Orders</button>
</div>
</div>
</div>
</div>
<!-- Confirmation Modal -->
<div class="modal fade" id="mdl_confirmation" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content shadow-lg">
<div class="modal-header">
<h5 class="modal-title">Confirm</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body fw-bold" id="lbl_confirmation_body">
Placeholder text
</div>
<div class="modal-footer justify-content-center">
<button type="button" class="btn btn-primary me-3" id="btn_confirm_left">Cancel</button>
<button type="button" class="btn btn-secondary" id="btn_confirm_right">Confirm</button>
</div>
</div>
</div>
</div>
<!-- Notification Modal -->
<div class="modal fade" id="mdl_notification" tabindex="-1" aria-labelledby="lbl_notification_title"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content shadow-lg">
<div class="modal-header">
<h5 class="modal-title" id="lbl_notification_title">Info</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body fs-6 fw-bold" id="lbl_notification_body">
Please contact EMSystems to solve the issue.
</div>
<div class="modal-footer justify-content-center">
<button type="button" id="btn_modal_close" class="btn btn-primary">Close</button>
</div>
</div>
</div>
</div>
</div>
<div class="container-fluid p-2 d-flex flex-column h-100">
<div class="d-flex align-items-center justify-content-between mb-2 flex-shrink-0">
<h5 class="mb-0" id="lbl_project_name"></h5>
<div class="d-flex gap-2">
<button id="btn_add_row" class="btn btn-sm btn-outline-success">Add Row</button>
<button id="btn_reload" class="btn btn-sm btn-outline-primary">Reload</button>
</div>
</div>
<div id="tasks_table_host" class="flex-grow-1 min-vh-0"></div>
</div>
<div class="container py-4">
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-4">
<div>
<div id="lbl_test" class="h2 fw-semibold mb-1">Test Form</div>
<div class="text-muted">Quick API + JWT/version diagnostics</div>
</div>
<button id="btn_test_api" class="btn btn-primary">Test API</button>
</div>
<div class="card border-0 shadow-sm">
<div class="card-header bg-body-tertiary d-flex align-items-center justify-content-between">
<div class="fw-semibold">Debug Output</div>
</div>
<div class="card-body" style="min-height: 70vh;">
<textarea id="memo_test_debug"
class="form-control font-monospace h-100"
style="min-height: 65vh;"
rows="28"
placeholder="Click Test API to populate diagnostics..."></textarea>
</div>
</div>
</div>
{
"AuthUrl" : "http://localhost:2001/emsys/emt3/auth/",
"ApiUrl" : "http://localhost:2001/emsys/emt3/api/"
}
is-invalid .form-check-input {
border: 1px solid #dc3545 !important;
}
.is-invalid .form-check-label {
color: #dc3545 !important;
}
.btn-primary {
background-color: #286090 !important;
border-color: #286090 !important;
color: #fff !important;
}
.btn-primary:hover {
background-color: #204d74 !important;
border-color: #204d74 !important;
}
@keyframes slideInLeft {
from {
transform: translateX(-120%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.toast.slide-in {
animation: slideInLeft 0.4s ease-out forwards;
}
#spinner {
position: fixed !important;
z-index: 9999 !important;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.lds-roller {
display: inline-block;
position: relative;
width: 80px;
height: 80px;
}
.lds-roller div {
animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
transform-origin: 40px 40px;
}
.lds-roller div:after {
content: " ";
display: block;
position: absolute;
width: 10px;
height: 10px;
border-radius: 50%;
background: #204d74;
margin: -5px 0 0 -5px;
}
.lds-roller div:nth-child(1) {
animation-delay: -0.036s;
}
.lds-roller div:nth-child(1):after {
top: 63px;
left: 63px;
}
.lds-roller div:nth-child(2) {
animation-delay: -0.072s;
}
.lds-roller div:nth-child(2):after {
top: 68px;
left: 56px;
}
.lds-roller div:nth-child(3) {
animation-delay: -0.108s;
}
.lds-roller div:nth-child(3):after {
top: 71px;
left: 48px;
}
.lds-roller div:nth-child(4) {
animation-delay: -0.144s;
}
.lds-roller div:nth-child(4):after {
top: 72px;
left: 40px;
}
.lds-roller div:nth-child(5) {
animation-delay: -0.18s;
}
.lds-roller div:nth-child(5):after {
top: 71px;
left: 32px;
}
.lds-roller div:nth-child(6) {
animation-delay: -0.216s;
}
.lds-roller div:nth-child(6):after {
top: 68px;
left: 24px;
}
.lds-roller div:nth-child(7) {
animation-delay: -0.252s;
}
.lds-roller div:nth-child(7):after {
top: 63px;
left: 17px;
}
.lds-roller div:nth-child(8) {
animation-delay: -0.288s;
}
.lds-roller div:nth-child(8):after {
top: 56px;
left: 12px;
}
@keyframes lds-roller {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<link href="data:;base64,=" rel="icon"/>
<title>EM Systems Template App</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/2.3.1/css/flag-icon.min.css" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.0/css/all.min.css" rel="stylesheet"/>
<link href="css/app.css" rel="stylesheet"/>
<link href="css/spinner.css" rel="stylesheet"/>
<script crossorigin="anonymous" integrity="sha256-eKhayi8LEQwp4NKxN+CfCh+3qOVUtJn3QNZ0TciWLP4=" src="https://code.jquery.com/jquery-3.7.1.js"></script>
<link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
<script src="emT3WebApp.js"></script>
</head>
<body>
<noscript>Your browser does not support JavaScript!</noscript>
<script>rtl.run();</script>
</body>
</html>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment