* Webwidget embryonal version
52
demo/webwidget/designdemo/design.css
Normal file
@ -0,0 +1,52 @@
|
||||
button[data-widget-class] {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
#toolbar {
|
||||
width: 80%;
|
||||
background-color: #DDDDDD;
|
||||
margin-bottom: 32px;
|
||||
padding: 4px 4px 4px 4px;
|
||||
margin-left: 30px;
|
||||
}
|
||||
#designpage {
|
||||
background-color: #A0A0A0;
|
||||
width: 80%;
|
||||
height: 80vh;
|
||||
margin-left: 30px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.designerActive {
|
||||
position: relative;
|
||||
border: 1px dashed #87cefa;
|
||||
}
|
||||
|
||||
.designerToolbar {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: -18px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* justify-content: space-around; */
|
||||
}
|
||||
|
||||
.designerDragHandle{
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
||||
.designerPlaceholder {
|
||||
border: 3px dotted black;
|
||||
margin: 1em 1em 1em 1em;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.source {
|
||||
display: flex;
|
||||
width: 540px;
|
||||
margin: 10px auto;
|
||||
font-size: 12px;
|
||||
}
|
29
demo/webwidget/designdemo/designdemo.html
Normal file
@ -0,0 +1,29 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/smoothness/jquery-ui.css">
|
||||
<link rel="stylesheet" href="design.css">
|
||||
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
|
||||
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
|
||||
<title>Designdemo</title>
|
||||
<script src="designdemo.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="toolbar"></div>
|
||||
<div id="designpage"></div>
|
||||
<div class="source">
|
||||
Created using <a target="_blank" href="https://wiki.freepascal.org/pas2js">pas2js.</a>
|
||||
Sources: <a target="new" href="designdemo.lpr">Program</a>
|
||||
<a target="new" href="designer.pp">unit</a>.
|
||||
|
||||
</div>
|
||||
<script>
|
||||
window.addEventListener("load", rtl.run);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
103
demo/webwidget/designdemo/designdemo.lpi
Normal file
@ -0,0 +1,103 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CONFIG>
|
||||
<ProjectOptions>
|
||||
<Version Value="12"/>
|
||||
<General>
|
||||
<Flags>
|
||||
<MainUnitHasCreateFormStatements Value="False"/>
|
||||
<MainUnitHasTitleStatement Value="False"/>
|
||||
<MainUnitHasScaledStatement Value="False"/>
|
||||
<Runnable Value="False"/>
|
||||
</Flags>
|
||||
<SessionStorage Value="InProjectDir"/>
|
||||
<Title Value="designdemo"/>
|
||||
<UseAppBundle Value="False"/>
|
||||
<ResourceType Value="res"/>
|
||||
</General>
|
||||
<CustomData Count="3">
|
||||
<Item0 Name="MaintainHTML" Value="1"/>
|
||||
<Item1 Name="PasJSWebBrowserProject" Value="1"/>
|
||||
<Item2 Name="RunAtReady" Value="1"/>
|
||||
</CustomData>
|
||||
<BuildModes>
|
||||
<Item Name="Default" Default="True"/>
|
||||
</BuildModes>
|
||||
<PublishOptions>
|
||||
<Version Value="2"/>
|
||||
<UseFileFilters Value="True"/>
|
||||
</PublishOptions>
|
||||
<RunParams>
|
||||
<FormatVersion Value="2"/>
|
||||
<Modes Count="0"/>
|
||||
</RunParams>
|
||||
<RequiredPackages Count="1">
|
||||
<Item1>
|
||||
<PackageName Value="lazwebwidgets"/>
|
||||
</Item1>
|
||||
</RequiredPackages>
|
||||
<Units>
|
||||
<Unit>
|
||||
<Filename Value="designdemo.lpr"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
</Unit>
|
||||
<Unit>
|
||||
<Filename Value="designdemo.html"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
<CustomData Count="1">
|
||||
<Item0 Name="PasJSIsProjectHTMLFile" Value="1"/>
|
||||
</CustomData>
|
||||
</Unit>
|
||||
<Unit>
|
||||
<Filename Value="designer.pp"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
</Unit>
|
||||
<Unit>
|
||||
<Filename Value="webideclient.pp"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
</Unit>
|
||||
</Units>
|
||||
</ProjectOptions>
|
||||
<CompilerOptions>
|
||||
<Version Value="11"/>
|
||||
<Target>
|
||||
<Filename Value="designdemo"/>
|
||||
</Target>
|
||||
<SearchPaths>
|
||||
<IncludeFiles Value="$(ProjOutDir)"/>
|
||||
<UnitOutputDirectory Value="js"/>
|
||||
</SearchPaths>
|
||||
<Parsing>
|
||||
<SyntaxOptions>
|
||||
<AllowLabel Value="False"/>
|
||||
<CPPInline Value="False"/>
|
||||
<UseAnsiStrings Value="False"/>
|
||||
</SyntaxOptions>
|
||||
</Parsing>
|
||||
<CodeGeneration>
|
||||
<TargetOS Value="browser"/>
|
||||
</CodeGeneration>
|
||||
<Linking>
|
||||
<Debugging>
|
||||
<GenerateDebugInfo Value="False"/>
|
||||
<UseLineInfoUnit Value="False"/>
|
||||
</Debugging>
|
||||
</Linking>
|
||||
<Other>
|
||||
<CustomOptions Value="-Jeutf-8 -Jirtl.js -Jc -Jminclude"/>
|
||||
<CompilerPath Value="$(pas2js)"/>
|
||||
</Other>
|
||||
</CompilerOptions>
|
||||
<Debugging>
|
||||
<Exceptions Count="3">
|
||||
<Item1>
|
||||
<Name Value="EAbort"/>
|
||||
</Item1>
|
||||
<Item2>
|
||||
<Name Value="ECodetoolError"/>
|
||||
</Item2>
|
||||
<Item3>
|
||||
<Name Value="EFOpenError"/>
|
||||
</Item3>
|
||||
</Exceptions>
|
||||
</Debugging>
|
||||
</CONFIG>
|
35
demo/webwidget/designdemo/designdemo.lpr
Normal file
@ -0,0 +1,35 @@
|
||||
program designdemo;
|
||||
|
||||
{$mode objfpc}
|
||||
{$DEFINE USEIDE}
|
||||
|
||||
uses
|
||||
browserapp, JS, Classes, SysUtils, Web, designer, webideclient;
|
||||
|
||||
type
|
||||
TMyApplication = class(TBrowserApplication)
|
||||
Public
|
||||
FDemo : TDesignDemo;
|
||||
FIDEIntf : TIDEClient;
|
||||
procedure doRun; override;
|
||||
end;
|
||||
|
||||
procedure TMyApplication.doRun;
|
||||
|
||||
begin
|
||||
FDemo:=TDesignDemo.Create(Self);
|
||||
{$IFDEF USEIDE}
|
||||
FIDEIntf:=TIDEClient.Create(Self);
|
||||
FDemo.IDEClient:=FIDEintf;
|
||||
FIDEIntf.RegisterClient;
|
||||
{$ENDIF}
|
||||
end;
|
||||
|
||||
var
|
||||
Application : TMyApplication;
|
||||
|
||||
begin
|
||||
Application:=TMyApplication.Create(nil);
|
||||
Application.Initialize;
|
||||
Application.Run;
|
||||
end.
|
353
demo/webwidget/designdemo/designer.pp
Normal file
@ -0,0 +1,353 @@
|
||||
unit designer;
|
||||
|
||||
{$mode objfpc}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils, libjquery, webwidget, htmlwidgets, contnrs, js, web, webideclient;
|
||||
|
||||
Type
|
||||
|
||||
{ TRegisteredWidget }
|
||||
|
||||
TRegisteredWidget = Class
|
||||
Private
|
||||
FClass : TWebwidgetClass;
|
||||
FImageName : String;
|
||||
Public
|
||||
Constructor Create(aClass : TWebwidgetClass; aImageName : String);
|
||||
Property WidgetClass : TWebwidgetClass Read FClass;
|
||||
Property ImageName : String Read FimageName;
|
||||
end;
|
||||
|
||||
TWidgetButtonWidget = Class(TButtonWidget)
|
||||
MyWidget : TRegisteredWidget;
|
||||
end;
|
||||
|
||||
TSortable = Class helper for TJQuery
|
||||
Procedure sortable(Options : TJSObject); external name 'sortable'; overload;
|
||||
Procedure sortable(Options : string); external name 'sortable'; overload;
|
||||
end;
|
||||
|
||||
{ TDesignDemo }
|
||||
|
||||
TDesignDemo = class(TComponent)
|
||||
private
|
||||
FIDEClient: TIDEClient;
|
||||
procedure AddWidgetByName(aID: NativeInt; AName: String);
|
||||
function CreateNewWidget(aParent: TCustomWebWidget; aClass: TCustomWebWidgetClass): TCustomWebWidget;
|
||||
function DoActive(Event: TEventListenerEvent): boolean;
|
||||
procedure DoCommandsReceived(Sender: TObject; aCommands: TJSArray);
|
||||
procedure DoWidgetAddClick(Sender: TObject; Event: TJSEvent);
|
||||
procedure SetIDEClient(AValue: TIDEClient);
|
||||
function SortableOptions: TJSObject;
|
||||
function StreamWidget(aWidget: TCustomWebWidget): String;
|
||||
Public
|
||||
FConfirmAdd : NativeInt;
|
||||
FAddWidget : TRegisteredWidget;
|
||||
FPage : TWebPage;
|
||||
FToolBar : TContainerWidget;
|
||||
FButtons : Array[1..10] of TButtonWidget;
|
||||
FWidgetButtons : Array of TWidgetButtonWidget;
|
||||
FRegisteredWidgets : TObjectList;
|
||||
Constructor Create(aOwner : TComponent); override;
|
||||
Destructor Destroy; override;
|
||||
Procedure RegisterWidgets;
|
||||
Procedure RegisterWidget(aClass : TWebWidgetClass; aImageName : String);
|
||||
procedure SetAddMode(aRegisteredWidget: TRegisteredWidget);
|
||||
Procedure FillToolBar;
|
||||
Procedure SetupPage;
|
||||
property IDEClient: TIDEClient Read FIDEClient Write SetIDEClient;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
Const
|
||||
SSortableSelect = '#designpage, #designpage [data-ww-element-content]';
|
||||
|
||||
Type
|
||||
|
||||
{ TJumboWidget }
|
||||
|
||||
TJumboWidget = class(TCustomTemplateWidget)
|
||||
Public
|
||||
Constructor Create(aOwner: TComponent); override;
|
||||
end;
|
||||
type
|
||||
TWidgetHack = Class(TCustomWebWidget)
|
||||
Property Element;
|
||||
Property ElementID;
|
||||
Property TopElement;
|
||||
Property ContentElement;
|
||||
end;
|
||||
TPageHack = Class(TWebPage)
|
||||
Property Element;
|
||||
Property ElementID;
|
||||
end;
|
||||
|
||||
{ TJumboWidget }
|
||||
|
||||
constructor TJumboWidget.Create(aOwner: TComponent);
|
||||
begin
|
||||
inherited Create(aOwner);
|
||||
Template.Text:='<div class="jumbotron">'+sLineBreak+
|
||||
'<h1 class="display-4">Hello, world!</h1>'+sLineBreak+
|
||||
'<p class="lead">This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured content or information.</p>'+sLineBreak+
|
||||
'<hr class="my-4">'+sLineBreak+
|
||||
'<p>It uses utility classes for typography and spacing to space content out within the larger container.</p>'+sLineBreak+
|
||||
'<p class="lead">'+sLineBreak+
|
||||
' <a class="btn btn-primary btn-lg" href="#" role="button">Learn more</a>'+sLineBreak+
|
||||
'</p>'+sLineBreak+
|
||||
'</div>';
|
||||
end;
|
||||
|
||||
|
||||
{ TRegisteredWidget }
|
||||
|
||||
constructor TRegisteredWidget.Create(aClass: TWebwidgetClass; aImageName: String);
|
||||
begin
|
||||
FClass:=aClass;
|
||||
FImageName:=aImageName;
|
||||
end;
|
||||
|
||||
{ TDesignDemo }
|
||||
|
||||
function TDesignDemo.CreateNewWidget(aParent : TCustomWebWidget; aClass : TCustomWebWidgetClass) : TCustomWebWidget;
|
||||
|
||||
begin
|
||||
Result:=aClass.Create(FPage);
|
||||
Result.Name:=Result.ClassName+IntToStr(FPage.ChildCount);
|
||||
Result.Parent:=aParent;
|
||||
Result.Refresh;
|
||||
if Assigned(IDEClient) then
|
||||
IDEClient.SendAction('create',New(['widget',Result.Name,'class',Result.ClassName]));
|
||||
|
||||
end;
|
||||
|
||||
function TDesignDemo.StreamWidget(aWidget : TCustomWebWidget) : String;
|
||||
|
||||
Var
|
||||
S: TBytesStream;
|
||||
T : TStringStream;
|
||||
|
||||
begin
|
||||
T:=Nil;
|
||||
S:=TBytesStream.Create(Nil);
|
||||
try
|
||||
S.WriteComponent(aWidget);
|
||||
S.Position:=0;
|
||||
T:=TStringStream.Create('');
|
||||
ObjectBinaryToText(S,T);
|
||||
Result:=T.DataString;
|
||||
finally
|
||||
T.Free;
|
||||
S.Free;
|
||||
end;
|
||||
end;
|
||||
function TDesignDemo.DoActive(Event: TEventListenerEvent): boolean;
|
||||
|
||||
Const
|
||||
Toolbar = '<div class="designerToolbar">' +
|
||||
'<div class="designerDragHandle ui-icon ui-icon-arrow-4"></div>' +
|
||||
'<div class="designerDelete ui-icon ui-icon-trash"></div>' +
|
||||
'</div>';
|
||||
|
||||
var
|
||||
aParent,aNew : TCustomWebWidget;
|
||||
aNewActive : TJSHTMLElement;
|
||||
aNewWidget : TRegisteredWidget;
|
||||
|
||||
begin
|
||||
Result:=True;
|
||||
JQuery('.designerActive').removeClass('designerActive');
|
||||
JQuery('.designerToolbar').remove();
|
||||
aNewActive:=TJSHTMLElement(event.target);
|
||||
aParent:=FPage.FindWidgetByID(String(aNewActive.dataset[STopElementData]));
|
||||
if (FAddWidget<>Nil) and (aParent<>Nil) then
|
||||
begin
|
||||
aNewWidget:=FAddWidget;
|
||||
FAddWidget:=Nil;
|
||||
if aParent<>Nil then
|
||||
begin
|
||||
aNew:=CreateNewWidget(aParent,aNewWidget.WidgetClass);
|
||||
aNewActive:=TWidgetHack(aNew).TopElement;
|
||||
if Assigned(TWidgetHack(aNew).ContentElement) then
|
||||
JQuery(TWidgetHack(aNew).ContentElement).Sortable(SortableOptions);
|
||||
jQuery(aNewActive).on_('click',@DoActive);
|
||||
aParent:=aNew;
|
||||
end;
|
||||
end;
|
||||
JQuery(aNewActive).AddClass('designerActive').prepend(toolbar);
|
||||
if assigned(aParent) and Assigned(IDEClient) then
|
||||
IDEClient.SendAction('select',New(['widget',aParent.Name,'class',aParent.ClassName,'state',StreamWidget(aParent)]));
|
||||
end;
|
||||
|
||||
procedure TDesignDemo.SetAddMode(aRegisteredWidget : TRegisteredWidget);
|
||||
|
||||
begin
|
||||
FAddWidget:=aRegisteredWidget;
|
||||
end;
|
||||
|
||||
procedure TDesignDemo.DoWidgetAddClick(Sender: TObject; Event: TJSEvent);
|
||||
begin
|
||||
SetAddMode((Sender as TWidgetButtonWidget).MyWidget);
|
||||
end;
|
||||
|
||||
procedure TDesignDemo.AddWidgetByName(aID : NativeInt; AName : String);
|
||||
|
||||
Var
|
||||
I : integer;
|
||||
Btn : TWidgetButtonWidget;
|
||||
|
||||
begin
|
||||
I:=FRegisteredWidgets.Count-1;
|
||||
While (i>=0) and Not SameText(TRegisteredWidget(FRegisteredWidgets[i]).WidgetClass.ClassName,aName) do
|
||||
Dec(I);
|
||||
if I<0 then exit;
|
||||
SetAddMode(TRegisteredWidget(FRegisteredWidgets[i]));
|
||||
FConfirmAdd:=aID;
|
||||
end;
|
||||
|
||||
procedure TDesignDemo.DoCommandsReceived(Sender: TObject; aCommands: TJSArray);
|
||||
|
||||
var
|
||||
J,P : TJSOBject;
|
||||
aName : String;
|
||||
aID : NativeInt;
|
||||
I : integer;
|
||||
|
||||
begin
|
||||
for I:=0 to aCommands.Length-1 do
|
||||
begin
|
||||
J:=TJSObject(aCommands[i]);
|
||||
aName:=String(J['name']);
|
||||
aID:=NativeInt(J['id']);
|
||||
p:=TJSObject(J['payload']);
|
||||
case aName of
|
||||
'addWidget' : AddWidgetByName(aID,String(P['class']));
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TDesignDemo.SetIDEClient(AValue: TIDEClient);
|
||||
begin
|
||||
if FIDEClient=AValue then Exit;
|
||||
FIDEClient:=AValue;
|
||||
if assigned(FIDEClient) then
|
||||
begin
|
||||
FIDEClient.OnCommands:=@DoCommandsReceived;
|
||||
FIDEClient.StartCommandPolling;
|
||||
FToolBar.Visible:=False;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TDesignDemo.SortableOptions: TJSObject;
|
||||
|
||||
begin
|
||||
Result:=New([
|
||||
'items','> [data-ww-element-top]',
|
||||
'connectWith','[data-ww-element-content]',
|
||||
'placeholder','designerPlaceholder',
|
||||
'tolerance','pointer',
|
||||
// 'containment',TPageHack(FPage).Element,
|
||||
// 'handle','[data-ww-element-top]',
|
||||
'cancel',''
|
||||
]);
|
||||
end;
|
||||
|
||||
constructor TDesignDemo.Create(aOwner: TComponent);
|
||||
|
||||
begin
|
||||
inherited Create(aOwner);
|
||||
FRegisteredWidgets:=TObjectList.Create;
|
||||
RegisterWidgets;
|
||||
FillToolBar;
|
||||
SetUpPage;
|
||||
// []
|
||||
JQuery(SSortableSelect).Sortable(SortableOptions);
|
||||
jQuery('#designpage').on_('click','[data-ww-element-top]',@DoActive);
|
||||
jQuery('#designpage').on_('click',@DoActive);
|
||||
end;
|
||||
|
||||
destructor TDesignDemo.Destroy;
|
||||
begin
|
||||
FreeAndNil(FRegisteredWidgets);
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
procedure TDesignDemo.RegisterWidgets;
|
||||
|
||||
begin
|
||||
RegisterWidget(TButtonWidget,'button');
|
||||
RegisterWidget(TCheckBoxInputWidget,'checkbox');
|
||||
RegisterWidget(TRadioInputWidget,'radio');
|
||||
RegisterWidget(TTextInputWidget,'edit');
|
||||
RegisterWidget(TImageWidget,'image');
|
||||
RegisterWidget(TTextAreaWidget,'memo');
|
||||
RegisterWidget(TSelectWidget,'select');
|
||||
RegisterWidget(TContainerWidget,'container');
|
||||
RegisterWidget(TJumboWidget,'jumbo');
|
||||
end;
|
||||
|
||||
procedure TDesignDemo.RegisterWidget(aClass: TWebWidgetClass; aImageName: String);
|
||||
begin
|
||||
FRegisteredWidgets.Add(TRegisteredWidget.Create(aClass,aImageName));
|
||||
end;
|
||||
|
||||
procedure TDesignDemo.SetupPage;
|
||||
|
||||
Const
|
||||
ButtonClasses : Array[0..8] of string
|
||||
= ('primary','secondary','success','danger','warning','info','light','dark','link');
|
||||
|
||||
var
|
||||
I : Integer;
|
||||
|
||||
begin
|
||||
FPage:=TWebPage.Create(Self);
|
||||
FPage.ElementID:='designpage';
|
||||
FPage.Refresh;
|
||||
For I:=0 to 9 do
|
||||
begin
|
||||
FButtons[I]:=TWidgetButtonWidget.Create(FPage);
|
||||
FButtons[I].Classes:='btn btn-'+ButtonClasses[I mod 9];
|
||||
FButtons[I].Text:='Button #'+IntToStr(I+1);
|
||||
FButtons[I].Parent:=FPage;
|
||||
FButtons[I].Refresh;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TDesignDemo.FillToolBar;
|
||||
|
||||
Var
|
||||
RW : TRegisteredWidget;
|
||||
I : Integer;
|
||||
Btn : TWidgetButtonWidget;
|
||||
|
||||
begin
|
||||
FToolBar:=TContainerWidget.Create(Self);
|
||||
FToolbar.Styles.EnsureStyle('min-height','34px');
|
||||
FToolbar.Styles.RemoveStyle('width');
|
||||
FToolbar.Styles.RemoveStyle('height');
|
||||
FToolbar.ElementId:='toolbar';
|
||||
FToolbar.Refresh;
|
||||
SetLength(FWidgetButtons,FRegisteredWidgets.Count);
|
||||
For I:=0 to FRegisteredWidgets.Count-1 do
|
||||
begin
|
||||
RW:=TRegisteredWidget(FRegisteredWidgets[I]);
|
||||
Btn:=TWidgetButtonWidget.Create(Self);
|
||||
FWidgetButtons[I]:=Btn;
|
||||
Btn.MyWidget:=RW;
|
||||
Btn.Classes:='btn btn-light';
|
||||
Btn.Text:='';
|
||||
Btn.Parent:=FToolbar;
|
||||
Btn.Styles.Add('background-image','url("widgets/'+RW.ImageName+'.png")');
|
||||
Btn.Refresh;
|
||||
Btn.Data['widgetClass']:=RW.WidgetClass.ClassName;
|
||||
Btn.OnClick:=@DoWidgetAddClick;
|
||||
end;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
232
demo/webwidget/designdemo/webideclient.pp
Normal file
@ -0,0 +1,232 @@
|
||||
unit webideclient;
|
||||
|
||||
{$mode objfpc}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils, js, web;
|
||||
|
||||
type
|
||||
TIDEClient = Class;
|
||||
|
||||
TIDEResponseHandler = Procedure (aCode : Integer; aCodeText : String; aPayload : TJSObject) of object;
|
||||
|
||||
{ TIDERequest }
|
||||
|
||||
TIDERequest = Class(TObject)
|
||||
Private
|
||||
FXHR : TJSXMLHttpRequest;
|
||||
FOnResponse: TIDEResponseHandler;
|
||||
Procedure ProcessResponse;
|
||||
procedure DoStateChange;
|
||||
Public
|
||||
Constructor Create(aMethod, aURl: String; aPayLoad: TJSObject; aOnResponse: TIDEResponseHandler);
|
||||
end;
|
||||
|
||||
{ TIDEClient }
|
||||
TCommandEvent = Procedure (Sender : TObject; aCommands : TJSArray) of object;
|
||||
TActionEvent = Procedure (Sender : TObject; aID : Int64; aName : String; aPayload : TJSObject) of object;
|
||||
|
||||
TIDEClient = Class(TComponent)
|
||||
private
|
||||
FActionID : NativeInt;
|
||||
FOnActionResponse: TActionEvent;
|
||||
FPollID : NativeInt;
|
||||
FCommandPollInterval : Integer;
|
||||
FClientID: NativeInt;
|
||||
FIDEURL: String;
|
||||
FOnCommands: TCommandEvent;
|
||||
FLastPoll : TIDERequest;
|
||||
FStartPolling : Boolean;
|
||||
procedure DoCommandPoll;
|
||||
procedure OnActionSent(aCode: Integer; aCodeText: String; aPayload: TJSObject);
|
||||
procedure OnClientRegistered(aCode: Integer; aCodeText: String; aPayload: TJSObject);
|
||||
procedure OnCommandsReceived(aCode: Integer; aCodeText: String; aPayload: TJSObject);
|
||||
Public
|
||||
Constructor Create(aOwner : TComponent); override;
|
||||
Procedure RegisterClient;
|
||||
Procedure UnRegisterClient;
|
||||
Procedure StartCommandPolling;
|
||||
Procedure StopCommandPolling;
|
||||
Function GetNextID : NativeInt;
|
||||
procedure SendAction(Const aName : String; aPayLoad : TJSObject);
|
||||
Property IDEURL : String read FIDEURL Write FIDEURL;
|
||||
Property ClientID : Int64 read FClientID Write FClientID;
|
||||
Property CommandPollInterval : Integer Read FCommandPollInterval Write FCommandPollInterval;
|
||||
Property OnCommands : TCommandEvent Read FOnCommands Write FOnCommands;
|
||||
Property OnActionResponse : TActionEvent Read FOnActionResponse Write FOnActionResponse;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TIDEClient }
|
||||
|
||||
|
||||
procedure TIDEClient.DoCommandPoll;
|
||||
|
||||
begin
|
||||
if Not Assigned(FLastPoll) then
|
||||
FLastPoll:=TIDERequest.Create('Get',IDEURL+'Command/'+IntToStr(ClientID)+'/',Nil,@OnCommandsReceived);
|
||||
end;
|
||||
|
||||
procedure TIDEClient.OnActionSent(aCode: Integer; aCodeText: String; aPayload: TJSObject);
|
||||
|
||||
Var
|
||||
aID : NativeInt;
|
||||
aName : string;
|
||||
aActionPayload : TJSObject;
|
||||
|
||||
begin
|
||||
if (aCode div 100)=2 then
|
||||
begin
|
||||
aID:=NativeInt(aPayLoad['id']);
|
||||
aName:=String(aPayLoad['name']);
|
||||
aActionPayLoad:=TJSObject(aPayLoad['payload']);
|
||||
If Assigned(OnActionResponse) then
|
||||
OnActionResponse(Self,aID,aName,aActionPayload);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TIDEClient.OnClientRegistered(aCode: Integer; aCodeText: String; aPayload: TJSObject);
|
||||
|
||||
begin
|
||||
if (aCode div 100)=2 then
|
||||
begin
|
||||
FClientID:=NativeInt(aPayload['id']);
|
||||
if FStartPolling then
|
||||
StartCommandPolling;
|
||||
end
|
||||
else
|
||||
FClientID:=0;
|
||||
end;
|
||||
|
||||
procedure TIDEClient.OnCommandsReceived(aCode: Integer; aCodeText: String; aPayload: TJSObject);
|
||||
|
||||
Var
|
||||
A: TJSArray;
|
||||
|
||||
begin
|
||||
FLastPoll:=Nil;
|
||||
if (aCode div 100)<>2 then
|
||||
exit;
|
||||
if Assigned(aPayload) and isArray(aPayload['commands']) then
|
||||
begin
|
||||
A:=TJSArray(aPayload['commands']);
|
||||
if (A.Length>0) then
|
||||
OnCommands(Self,A);
|
||||
end;
|
||||
end;
|
||||
|
||||
constructor TIDEClient.Create(aOwner: TComponent);
|
||||
begin
|
||||
Inherited;
|
||||
FLastPoll:=Nil;
|
||||
IDEURL:='http://'+Window.location.hostname+':'+Window.location.port+'/IDE/';
|
||||
end;
|
||||
|
||||
procedure TIDEClient.RegisterClient;
|
||||
|
||||
Var
|
||||
P : TJSObject;
|
||||
Req : TIDERequest;
|
||||
|
||||
begin
|
||||
P:=New(['url',window.locationString]);
|
||||
req:=TIDERequest.Create('POST',IDEURL+'Client',P,@OnClientRegistered);
|
||||
end;
|
||||
|
||||
procedure TIDEClient.UnRegisterClient;
|
||||
|
||||
Var
|
||||
Req : TIDERequest;
|
||||
|
||||
begin
|
||||
Req:=TIDERequest.Create('DELETE',IDEURL+'Client/'+IntToStr(ClientID),Nil,@OnClientRegistered);
|
||||
end;
|
||||
|
||||
procedure TIDEClient.StartCommandPolling;
|
||||
begin
|
||||
if ClientID<>0 then
|
||||
FPollID:=Window.setInterval(@DoCommandPoll,FCommandPollInterval)
|
||||
else
|
||||
FStartPolling:=True;
|
||||
end;
|
||||
|
||||
procedure TIDEClient.StopCommandPolling;
|
||||
begin
|
||||
FStartPolling:=False;
|
||||
if (FPollID>0) then
|
||||
Window.clearInterval(FPollID);
|
||||
end;
|
||||
|
||||
function TIDEClient.GetNextID: NativeInt;
|
||||
begin
|
||||
Inc(FActionID);
|
||||
Result:=FActionID;
|
||||
end;
|
||||
|
||||
procedure TIDEClient.SendAction(const aName: String; aPayLoad: TJSObject);
|
||||
|
||||
Var
|
||||
aAction : TJSObject;
|
||||
aID : NativeInt;
|
||||
req: TIDERequest;
|
||||
|
||||
begin
|
||||
aID:=GetNextID;
|
||||
aAction:=New(['id',aID,
|
||||
'name',aName,
|
||||
'payload',aPayLoad]);
|
||||
req:=TIDERequest.Create('POST',IDEURL+'Action/'+IntToStr(ClientID)+'/'+IntToStr(aID),aAction,@OnActionSent);
|
||||
end;
|
||||
|
||||
{ TIDERequest }
|
||||
|
||||
procedure TIDERequest.ProcessResponse;
|
||||
|
||||
var
|
||||
P : TJSObject;
|
||||
|
||||
begin
|
||||
if ((FXHR.Status div 100)=2) and (FXHR.ResponseHeaders['Content-Type']='application/json') then
|
||||
P:=TJSJSON.parseObject(FXHR.responseText)
|
||||
else
|
||||
P:=Nil;
|
||||
if Assigned(FOnResponse) then
|
||||
FOnResponse(FXHR.Status,FXHR.StatusText,P);
|
||||
end;
|
||||
|
||||
procedure TIDERequest.DoStateChange;
|
||||
begin
|
||||
case FXHR.readyState of
|
||||
TJSXMLHttpRequest.DONE :
|
||||
begin
|
||||
if Assigned(FOnResponse) then
|
||||
ProcessResponse;
|
||||
Free;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
constructor TIDERequest.Create(aMethod, aURl: String; aPayLoad: TJSObject; aOnResponse: TIDEResponseHandler);
|
||||
|
||||
Var
|
||||
S : String;
|
||||
|
||||
begin
|
||||
FOnResponse:=aOnResponse;
|
||||
FXHR:=TJSXMLHttpRequest.New;
|
||||
FXHR.open(aMethod,aURL);
|
||||
if assigned(aPayload) then
|
||||
S:=TJSJSON.Stringify(aPayload)
|
||||
else
|
||||
S:='';
|
||||
FXHR.setRequestHeader('Content-Type','application/json');
|
||||
FXHR.onreadystatechange:=@DoStateChange;
|
||||
FXHR.send(S);
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
|
BIN
demo/webwidget/designdemo/widgets/button.png
Normal file
After Width: | Height: | Size: 257 B |
BIN
demo/webwidget/designdemo/widgets/checkbox.png
Normal file
After Width: | Height: | Size: 375 B |
BIN
demo/webwidget/designdemo/widgets/container.png
Normal file
After Width: | Height: | Size: 194 B |
BIN
demo/webwidget/designdemo/widgets/edit.png
Normal file
After Width: | Height: | Size: 421 B |
BIN
demo/webwidget/designdemo/widgets/image.png
Normal file
After Width: | Height: | Size: 472 B |
BIN
demo/webwidget/designdemo/widgets/jumbo.png
Normal file
After Width: | Height: | Size: 781 B |
BIN
demo/webwidget/designdemo/widgets/memo.png
Normal file
After Width: | Height: | Size: 275 B |
BIN
demo/webwidget/designdemo/widgets/radio.png
Normal file
After Width: | Height: | Size: 699 B |
BIN
demo/webwidget/designdemo/widgets/select.png
Normal file
After Width: | Height: | Size: 257 B |
1553
demo/webwidget/nativedesign/frmmain.lfm
Normal file
556
demo/webwidget/nativedesign/frmmain.pp
Normal file
@ -0,0 +1,556 @@
|
||||
unit frmmain;
|
||||
|
||||
{$mode objfpc}{$H+}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils, webideintf, Forms, Controls, Graphics, Dialogs, EditBtn, ExtCtrls, ComCtrls, StdCtrls, ActnList,
|
||||
{$IFDEF LINUX}
|
||||
WebBrowserCtrls, WebBrowserIntf,
|
||||
{$ELSE}
|
||||
{$IFDEF WINDOWS}
|
||||
Windows, Messages, uCEFChromium, uCEFWindowParent, uCEFChromiumWindow, uCEFTypes, uCEFInterfaces, uCEFWinControl, uCEFApplication,
|
||||
{$ELSE}
|
||||
{$ERROR Unsupported platform}
|
||||
{$ENDIF}
|
||||
{$ENDIF} fpJSON;
|
||||
|
||||
type
|
||||
|
||||
{ TMainForm }
|
||||
|
||||
TMainForm = class(TForm)
|
||||
AGoExternal: TAction;
|
||||
AGo: TAction;
|
||||
ALWidgets: TActionList;
|
||||
FEProject: TFileNameEdit;
|
||||
ILWidgets: TImageList;
|
||||
MLog: TMemo;
|
||||
PCDesigner: TPageControl;
|
||||
Project: TLabel;
|
||||
PBottom: TPanel;
|
||||
TBExternalGo: TToolButton;
|
||||
tmrShowChromium: TTimer;
|
||||
TSInspector: TTabSheet;
|
||||
TSBrowser: TTabSheet;
|
||||
TSLog: TTabSheet;
|
||||
TBWidgets: TToolBar;
|
||||
TBGo: TToolButton;
|
||||
ToolButton1: TToolButton;
|
||||
procedure AGoExecute(Sender: TObject);
|
||||
procedure AGoExternalExecute(Sender: TObject);
|
||||
procedure AGoUpdate(Sender: TObject);
|
||||
procedure DEProjectEditingDone(Sender: TObject);
|
||||
procedure FormCloseQuery(Sender: TObject; var CanClose: boolean);
|
||||
procedure FormCreate(Sender: TObject);
|
||||
procedure FormShow(Sender: TObject);
|
||||
procedure tmrShowChromiumTimer(Sender: TObject);
|
||||
private
|
||||
FClientID : Int64; // Just one for now
|
||||
FDesignCaption : String;
|
||||
FWebIDEIntf : TIDEServer;
|
||||
FWidgetCount : Integer;
|
||||
FWidgets : Array of String;
|
||||
FURL : String;
|
||||
FURLCount : Integer;
|
||||
FCanClose : Boolean;
|
||||
FAllowGo: Boolean;
|
||||
{$IFDEF LINUX}
|
||||
FWBDesign : TWebBrowser;
|
||||
FWIDesign : TWebInspector;
|
||||
FLastEmbeddedURI : String;
|
||||
procedure wbDesignConsoleMessage(Sender: TObject; const Message, Source: string; Line: Integer);
|
||||
procedure wbDesignError(Sender: TObject; const Uri: string; ErrorCode: LongWord; const ErrorMessage: string; var Handled: Boolean);
|
||||
procedure wbDesignFavicon(Sender: TObject);
|
||||
procedure wbDesignHitTest(Sender: TObject; X, Y: Integer; HitTest: TWebHitTest; const Link, Media: string);
|
||||
procedure wbDesignLoadStatusChange(Sender: TObject);
|
||||
procedure wbDesignLocationChange(Sender: TObject);
|
||||
procedure wbDesignNavigate(Sender: TObject; const Uri: string; var aAction: TWebNavigateAction);
|
||||
procedure wbDesignProgress(Sender: TObject; Progress: Integer);
|
||||
procedure wbDesignRequest(Sender: TObject; var Uri: string);
|
||||
procedure wbDesignScriptDialog(Sender: TObject; Dialog: TWebScriptDialog; const Message: string; var Input: string; var Accepted: Boolean; var Handled: Boolean);
|
||||
{$ENDIF}
|
||||
{$IFDEF WINDOWS}
|
||||
FClosing : Boolean;
|
||||
cwDesign : TChromiumWindow;
|
||||
procedure WMMove(var aMessage : TWMMove); message WM_MOVE;
|
||||
procedure WMMoving(var aMessage : TMessage); message WM_MOVING;
|
||||
// You also have to handle these two messages to set GlobalCEFApp.OsmodalLoop
|
||||
procedure WMEnterMenuLoop(var aMessage: TMessage); message WM_ENTERMENULOOP;
|
||||
procedure WMExitMenuLoop(var aMessage: TMessage); message WM_EXITMENULOOP;
|
||||
procedure cwBeforeClose(Sender: TObject);
|
||||
procedure cwClose(Sender: TObject);
|
||||
procedure cwAfterCreated(Sender: TObject);
|
||||
procedure cwOnBeforePopup(Sender: TObject;
|
||||
const browser: ICefBrowser; const frame: ICefFrame; const targetUrl,
|
||||
targetFrameName: ustring; targetDisposition: TCefWindowOpenDisposition;
|
||||
userGesture: Boolean; const popupFeatures: TCefPopupFeatures;
|
||||
var windowInfo: TCefWindowInfo; var client: ICefClient;
|
||||
var settings: TCefBrowserSettings;
|
||||
var extra_info: ICefDictionaryValue;
|
||||
var noJavascriptAccess: Boolean;
|
||||
var Result: Boolean);
|
||||
{$ENDIF}
|
||||
function GetProjectURL: String;
|
||||
procedure DoAddWidget(Sender: TObject);
|
||||
procedure DoAction(Sender: TObject; aExchange: TIDEExchange);
|
||||
procedure DoClientCame(Sender: TObject; aClient: TIDEClient);
|
||||
procedure DoClientLeft(Sender: TObject; aClient: TIDEClient);
|
||||
procedure DoLogRequest(Sender: TObject; aURL: String);
|
||||
procedure IsWidgetEnabled(Sender: TObject);
|
||||
procedure LogRequest;
|
||||
Procedure RegisterWidgets;
|
||||
Procedure RegisterWidget(aWidget: String; aImageIndex : Integer);
|
||||
procedure SetUpEmbeddedBrowser;
|
||||
public
|
||||
Procedure Log(Msg : String);
|
||||
Procedure Log(Fmt : String; Args : Array of const);
|
||||
end;
|
||||
|
||||
var
|
||||
MainForm: TMainForm;
|
||||
|
||||
implementation
|
||||
|
||||
uses lclintf, fpmimetypes;
|
||||
|
||||
{$R *.lfm}
|
||||
|
||||
{ TMainForm }
|
||||
|
||||
procedure TMainForm.DEProjectEditingDone(Sender: TObject);
|
||||
begin
|
||||
FWebIDEIntf.ProjectDir:=ExtractFilePath(FEProject.FileName);
|
||||
end;
|
||||
|
||||
procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: boolean);
|
||||
begin
|
||||
FWebIDEIntf.Active:=False;
|
||||
CanClose:=FCanClose;
|
||||
{$IFDEF WINDOWS}
|
||||
if not(FClosing) then
|
||||
begin
|
||||
FClosing := True;
|
||||
Visible := False;
|
||||
cwDesign.CloseBrowser(True);
|
||||
end;
|
||||
{$ENDIF}
|
||||
end;
|
||||
|
||||
Function TMainForm.GetProjectURL : String;
|
||||
|
||||
begin
|
||||
Result:=Format('http://localhost:%d/Project/%s',[FWebIDEIntf.Port,ExtractFileName(FEProject.FileName)]);
|
||||
end;
|
||||
|
||||
procedure TMainForm.AGoExecute(Sender: TObject);
|
||||
|
||||
Var
|
||||
URL : String;
|
||||
|
||||
begin
|
||||
URL:=GetProjectURL;
|
||||
Log('Going to URL: %s',[URL]);
|
||||
{$IFDEF LINUX}
|
||||
FWBDesign.Location:=URL;
|
||||
{$ENDIF}
|
||||
{$IFDEF WINDOWS}
|
||||
cwDesign.LoadURL(URL);
|
||||
{$ENDIF}
|
||||
end;
|
||||
|
||||
procedure TMainForm.AGoExternalExecute(Sender: TObject);
|
||||
Var
|
||||
URL : String;
|
||||
|
||||
begin
|
||||
URL:=GetProjectURL;
|
||||
Log('Going to URL: %s',[URL]);
|
||||
OpenURL(URL);
|
||||
end;
|
||||
|
||||
procedure TMainForm.AGoUpdate(Sender: TObject);
|
||||
begin
|
||||
(Sender as Taction).Enabled:=FAllowGo;
|
||||
end;
|
||||
|
||||
procedure TMainForm.FormCreate(Sender: TObject);
|
||||
|
||||
|
||||
|
||||
begin
|
||||
FAllowGo:=False;
|
||||
FDesignCaption:=Caption;
|
||||
{$IFDEF Linux}
|
||||
MimeTypes.LoadFromFile('/etc/mime.types');
|
||||
{$ENDIF}
|
||||
{$IFDEF WINDOWS}
|
||||
MimeTypes.LoadFromFile(ExtractFilePath(Paramstr(0))+'mime.types');
|
||||
{$ENDIF}
|
||||
FEProject.FileName:=ExtractFilePath(Paramstr(0))+'designdemo'+PathDelim+'designdemo.html';
|
||||
FWebIDEIntf:=TIDEServer.Create(Self);
|
||||
FWebIDEIntf.ProjectDir:=ExtractFilePath(FEProject.FileName);
|
||||
FWebIDEIntf.OnClientAdded:=@DoClientCame;
|
||||
FWebIDEIntf.OnClientRemoved:=@DoClientLeft;
|
||||
FWebIDEIntf.OnRequest:=@DoLogRequest;
|
||||
FWebIDEIntf.OnAction:=@DoAction;
|
||||
FWebIDEIntf.Active:=True;
|
||||
RegisterWidgets;
|
||||
SetUpEmbeddedBrowser;
|
||||
end;
|
||||
|
||||
procedure TMainForm.FormShow(Sender: TObject);
|
||||
begin
|
||||
{$IFDEF WINDOWS}
|
||||
with cwDesign do
|
||||
begin
|
||||
ChromiumBrowser.OnBeforePopup := @cwOnBeforePopup;
|
||||
if not CreateBrowser then
|
||||
tmrShowChromium.Enabled := True;
|
||||
end;
|
||||
{$ENDIF}
|
||||
end;
|
||||
|
||||
procedure TMainForm.tmrShowChromiumTimer(Sender: TObject);
|
||||
begin
|
||||
tmrShowChromium.Enabled := False;
|
||||
{$IFDEF WINDOWS}
|
||||
With cwDesign do
|
||||
if not (CreateBrowser or Initialized) then
|
||||
tmrShowChromium.Enabled := True;
|
||||
{$ENDIF}
|
||||
end;
|
||||
|
||||
{$IFDEF WINDOWS}
|
||||
procedure TMainForm.SetUpEmbeddedBrowser;
|
||||
|
||||
begin
|
||||
FCanClose:=False;
|
||||
cwDesign:=TChromiumWindow.Create(Self);
|
||||
With cwDesign do
|
||||
begin
|
||||
Parent:=TSBrowser;
|
||||
Align:=alClient;
|
||||
OnClose:=@cwClose;
|
||||
OnBeforeClose:=@cwBeforeClose;
|
||||
OnAfterCreated:=@cwAfterCreated;
|
||||
end;
|
||||
TSInspector.TabVisible:=False;
|
||||
end;
|
||||
|
||||
procedure TMainForm.WMMove(var aMessage : TWMMove);
|
||||
|
||||
begin
|
||||
inherited;
|
||||
if (cwDesign <> nil) then
|
||||
cwDesign.NotifyMoveOrResizeStarted;
|
||||
end;
|
||||
|
||||
procedure TMainForm.WMMoving(var aMessage : TMessage);
|
||||
|
||||
begin
|
||||
inherited;
|
||||
if (cwDesign <> nil) then
|
||||
cwDesign.NotifyMoveOrResizeStarted;
|
||||
end;
|
||||
|
||||
procedure TMainForm.WMEnterMenuLoop(var aMessage: TMessage);
|
||||
|
||||
begin
|
||||
inherited;
|
||||
if (aMessage.wParam = 0) and (GlobalCEFApp <> nil) then
|
||||
GlobalCEFApp.OsmodalLoop := True;
|
||||
end;
|
||||
|
||||
procedure TMainForm.WMExitMenuLoop(var aMessage: TMessage);
|
||||
|
||||
begin
|
||||
inherited;
|
||||
if (aMessage.wParam = 0) and (GlobalCEFApp <> nil) then
|
||||
GlobalCEFApp.OsmodalLoop := False;
|
||||
end;
|
||||
|
||||
procedure TMainForm.cwBeforeClose(Sender: TObject);
|
||||
begin
|
||||
FCanClose := True;
|
||||
PostMessage(Handle, WM_CLOSE, 0, 0);
|
||||
end;
|
||||
|
||||
|
||||
procedure TMainForm.cwClose(Sender: TObject);
|
||||
begin
|
||||
// DestroyChildWindow will destroy the child window created by CEF at the top of the Z order.
|
||||
if not(cwDesign.DestroyChildWindow) then
|
||||
begin
|
||||
FCanClose := True;
|
||||
Close;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TMainForm.cwOnBeforePopup(Sender: TObject;
|
||||
const browser: ICefBrowser; const frame: ICefFrame; const targetUrl,
|
||||
targetFrameName: ustring; targetDisposition: TCefWindowOpenDisposition;
|
||||
userGesture: Boolean; const popupFeatures: TCefPopupFeatures;
|
||||
var windowInfo: TCefWindowInfo; var client: ICefClient;
|
||||
var settings: TCefBrowserSettings;
|
||||
var extra_info: ICefDictionaryValue;
|
||||
var noJavascriptAccess: Boolean;
|
||||
var Result: Boolean);
|
||||
begin
|
||||
// For simplicity, this demo blocks all popup windows and new tabs
|
||||
Result := (targetDisposition in [WOD_NEW_FOREGROUND_TAB, WOD_NEW_BACKGROUND_TAB, WOD_NEW_POPUP, WOD_NEW_WINDOW]);
|
||||
end;
|
||||
|
||||
procedure TMainForm.cwAfterCreated(Sender: TObject);
|
||||
begin
|
||||
// Now the browser is fully initialized we can load the initial web page.
|
||||
FAllowGo:=True;
|
||||
end;
|
||||
|
||||
{$ENDIF}
|
||||
|
||||
{$IFDEF LINUX}
|
||||
procedure TMainForm.SetUpEmbeddedBrowser;
|
||||
|
||||
begin
|
||||
FAllowGo:=True;
|
||||
FCanClose:=True;
|
||||
FWBDesign:=TWebBrowser.Create(Self);
|
||||
With FWBDesign do
|
||||
begin
|
||||
Parent:=TSBrowser;
|
||||
Align:=alClient;
|
||||
DesignMode := False;
|
||||
SourceView := False;
|
||||
ZoomContent := False;
|
||||
ZoomFactor := 1;
|
||||
{ lots of logging }
|
||||
OnConsoleMessage:=@wbDesignConsoleMessage;
|
||||
OnScriptDialog:=@wbDesignScriptDialog;
|
||||
OnError:=@wbDesignError;
|
||||
OnFavicon:=@wbDesignFavicon;
|
||||
OnHitTest:=@wbDesignHitTest;
|
||||
OnLoadStatusChange:=@wbDesignLoadStatusChange;
|
||||
OnLocationChange:=@wbDesignLocationChange;
|
||||
OnNavigate:=@wbDesignNavigate;
|
||||
OnProgress:=@wbDesignProgress;
|
||||
OnRequest:=@wbDesignRequest;
|
||||
end;
|
||||
FWIDesign:=TWebInspector.Create(Self);
|
||||
With FWIDesign do
|
||||
begin
|
||||
Parent:=TSInspector;
|
||||
Align:=alClient;
|
||||
Active:=True;
|
||||
WebBrowser:=FWBDesign;
|
||||
end;
|
||||
TSInspector.TabVisible:=true;
|
||||
PCDesigner.ActivePage:=TSBrowser;
|
||||
end;
|
||||
|
||||
procedure TMainForm.wbDesignConsoleMessage(Sender: TObject; const Message, Source: string; Line: Integer);
|
||||
begin
|
||||
Log('Console message: %s (%s: %d)',[Message,Source,Line]);
|
||||
end;
|
||||
|
||||
|
||||
procedure TMainForm.wbDesignError(Sender: TObject; const Uri: string; ErrorCode: LongWord; const ErrorMessage: string;
|
||||
var Handled: Boolean);
|
||||
begin
|
||||
Log('Error: %s, code: %d, Message: %s',[URI,ErrorCode,ErrorMessage]);
|
||||
Handled:=True;
|
||||
end;
|
||||
|
||||
procedure TMainForm.wbDesignFavicon(Sender: TObject);
|
||||
begin
|
||||
Log('Favicon available/missed');
|
||||
end;
|
||||
|
||||
procedure TMainForm.wbDesignHitTest(Sender: TObject; X, Y: Integer; HitTest: TWebHitTest; const Link, Media: string);
|
||||
begin
|
||||
// Log('Hit test (%d,%d) link: %s, media: %s',[x,y,link,media]);
|
||||
end;
|
||||
|
||||
procedure TMainForm.wbDesignLoadStatusChange(Sender: TObject);
|
||||
begin
|
||||
Log('Load status change');
|
||||
end;
|
||||
|
||||
procedure TMainForm.wbDesignLocationChange(Sender: TObject);
|
||||
begin
|
||||
Log('Location change');
|
||||
end;
|
||||
|
||||
procedure TMainForm.wbDesignNavigate(Sender: TObject; const Uri: string; var aAction: TWebNavigateAction);
|
||||
begin
|
||||
Log('Navigation: %s',[URI]);
|
||||
aAction:=naAllow;
|
||||
end;
|
||||
|
||||
procedure TMainForm.wbDesignProgress(Sender: TObject; Progress: Integer);
|
||||
begin
|
||||
Log('Progress: %d',[Progress])
|
||||
end;
|
||||
|
||||
procedure TMainForm.wbDesignRequest(Sender: TObject; var Uri: string);
|
||||
begin
|
||||
if Uri<>FLastEmbeddedURI then
|
||||
Log('Embedded browser doing request : %s',[URI]);
|
||||
FLastEmbeddedURI:=URI;
|
||||
end;
|
||||
|
||||
procedure TMainForm.wbDesignScriptDialog(Sender: TObject; Dialog: TWebScriptDialog; const Message: string; var Input: string;
|
||||
var Accepted: Boolean; var Handled: Boolean);
|
||||
begin
|
||||
Log('Script dialog Message: %s; Input : %s',[message,Input]);
|
||||
Accepted:=true;
|
||||
Handled:=true;
|
||||
end;
|
||||
{$ENDIF}
|
||||
|
||||
procedure TMainForm.DoAction(Sender: TObject; aExchange: TIDEExchange);
|
||||
|
||||
var
|
||||
PayJSON : TJSONObject;
|
||||
|
||||
begin
|
||||
payJSON:=Nil;
|
||||
if Not (aExchange.Payload is TJSONObject) then
|
||||
begin
|
||||
Log('Payload is not JSON Object');
|
||||
exit;
|
||||
end;
|
||||
payJSON:=aExchange.Payload as TJSONObject;
|
||||
with aExchange do
|
||||
case Name of
|
||||
'create':
|
||||
Log('Browser created widget of class %s, name %s',[PayJSON.Get('class',''),PayJSON.Get('widget','')]);
|
||||
'select':
|
||||
begin
|
||||
Log('Browser selected widget of class %s, name %s',[PayJSON.Get('class',''),PayJSON.Get('widget','')]);
|
||||
Log('Selected widget state: '+PayJSON.Get('state',''));
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TMainForm.DoClientCame(Sender: TObject; aClient: TIDEClient);
|
||||
begin
|
||||
if FClientID>0 then
|
||||
Log('Ignoring second client (id: %d) attachment.',[aClient.ID])
|
||||
else
|
||||
begin
|
||||
FClientID:=aClient.ID;
|
||||
Caption:=FDesignCaption+Format(' [Client: %d]',[FClientID]);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TMainForm.DoAddWidget(Sender: TObject);
|
||||
|
||||
Var
|
||||
Cmd : TIDECommand;
|
||||
aName : String;
|
||||
|
||||
begin
|
||||
aName:=FWidgets[(Sender as TAction).Tag];
|
||||
Cmd:=TIDECommand.Create;
|
||||
Cmd.NeedsConfirmation:=True;
|
||||
Cmd.ClientID:=FClientID;
|
||||
Cmd.name:='addWidget';
|
||||
Cmd.PayLoad:=TJSONObject.Create(['class','T'+aName+'Widget']);
|
||||
FWebIDEIntf.SendCommand(cmd);
|
||||
end;
|
||||
|
||||
procedure TMainForm.DoClientLeft(Sender: TObject; aClient: TIDEClient);
|
||||
begin
|
||||
if (aClient.ID=FClientID) then
|
||||
begin
|
||||
FClientID:=-1;
|
||||
Caption:=FDesignCaption;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TMainForm.LogRequest;
|
||||
|
||||
begin
|
||||
if (FURLCount=1) then // avoid excessive logging, command loop is on very short interval.
|
||||
Log('Internal server request received: '+FURL);
|
||||
end;
|
||||
|
||||
procedure TMainForm.DoLogRequest(Sender: TObject; aURL: String);
|
||||
begin
|
||||
if (aURL<>FURL) then
|
||||
begin
|
||||
FURLCount:=1;
|
||||
FURL:=aURL
|
||||
end
|
||||
else
|
||||
Inc(FURLCount);
|
||||
TThread.Synchronize(TThread.CurrentThread,@LogRequest);
|
||||
end;
|
||||
|
||||
procedure TMainForm.IsWidgetEnabled(Sender: TObject);
|
||||
begin
|
||||
(Sender as TAction).Enabled:=(FClientID<>-1);
|
||||
end;
|
||||
|
||||
procedure TMainForm.RegisterWidgets;
|
||||
begin
|
||||
SetLength(FWidgets,9);
|
||||
FWidgetCount:=0;
|
||||
RegisterWidget('Button',2);
|
||||
RegisterWidget('Checkbox',3);
|
||||
RegisterWidget('Radio',4);
|
||||
RegisterWidget('Edit',5);
|
||||
RegisterWidget('Image',6);
|
||||
RegisterWidget('TextArea',7);
|
||||
RegisterWidget('Select',8);
|
||||
RegisterWidget('Container',9);
|
||||
RegisterWidget('Jumbo',10);
|
||||
end;
|
||||
|
||||
procedure TMainForm.RegisterWidget(aWidget: String; aImageIndex: Integer);
|
||||
|
||||
Var
|
||||
A : TAction;
|
||||
B : TToolButton;
|
||||
L,i : Integer;
|
||||
|
||||
begin
|
||||
FWidgets[FWidgetCount]:=aWidget;
|
||||
A:=TAction.Create(Self);
|
||||
A.ActionList:=ALWidgets;
|
||||
A.Name:='AAdd'+aWidget;
|
||||
A.Hint:='Add '+aWidget;
|
||||
A.Caption:='Add '+aWidget;
|
||||
A.ImageIndex:=aImageIndex;
|
||||
A.Tag:=FWidgetCount;
|
||||
A.OnExecute:=@DoAddWidget;
|
||||
A.OnUpdate:=@IsWidgetEnabled;
|
||||
L:=0;
|
||||
For I:=0 to TBWidgets.ControlCount-1 do
|
||||
if TBWidgets.Controls[i].BoundsRect.Right>L then
|
||||
L:=TBWidgets.Controls[i].BoundsRect.Right;
|
||||
B:=TToolButton.Create(Self);
|
||||
B.Parent:=TBWidgets;
|
||||
B.Left:=L;
|
||||
B.Height:=32;
|
||||
B.Action:=A;
|
||||
inc(FWidgetCount);
|
||||
// TBWidgets.AddControl;;
|
||||
|
||||
end;
|
||||
|
||||
procedure TMainForm.Log(Msg: String);
|
||||
begin
|
||||
MLog.Lines.Add(Msg);
|
||||
end;
|
||||
|
||||
procedure TMainForm.Log(Fmt: String; Args: array of const);
|
||||
begin
|
||||
Log(Format(Fmt,Args));
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
|
BIN
demo/webwidget/nativedesign/nativedesigner.ico
Normal file
After Width: | Height: | Size: 64 KiB |
91
demo/webwidget/nativedesign/nativedesigner.lpi
Normal file
@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CONFIG>
|
||||
<ProjectOptions>
|
||||
<Version Value="12"/>
|
||||
<General>
|
||||
<Flags>
|
||||
<SaveOnlyProjectUnits Value="True"/>
|
||||
<SaveJumpHistory Value="False"/>
|
||||
<SaveFoldState Value="False"/>
|
||||
</Flags>
|
||||
<SessionStorage Value="InProjectDir"/>
|
||||
<Title Value="nativedesigner"/>
|
||||
<Scaled Value="True"/>
|
||||
<ResourceType Value="res"/>
|
||||
<UseXPManifest Value="True"/>
|
||||
<XPManifest>
|
||||
<DpiAware Value="True"/>
|
||||
</XPManifest>
|
||||
<Icon Value="0"/>
|
||||
</General>
|
||||
<BuildModes>
|
||||
<Item Name="Default" Default="True"/>
|
||||
</BuildModes>
|
||||
<PublishOptions>
|
||||
<Version Value="2"/>
|
||||
<UseFileFilters Value="True"/>
|
||||
</PublishOptions>
|
||||
<RunParams>
|
||||
<FormatVersion Value="2"/>
|
||||
<Modes Count="0"/>
|
||||
</RunParams>
|
||||
<RequiredPackages Count="3">
|
||||
<Item1>
|
||||
<PackageName Value="weblaz"/>
|
||||
</Item1>
|
||||
<Item2>
|
||||
<PackageName Value="WebBrowser"/>
|
||||
</Item2>
|
||||
<Item3>
|
||||
<PackageName Value="LCL"/>
|
||||
</Item3>
|
||||
</RequiredPackages>
|
||||
<Units>
|
||||
<Unit>
|
||||
<Filename Value="nativedesigner.lpr"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
</Unit>
|
||||
<Unit>
|
||||
<Filename Value="frmmain.pp"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
<ComponentName Value="MainForm"/>
|
||||
<HasResources Value="True"/>
|
||||
<ResourceBaseClass Value="Form"/>
|
||||
</Unit>
|
||||
<Unit>
|
||||
<Filename Value="webideintf.pp"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
</Unit>
|
||||
</Units>
|
||||
</ProjectOptions>
|
||||
<CompilerOptions>
|
||||
<Version Value="11"/>
|
||||
<Target>
|
||||
<Filename Value="nativedesigner"/>
|
||||
</Target>
|
||||
<SearchPaths>
|
||||
<IncludeFiles Value="$(ProjOutDir)"/>
|
||||
<UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
|
||||
</SearchPaths>
|
||||
<Linking>
|
||||
<Options>
|
||||
<Win32>
|
||||
<GraphicApplication Value="True"/>
|
||||
</Win32>
|
||||
</Options>
|
||||
</Linking>
|
||||
</CompilerOptions>
|
||||
<Debugging>
|
||||
<Exceptions Count="3">
|
||||
<Item1>
|
||||
<Name Value="EAbort"/>
|
||||
</Item1>
|
||||
<Item2>
|
||||
<Name Value="ECodetoolError"/>
|
||||
</Item2>
|
||||
<Item3>
|
||||
<Name Value="EFOpenError"/>
|
||||
</Item3>
|
||||
</Exceptions>
|
||||
</Debugging>
|
||||
</CONFIG>
|
22
demo/webwidget/nativedesign/nativedesigner.lpr
Normal file
@ -0,0 +1,22 @@
|
||||
program nativedesigner;
|
||||
|
||||
{$mode objfpc}{$H+}
|
||||
|
||||
uses
|
||||
{$IFDEF UNIX}
|
||||
cthreads,
|
||||
{$ENDIF}
|
||||
Interfaces, // this includes the LCL widgetset
|
||||
Forms, frmmain, webideintf
|
||||
{ you can add units after this };
|
||||
|
||||
{$R *.res}
|
||||
|
||||
begin
|
||||
RequireDerivedFormResource:=True;
|
||||
Application.Scaled:=True;
|
||||
Application.Initialize;
|
||||
Application.CreateForm(TMainForm, MainForm);
|
||||
Application.Run;
|
||||
end.
|
||||
|
BIN
demo/webwidget/nativedesign/nativedesigner.res
Normal file
817
demo/webwidget/nativedesign/webideintf.pp
Normal file
@ -0,0 +1,817 @@
|
||||
unit webideintf;
|
||||
|
||||
{$mode objfpc}{$H+}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
fpMimeTypes, Classes, SysUtils, StrUtils, httpdefs, fphttpclient,custhttpapp, fpjson, jsonparser, httproute;
|
||||
|
||||
Const
|
||||
SFilesURL = '/Project/';
|
||||
SIDEURL = '/IDE/';
|
||||
|
||||
Type
|
||||
TClientObject = Class(TObject)
|
||||
Private
|
||||
FID: Int64;
|
||||
public
|
||||
Procedure FromJSON(aJSON : TJSONObject); virtual; abstract;
|
||||
Procedure ToJSON(aJSON : TJSONObject); virtual; abstract;
|
||||
Property ID : Int64 Read FID Write FID;
|
||||
end;
|
||||
{ TIDEClient }
|
||||
|
||||
TIDEClient = Class(TClientObject)
|
||||
private
|
||||
FURL: String;
|
||||
Public
|
||||
Procedure FromJSON(aJSON : TJSONObject); override;
|
||||
Procedure ToJSON(aJSON : TJSONObject); override;
|
||||
Property URL : String Read FURL Write FURL;
|
||||
end;
|
||||
{ TIDEExchange }
|
||||
|
||||
TIDEExchange = Class(TClientObject)
|
||||
private
|
||||
FClientID: Int64;
|
||||
FName: String;
|
||||
FPayLoad: TJSONData;
|
||||
Public
|
||||
Destructor Destroy; override;
|
||||
Procedure FromJSON(aJSON : TJSONObject); override;
|
||||
Procedure ToJSON(aJSON : TJSONObject); override;
|
||||
Property ClientID : Int64 Read FClientID Write FClientID;
|
||||
Property Name : String Read FName Write FName;
|
||||
Property PayLoad : TJSONData Read FPayLoad Write FPayLoad;
|
||||
end;
|
||||
|
||||
TIDEAction = Class(TIDEExchange)
|
||||
end;
|
||||
|
||||
{ TClientObjectList }
|
||||
|
||||
TClientObjectList = Class(TThreadList)
|
||||
Public
|
||||
Function FindID(aID : int64) : TClientObject;
|
||||
end;
|
||||
|
||||
{ TIDECommand }
|
||||
|
||||
TIDECommand = Class(TIDEExchange)
|
||||
private
|
||||
FConfirmed: Boolean;
|
||||
FNeedsConfirmation: Boolean;
|
||||
FSent: Boolean;
|
||||
Public
|
||||
Property NeedsConfirmation : Boolean Read FNeedsConfirmation Write FNeedsConfirmation;
|
||||
Property Sent : Boolean Read FSent Write FSent;
|
||||
Property Confirmed : Boolean Read FConfirmed Write FConfirmed;
|
||||
end;
|
||||
|
||||
{ TIDEThread }
|
||||
|
||||
TIDEThread = Class(TThread)
|
||||
Private
|
||||
FHandler : TFPHTTPServerHandler;
|
||||
FExceptionClass : String;
|
||||
FExceptionMessage : String;
|
||||
Public
|
||||
Constructor Create(aHandler : TFPHTTPServerHandler);
|
||||
Procedure Execute; override;
|
||||
end;
|
||||
|
||||
|
||||
TIDENotification = Procedure(Sender : TObject; aExchange : TIDEExchange) of object;
|
||||
TIDEClientNotification = Procedure(Sender : TObject; aClient : TIDEClient) of object;
|
||||
TIDERequestNotification = Procedure(Sender : TObject; aURL : String) of object;
|
||||
|
||||
{ TIDEServer }
|
||||
|
||||
TIDEServer = Class(TComponent)
|
||||
private
|
||||
FOnRequest: TIDERequestNotification;
|
||||
FQuitting : Boolean;
|
||||
FClients,
|
||||
FCommands,
|
||||
FActions : TClientObjectList;
|
||||
FIDCounter: Int64;
|
||||
FOnAction: TIDENotification;
|
||||
FOnClient: TIDEClientNotification;
|
||||
FOnClientRemoved: TIDEClientNotification;
|
||||
FOnConfirmCommand: TIDENotification;
|
||||
FProjectDir: String;
|
||||
FWebHandler : TFPHTTPServerHandler;
|
||||
FThread : TIDEThread;
|
||||
FLastAction : TIDEAction;
|
||||
FLastCommand : TIDECommand;
|
||||
FLastClient : TIDEClient;
|
||||
function CheckClient(aRequest: TRequest): INt64;
|
||||
procedure DeActivatedThread(Sender: TObject);
|
||||
function Do404(is404: boolean; aResponse: TResponse): Boolean;
|
||||
procedure DoEvent(aProc: TThreadMethod);
|
||||
procedure DoQuit(ARequest: TRequest; AResponse: TResponse);
|
||||
procedure DoRouteRequest(Sender: TObject; ARequest: TRequest; AResponse: TResponse);
|
||||
function GetAction(Index : Integer): TIDEAction;
|
||||
function GetActionCount: Integer;
|
||||
function GetPort: Integer;
|
||||
function GetActive: Boolean;
|
||||
procedure SetActive(AValue: Boolean);
|
||||
procedure SetPort(AValue: Integer);
|
||||
procedure SetProjectDir(AValue: String);
|
||||
Protected
|
||||
procedure RegisterRoutes; virtual;
|
||||
// HTTP request extraction
|
||||
procedure GetClientObjectFromRequest(ARequest: TRequest; AObject: TClientObject);
|
||||
function GetActionFromRequest(ARequest: TRequest): TIDEAction;
|
||||
function GetCommandFromRequest(ARequest: TRequest): TIDECommand;
|
||||
function GetClientFromRequest(ARequest: TRequest): TIDEClient;
|
||||
function GetJSONFromRequest(ARequest: TRequest): TJSONObject;
|
||||
// Sending responses
|
||||
procedure SendClientObjectResponse(AObject: TClientObject; AResponse: TResponse);
|
||||
Procedure SendJSONResponse(aJSON : TJSONObject; aResponse : TResponse);
|
||||
// HTTP route handlers
|
||||
procedure DoDeleteAction(ARequest: TRequest; AResponse: TResponse); virtual;
|
||||
procedure DoDeleteClient(ARequest: TRequest; AResponse: TResponse); virtual;
|
||||
procedure DoGetCommand(ARequest: TRequest; AResponse: TResponse);virtual;
|
||||
procedure DoGetFile(ARequest: TRequest; AResponse: TResponse);virtual;
|
||||
procedure DoPostAction(ARequest: TRequest; AResponse: TResponse);virtual;
|
||||
procedure DoPostClient(ARequest: TRequest; AResponse: TResponse);virtual;
|
||||
procedure DoPutCommand(ARequest: TRequest; AResponse: TResponse);virtual;
|
||||
// Event handler synchronisation. Rework this to objects
|
||||
Procedure DoOnAction;
|
||||
Procedure DoOnConfirmCommand;
|
||||
Procedure DoOnClientAdded;
|
||||
Procedure DoOnClientRemoved;
|
||||
Public
|
||||
Constructor Create(aOwner : TComponent); override;
|
||||
Destructor Destroy; override;
|
||||
Function GetNextCounter : Int64;
|
||||
// Public API to communicate with browser
|
||||
Function SendCommand(aCommand : TIDECommand) : Int64;
|
||||
Procedure GetClientActions(aClientID : Int64; aList : TFPList);
|
||||
Function DeleteAction(aID: Int64; Const aClientID : Int64 = -1): Boolean;
|
||||
// Public properties
|
||||
Property ProjectDir : String Read FProjectDir Write SetProjectDir;
|
||||
Property Port : Integer Read GetPort Write SetPort;
|
||||
Property Active : Boolean read GetActive write SetActive;
|
||||
Property ActionCount : Integer Read GetActionCount;
|
||||
Property Action[Index : Integer] : TIDEAction Read GetAction;
|
||||
// Events
|
||||
Property OnRequest : TIDERequestNotification Read FOnRequest Write FOnRequest;
|
||||
Property OnConfirmCommand : TIDENotification Read FOnConfirmCommand Write FOnConfirmCommand;
|
||||
Property OnAction : TIDENotification Read FOnAction Write FOnAction;
|
||||
Property OnClientAdded : TIDEClientNotification Read FOnClient Write FOnClient;
|
||||
Property OnClientRemoved : TIDEClientNotification Read FOnClientRemoved Write FOnClientRemoved;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TClientObjectList }
|
||||
|
||||
|
||||
function TClientObjectList.FindID(aID: int64): TClientObject;
|
||||
|
||||
Var
|
||||
L : TList;
|
||||
I : integer;
|
||||
|
||||
begin
|
||||
Result:=Nil;
|
||||
L:=LockList;
|
||||
try
|
||||
I:=L.Count-1;
|
||||
While (Result=Nil) and (I>=0) do
|
||||
begin
|
||||
Result:=TClientObject(L[i]);
|
||||
if Result.ID<>aID then
|
||||
Result:=nil;
|
||||
Dec(I);
|
||||
end;
|
||||
finally
|
||||
UnlockList;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TIDEClient }
|
||||
|
||||
procedure TIDEClient.FromJSON(aJSON: TJSONObject);
|
||||
begin
|
||||
FID:=aJSON.Get('id',Int64(-1));
|
||||
FURL:=aJSON.Get('url','');
|
||||
end;
|
||||
|
||||
procedure TIDEClient.ToJSON(aJSON: TJSONObject);
|
||||
begin
|
||||
aJSON.Add('id',ID);
|
||||
aJSON.Add('url',url);
|
||||
end;
|
||||
|
||||
{ TIDEExchange }
|
||||
|
||||
destructor TIDEExchange.Destroy;
|
||||
begin
|
||||
FreeAndNil(FPayload);
|
||||
Inherited;
|
||||
end;
|
||||
|
||||
procedure TIDEExchange.FromJSON(aJSON: TJSONObject);
|
||||
|
||||
Var
|
||||
P : TJSONObject;
|
||||
|
||||
begin
|
||||
ID:=aJSON.Get('id',Int64(0));
|
||||
Name:=aJSON.Get('name','');
|
||||
P:=aJSON.Get('payload',TJSONObject(Nil));
|
||||
if Assigned(P) then
|
||||
Payload:=aJSON.Extract('payload');
|
||||
end;
|
||||
|
||||
procedure TIDEExchange.ToJSON(aJSON: TJSONObject);
|
||||
begin
|
||||
aJSON.Add('id',ID);
|
||||
aJSON.Add('name',name);
|
||||
if Assigned(Payload) then
|
||||
aJSON.Add('payload',Payload.Clone);
|
||||
end;
|
||||
|
||||
{ TIDEThread }
|
||||
|
||||
|
||||
constructor TIDEThread.Create(aHandler: TFPHTTPServerHandler);
|
||||
begin
|
||||
FHandler:=AHandler;
|
||||
FreeOnTerminate:=True;
|
||||
Inherited Create(False);
|
||||
end;
|
||||
|
||||
procedure TIDEThread.Execute;
|
||||
begin
|
||||
try
|
||||
FHandler.Run;
|
||||
FHandler:=nil;
|
||||
except
|
||||
On E : Exception do
|
||||
begin
|
||||
FExceptionClass:=E.ClassName;
|
||||
FExceptionMessage:=E.Message;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TIDEServer }
|
||||
|
||||
function TIDEServer.GetAction(Index : Integer): TIDEAction;
|
||||
|
||||
Var
|
||||
L : TList;
|
||||
|
||||
begin
|
||||
L:=FActions.LockList;
|
||||
try
|
||||
Result:=TIDEAction(L.Items[Index]);
|
||||
finally
|
||||
FActions.UnlockList;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TIDEServer.DeActivatedThread(Sender: TObject);
|
||||
begin
|
||||
FThread:=Nil;
|
||||
end;
|
||||
|
||||
function TIDEServer.GetActionCount: Integer;
|
||||
|
||||
Var
|
||||
L : TList;
|
||||
|
||||
begin
|
||||
L:=FActions.LockList;
|
||||
try
|
||||
Result:=L.Count;
|
||||
finally
|
||||
FActions.UnlockList;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TIDEServer.GetActive: Boolean;
|
||||
begin
|
||||
Result:=Assigned(FThread);
|
||||
end;
|
||||
|
||||
function TIDEServer.GetPort: Integer;
|
||||
begin
|
||||
Result:=FWebHandler.Port;
|
||||
end;
|
||||
|
||||
procedure TIDEServer.SetActive(AValue: Boolean);
|
||||
begin
|
||||
if Active=AValue then Exit;
|
||||
if AValue then
|
||||
begin
|
||||
FThread:=TIDEThread.Create(FWebHandler);
|
||||
FThread.OnTerminate:=@DeActivatedThread;
|
||||
end
|
||||
else
|
||||
begin
|
||||
FWebHandler.Terminate; // will cause thread to stop.
|
||||
try
|
||||
// Send a Quit request just in case. Normally this should fail.
|
||||
FQuitting:=True;
|
||||
TFPHTTPClient.SimpleGet(Format('http://localhost:%d/Quit',[Port]));
|
||||
except
|
||||
FQuitting:=False;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TIDEServer.SetPort(AValue: Integer);
|
||||
begin
|
||||
FWebHandler.Port:=aValue;
|
||||
end;
|
||||
|
||||
procedure TIDEServer.SetProjectDir(AValue: String);
|
||||
begin
|
||||
if FProjectDir=AValue then Exit;
|
||||
FProjectDir:=IncludeTrailingPathDelimiter(AValue);
|
||||
end;
|
||||
|
||||
procedure TIDEServer.DoOnAction;
|
||||
begin
|
||||
If Assigned(FOnAction) then
|
||||
FonAction(Self,FLastAction);
|
||||
FLastAction:=Nil;
|
||||
end;
|
||||
|
||||
procedure TIDEServer.DoOnConfirmCommand;
|
||||
begin
|
||||
If Assigned(FOnAction) then
|
||||
FonAction(Self,FLastCommand);
|
||||
FLastCommand:=Nil;
|
||||
end;
|
||||
|
||||
procedure TIDEServer.DoOnClientAdded;
|
||||
begin
|
||||
if Assigned(FOnClient) then
|
||||
FOnClient(Self,FLastClient);
|
||||
FLastClient:=Nil;
|
||||
end;
|
||||
|
||||
procedure TIDEServer.DoOnClientRemoved;
|
||||
begin
|
||||
if Assigned(FOnClientRemoved) then
|
||||
FOnClientRemoved(Self,FLastClient);
|
||||
FLastClient:=Nil;
|
||||
end;
|
||||
|
||||
procedure TIDEServer.DoGetCommand(ARequest: TRequest; AResponse: TResponse);
|
||||
|
||||
Var
|
||||
L : TList;
|
||||
I : integer;
|
||||
J,C : TJSONObject;
|
||||
A :TJSONArray;
|
||||
Cmd : TIDECommand;
|
||||
L2 : TFPList;
|
||||
aClient : Int64;
|
||||
|
||||
begin
|
||||
aClient:=CheckClient(aRequest);
|
||||
J:=nil;
|
||||
A:=nil;
|
||||
L:=FCommands.LockList;
|
||||
try
|
||||
L2:=TFPList.Create;
|
||||
J:=TJSONObject.Create;
|
||||
A:=TJSONArray.Create;
|
||||
J.Add('commands',A);
|
||||
For I:=0 to L.Count-1 do
|
||||
begin
|
||||
CMD:=TIDECommand(L[i]);
|
||||
if Not Cmd.Sent and (Cmd.ClientID=aClient) then
|
||||
begin
|
||||
C:=TJSONObject.Create;
|
||||
Cmd.ToJSON(C);
|
||||
A.Add(C);
|
||||
L2.Add(C);
|
||||
end;
|
||||
end;
|
||||
SendJSONResponse(J,aResponse);
|
||||
// Remove sent from list
|
||||
for I:=0 to L2.Count-1 do
|
||||
begin
|
||||
Cmd:=TIDECommand(L[i]);
|
||||
if Cmd.NeedsConfirmation then
|
||||
Cmd.Sent:=True
|
||||
else
|
||||
begin
|
||||
Cmd.Free;
|
||||
L.Remove(Cmd);
|
||||
end;
|
||||
end;
|
||||
finally
|
||||
J.Free;
|
||||
FCommands.UnLockList;
|
||||
l2.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TIDEServer.DoPutCommand(ARequest: TRequest; AResponse: TResponse);
|
||||
|
||||
Var
|
||||
cmd,oCmd : TIDECommand;
|
||||
aID,aClient : Int64;
|
||||
|
||||
begin
|
||||
aClient:=CheckClient(aRequest);
|
||||
aID:=StrToIntDef(aRequest.RouteParams['ID'],-1);
|
||||
cmd:=TIDECommand.Create;
|
||||
try
|
||||
GetClientObjectFromRequest(aRequest,Cmd);
|
||||
cmd.ClientID:=aClient;
|
||||
oCmd:=TIDECommand(FCommands.FindID(aID));
|
||||
if Do404((oCmd=Nil) or (oCmd.ClientID<>aClient),aResponse) then
|
||||
exit;
|
||||
// Later on we can add more modifications
|
||||
oCmd.Confirmed:=True;
|
||||
aResponse.Code:=204;
|
||||
aResponse.CodeText:='OK';
|
||||
aResponse.SendResponse;
|
||||
FLastCommand:=oCmd;
|
||||
DoEvent(@DoOnConfirmCommand);
|
||||
FCommands.Remove(oCmd);
|
||||
Finally
|
||||
cmd.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TIDEServer.DoQuit(ARequest: TRequest; AResponse: TResponse);
|
||||
begin
|
||||
if FQuitting then
|
||||
aResponse.Code:=200
|
||||
else
|
||||
aResponse.Code:=401;
|
||||
aResponse.SendResponse;
|
||||
end;
|
||||
|
||||
procedure TIDEServer.DoRouteRequest(Sender: TObject; ARequest: TRequest; AResponse: TResponse);
|
||||
begin
|
||||
If Assigned(FonRequest) then
|
||||
FOnRequest(Self,aRequest.URI);
|
||||
end;
|
||||
|
||||
function TIDEServer.GetJSONFromRequest(ARequest: TRequest): TJSONObject;
|
||||
|
||||
var
|
||||
D : TJSONData;
|
||||
|
||||
begin
|
||||
if ARequest.ContentType<>'application/json' then
|
||||
Raise Exception.Create('Not valid JSON payload: content type must be application/json');
|
||||
D:=GetJSON(ARequest.Content);
|
||||
if Not (D is TJSONObject) then
|
||||
begin
|
||||
FreeAndNil(D);
|
||||
Raise EJSON.Create('Payload is valid JSON but not a JSON object');
|
||||
end;
|
||||
Result:=D as TJSONObject;
|
||||
end;
|
||||
|
||||
procedure TIDEServer.SendJSONResponse(aJSON: TJSONObject; aResponse: TResponse);
|
||||
|
||||
Var
|
||||
JS : TJSONStringType;
|
||||
|
||||
begin
|
||||
JS:=aJSON.AsJSON;
|
||||
aResponse.FreeContentStream:=True;
|
||||
aResponse.ContentStream:=TMemoryStream.Create;
|
||||
aResponse.ContentStream.WriteBuffer(JS[1],Length(JS));
|
||||
aResponse.ContentLength:=Length(JS);
|
||||
aResponse.ContentType:='application/json';
|
||||
aResponse.SendResponse;
|
||||
end;
|
||||
|
||||
procedure TIDEServer.GetClientObjectFromRequest(ARequest: TRequest; AObject: TClientObject);
|
||||
|
||||
Var
|
||||
J : TJSONObject;
|
||||
|
||||
begin
|
||||
J:=GetJSONFromRequest(aRequest);
|
||||
try
|
||||
AObject.FromJSON(J);
|
||||
finally
|
||||
J.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TIDEServer.SendClientObjectResponse(AObject: TClientObject; AResponse: TResponse);
|
||||
|
||||
Var
|
||||
J : TJSONObject;
|
||||
|
||||
begin
|
||||
J:=TJSONObject.Create;
|
||||
try
|
||||
aObject.ToJSON(J);
|
||||
SendJSONResponse(J,aResponse);
|
||||
finally
|
||||
J.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TIDEServer.GetActionFromRequest(ARequest: TRequest): TIDEAction;
|
||||
|
||||
begin
|
||||
Result:=TIDEAction.Create;
|
||||
try
|
||||
GetClientObjectFromRequest(aRequest,Result);
|
||||
except
|
||||
Result.Free;
|
||||
raise;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TIDEServer.GetCommandFromRequest(ARequest: TRequest): TIDECommand;
|
||||
|
||||
begin
|
||||
Result:=TIDECommand.Create;
|
||||
try
|
||||
GetClientObjectFromRequest(aRequest,Result);
|
||||
except
|
||||
Result.Free;
|
||||
Raise;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TIDEServer.GetClientFromRequest(ARequest: TRequest): TIDEClient;
|
||||
begin
|
||||
Result:=TIDEClient.Create;
|
||||
try
|
||||
GetClientObjectFromRequest(aRequest,Result);
|
||||
except
|
||||
Result.Free;
|
||||
Raise;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TIDEServer.DoPostAction(ARequest: TRequest; AResponse: TResponse);
|
||||
|
||||
var
|
||||
A : TIDEAction;
|
||||
aId,aClient : Int64;
|
||||
|
||||
begin
|
||||
aClient:=CheckClient(aRequest);
|
||||
aID:=StrToInt64Def(aRequest.RouteParams['ID'],-1);
|
||||
Try
|
||||
A:=GetACtionFromRequest(aRequest);
|
||||
A.ClientID:=aClient;
|
||||
if A.ID=0 then
|
||||
a.ID:=aID;
|
||||
FActions.Add(A);
|
||||
FLastAction:=A;
|
||||
DoEvent(@DoOnAction);
|
||||
AResponse.Code:=201;
|
||||
AResponse.Codetext:='Created';
|
||||
except
|
||||
On E: Exception do
|
||||
begin
|
||||
AResponse.Code:=400;
|
||||
AResponse.Codetext:='Invalid Param';
|
||||
AResponse.Content:='Invalid data ('+E.ClassName+'): '+E.Message;
|
||||
end;
|
||||
end;
|
||||
aResponse.SendResponse;
|
||||
end;
|
||||
|
||||
function TIDEServer.CheckClient(aRequest: TRequest): INt64;
|
||||
|
||||
Var
|
||||
S : String;
|
||||
|
||||
begin
|
||||
S:=ARequest.RouteParams['Client'];
|
||||
if (S='') then
|
||||
Raise EJSON.Create('Missing client ID in request');
|
||||
if Not TryStrToInt64(S,Result) then
|
||||
Raise EJSON.CreateFmt('Invalid client ID: %s',[S]);
|
||||
end;
|
||||
|
||||
procedure TIDEServer.DoDeleteAction(ARequest: TRequest; AResponse: TResponse);
|
||||
|
||||
var
|
||||
SID : String;
|
||||
ID,aClient : Int64;
|
||||
|
||||
begin
|
||||
Try
|
||||
aClient:=CheckClient(ARequest);
|
||||
SID:=ARequest.RouteParams['ID'];
|
||||
ID:=StrtoInt64Def(SID,-1);
|
||||
if Do404((ID=-1) or not (DeleteAction(ID,aClient)),aResponse) then
|
||||
exit;
|
||||
AResponse.Code:=204;
|
||||
AResponse.Codetext:='No content';
|
||||
aResponse.SendResponse;
|
||||
except
|
||||
On E: Exception do
|
||||
begin
|
||||
AResponse.Code:=400;
|
||||
AResponse.Codetext:='Invalid Param';
|
||||
AResponse.Content:='Invalid data ('+E.ClassName+'): '+E.Message;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TIDEServer.DoGetFile(ARequest: TRequest; AResponse: TResponse);
|
||||
|
||||
Var
|
||||
FN : String;
|
||||
|
||||
begin
|
||||
FN:=ARequest.URL;
|
||||
if AnsiStartsText(SFilesURL,FN) then
|
||||
Delete(FN,1,Length(SFilesURL));
|
||||
FN:=ExpandFileName(FProjectDir+FN);
|
||||
if Pos('..',ExtractRelativepath(FProjectDir,FN))<>0 then
|
||||
begin
|
||||
aResponse.Code:=401;
|
||||
aResponse.CodeText:='Forbidden';
|
||||
aResponse.Content:='<H1>Forbidden</H1>';
|
||||
end
|
||||
else if Do404(Not FileExists(FN),aResponse) then
|
||||
exit;
|
||||
aResponse.FreeContentStream:=True;
|
||||
aResponse.ContentStream:=TFileStream.Create(FN,fmOpenRead or fmShareDenyWrite);
|
||||
aResponse.ContentLength:=aResponse.ContentStream.Size;
|
||||
aResponse.ContentType:=MimeTypes.GetMimeType(ExtractFileExt(FN));
|
||||
if aResponse.ContentType='' then
|
||||
aResponse.ContentType:='text/html';
|
||||
aResponse.SendResponse;
|
||||
end;
|
||||
|
||||
|
||||
constructor TIDEServer.Create(aOwner: TComponent);
|
||||
begin
|
||||
Inherited;
|
||||
FProjectDir:=ExtractFilePath(Paramstr(0));
|
||||
FActions:=TClientObjectList.Create;
|
||||
FCommands:=TClientObjectList.Create;
|
||||
FClients:=TClientObjectList.Create;
|
||||
FWebHandler:=TFPHTTPServerHandler.Create(Self);
|
||||
FWebHandler.Port:=8080;
|
||||
RegisterRoutes;
|
||||
end;
|
||||
|
||||
procedure TIDEServer.DoEvent(aProc : TThreadMethod);
|
||||
|
||||
begin
|
||||
if Assigned(FThread) then
|
||||
FThread.Synchronize(aProc)
|
||||
else
|
||||
aProc;
|
||||
end;
|
||||
|
||||
procedure TIDEServer.DoPostClient(ARequest: TRequest; AResponse: TResponse);
|
||||
|
||||
Var
|
||||
aClient : TIDEClient;
|
||||
|
||||
begin
|
||||
aClient:=GetClientFromRequest(aRequest);
|
||||
aClient.FID:=GetNextCounter;
|
||||
FClients.Add(aClient);
|
||||
SendClientObjectResponse(aClient,aResponse);
|
||||
FLastClient:=aClient;
|
||||
DoEvent(@DoOnClientAdded);
|
||||
end;
|
||||
|
||||
function TIDEServer.Do404(is404: boolean; aResponse: TResponse): Boolean;
|
||||
|
||||
begin
|
||||
Result:=is404;
|
||||
if Result then
|
||||
begin
|
||||
aResponse.Code:=404;
|
||||
aResponse.Codetext:='Not found';
|
||||
aResponse.SendResponse;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TIDEServer.DoDeleteClient(ARequest: TRequest; AResponse: TResponse);
|
||||
|
||||
Var
|
||||
aClientID : Int64;
|
||||
aClient : TIDEClient;
|
||||
|
||||
begin
|
||||
aClientID:=CheckClient(aRequest);
|
||||
aClient:=TIDEClient(FClients.FindID(aClientID));
|
||||
if Do404(not Assigned(aClient),aResponse) then
|
||||
exit;
|
||||
FLastClient:=aClient;
|
||||
DoEvent(@DoOnClientRemoved);
|
||||
FClients.Remove(aClient);
|
||||
end;
|
||||
|
||||
|
||||
procedure TIDEServer.RegisterRoutes;
|
||||
|
||||
begin
|
||||
// get command
|
||||
HTTPRouter.RegisterRoute(SIDEURL+'Quit',rmGet,@DoQuit);
|
||||
HTTPRouter.RegisterRoute(SIDEURL+'Client/',rmPost,@DoPostClient);
|
||||
HTTPRouter.RegisterRoute(SIDEURL+'Client/:Client',rmDelete,@DoDeleteClient);
|
||||
HTTPRouter.RegisterRoute(SIDEURL+'Command/:Client/',rmGet,@DoGetCommand);
|
||||
// PUT command for confirm.
|
||||
HTTPRouter.RegisterRoute(SIDEURL+'Command/:Client/:ID',rmPut,@DoPutCommand);
|
||||
// POST action
|
||||
HTTPRouter.RegisterRoute(SIDEURL+'Action/:Client/:ID',rmPost,@DoPostAction);
|
||||
HTTPRouter.RegisterRoute(SIDEURL+'Action/:Client/:ID',rmDelete,@DoDeleteAction);
|
||||
// GET file
|
||||
HTTPRouter.RegisterRoute(SFilesURL+'*',rmGet,@DoGetFile,true);
|
||||
HTTPRouter.BeforeRequest:=@DoRouteRequest;
|
||||
end;
|
||||
|
||||
destructor TIDEServer.Destroy;
|
||||
begin
|
||||
Active:=False;
|
||||
While Active do
|
||||
Sleep(20);
|
||||
FreeAndNil(FActions);
|
||||
FreeAndNil(FCommands);
|
||||
FreeAndNil(FClients);
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
function TIDEServer.GetNextCounter: Int64;
|
||||
begin
|
||||
Inc(FIDCounter);
|
||||
Result:=FIDCounter;
|
||||
end;
|
||||
|
||||
function TIDEServer.SendCommand(aCommand: TIDECommand): Int64;
|
||||
begin
|
||||
Result:=GetNextCounter;
|
||||
aCommand.ID:=Result;
|
||||
FCommands.Add(aCommand);
|
||||
end;
|
||||
|
||||
function TIDEServer.DeleteAction(aID: Int64; const aClientID: Int64): Boolean;
|
||||
|
||||
Var
|
||||
P : TIDEAction;
|
||||
L : TList;
|
||||
I : Integer;
|
||||
|
||||
begin
|
||||
P:=nil;
|
||||
L:=FActions.LockList;
|
||||
try
|
||||
I:=L.Count-1;
|
||||
While (I>=0) and (P=Nil) do
|
||||
begin
|
||||
P:=TIDEAction(L[i]);
|
||||
if P.ID<>AID then P:=Nil;
|
||||
Dec(i)
|
||||
end;
|
||||
finally
|
||||
L.Free;
|
||||
end;
|
||||
Result:=(P<>Nil) and ((aClientID=-1) or (P.ClientID=aClientID));
|
||||
if Result then
|
||||
FActions.Remove(P);
|
||||
end;
|
||||
|
||||
procedure TIDEServer.GetClientActions(aClientID: Int64; aList: TFPList);
|
||||
|
||||
Var
|
||||
P : TIDEAction;
|
||||
L : TList;
|
||||
I : Integer;
|
||||
begin
|
||||
P:=nil;
|
||||
L:=FActions.LockList;
|
||||
try
|
||||
I:=L.Count-1;
|
||||
While (I>=0) and (P=Nil) do
|
||||
begin
|
||||
P:=TIDEAction(L[i]);
|
||||
if P.ClientID=aClientID then
|
||||
begin
|
||||
aList.Add(P);
|
||||
L.Delete(I);
|
||||
end;
|
||||
Dec(i);
|
||||
end;
|
||||
finally
|
||||
L.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
1587
packages/webwidget/htmlwidgets.pp
Normal file
49
packages/webwidget/lazwebwidgets.lpk
Normal file
@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CONFIG>
|
||||
<Package Version="4">
|
||||
<PathDelim Value="\"/>
|
||||
<Name Value="lazwebwidgets"/>
|
||||
<Type Value="RunTimeOnly"/>
|
||||
<AutoUpdate Value="Manually"/>
|
||||
<CompilerOptions>
|
||||
<Version Value="11"/>
|
||||
<PathDelim Value="\"/>
|
||||
<SearchPaths>
|
||||
<UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
|
||||
</SearchPaths>
|
||||
<Other>
|
||||
<ExecuteBefore>
|
||||
<Command Value="$MakeExe(IDE,pas2js) -O- -Jc -vbq lazwebwidgets.pas"/>
|
||||
<Parsers Count="1">
|
||||
<Item1 Value="Pas2JS"/>
|
||||
</Parsers>
|
||||
</ExecuteBefore>
|
||||
</Other>
|
||||
<SkipCompiler Value="True"/>
|
||||
</CompilerOptions>
|
||||
<Files Count="2">
|
||||
<Item1>
|
||||
<Filename Value="webwidget.pas"/>
|
||||
<UnitName Value="webwidget"/>
|
||||
</Item1>
|
||||
<Item2>
|
||||
<Filename Value="htmlwidgets.pp"/>
|
||||
<UnitName Value="htmlwidgets"/>
|
||||
</Item2>
|
||||
</Files>
|
||||
<RequiredPkgs Count="2">
|
||||
<Item1>
|
||||
<PackageName Value="pas2js_rtl"/>
|
||||
</Item1>
|
||||
<Item2>
|
||||
<PackageName Value="FCL"/>
|
||||
</Item2>
|
||||
</RequiredPkgs>
|
||||
<UsageOptions>
|
||||
<UnitPath Value="$(PkgOutDir)"/>
|
||||
</UsageOptions>
|
||||
<PublishOptions>
|
||||
<Version Value="2"/>
|
||||
</PublishOptions>
|
||||
</Package>
|
||||
</CONFIG>
|
15
packages/webwidget/lazwebwidgets.pas
Normal file
@ -0,0 +1,15 @@
|
||||
{ This file was automatically created by Lazarus. Do not edit!
|
||||
This source is only used to compile and install the package.
|
||||
}
|
||||
|
||||
unit lazwebwidgets;
|
||||
|
||||
{$warn 5023 off : no warning about unused units}
|
||||
interface
|
||||
|
||||
uses
|
||||
webwidget;
|
||||
|
||||
implementation
|
||||
|
||||
end.
|
42
packages/webwidget/tests/btnrun.pp
Normal file
@ -0,0 +1,42 @@
|
||||
unit btnrun;
|
||||
|
||||
{$mode objfpc}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes, fpcunitreport, BrowserConsole, web;
|
||||
|
||||
Type
|
||||
|
||||
{ TConsoleRunner }
|
||||
|
||||
TConsoleRunner = Class(TRunForm)
|
||||
Private
|
||||
FRun : TJSHTMLButtonElement;
|
||||
function DoRunTest(aEvent: TJSMouseEvent): boolean;
|
||||
public
|
||||
procedure initialize; override;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TConsoleRunner }
|
||||
|
||||
function TConsoleRunner.DoRunTest(aEvent: TJSMouseEvent): boolean;
|
||||
begin
|
||||
Result:=False;
|
||||
ResetConsole;
|
||||
If Assigned(OnRun) then
|
||||
OnRun(Self);
|
||||
end;
|
||||
|
||||
procedure TConsoleRunner.initialize;
|
||||
begin
|
||||
FRun:=TJSHTMLButtonElement(document.getElementById('RunTest'));
|
||||
FRun.onClick:=@DoRunTest;
|
||||
ResetConsole;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
1155
packages/webwidget/tests/tchtmlwidgets.pp
Normal file
1760
packages/webwidget/tests/tcwidget.pp
Normal file
19
packages/webwidget/tests/testwidgets.html
Normal file
@ -0,0 +1,19 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Test widgets</title>
|
||||
<script src="testwidgets.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<button class="btn btn-default" id="RunTest">Run tests</button>
|
||||
<div id="fpcunit-controller"></div>
|
||||
<div id="fpcunit"></div>
|
||||
<div id="widget-window"></div>
|
||||
<div id="pasjsconsole"></div>
|
||||
<script>
|
||||
rtl.run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
108
packages/webwidget/tests/testwidgets.lpi
Normal file
@ -0,0 +1,108 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CONFIG>
|
||||
<ProjectOptions>
|
||||
<Version Value="12"/>
|
||||
<General>
|
||||
<Flags>
|
||||
<MainUnitHasCreateFormStatements Value="False"/>
|
||||
<MainUnitHasTitleStatement Value="False"/>
|
||||
<MainUnitHasScaledStatement Value="False"/>
|
||||
<Runnable Value="False"/>
|
||||
</Flags>
|
||||
<SessionStorage Value="InProjectDir"/>
|
||||
<Title Value="testwidgets"/>
|
||||
<UseAppBundle Value="False"/>
|
||||
<ResourceType Value="res"/>
|
||||
</General>
|
||||
<CustomData Count="2">
|
||||
<Item0 Name="MaintainHTML" Value="1"/>
|
||||
<Item1 Name="PasJSWebBrowserProject" Value="1"/>
|
||||
</CustomData>
|
||||
<BuildModes>
|
||||
<Item Name="Default" Default="True"/>
|
||||
</BuildModes>
|
||||
<PublishOptions>
|
||||
<Version Value="2"/>
|
||||
<UseFileFilters Value="True"/>
|
||||
</PublishOptions>
|
||||
<RunParams>
|
||||
<FormatVersion Value="2"/>
|
||||
<Modes Count="0"/>
|
||||
</RunParams>
|
||||
<RequiredPackages Count="1">
|
||||
<Item1>
|
||||
<PackageName Value="lazwebwidgets"/>
|
||||
</Item1>
|
||||
</RequiredPackages>
|
||||
<Units>
|
||||
<Unit>
|
||||
<Filename Value="testwidgets.lpr"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
</Unit>
|
||||
<Unit>
|
||||
<Filename Value="testwidgets.html"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
<CustomData Count="1">
|
||||
<Item0 Name="PasJSIsProjectHTMLFile" Value="1"/>
|
||||
</CustomData>
|
||||
</Unit>
|
||||
<Unit>
|
||||
<Filename Value="btnrun.pp"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
</Unit>
|
||||
<Unit>
|
||||
<Filename Value="tcwidget.pp"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
<UnitName Value="tcWidget"/>
|
||||
</Unit>
|
||||
<Unit>
|
||||
<Filename Value="tchtmlwidgets.pp"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
<UnitName Value="tcHTMLWidgets"/>
|
||||
</Unit>
|
||||
</Units>
|
||||
</ProjectOptions>
|
||||
<CompilerOptions>
|
||||
<Version Value="11"/>
|
||||
<Target FileExt=".js">
|
||||
<Filename Value="testwidgets"/>
|
||||
</Target>
|
||||
<SearchPaths>
|
||||
<IncludeFiles Value="$(ProjOutDir)"/>
|
||||
<UnitOutputDirectory Value="js"/>
|
||||
</SearchPaths>
|
||||
<Parsing>
|
||||
<SyntaxOptions>
|
||||
<AllowLabel Value="False"/>
|
||||
<CPPInline Value="False"/>
|
||||
<UseAnsiStrings Value="False"/>
|
||||
</SyntaxOptions>
|
||||
</Parsing>
|
||||
<CodeGeneration>
|
||||
<TargetOS Value="browser"/>
|
||||
</CodeGeneration>
|
||||
<Linking>
|
||||
<Debugging>
|
||||
<GenerateDebugInfo Value="False"/>
|
||||
<UseLineInfoUnit Value="False"/>
|
||||
</Debugging>
|
||||
</Linking>
|
||||
<Other>
|
||||
<CustomOptions Value="-Jeutf-8 -Jirtl.js -Jc -Jminclude"/>
|
||||
<CompilerPath Value="$(pas2js)"/>
|
||||
</Other>
|
||||
</CompilerOptions>
|
||||
<Debugging>
|
||||
<Exceptions Count="3">
|
||||
<Item1>
|
||||
<Name Value="EAbort"/>
|
||||
</Item1>
|
||||
<Item2>
|
||||
<Name Value="ECodetoolError"/>
|
||||
</Item2>
|
||||
<Item3>
|
||||
<Name Value="EFOpenError"/>
|
||||
</Item3>
|
||||
</Exceptions>
|
||||
</Debugging>
|
||||
</CONFIG>
|
16
packages/webwidget/tests/testwidgets.lpr
Normal file
@ -0,0 +1,16 @@
|
||||
program testwidgets;
|
||||
|
||||
{$mode objfpc}
|
||||
|
||||
uses
|
||||
browserconsole, {browsertestrunner} consoletestrunner, JS, Classes, SysUtils, Web, btnrun, tcWidget, tchtmlwidgets;
|
||||
|
||||
var
|
||||
Application : TTestRunner;
|
||||
|
||||
begin
|
||||
Application:=TTestRunner.Create(nil);
|
||||
Application.RunFormClass:=TConsoleRunner;
|
||||
Application.Initialize;
|
||||
Application.Run;
|
||||
end.
|