* Webwidget embryonal version

This commit is contained in:
michael 2019-08-04 21:22:33 +00:00
parent f0deef7fcc
commit eab2006134
32 changed files with 10571 additions and 0 deletions

View 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;
}

View 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 &nbsp; <a target="_blank" href="https://wiki.freepascal.org/pas2js">pas2js.</a>
&nbsp;&nbsp;Sources: &nbsp; <a target="new" href="designdemo.lpr">Program</a> &nbsp;
<a target="new" href="designer.pp">unit</a>.
</div>
<script>
window.addEventListener("load", rtl.run);
</script>
</body>
</html>

View 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>

View 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.

View 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.

View 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

File diff suppressed because it is too large Load Diff

View 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View 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>

View 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.

Binary file not shown.

View 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.

File diff suppressed because it is too large Load Diff

View 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>

View 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.

View 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.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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>

View 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>

View 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.

File diff suppressed because it is too large Load Diff