diff --git a/Changes.md b/Changes.md index 3a568d3..0bcdc64 100644 --- a/Changes.md +++ b/Changes.md @@ -3,6 +3,12 @@ WebPerl Changelog ================= +2019-08-03: v0.11-beta +---------------------- + +- Updated for Emscripten 1.38.31 / latest Fastcomp (1.38.40) and Perl v5.30.0 + + 2019-03-03: v0.09-beta ---------------------- diff --git a/build/build.pl b/build/build.pl index cadafc5..42b8fd8 100755 --- a/build/build.pl +++ b/build/build.pl @@ -95,9 +95,11 @@ =head1 Author, Copyright, and License # first, we need to take a guess which version of the patch to apply. my $libraryjs = file($ENV{EMSCRIPTEN}, 'src', 'library.js')->slurp; my $patchf; - if ( $libraryjs=~/\b\QERRNO_CODES.EAGAIN\E\b/ ) + if ( $libraryjs=~/\b\Q___setErrNo(ERRNO_CODES.\E(EAGAIN|ENOTSUP)\b/ ) { $patchf = 'emscripten_1.38.10_eagain.patch' } - elsif ( $libraryjs=~/\b\QcDefine('EAGAIN')\E/ ) + elsif ( $libraryjs=~/no shell available\s+setErrNo\Q({{{ cDefine('EAGAIN') }}})\E/ ) + { $patchf = 'emscripten_1.39.16_eagain.patch' } + elsif ( $libraryjs=~/\b\QcDefine('EAGAIN')\E/ ) # note that this appears in 1.38.1* versions too { $patchf = 'emscripten_1.38.28_eagain.patch' } else { die "Could not figure out which library.js patch to use" } #TODO Later: we should probably verify the Emscripten version too, and in the future we may need different patches for different versions @@ -161,6 +163,7 @@ =head1 Author, Copyright, and License unless $tags=~/^\Q$C{PERLVER}\E$/m; my $branches = git 'branch', '--list', {show_cmd=>$VERBOSE}; die "could not find branch '$C{PERL_BRANCH}', is this the right repository?" + . " (or the WebPerl author forgot to push tags to the emperl5 repo)" unless $branches=~/^\*?\s*\b\Q$C{PERL_BRANCH}\E$/m; say STDERR "# Found tag '$C{PERLVER}' and branch '$C{PERL_BRANCH}' in $C{PERLSRCDIR}"; } diff --git a/build/emperl_config.sh b/build/emperl_config.sh index 6930493..52d86ad 100755 --- a/build/emperl_config.sh +++ b/build/emperl_config.sh @@ -27,7 +27,7 @@ export EMPERL_OUTPUTDIR="$BASEDIR/work/outputperl" # Don't edit the following options unless you know what you're doing! # Note to self: In build.pl, we take advantage of the fact that on Perls >=v5.10.0, "$^V" is the same as the tag name. -export EMPERL_PERLVER="v5.28.1" +export EMPERL_PERLVER="v5.30.0" export EMPERL_PREFIX="/opt/perl" # Note: strace shows this is how file_packager.py is called: ["/usr/bin/python", "/home/haukex/emsdk/emscripten/1.38.28/tools/file_packager.py", "emperl.data", "--from-emcc", "--export-name=Module", "--preload", "/home/haukex/code/webperl/work/outputperl/opt/perl@/opt/perl", "--no-heap-copy"] export EMPERL_PRELOAD_FILE="$EMPERL_OUTPUTDIR$EMPERL_PREFIX@$EMPERL_PREFIX" diff --git a/build/emscripten_1.39.16_eagain.patch b/build/emscripten_1.39.16_eagain.patch new file mode 100644 index 0000000..66ebfd4 --- /dev/null +++ b/build/emscripten_1.39.16_eagain.patch @@ -0,0 +1,19 @@ +--- library.js.orig 2020-05-18 17:14:18.682328912 +0200 ++++ library.js 2020-05-18 17:14:48.366639562 +0200 +@@ -271,7 +271,7 @@ + // pid_t fork(void); + // http://pubs.opengroup.org/onlinepubs/000095399/functions/fork.html + // We don't support multiple processes. +- setErrNo({{{ cDefine('EAGAIN') }}}); ++ setErrNo({{{ cDefine('ENOTSUP') }}}); + return -1; + }, + vfork: 'fork', +@@ -696,7 +696,7 @@ + // http://pubs.opengroup.org/onlinepubs/000095399/functions/system.html + // Can't call external programs. + if (!command) return 0; // no shell available +- setErrNo({{{ cDefine('EAGAIN') }}}); ++ setErrNo({{{ cDefine('ENOTSUP') }}}); + return -1; + }, diff --git a/experiments/gui_basic/.gitignore b/experiments/gui_basic/.gitignore new file mode 100644 index 0000000..b6bf89b --- /dev/null +++ b/experiments/gui_basic/.gitignore @@ -0,0 +1,5 @@ +/database.db +/web/webperl.js +/web/emperl.* +/gui_basic +/gui_basic.exe diff --git a/experiments/gui_basic/README.md b/experiments/gui_basic/README.md new file mode 100644 index 0000000..08b3c51 --- /dev/null +++ b/experiments/gui_basic/README.md @@ -0,0 +1,50 @@ + +WebPerl Basic GUI Example +========================= + +This is a demo of a very basic GUI using WebPerl. It consists of a +local web server, which includes code to access an SQLite database, +and this web server also serves up WebPerl code to a browser, where +the GUI is implemented as HTML with Perl. + +To get this to work, you will need to copy the `webperl.js` and the +three `emperl.*` files from the main `web` directory to the `web` +subdirectory in this project. + +Note that this should not be considered production-ready, as there +are several key features missing, such as HTTPS or access control. + +Also, a limitation is that the server does not know when the browser +window is closed, so it must be stopped manually. + +You can pack this application into a single executable using: + + DOING_PAR_PACKER=1 pp -o gui_basic -z 9 -x -a gui_basic_app.psgi -a web gui_basic.pl + +Note: I'm not yet sure why, but sometimes this fails with errors such +as *"error extracting info from -c/-x file"*, in that case just try +the above command again. + + +Author, Copyright, and License +============================== + +**WebPerl - <http://webperl.zero-g.net>** + +Copyright (c) 2019 Hauke Daempfling (haukex@zero-g.net) +at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB), +Berlin, Germany, <http://www.igb-berlin.de> + +This program is free software; you can redistribute it and/or modify +it under the same terms as Perl 5 itself: either the GNU General Public +License as published by the Free Software Foundation (either version 1, +or, at your option, any later version), or the "Artistic License" which +comes with Perl 5. + +This program is distributed in the hope that it will be useful, but +**WITHOUT ANY WARRANTY**; without even the implied warranty of +**MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE**. +See the licenses for details. + +You should have received a copy of the licenses along with this program. +If not, see <http://perldoc.perl.org/index-licence.html>. diff --git a/experiments/gui_basic/gui_basic.pl b/experiments/gui_basic/gui_basic.pl new file mode 100755 index 0000000..b23006d --- /dev/null +++ b/experiments/gui_basic/gui_basic.pl @@ -0,0 +1,50 @@ +#!/usr/bin/env perl +use warnings; +use 5.018; +use FindBin; +use File::Spec::Functions qw/catdir/; +use Plack::Runner (); +use Starman (); +use Browser::Open qw/open_browser/; + +# This just serves up gui_basic_app.psgi in the Starman web server. +# You can also say "plackup gui_basic_app.psgi" instead. + +BEGIN { + my $dir = $ENV{PAR_TEMP} ? catdir($ENV{PAR_TEMP},'inc') : $FindBin::Bin; + chdir $dir or die "chdir $dir: $!"; +} + +my $SERV_PORT = 5000; +my $THE_APP = 'gui_basic_app.psgi'; + +# AFAICT, both Plack::Runner->new(@args) and ->parse_options(@argv) set +# options, and these options are shared between "Starman::Server" +# (documented in "starman") and "Plack::Runner" (documented in "plackup"). +my @args = ( + server => 'Starman', loader => 'Delayed', env => 'development', + version_cb => sub { print "Starman $Starman::VERSION\n" } ); +my @argv = ( '--listen', "localhost:$SERV_PORT", $THE_APP ); +my $runner = Plack::Runner->new(@args); +$runner->parse_options(@argv); +$runner->set_options(argv => \@argv); +die "loader shouldn't be Restarter" if $runner->{loader} eq 'Restarter'; + +if ($ENV{DOING_PAR_PACKER}) { + require Plack::Util; + Plack::Util::load_psgi($THE_APP); # for dependency resolution + # arrange to have the server shut down in a few moments + my $procpid = $$; + my $pid = fork(); + if (!defined $pid) { die "fork failed" } + elsif ($pid==0) { sleep 5; kill 'INT', $procpid; exit; } # child + print "====> Please wait a few seconds...\n"; +} +else { + # There's a small chance here that the browser could open before the server + # starts up. In that case, a reload of the browser window is needed. + print "Attempting to open in browser: http://localhost:$SERV_PORT/\n"; + open_browser("http://localhost:$SERV_PORT/"); +} + +$runner->run; diff --git a/experiments/gui_basic/gui_basic_app.psgi b/experiments/gui_basic/gui_basic_app.psgi new file mode 100644 index 0000000..d75a32a --- /dev/null +++ b/experiments/gui_basic/gui_basic_app.psgi @@ -0,0 +1,67 @@ +#!/usr/bin/env perl +use warnings; +use 5.018; +use Plack::MIME; +use Plack::Builder qw/builder enable mount/; +use Plack::Request (); +use Plack::Response (); # declare compile-time dependency +use Cpanel::JSON::XS qw/decode_json encode_json/; +use DBI (); +use DBD::SQLite (); # declare compile-time dependency +use HTML::Tiny (); + +# This is the server-side code. + +# note we rely on gui_basic.pl to set the working directory correctly +my $SERV_ROOT = 'web'; +my $DB_FILE = 'database.db'; + +my $dbh = DBI->connect("DBI:SQLite:dbname=$DB_FILE", + undef, undef, { RaiseError=>1, AutoCommit=>1 }); + +$dbh->do(q{ CREATE TABLE IF NOT EXISTS FooBar ( + foo VARCHAR(255), bar VARCHAR(255) ) }); + +# This sends HTML to the browser, but we could also send JSON +# and build the HTML table dynamically in the browser. +my $app_select = sub { + state $html = HTML::Tiny->new; + state $sth_select = $dbh->prepare(q{ SELECT rowid,foo,bar FROM FooBar }); + $sth_select->execute; + my $data = $sth_select->fetchall_arrayref; + my $out = $html->table( + [ \'tr', + [ \'th', 'rowid', 'foo', 'bar' ], + map { [ \'td', @$_ ] } @$data + ] ); + return [ 200, [ "Content-Type"=>"text/html" ], [ $out ] ]; +}; + +# This is an example of one way to communicate with JSON. +my $app_insert = sub { + my $req = Plack::Request->new(shift); + state $sth_insert = $dbh->prepare(q{ INSERT INTO FooBar (foo,bar) VALUES (?,?) }); + my $rv = eval { # catch errors and return as 500 Server Error + my $content = decode_json( $req->content ); + $sth_insert->execute($content->{foo}, $content->{bar}); + { ok=>1 }; # return value from eval, sent to client as JSON + }; my $e = $@||'unknown error'; + my $res = $req->new_response($rv ? 200 : 500); + $res->content_type($rv ? 'application/json' : 'text/plain'); + $res->body($rv ? encode_json($rv) : 'Server Error: '.$e); + return $res->finalize; +}; + +Plack::MIME->add_type(".js" => "application/javascript"); +Plack::MIME->add_type(".data" => "application/octet-stream"); +Plack::MIME->add_type(".mem" => "application/octet-stream"); +Plack::MIME->add_type(".wasm" => "application/wasm"); + +builder { + enable 'SimpleLogger'; + enable 'Static', + path => sub { s#\A/\z#/index.html#; /\.(?:html?|js|css|data|mem|wasm|pl)\z/i }, + root => $SERV_ROOT; + mount '/select' => $app_select; + mount '/insert' => $app_insert; +} diff --git a/experiments/gui_basic/web/index.html b/experiments/gui_basic/web/index.html new file mode 100644 index 0000000..89acfa1 --- /dev/null +++ b/experiments/gui_basic/web/index.html @@ -0,0 +1,32 @@ +<!doctype html> +<html lang="en-us"> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <title>WebPerl GUI Demo</title> + <script src="webperl.js"></script> + <script type="text/perl" src="web.pl"></script> +</head> +<body style="font-family:sans-serif;"> +<h1>WebPerl GUI Demo</h1> + +<div id="datatable"><i>No data loaded yet...</i></div> +<div><button id="reload_data">Reload Data</button></div> + +<div style="margin-top:1em"> + <div> + <label for="input_foo">foo</label> + <input type="text" id="input_foo"> + </div> + <div> + <label for="input_bar">bar</label> + <input type="text" id="input_bar"> + </div> + <div> + <button id="do_insert">Insert Data</button> + </div> +</div> + +<p>Powered by <a href="http://webperl.zero-g.net" target="_blank">WebPerl</a> (beta)</p> + +</body> +</html> diff --git a/experiments/gui_basic/web/web.pl b/experiments/gui_basic/web/web.pl new file mode 100644 index 0000000..d4fb476 --- /dev/null +++ b/experiments/gui_basic/web/web.pl @@ -0,0 +1,69 @@ +#!perl +use warnings; +use 5.028; +use WebPerl qw/js js_new sub1 encode_json/; + +# This is the code that WebPerl runs in the browser. It is loaded by index.html. + +sub do_xhr { + my %args = @_; + die "must specify a url" unless $args{url}; + $args{fail} ||= sub { js('window')->alert(shift) }; + my $xhr = js_new('XMLHttpRequest'); + $xhr->addEventListener("error", sub1 { + $args{fail}->("XHR Error on $args{url}: ".(shift->{textContent}||"unknown")); + return; + }); + $xhr->addEventListener("load", sub1 { + if ($xhr->{status}==200) { + $args{done}->($xhr->{response}) if $args{done}; + } + else { + $args{fail}->("XHR Error on $args{url}: ".$xhr->{status}." ".$xhr->{statusText}); + } + return; + }); + $xhr->addEventListener("loadend", sub1 { + $args{always}->() if $args{always}; + return; + }); + # when given data, default to POST (JSON), otherwise GET + if ($args{data}) { + $xhr->open($args{method}||'POST', $args{url}); + $xhr->setRequestHeader('Content-Type', 'application/json'); + $xhr->send(encode_json($args{data})); + } + else { + $xhr->open($args{method}||'GET', $args{url}); + $xhr->send(); + } + return; +} + +my $document = js('document'); + +my $btn_reload = $document->getElementById('reload_data'); +sub do_reload { + state $dtbl = $document->getElementById('datatable'); + $btn_reload->{disabled} = 1; + do_xhr(url => 'select', + done => sub { $dtbl->{innerHTML} = shift; }, + always => sub { $btn_reload->{disabled} = 0; } ); + return; +} +$btn_reload->addEventListener("click", \&do_reload); + +my $btn_insert = $document->getElementById('do_insert'); +sub do_insert { + state $txt_foo = $document->getElementById('input_foo'); + state $txt_bar = $document->getElementById('input_bar'); + $btn_insert->{disabled} = 1; + do_xhr(url => 'insert', + data => { foo=>$txt_foo->{value}, bar=>$txt_bar->{value} }, + always => sub { $btn_insert->{disabled} = 0; do_reload; } ); + return; +} +$btn_insert->addEventListener("click", \&do_insert); + +do_reload; # initial load + diff --git a/experiments/gui_sweet/.gitignore b/experiments/gui_sweet/.gitignore new file mode 100644 index 0000000..4dd1fbf --- /dev/null +++ b/experiments/gui_sweet/.gitignore @@ -0,0 +1,4 @@ +/public/webperl.js +/public/emperl.* +/gui_sweet +/gui_sweet.exe diff --git a/experiments/gui_sweet/README.md b/experiments/gui_sweet/README.md new file mode 100644 index 0000000..04a43b1 --- /dev/null +++ b/experiments/gui_sweet/README.md @@ -0,0 +1,44 @@ + +WebPerl Advanced GUI Example +============================ + +Similar to the "WebPerl Basic GUI Example", this is a demo of a GUI +using WebPerl, but using [Bootstrap](https://getbootstrap.com/) +and [jQuery](https://jquery.com/) instead of plain JavaScript, +and [Mojolicious](https://mojolicious.org/) instead of plain Plack. + +To get this to work, you will need to copy the `webperl.js` and the +three `emperl.*` files from the main `web` directory to the `public` +subdirectory in this project. + +Also, a limitation is that the server does not know when the browser +window is closed, so it must be stopped manually. + +You can pack this application into a single executable using `do_pp.pl`. +Note: I'm not yet sure why, but sometimes this fails with errors such +as *"error extracting info from -c/-x file"*, in that case just try +the command again. + + +Author, Copyright, and License +============================== + +**WebPerl - <http://webperl.zero-g.net>** + +Copyright (c) 2019 Hauke Daempfling (haukex@zero-g.net) +at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB), +Berlin, Germany, <http://www.igb-berlin.de> + +This program is free software; you can redistribute it and/or modify +it under the same terms as Perl 5 itself: either the GNU General Public +License as published by the Free Software Foundation (either version 1, +or, at your option, any later version), or the "Artistic License" which +comes with Perl 5. + +This program is distributed in the hope that it will be useful, but +**WITHOUT ANY WARRANTY**; without even the implied warranty of +**MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE**. +See the licenses for details. + +You should have received a copy of the licenses along with this program. +If not, see <http://perldoc.perl.org/index-licence.html>. diff --git a/experiments/gui_sweet/do_pp.pl b/experiments/gui_sweet/do_pp.pl new file mode 100755 index 0000000..18b5a68 --- /dev/null +++ b/experiments/gui_sweet/do_pp.pl @@ -0,0 +1,23 @@ +#!/usr/bin/env perl +use warnings; +use strict; +use File::Basename qw/fileparse/; +use File::Spec::Functions qw/catfile/; +use File::Temp qw/tempfile/; + +# this attempts to locate Mojo's default server.crt/server.key files +chomp( my $dir = `perldoc -l Mojo::IOLoop::Server` ); +die "perldoc -l failed, \$?=$?" if $? || !-e $dir; +(undef, $dir) = fileparse($dir); + +# set up a file for pp's -A switch +my ($tfh, $tfn) = tempfile(UNLINK=>1); +print {$tfh} catfile($dir,'resources','server.crt'),";server.crt\n"; +print {$tfh} catfile($dir,'resources','server.key'),";server.key\n"; +close $tfh; + +my @args = (qw/ -a public -a templates -A /, $tfn); + +local $ENV{DOING_PAR_PACKER}=1; +system(qw/ pp -o gui_sweet -z 9 -x /,@args,'gui_sweet.pl')==0 + or die "pp failed, \$?=$?"; diff --git a/experiments/gui_sweet/gui_sweet.pl b/experiments/gui_sweet/gui_sweet.pl new file mode 100755 index 0000000..fdb6169 --- /dev/null +++ b/experiments/gui_sweet/gui_sweet.pl @@ -0,0 +1,77 @@ +#!/usr/bin/env perl +use Mojolicious::Lite; +use Mojo::Util qw/md5_sum/; +use FindBin; +use File::Spec::Functions qw/catdir/; +use Browser::Open qw/open_browser/; + +# This is the server-side code. + +my $SERV_PORT = 3000; + +my ($SSLCERTS,$HOMEDIR); +BEGIN { + $HOMEDIR = $ENV{PAR_TEMP} ? catdir($ENV{PAR_TEMP},'inc') : $FindBin::Bin; + chdir $HOMEDIR or die "chdir $HOMEDIR: $!"; + # do_pp.pl pulls the default Mojo SSL certs into the archive for us + $SSLCERTS = $ENV{PAR_TEMP} ? '?cert=./server.crt&key=./server.key' : ''; +} + +app->static->paths([catdir($HOMEDIR,'public')]); +app->renderer->paths([catdir($HOMEDIR,'templates')]); +app->secrets(['Hello, Perl World!']); +app->types->type(js => "application/javascript"); +app->types->type(data => "application/octet-stream"); +app->types->type(mem => "application/octet-stream"); +app->types->type(wasm => "application/wasm"); + +# Authentication and browser-launching stuff (optional) +my $TOKEN = md5_sum(rand(1e15).time); +hook before_server_start => sub { + my ($server, $app) = @_; + my @urls = map {Mojo::URL->new($_)->query(token=>$TOKEN)} @{$server->listen}; + my $url = shift @urls or die "No urls?"; + if ($ENV{DOING_PAR_PACKER}) { + # arrange to have the server shut down in a few moments + my $procpid = $$; + my $pid = fork(); + if (!defined $pid) { die "fork failed" } + elsif ($pid==0) { sleep 5; kill 'USR1', $procpid; exit; } # child + print "====> Please wait a few seconds...\n"; + $SIG{USR1} = sub { $server->stop; exit }; + } + else { + print "Attempting to open in browser: $url\n"; + open_browser($url); + } +}; +under sub { + my $c = shift; + return 1 if ($c->param('token')//'') eq $TOKEN; + $c->render(text => 'Bad token!', status => 403); + return undef; +}; + +get '/' => sub { shift->render } => 'index'; + +post '/example' => sub { + my $c = shift; + my $data = $c->req->json; + # can do anything here, this is just an example + $data->{string} = reverse $data->{string}; + $c->render(json => $data); +}; + +app->start('daemon', '-l', "https://localhost:$SERV_PORT$SSLCERTS"); + +__DATA__ + +@@ index.html.ep +% layout 'main', title => 'WebPerl GUI Demo'; +<main role="main" class="container"> + <div> + <h1>WebPerl Advanced GUI Demo</h1> + <p class="lead">Hello, Perl World!</p> + <div id="buttons"></div> + </div> +</main> diff --git a/experiments/gui_sweet/public/web.pl b/experiments/gui_sweet/public/web.pl new file mode 100644 index 0000000..7a6239a --- /dev/null +++ b/experiments/gui_sweet/public/web.pl @@ -0,0 +1,44 @@ +#!perl +use warnings; +use 5.028; +use WebPerl qw/js sub1 encode_json/; + +# This is the code that WebPerl runs in the browser. It is loaded by index.html. + +my $window = js('window'); +my $document = js('document'); +my $jq = js('jQuery'); + +sub do_ajax { + my %args = @_; + die "must specify a url" unless $args{url}; + $args{fail} ||= sub { $window->alert(shift) }; + $jq->ajax( $args{url}, { + $args{data} # when given data, default to POST (JSON), otherwise GET + ? ( method=>$args{method}||'POST', + data=>encode_json($args{data}) ) + : ( method=>$args{method}||'GET' ), + } )->done( sub1 { + $args{done}->(shift) if $args{done}; + } )->fail( sub1 { + my ($jqXHR, $textStatus, $errorThrown) = @_; + $args{fail}->("AJAX Failed! ($errorThrown)"); + } )->always( sub1 { + $args{always}->() if $args{always}; + } ); + return; +} + +# slightly hacky way to get the access token, but it works fine +my ($token) = $window->{location}{search}=~/\btoken=([a-fA-F0-9]+)\b/; + +my $btn = $jq->('<button>', { text=>"Click me!" } ); +$btn->click(sub { + $btn->prop('disabled',1); + do_ajax( url=>"/example?token=$token", + data => { string=>"rekcaH lreP rehtonA tsuJ" }, + done => sub { $window->alert("The server says: ".shift->{string}) }, + always => sub { $btn->prop('disabled',0); } ); +} ); +$btn->appendTo( $jq->('#buttons') ); + diff --git a/experiments/gui_sweet/templates/layouts/main.html.ep b/experiments/gui_sweet/templates/layouts/main.html.ep new file mode 100644 index 0000000..b6219be --- /dev/null +++ b/experiments/gui_sweet/templates/layouts/main.html.ep @@ -0,0 +1,50 @@ +<!doctype html> +<html lang="en-us"> +<head> + <meta charset="utf-8"> + <title><%= title %></title> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> + <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> + <style> + body { padding-top: 5rem; } + </style> +</head> +<body> + <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark"> + <a class="navbar-brand" href="#"><%= title %></a> + <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation"> + <span class="navbar-toggler-icon"></span> + </button> + <div class="collapse navbar-collapse" id="navbarCollapse"> + <ul class="navbar-nav mr-auto"> + <li class="nav-item active"> + <a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a> + </li> + <li class="nav-item"> + <a class="nav-link" href="#">Link</a> + </li> + <li class="nav-item"> + <a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a> + </li> + <li class="nav-item dropdown"> + <a class="nav-link dropdown-toggle" href="#" id="dropdown01" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</a> + <div class="dropdown-menu" aria-labelledby="dropdown01"> + <a class="dropdown-item" href="#">Action</a> + <a class="dropdown-item" href="#">Another action</a> + <a class="dropdown-item" href="#">Something else here</a> + </div> + </li> + </ul> + </div> + </nav> + + <%= content %> + + <!-- Bootstrap wants its script tags at the end of the body element, so we'll put everything here: --> + <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script> + <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script> + <script src="webperl.js"></script> + <script type="text/perl" src="web.pl"></script> +</body> +</html> diff --git a/experiments/use_http.html b/experiments/use_http.html new file mode 100644 index 0000000..f891234 --- /dev/null +++ b/experiments/use_http.html @@ -0,0 +1,59 @@ +<!doctype html> +<html lang="en-us"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> +<title>WebPerl Sync HTTP Demo</title> + +<!-- +This is a demo of dynamically loading modules via synchronous +XMLHttpRequests. + +WARNING: Please note that https://xhr.spec.whatwg.org/ says: +"Synchronous XMLHttpRequest outside of workers is in the process of +being removed from the web platform as it has detrimental effects to +the end user’s experience. (This is a long process that takes many +years.)" + +The method was first described by LanX at +https://www.perlmonks.org/?node_id=1225490 +Thank you! :-) +--> + +<script src="webperl.js"></script> +<script type="text/perl"> +use warnings; +use 5.028; +use WebPerl qw/js js_new/; + +BEGIN { + push @INC, sub { + my (undef,$file) = @_; + + # sadly, MetaCPAN doesn't send CORS headers (yet) + #my $url = 'https://fastapi.metacpan.org/v1/source/' + # . ( $file =~ s/\//::/r =~ s/\.pm$//ir ); + + # this requires one to copy Dump.pm into web/Data/: + my $url = $file; + + my $xhr = js_new('XMLHttpRequest'); + $xhr->open('GET', $url, 0); + $xhr->send(); + if ($xhr->{status}==200) + { return \$xhr->{responseText} } + else { return } + }; +} + +use Data::Dump 'pp'; +js('window')->alert(pp({Hello=>"World!"})); + +</script> + +</head> +<body> + +<p>Output: see JS console</p> + +</body> +</html> diff --git a/web/mini_ide/emscr_ide.js b/web/mini_ide/emscr_ide.js index 7d60fd6..6b0249d 100644 --- a/web/mini_ide/emscr_ide.js +++ b/web/mini_ide/emscr_ide.js @@ -123,6 +123,7 @@ function make_emscr_ide (textarea, callbacks) { document.body.appendChild(link); link.click(); document.body.removeChild(link); + //TODO: do we need to do URL.revokeObjectURL(link.href); ? }); file_open.click(function () { diff --git a/web/webperl.js b/web/webperl.js index c427043..3ed51fc 100644 --- a/web/webperl.js +++ b/web/webperl.js @@ -28,7 +28,7 @@ var Perl = { trace: false, // user may enable this endAfterMain: false, // user may enable this (before Perl.init) noMountIdbfs: false, // user may enable this (before Perl.start) - WebPerlVersion: 'v0.09-beta', // user may read (only!) this + WebPerlVersion: 'v0.11-beta', // user may read (only!) this state: "Uninitialized", // user may read (only!) this exitStatus: undefined, // user may read (only!) this Util: {},