From 225589eb374fbea807c26fe3fa92e521b6d6c03c Mon Sep 17 00:00:00 2001 From: Vincent Nivoliers <vincent.nivoliers@univ-lyon1.fr> Date: Tue, 13 Sep 2016 16:53:34 +0200 Subject: [PATCH] integrating programming into explorer --- css/style.css | 19 +- explorer.php | 427 +++++++++++++++++++++++++++++++++++++++-- js/explorer.js | 8 - js/pgm_compiler.js | 135 ++++++++++--- js/pgm_construction.js | 242 ++++++++++++++++++++--- sortable_test.html | 28 ++- 6 files changed, 771 insertions(+), 88 deletions(-) diff --git a/css/style.css b/css/style.css index 463ffa3..87fcfb3 100644 --- a/css/style.css +++ b/css/style.css @@ -371,15 +371,13 @@ script { overflow : hidden ; } -@media(min-width:768px){ - #pgm-title { - margin-top : 9px ; - } +#pgm-title { + margin-top : 9px ; + margin-right : 10px ; } #desktop-switches { - margin-left : auto ; - vertical-align : middle ; + margin-right : 10px ; } .pgm-recv { @@ -425,7 +423,6 @@ script { width : 100% } - .pgm-block.add-target, .pgm-command.add-target { border-top : 15px solid #337ab7 !important ; @@ -537,6 +534,14 @@ input.add-target { display : block ; } +#library { + position : relative ; +} + +#library > .panel { + /*position : fixed ;*/ +} + @media(max-width:767px){ .col-xs-12 { padding : 0px ; diff --git a/explorer.php b/explorer.php index f291c78..a7f827a 100644 --- a/explorer.php +++ b/explorer.php @@ -1,6 +1,7 @@ <!DOCTYPE html> <html> <head> + <!--{{{ header--> <title>Explosurf</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> @@ -18,23 +19,7 @@ href="<?php echo $rootpath?>/css/bootstrap.css" /> <link rel="stylesheet" type="text/css" media="screen" href="<?php echo $rootpath?>/css/style.css" /> - - <script type="text/javascript" - src="<?php echo $rootpath?>/js/EventHelpers.js"> - </script> - <script type="text/javascript" - src="<?php echo $rootpath?>/js/logger.js"> - </script> - <script type="text/javascript" - src="<?php echo $rootpath?>/js/fakeLocalStorage.js"> - </script> - <script type="text/javascript" - src="<?php echo $rootpath?>/js/explorer.js"> - </script> - <script type="text/javascript" - src="<?php echo $rootpath?>/js/ui.js"> - </script> - + <!--}}}--> </head> <body onload="javascript:init( <?php @@ -52,6 +37,7 @@ onunload="javascript:get_out();" > <div class="container"> + <!--{{{ top bar--> <div class="navbar navbar-default"> <div class="container-fluid"> <div class="navbar-header"> @@ -86,8 +72,10 @@ </div> </div> </div> + <!--}}}--> <div class="container-fluid"> + <!--{{{ help--> <div class="panel panel-default collapse" id="help-panel"> <div class="panel-heading"> <h2 class="panel-title">Aide</h2> @@ -198,6 +186,8 @@ </p> </div> </div> + <!--}}}--> + <!--{{{ map and notes--> <div class="row"> <div class="col-md-9"> <div class="panel panel-default"> @@ -327,6 +317,387 @@ </div> </div> </div> + <!--}}}--> + <!--{{{ programming interface--> + <div class="row row-offcanvas row-offcanvas-left" id="program"> + <!--{{{ commands--> + <div class="col-xs-12 col-sm-4 sidebar-offcanvas" id="library"> + <div class="panel panel-default"> + <!--{{{ tabs--> + <div class="panel-heading tabbed-heading"> + <ul class="nav nav-tabs"> + <li class="active"> + <a class="glyphicon glyphicon-move" data-toggle="tab" href=#mv_cmds></a> + </li> + <li> + <a class="glyphicon glyphicon-pencil" data-toggle="tab" href=#var_cmds></a> + </li> + <li> + <a class="glyphicon glyphicon-random" data-toggle="tab" href=#ctrl_cmds></a> + </li> + <li> + <a class="glyphicon glyphicon-ok" data-toggle="tab" href=#test_cmds></a> + </li> + <li> + <a class="glyphicon glyphicon-plus" data-toggle="tab" href=#arth_cmds></a> + </li> + <li> + <a class="glyphicon glyphicon-font" data-toggle="tab" href=#txt_cmds></a> + </li> + </ul> + <a class="glyphicon glyphicon-remove hidden" + id="btn-close-library" + ></a> + </div> + <!--}}}--> + <div class="tab-content panel-collapse no-top-border-lists"> + <!--{{{ movement--> + <ul id="mv_cmds" class="list-group tab-pane fade library-group in active"> + <li class="list-group-item pgm-command pgm-type-direction cmd-dir-up"> + <span class="glyphicon glyphicon-circle-arrow-up"></span> + haut + </li> + <li class="list-group-item pgm-command pgm-type-direction cmd-dir-left"> + <span class="glyphicon glyphicon-circle-arrow-left"></span> + gauche + </li> + <li class="list-group-item pgm-command pgm-type-direction cmd-dir-down"> + <span class="glyphicon glyphicon-circle-arrow-down"></span> + bas + </li> + <li class="list-group-item pgm-command pgm-type-direction cmd-dir-right"> + <span class="glyphicon glyphicon-circle-arrow-right"></span> + droite + </li> + <li class="list-group-item pgm-command pgm-type-color cmd-col-red"> + <span class="glyphicon glyphicon-adjust text-danger"></span> + rouge + </li> + <li class="list-group-item pgm-command pgm-type-color cmd-col-yellow"> + <span class="glyphicon glyphicon-adjust text-warning"></span> + jaune + </li> + <li class="list-group-item pgm-command pgm-type-color cmd-col-green"> + <span class="glyphicon glyphicon-adjust text-success"></span> + vert + </li> + <li class="list-group-item pgm-command pgm-type-color cmd-col-blue"> + <span class="glyphicon glyphicon-adjust text-primary"></span> + bleu + </li> + <li class="list-group-item pgm-command pgm-type-color cmd-col-of"> + <span class="glyphicon glyphicon-transfer"></span> + couleur de + <div class="pgm-recv pgm-recv-direction"> + <div class="placeholder">direction</div> + </div> + </li> + <li class="list-group-item pgm-command pgm-type-direction cmd-dir-of"> + <span class="glyphicon glyphicon-transfer"></span> + direction de + <div class="pgm-recv pgm-recv-color"> + <div class="placeholder">couleur</div> + </div> + </li> + <li class="list-group-item pgm-command pgm-type-direction cmd-next-dir"> + <span class="glyphicon glyphicon-refresh"></span> + direction suivante de + <div class="pgm-recv pgm-recv-direction"> + <div class="placeholder">direction</div> + </div> + </li> + <li class="list-group-item pgm-command pgm-type-color cmd-next-col"> + <span class="glyphicon glyphicon-refresh"></span> + couleur suivante de + <div class="pgm-recv pgm-recv-color"> + <div class="placeholder">couleur</div> + </div> + </li> + </ul> + <!--}}}--> + <!--{{{ vaiables--> + <ul id="var_cmds" class="list-group tab-pane fade library-group"> + <li class="list-group-item pgm-command pgm-type-void cmd-var-local-set"> + <span class="glyphicon glyphicon-map-marker"></span> + <div class="pgm-recv pgm-recv-str"> + <input size=8 placeholder="variable" disabled></input> + </div> + ← + <div class="pgm-recv pgm-recv-str"> + <input size=8 placeholder="valeur" disabled></input> + </div> + </li> + <li class="list-group-item pgm-command pgm-type-read cmd-var-local-read"> + <span class="glyphicon glyphicon-map-marker"></span> + <span class="glyphicon glyphicon-search"></span> + <div class="pgm-recv pgm-recv-str"> + <input size=8 placeholder="variable" disabled></input> + </div> + </li> + <li class="list-group-item pgm-command pgm-type-void cmd-var-local-delete-var"> + <span class="glyphicon glyphicon-map-marker"></span> + <span class="glyphicon glyphicon-trash"></span> + <div class="pgm-recv pgm-recv-str"> + <input size=8 placeholder="variable" disabled></input> + </div> + </li> + <li class="list-group-item pgm-command pgm-type-void cmd-var-local-delete-tile"> + <span class="glyphicon glyphicon-map-marker"></span> + <span class="glyphicon glyphicon-trash"></span> + tout sur la case + </li> + <li class="list-group-item pgm-command pgm-type-void cmd-var-local-delete-world"> + <span class="glyphicon glyphicon-map-marker"></span> + <span class="glyphicon glyphicon-trash"></span> + tout sur toutes les cases + </li> + <li class="list-group-item pgm-command pgm-type-void cmd-var-global-set"> + <span class="glyphicon glyphicon-globe"></span> + <div class="pgm-recv pgm-recv-str"> + <input size=8 placeholder="variable" disabled></input> + </div> + ← + <div class="pgm-recv pgm-recv-str"> + <input size=8 placeholder="valeur" disabled></input> + </div> + </li> + <li class="list-group-item pgm-command pgm-type-read cmd-var-global-read"> + <span class="glyphicon glyphicon-globe"></span> + <span class="glyphicon glyphicon-search"></span> + <div class="pgm-recv pgm-recv-str"> + <input size=8 placeholder="variable" disabled></input> + </div> + </li> + <li class="list-group-item pgm-command pgm-type-void cmd-var-global-delete-var"> + <span class="glyphicon glyphicon-globe"></span> + <span class="glyphicon glyphicon-trash"></span> + <div class="pgm-recv pgm-recv-str"> + <input size=8 placeholder="variable" disabled></input> + </div> + </li> + <li class="list-group-item pgm-command pgm-type-void cmd-var-global-delete-all"> + <span class="glyphicon glyphicon-globe"></span> + <span class="glyphicon glyphicon-trash"></span> + tout + </li> + </ul> + <!--}}}--> + <!--{{{ control--> + <ul id="ctrl_cmds" class="list-group tab-pane fade library-group"> + <li class="list-group-item panel panel-default pgm-block pgm-type-void cmd-block-if"> + <div class="panel-heading"> + Si + <div class="pgm-recv pgm-recv-bool"> + <div class="placeholder">test</div> + </div> + </div> + <ul class="list-group pgm-recv-void"> + <li class="list-group-item pgm-command block-add no-sort hidden"> + <span class="glyphicon glyphicon-plus"></span> + </li> + </ul> + </li> + <li class="list-group-item panel panel-default pgm-block pgm-type-void cmd-block-if-else"> + <div class="panel-heading"> + Si + <div class="pgm-recv pgm-recv-bool"> + <div class="placeholder">test</div> + </div> + </div> + <ul class="list-group pgm-recv-void"> + <li class="list-group-item pgm-command block-add no-sort hidden"> + <span class="glyphicon glyphicon-plus"></span> + </li> + </ul> + <div class="panel-heading"> + Sinon + </div> + <ul class="list-group pgm-recv-void"> + <li class="list-group-item pgm-command block-add no-sort hidden"> + <span class="glyphicon glyphicon-plus"></span> + </li> + </ul> + </li> + <li class="list-group-item panel panel-default pgm-block pgm-type-void cmd-block-while"> + <div class="panel-heading"> + Tant que + <div class="pgm-recv pgm-recv-bool"> + <div class="placeholder">test</div> + </div> + </div> + <ul class="list-group pgm-recv-void"> + <li class="list-group-item pgm-command block-add no-sort hidden"> + <span class="glyphicon glyphicon-plus"></span> + </li> + </ul> + </li> + </ul> + <!--}}}--> + <!--{{{ tests--> + <ul id="test_cmds" class="list-group tab-pane fade library-group"> + <li class="list-group-item pgm-command pgm-type-bool cmd-test-equal"> + <div class="pgm-recv pgm-recv-str"> + <input size=8 placeholder="valeur" disabled></input> + </div> + = + <div class="pgm-recv pgm-recv-str"> + <input size=8 placeholder="valeur" disabled></input> + </div> + </li> + <li class="list-group-item pgm-command pgm-type-bool cmd-test-gt"> + <div class="pgm-recv pgm-recv-int"> + <input size=8 placeholder="valeur" disabled></input> + </div> + > + <div class="pgm-recv pgm-recv-int"> + <input size=8 placeholder="valeur" disabled></input> + </div> + </li> + <li class="list-group-item pgm-command pgm-type-bool cmd-test-ge"> + <div class="pgm-recv pgm-recv-int"> + <input size=8 placeholder="valeur" disabled></input> + </div> + ≥ + <div class="pgm-recv pgm-recv-int"> + <input size=8 placeholder="valeur" disabled></input> + </div> + </li> + <li class="list-group-item pgm-command pgm-type-bool cmd-test-not"> + non + <div class="pgm-recv pgm-recv-bool"> + <div class="placeholder">test</div> + </div> + </li> + <li class="list-group-item pgm-command pgm-type-bool cmd-test-or"> + <div class="pgm-recv pgm-recv-bool"> + <div class="placeholder">test</div> + </div> + ou + <div class="pgm-recv pgm-recv-bool"> + <div class="placeholder">test</div> + </div> + </li> + <li class="list-group-item pgm-command pgm-type-bool cmd-test-and"> + <div class="pgm-recv pgm-recv-bool"> + <div class="placeholder">test</div> + </div> + et + <div class="pgm-recv pgm-recv-bool"> + <div class="placeholder">test</div> + </div> + </li> + <li class="list-group-item pgm-command pgm-type-bool cmd-test-var-exists-local"> + <span class="glyphicon glyphicon-map-marker"></span> + <div class="pgm-recv pgm-recv-str"> + <input size=8 placeholder="variable" disabled></input> + </div> + existe + </li> + <li class="list-group-item pgm-command pgm-type-bool cmd-test-var-exists-global"> + <span class="glyphicon glyphicon-globe"></span> + <div class="pgm-recv pgm-recv-str"> + <input size=8 placeholder="variable" disabled></input> + </div> + existe + </li> + </ul> + <!--}}}--> + <!--{{{ arithmetics--> + <ul id="arth_cmds" class="list-group tab-pane fade library-group"> + <li class="list-group-item pgm-command pgm-type-int cmd-arith-plus"> + ( + <div class="pgm-recv pgm-recv-int"> + <input size=8 placeholder="entier" disabled></input> + </div> + + + <div class="pgm-recv pgm-recv-int"> + <input size=8 placeholder="entier" disabled></input> + </div> + ) + </li> + <li class="list-group-item pgm-command pgm-type-int cmd-arith-minus"> + ( + <div class="pgm-recv pgm-recv-int"> + <input size=8 placeholder="entier" disabled></input> + </div> + - + <div class="pgm-recv pgm-recv-int"> + <input size=8 placeholder="entier" disabled></input> + </div> + ) + </li> + <li class="list-group-item pgm-command pgm-type-int cmd-arith-mult"> + ( + <div class="pgm-recv pgm-recv-int"> + <input size=8 placeholder="entier" disabled></input> + </div> + × + <div class="pgm-recv pgm-recv-int"> + <input size=8 placeholder="entier" disabled></input> + </div> + ) + </li> + <li class="list-group-item pgm-command pgm-type-int cmd-arith-div"> + ( + <div class="pgm-recv pgm-recv-int"> + <input size=8 placeholder="entier" disabled></input> + </div> + ÷ + <div class="pgm-recv pgm-recv-int"> + <input size=8 placeholder="entier" disabled></input> + </div> + ) + </li> + </ul> + <!--}}}--> + <!--{{{ strings--> + <ul id="txt_cmds" class="list-group tab-pane fade library-group"> + <li class="list-group-item pgm-command pgm-type-str cmd-str-concat"> + <span class="glyphicon glyphicon-paperclip"></span> + <div class="pgm-recv pgm-recv-str"> + <input size=8 placeholder="debut" disabled></input> + </div> + <div class="pgm-recv pgm-recv-str"> + <input size=8 placeholder="fin" disabled></input> + </div> + </li> + </ul> + <!--}}}--> + </div> + </div> + </div> + <!---}}}--> + <!--{{{ program--> + <div class="col-xs-12 col-sm-8"> + <div class="panel panel-default pgm" id="pgm"> + <div class="panel-heading"> + <h3 class="panel-title pull-left" id="pgm-title">Programme</h3> + <div class="btn-group pull-right" role="group"> + <button class="btn btn-default" type="button" id="pgm-run" + onclick="javascript:Explosurf.pgm.run();" + > + <span class="glyphicon glyphicon-play"></span> + </button> + </div> + <div class="btn-group pull-right hidden-xs" role="group" id="desktop-switches"> + <button class="btn btn-default" type="button" id="desktop-ui-phone"> + <span class="glyphicon glyphicon-phone"></span> + </button> + <button class="btn btn-default" type="button" id="desktop-ui-copy"> + <span class="glyphicon glyphicon-copy"></span> + </button> + </div> + </div> + <ul class="list-group pgm-recv-void" id="pgm-main"> + <li class="list-group-item pgm-command block-add no-sort hidden"> + <span class="glyphicon glyphicon-plus"></span> + </li> + </ul> + </div> + </div> + <!--}}}--> + </div> + <!--}}}--> <?php // get the XML planet descriptor if(file_exists('extra.php')) { @@ -336,8 +707,30 @@ } ?> </div> + <!--{{{ mobile toolbar--> + <div class="btn-toolbar hidden" role="toolbar" id="phone-toolbar"> + <div class="btn-group" role="group"> + <button class="btn btn-default active" autofocus type="button" id="phone-tool-add"> + <span class="glyphicon glyphicon-plus"></span> + </button> + <button class="btn btn-default" type="button" id="phone-tool-cut"> + <span class="glyphicon glyphicon-scissors"></span> + </button> + <button class="btn btn-default" type="button" id="phone-tool-copy"> + <span class="glyphicon glyphicon-copy"></span> + </button> + <button class="btn btn-default" type="button" id="phone-tool-paste"> + <span class="glyphicon glyphicon-paste"></span> + </button> + </div> + </div> + <!--}}}--> <script type="text/javascript" src="<?php echo $rootpath?>/js/bootstrap-native.js"></script> + <script type="text/javascript" src="<?php echo $rootpath?>/js/sortable.js"></script> + <script type="text/javascript" src="<?php echo $rootpath?>/js/pgm_construction.js"></script> <script type="text/javascript" src="<?php echo $rootpath?>/js/pgm_compiler.js"></script> <script type="text/javascript" src="<?php echo $rootpath?>/js/notes.js"></script> + <script type="text/javascript" src="<?php echo $rootpath?>/js/explorer.js"></script> + <div class="visible-xs-block" id="mobile-tester"></div> </body> </html> diff --git a/js/explorer.js b/js/explorer.js index 564f823..b75e8eb 100644 --- a/js/explorer.js +++ b/js/explorer.js @@ -26,27 +26,21 @@ function Tile(XmlElement) { this.neighbour_edges = new Array(null,null,null,null) ; this.orientations = new Array(null,null,null,null) ; this.image = null ; - Logger.log("Parsing tile " + this.name) ; var children = XmlElement.childNodes ; for(var i=0; i<children.length; i++) { var child = children[i] ; if(child.nodeName == 'image') { this.image = child.getAttribute('name') ; - Logger.log(" Found image " + this.image) ; } else if (child.nodeName == 'edge') { var index = parseInt(child.getAttribute('origin_index')) ; - Logger.log(" Found edge " + index) ; var neighbour = child.getAttribute('neighbour') ; if(neighbour != '') { this.neighbours[index] = neighbour ; - Logger.log(" Neighbour is " + neighbour) ; var neighbour_edge = parseInt(child.getAttribute('opposite_index')) ; this.neighbour_edges[index] = neighbour_edge ; - Logger.log(" Neighbour edge is " + neighbour_edge) ; var orientation = child.getAttribute('orientation') ; this.orientations[index] = (orientation != "switch") ; - Logger.log(" Orientation is " + orientation) ; } } } @@ -112,7 +106,6 @@ Map.prototype.toString = function() { } handle_request_error = function(evt) { - Logger.log("Error getting XML file") ; } function openXML(filename) { @@ -195,7 +188,6 @@ function PlanetExplorer(filename) { } } } - //Logger.unmute() ; if(localStorage.getItem(this.prefix+"explorer:position")) { this.load_state() ; diff --git a/js/pgm_compiler.js b/js/pgm_compiler.js index a564ae9..10f024d 100644 --- a/js/pgm_compiler.js +++ b/js/pgm_compiler.js @@ -26,6 +26,7 @@ function block_compile(el) { res += ';' ; } } + return res ; } function get_integer(str, error_target) { @@ -73,7 +74,7 @@ function get_str(str, error_target) { function receiver_compile(el) { //type of the receiver - var type = el.className.match(/pgm-type-(\S+)/)[1] ; + var type = el.className.match(/pgm-recv-(\S+)/)[1] ; //search for the child to compile var target = null ; for(var i = 0; i < el.children.length; ++i) { @@ -95,10 +96,10 @@ function receiver_compile(el) { //compile child var rec_compile = "" ; if(target.tagName === "INPUT") { - rec_compile = target.value ; + rec_compile = '"' + target.value + '"' ; } else { - rec_compile = compile(target) ; + rec_compile = cmd_compile(target) ; } //handle runtime type errors var err_target = '"' + selector_path_to_root(target) + '"' ; @@ -125,7 +126,7 @@ function selector_path_to_root(el) { } else { var p = el.parentElement ; var i = [].indexOf.call(p.children, el) + 1 ; - return path_to_root(p) + ' ' + return selector_path_to_root(p) + ' ' + el.tagName.toLowerCase() + ':nth-child(' + i + ')' ; } @@ -255,7 +256,7 @@ function command_dir_up(el) { if(el.parentElement.className.match(/pgm-recv-void/)) { return 'explorer.go(2)' ; } else { - return "haut" ; + return '"haut"' ; } } @@ -265,7 +266,7 @@ function command_dir_left(el) { if(el.parentElement.className.match(/pgm-recv-void/)) { return 'explorer.go(3)' ; } else { - return "gauche" ; + return '"gauche"' ; } } @@ -275,16 +276,16 @@ function command_dir_down(el) { if(el.parentElement.className.match(/pgm-recv-void/)) { return 'explorer.go(0)' ; } else { - return "bas" ; + return '"bas"' ; } } pgm_elements['cmd-dir-down'] = command_dir_down ; -function pgm_command_dir_right(el) { +function command_dir_right(el) { if(el.parentElement.className.match(/pgm-recv-void/)) { return 'explorer.go(1)' ; } else { - return "droite" ; + return '"droite"' ; } } @@ -292,40 +293,46 @@ pgm_elements['cmd-dir-right'] = command_dir_right ; function command_col_red(el) { if(el.parentElement.className.match(/pgm-recv-void/)) { - return 'explorer.go(color_to_number("rouge"));' ; + return 'explorer.go(color_to_number("rouge"))' ; } else { - return "rouge" ; + return '"rouge"' ; } } -pgm_elements['cmd-dir-up'] = command_dir_up ; +pgm_elements['cmd-col-red'] = command_col_red ; function command_col_yellow(el) { if(el.parentElement.className.match(/pgm-recv-void/)) { - return 'explorer.go(color_to_number("yellow"));' ; + return 'explorer.go(color_to_number("yellow"))' ; } else { - return "jaune" ; + return '"jaune"' ; } } +pgm_elements['cmd-col-yellow'] = command_col_yellow ; + function command_col_green(el) { if(el.parentElement.className.match(/pgm-recv-void/)) { - return 'explorer.go(color_to_number("vert"));' ; + return 'explorer.go(color_to_number("vert"))' ; } else { - return "vert" ; + return '"vert"' ; } } +pgm_elements['cmd-col-green'] = command_col_green ; + function command_col_blue(el) { if(el.parentElement.className.match(/pgm-recv-void/)) { - return 'explorer.go(color_to_number("bleu"));' ; + return 'explorer.go(color_to_number("bleu"))' ; } else { - return "bleu" ; + return '"bleu"' ; } } +pgm_elements['cmd-col-blue'] = command_col_blue ; + function command_col_of(el) { - var receiver = el.children[2] ; + var receiver = el.children[1] ; var dir = receiver_compile(receiver) ; if(el.parentElement.className.match(/pgm-recv-void/)) { return 'explorer.go(direction_to_number(' + dir + '))' ; @@ -334,8 +341,10 @@ function command_col_of(el) { } } +pgm_elements['cmd-col-of'] = command_col_of ; + function command_dir_of(el) { - var receiver = el.children[2] ; + var receiver = el.children[1] ; var col = receiver_compile(receiver) ; if(el.parentElement.className.match(/pgm-recv-void/)) { return 'explorer.go(color_to_number(' + col + '))' ; @@ -344,8 +353,11 @@ function command_dir_of(el) { } } +pgm_elements['cmd-dir-of'] = command_dir_of ; + + function command_next_col(el) { - var receiver = el.children[2] ; + var receiver = el.children[1] ; var col = receiver_compile(receiver) ; if(el.parentElement.className.match(/pgm-recv-void/)) { return 'explorer.go((color_to_number(' + col + ') + 1)%4)' ; @@ -354,8 +366,10 @@ function command_next_col(el) { } } +pgm_elements['cmd-next-col'] = command_next_col ; + function command_next_dir(el) { - var receiver = el.children[2] ; + var receiver = el.children[1] ; var dir = receiver_compile(receiver) ; if(el.parentElement.className.match(/pgm-recv-void/)) { return 'explorer.go((direction_to_number(' + dir + ') + 1)%4)' ; @@ -364,6 +378,8 @@ function command_next_dir(el) { } } +pgm_elements['cmd-next-dir'] = command_next_dir ; + /*}}}*/ /* {{{ Variable management ================================================== */ @@ -374,8 +390,10 @@ function command_write_local(el) { return 'Explosurf.notes.set_variable(' + name + ', ' + value + ');' ; } +pgm_elements['cmd-var-local-set'] = command_write_local ; + function command_read_local(el) { - var name = receiver_compile(el.children[1]) ; + var name = receiver_compile(el.children[2]) ; if(el.parentElement.className.match(/pgm-recv-void/)) { var err_target = '"' + selector_path_to_root(el.children[2]) + '"' ; return 'explorer.go(get_movement(Explosurf.notes.get_variable(' + name + '), ' + err_target + '));' ; @@ -384,27 +402,37 @@ function command_read_local(el) { } } +pgm_elements['cmd-var-local-read'] = command_read_local ; + function command_delete_local(el) { - var name = receiver_compile(el.children[1]) ; + var name = receiver_compile(el.children[2]) ; return 'Explosurf.notes.clear_variable(' + name + ');' ; } +pgm_elements['cmd-var-local-delete-var'] = command_delete_local ; + function command_delete_tile_local(el) { return 'Explosurf.notes.clear_variable(".*");' ; } +pgm_elements['cmd-var-local-delete-tile'] = command_delete_tile_local ; + function command_delete_all_local(el) { return 'Explosurf.notes.clear_local_variables(".*");' ; } -function command_write_local(el) { +pgm_elements['cmd-var-local-delete-world'] = command_delete_all_local ; + +function command_write_global(el) { var name = receiver_compile(el.children[1]) ; var value = receiver_compile(el.children[2]) ; - return 'Explosurf.notes.set_variable(' + name + ', ' + value + ');' ; + return 'Explosurf.notes.set_variable(' + name + ', ' + value + ', true);' ; } +pgm_elements['cmd-var-global-set'] = command_write_global ; + function command_read_global(el) { - var name = receiver_compile(el.children[1]) ; + var name = receiver_compile(el.children[2]) ; if(el.parentElement.className.match(/pgm-recv-void/)) { var err_target = '"' + selector_path_to_root(el.children[2]) + '"' ; return 'explorer.go(get_movement(Explosurf.notes.get_variable(' + name + ', true), ' + err_target + '));' ; @@ -413,15 +441,21 @@ function command_read_global(el) { } } +pgm_elements['cmd-var-global-read'] = command_read_global ; + function command_delete_global(el) { - var name = receiver_compile(el.children[1]) ; + var name = receiver_compile(el.children[2]) ; return 'Explosurf.notes.clear_variable(' + name + ', true);' ; } +pgm_elements['cmd-var-global-delete-var'] = command_delete_global ; + function command_delete_all_global(el) { return 'Explosurf.notes.clear_variable(".*", true);' ; } +pgm_elements['cmd-var-global-delete-all'] = command_delete_all_global ; + /*}}}*/ /* {{{ Branching ============================================================ */ @@ -432,6 +466,8 @@ function block_if(el) { return 'if(' + test + '){' + block + '}' ; } +pgm_elements['cmd-block-if'] = block_if ; + function block_if_else(el) { var test = receiver_compile(el.children[0].children[0]) ; var blockif = block_compile(el.children[1]) ; @@ -439,12 +475,16 @@ function block_if_else(el) { return 'if(' + test + '){' + blockif + '} else {' + blockelse + '}' ; } +pgm_elements['cmd-block-if-else'] = block_if_else ; + function block_while(el) { var test = receiver_compile(el.children[0].children[0]) ; var block = block_compile(el.children[1]) ; return 'while(' + test + '){' + block + '}' ; } +pgm_elements['cmd-block-while'] = block_while ; + /*}}}*/ /* {{{ Boolean expressions ================================================== */ @@ -455,45 +495,61 @@ function command_equals(el) { return '(' + lhs + ' == ' + rhs + ')' ; } +pgm_elements['cmd-test-equal'] = command_equals ; + function command_gt(el) { var lhs = receiver_compile(el.children[0]) ; var rhs = receiver_compile(el.children[1]) ; return '(' + lhs + ' > ' + rhs + ')' ; } +pgm_elements['cmd-test-gt'] = command_gt ; + function command_ge(el) { var lhs = receiver_compile(el.children[0]) ; var rhs = receiver_compile(el.children[1]) ; return '(' + lhs + ' >= ' + rhs + ')' ; } +pgm_elements['cmd-test-ge'] = command_ge ; + function command_not(el) { var test = receiver_compile(el.children[0]) ; return '(! ' + test + ')' ; } +pgm_elements['cmd-test-not'] = command_not ; + function command_or(el) { var lhs = receiver_compile(el.children[0]) ; var rhs = receiver_compile(el.children[1]) ; return '(' + lhs + ' || ' + rhs + ')' ; } +pgm_elements['cmd-test-or'] = command_or ; + function command_and(el) { var lhs = receiver_compile(el.children[0]) ; var rhs = receiver_compile(el.children[1]) ; return '(' + lhs + ' && ' + rhs + ')' ; } +pgm_elements['cmd-test-and'] = command_and ; + function command_exists_local(el) { var name = receiver_compile(el.children[0]) ; return 'Explosurf.notes.check_variable(' + name + ')' ; } +pgm_elements['cmd-test-var-exists-local'] = command_exists_local ; + function command_exists_global(el) { var name = receiver_compile(el.children[0]) ; return 'Explosurf.notes.check_variable(' + name + ', true)' ; } +pgm_elements['cmd-test-var-exists-global'] = command_exists_global ; + /*}}}*/ /* {{{ Arithmetics ========================================================== */ @@ -504,18 +560,32 @@ function command_add(el) { return '(' + lhs + ' + ' + rhs + ')' ; } +pgm_elements['cmd-arith-plus'] = command_add ; + +function command_sub(el) { + var lhs = receiver_compile(el.children[0]) ; + var rhs = receiver_compile(el.children[1]) ; + return '(' + lhs + ' - ' + rhs + ')' ; +} + +pgm_elements['cmd-arith-minus'] = command_sub ; + function command_mult(el) { var lhs = receiver_compile(el.children[0]) ; var rhs = receiver_compile(el.children[1]) ; return '(' + lhs + ' * ' + rhs + ')' ; } +pgm_elements['cmd-arith-mult'] = command_mult ; + function command_div(el) { var lhs = receiver_compile(el.children[0]) ; var rhs = receiver_compile(el.children[1]) ; return '(' + lhs + ' / ' + rhs + ')' ; } +pgm_elements['cmd-arith-div'] = command_div ; + /*}}}*/ /* {{{ Strings ============================================================== */ @@ -526,10 +596,19 @@ function command_concat(el) { return lhs + ' + ' + rhs ; } +pgm_elements['cmd-str-concat'] = command_concat ; + /*}}}*/ /* {{{ Compilation ========================================================== */ +Pgm.run = function() { + var el = document.getElementById("pgm-main") ; + var program = block_compile(el) ; + console.log(program) ; + eval(program) ; +} + /*}}}*/ }) //end of namespace diff --git a/js/pgm_construction.js b/js/pgm_construction.js index c3089ed..6be04f2 100644 --- a/js/pgm_construction.js +++ b/js/pgm_construction.js @@ -1,4 +1,42 @@ -/********************** Desktop interface **********************/ +(function(factory) { + //namespacing + if(!window["Explosurf"]) { + window["Explosurf"] = {} ; + } + if(!window["Explosurf"]["pgm"]) { + window["Explosurf"]["pgm"] = {} ; + } + factory(window["Explosurf"]["pgm"]) ; +})(function(Pgm) { //namespace Explosurf.pgm + +/* {{{ General tools ======================================================== */ + +/* {{{ Class management */ + +function addClass(el,c) { + var re = new RegExp("(?:^|\\s)" + c + "(?!\\S)") ; + if(!el.className.match(re)) { + el.className += ' ' + c ; + } +} + +function removeClass(el,c) { + var re = new RegExp("(?:^|\\s)" + c + "(?!\\S)") ; + el.className = el.className.replace( re , '' ) ; +} + +/*}}}*/ + +function topOffset(el) { + return el.getBoundingClientRect().top + + window.pageYOffset + - el.ownerDocument.documentElement.clientTop ; +} + +/*}}}*/ + + +/* {{{ Desktop interface ==================================================== */ /* On desktop browser the program construction is handled using drag and drop. * */ @@ -60,7 +98,7 @@ function enable_input(el) { //remove the disabled attribute from the html element el.removeAttribute("disabled") ; //fix because inputs in draggables are hardly focusable - Sortable.utils.on(el, "mousedown", function(evt) { evt.target.focus() ;}) ; + el.addEventListener("mousedown", function(evt) { evt.target.focus() ;}) ; } /* a receiver is inline in a command. It is generally a string for variable @@ -208,6 +246,36 @@ function deactivate_copy_mode() { [].forEach.call(pgm_receivers, deactivate_cloning) ; } +function toggle_copy() { + var copy_switch = document.getElementById("desktop-ui-copy") ; + if(copy_switch.className.match(/active/)) { + removeClass(copy_switch, "active") ; + deactivate_copy_mode() ; + } else { + addClass(copy_switch, "active") ; + activate_copy_mode() ; + } +} + +document.getElementById("desktop-ui-copy").addEventListener( + 'click', + function(evt) { + toggle_copy() ; + } +) ; + +function copy_detect(evt){ + if(document.getElementsByClassName("sortable-ghost").length == 0) { + var evtobj=window.event? event : evt + if(evtobj.key === "Control") { + toggle_copy() ; + } + } +} + +document.addEventListener("keydown", copy_detect) ; +document.addEventListener("keyup", copy_detect) ; + function activate_sortables_callback(evt) { var el = evt.item ; activate_sortables(el) ; @@ -309,7 +377,9 @@ function desktop_deactivate() { desktop_deactivate_library() ; } -/********************** Mobile interface **********************/ +/*}}}*/ + +/* {{{ Mobile interface ===================================================== */ /* On mobile devices, drag and drop is difficult, so the program construction is * handled via clicks, a global context and various states @@ -363,10 +433,10 @@ function mobile_pgm_click(evt) { pgm_context.active_input = undefined ; //check tool and redirect accordingly var state = phone_tools_get() ; - if(state === 'tool-cut') return pgm_cut(evt) ; - if(state === 'tool-add') return pgm_add(evt) ; - if(state === 'tool-copy') return pgm_copy(evt) ; - if(state === 'tool-paste') return pgm_paste(evt) ; + if(state === 'phone-tool-cut') return pgm_cut(evt) ; + if(state === 'phone-tool-add') return pgm_add(evt) ; + if(state === 'phone-tool-copy') return pgm_copy(evt) ; + if(state === 'phone-tool-paste') return pgm_paste(evt) ; } /* addition : mark the element as target, open and filter the library */ @@ -449,7 +519,7 @@ function pgm_paste(evt) { //insert it in the container before_elt.parentElement.insertBefore(clone_elt, before_elt) ; //configure it to receive clicks - Sortable.utils.on(clone_elt, "click", mobile_pgm_click) ; + clone_elt.addEventListener("click", mobile_pgm_click) ; mobile_activate_blocks(clone_elt) ; //switch to add phone_tools_set("tool-add") ; @@ -480,6 +550,13 @@ function mobile_context_reset() { close_library() ; } +document.getElementById('btn-close-library').addEventListener( + 'click', + function(evt) { + mobile_context_reset() ; + } +) ; + /* Insert the chosen element and activate its events */ function insert_chosen(evt) { //check whether an addition is ongoing @@ -491,7 +568,7 @@ function insert_chosen(evt) { //insert it pgm_context.container.insertBefore(clone_elt, pgm_context.element) ; //activate it - Sortable.utils.on(clone_elt, "click", mobile_pgm_click) ; + clone_elt.addEventListener("click", mobile_pgm_click) ; mobile_activate_blocks(clone_elt) ; //reset the context (remove addition styling) mobile_context_reset() ; @@ -505,12 +582,12 @@ function mobile_activate_library() { //the commands are clickable for addition var library_cmds = library.getElementsByClassName("pgm-command") ; for(var i = 0; i < library_cmds.length; ++i) { - Sortable.utils.on(library_cmds[i], "click", insert_chosen) ; + library_cmds[i].addEventListener("click", insert_chosen) ; } //the blocks are clickable for addition var library_blocks = library.getElementsByClassName("pgm-block") ; for(var i = 0; i < library_blocks.length; ++i) { - Sortable.utils.on(library_blocks[i], "click", insert_chosen) ; + library_blocks[i].addEventListener("click", insert_chosen) ; } } @@ -520,12 +597,12 @@ function mobile_deactivate_library() { //deactivate the commands var library_cmds = library.getElementsByClassName("pgm-command") ; for(var i = 0; i < library_cmds.length; ++i) { - Sortable.utils.off(library_cmds[i], "click", insert_chosen) ; + library_cmds[i].removeEventListener("click", insert_chosen) ; } //deactivate the blocks var library_blocks = library.getElementsByClassName("pgm-block") ; for(var i = 0; i < library_blocks.length; ++i) { - Sortable.utils.off(library_blocks[i], "click", insert_chosen) ; + library_blocks[i].removeEventListener("click", insert_chosen) ; } } @@ -550,26 +627,26 @@ function mobile_activate_blocks(el) { //list pgm-blocks and activate them var pgm_blocks = el.getElementsByClassName("pgm-block") ; for(var i = 0; i < pgm_blocks.length; ++i) { - Sortable.utils.on(pgm_blocks[i], "click", mobile_pgm_click) ; + pgm_blocks[i].addEventListener("click", mobile_pgm_click) ; } //list pgm-commands and activate them var pgm_commands = el.getElementsByClassName('pgm-command') ; for(var i = 0; i < pgm_commands.length; ++i) { //commands inside receivers are not to be activated if(pgm_commands[i].parentElement.className.match(/pgm-recv-void/)) { - Sortable.utils.on(pgm_commands[i], "click", mobile_pgm_click) ; + pgm_commands[i].addEventListener("click", mobile_pgm_click) ; } } //list the inputs in the element (normally only one) and activate them var pgm_inputs = el.getElementsByTagName('input') ; for(var i = 0; i < pgm_inputs.length; ++i) { pgm_inputs[i].removeAttribute("disabled") ; - Sortable.utils.on(pgm_inputs[i], "click", input_click) ; + pgm_inputs[i].addEventListener("click", input_click) ; } //list the placeholders in the element (normally only one) and activate them var pgm_placeholders = el.getElementsByClassName('placeholder') ; for(var i = 0; i < pgm_placeholders.length; ++i) { - Sortable.utils.on(pgm_placeholders[i], "click", mobile_pgm_click) ; + pgm_placeholders[i].addEventListener("click", mobile_pgm_click) ; } } @@ -577,26 +654,141 @@ function mobile_deactivate_blocks(el) { var pgm_blocks = el.getElementsByClassName("pgm-block") ; //list pgm-blocks and deactivate them for(var i = 0; i < pgm_blocks.length; ++i) { - Sortable.utils.off(pgm_blocks[i], "click", mobile_pgm_click) ; + pgm_blocks[i].removeEventListener("click", mobile_pgm_click) ; } //list pgm-commands and deactivate them var pgm_commands = el.getElementsByClassName('pgm-command') ; for(var i = 0; i < pgm_commands.length; ++i) { //commands inside receivers are not to be deactivated if(pgm_commands[i].parentElement.className.match(/pgm-recv-void/)) { - Sortable.utils.off(pgm_commands[i], "click", mobile_pgm_click) ; + pgm_commands[i].removeEventListener("click", mobile_pgm_click) ; } } //list the inputs in the element (normally only one) and activate them var pgm_inputs = el.getElementsByTagName('input') ; for(var i = 0; i < pgm_inputs.length; ++i) { - Sortable.utils.off(pgm_inputs[i], "click", input_click) ; + pgm_inputs[i].removeEventListener("click", input_click) ; } //list the placeholders in the element (normally only one) and activate them var pgm_placeholders = el.getElementsByClassName('placeholder') ; for(var i = 0; i < pgm_placeholders.length; ++i) { - Sortable.utils.off(pgm_placeholders[i], "click", mobile_pgm_click) ; + pgm_placeholders[i].removeEventListener("click", mobile_pgm_click) ; + } +} + +function show_mobile_ui() { + var phone_toolbar = document.getElementById("phone-toolbar") ; + removeClass(phone_toolbar, "hidden") ; + var library_close = document.getElementById("btn-close-library") ; + removeClass(library_close, "hidden") ; + var block_adders_collection = document.getElementsByClassName('block-add') ; + //necessary, since it seems like appendChild messes with HTMLCollection + var block_adders = [].slice.call(block_adders_collection) ; + for(var i = 0; i < block_adders.length; ++i) { + var el = block_adders[i] ; + var el_parent = el.parentElement ; + removeClass(el, "hidden") ; + //replace adder at the end of the list + el_parent.appendChild(el) ; + } ; +} + +function hide_mobile_ui() { + var phone_toolbar = document.getElementById("phone-toolbar") ; + addClass(phone_toolbar, "hidden") ; + var library_close = document.getElementById("btn-close-library") ; + addClass(library_close, "hidden") ; + var block_adders = document.getElementsByClassName('block-add') ; + [].forEach.call(block_adders, function(el) { + addClass(el, "hidden") ; + }) ; +} + +function toggle_mobile() { + var mobile_switch = document.getElementById("desktop-ui-phone") ; + if(mobile_switch.className.match(/active/)) { + removeClass(mobile_switch, "active") ; + update_pgm_ui() ; + } else { + addClass(mobile_switch, "active") ; + update_pgm_ui() ; + } +} + +document.getElementById("desktop-ui-phone").addEventListener( + 'click', + function(evt) { + toggle_mobile() ; + } +) ; + +function is_mobile() { + var mobile_switch = document.getElementById("desktop-ui-phone") ; + if(mobile_switch.className.match(/active/)) { + return true ; } + var test_el = document.getElementById("mobile-tester") ; + return getComputedStyle(test_el ,null).display !== "none" ; +} + +function phone_tools_set(id) { + var phone_toolbar = document.getElementById("phone-toolbar") ; + var btns = phone_toolbar.getElementsByTagName("button") ; + for(var i = 0; i < btns.length; ++i) { + if(btns[i].id == id) { + addClass(btns[i], "active") ; + } else { + removeClass(btns[i], "active") ; + } + } +} + +document.getElementById('phone-tool-add').addEventListener( + 'click', + function(evt) { + phone_tools_set('phone-tool-add') ; + } +) ; + +document.getElementById('phone-tool-cut').addEventListener( + 'click', + function(evt) { + phone_tools_set('phone-tool-cut') ; + } +) ; + +document.getElementById('phone-tool-copy').addEventListener( + 'click', + function(evt) { + phone_tools_set('phone-tool-copy') ; + } +) ; + +document.getElementById('phone-tool-paste').addEventListener( + 'click', + function(evt) { + phone_tools_set('phone-tool-paste') ; + } +) ; + +function phone_tools_get() { + var phone_toolbar = document.getElementById("phone-toolbar") ; + var btns = phone_toolbar.getElementsByTagName("button") ; + for(var i = 0; i < btns.length; ++i) { + if(btns[i].className.match(/active/)) { + return btns[i].id ; + } + } +} + +function open_library() { + var elem = document.getElementById("program") ; + addClass(elem, "active") ; +} + +function close_library() { + var elem = document.getElementById("program") ; + removeClass(elem, "active") ; } function mobile_activate_program() { @@ -622,7 +814,9 @@ function mobile_deactivate() { hide_mobile_ui() ; } -/************************* Activation *************************/ +/*}}}*/ + +/* {{{ Activation =========================================================== */ function update_pgm_ui() { //check the display type : mobile or desktop @@ -644,3 +838,7 @@ document.addEventListener("DOMContentLoaded", function(event) { window.addEventListener("resize", function(event) { update_pgm_ui() ; }); + +/*}}}*/ + +}); diff --git a/sortable_test.html b/sortable_test.html index 6be9d0a..16c2f5b 100644 --- a/sortable_test.html +++ b/sortable_test.html @@ -10,6 +10,7 @@ <script type="text/javascript" src="js/ui.js"></script> </head> <body> + <!--{{{ programming interface--> <div class="container" id="root-container"> <!--{{{ navigation--> <nav class="navbar navbar-default"> @@ -42,9 +43,10 @@ <div class="container-fluid" id="pgm-container"> <div class="row row-offcanvas row-offcanvas-left" id="program"> - <!---{{{ commands--> + <!--{{{ commands--> <div class="col-xs-12 col-sm-4 sidebar-offcanvas" id="library"> <div class="panel panel-default"> + <!--{{{ tabs--> <div class="panel-heading tabbed-heading"> <ul class="nav nav-tabs"> <li class="active"> @@ -71,7 +73,9 @@ href="javascript:mobile_context_reset();" ></a> </div> + <!--}}}--> <div class="tab-content panel-collapse no-top-border-lists"> + <!--{{{ movement--> <ul id="mv_cmds" class="list-group tab-pane fade library-group in active"> <li class="list-group-item pgm-command pgm-type-direction cmd-dir-up"> <span class="glyphicon glyphicon-circle-arrow-up"></span> @@ -134,6 +138,8 @@ </div> </li> </ul> + <!--}}}--> + <!--{{{ vaiables--> <ul id="var_cmds" class="list-group tab-pane fade library-group"> <li class="list-group-item pgm-command pgm-type-void cmd-var-local-set"> <span class="glyphicon glyphicon-map-marker"></span> @@ -199,6 +205,8 @@ tout </li> </ul> + <!--}}}--> + <!--{{{ control--> <ul id="ctrl_cmds" class="list-group tab-pane fade library-group"> <li class="list-group-item panel panel-default pgm-block pgm-type-void cmd-block-if"> <div class="panel-heading"> @@ -248,6 +256,8 @@ </ul> </li> </ul> + <!--}}}--> + <!--{{{ tests--> <ul id="test_cmds" class="list-group tab-pane fade library-group"> <li class="list-group-item pgm-command pgm-type-bool cmd-test-equal"> <div class="pgm-recv pgm-recv-str"> @@ -267,7 +277,7 @@ <input size=8 placeholder="valeur" disabled></input> </div> </li> - <li class="list-group-item pgm-command pgm-type-bool cmd-test-gte"> + <li class="list-group-item pgm-command pgm-type-bool cmd-test-ge"> <div class="pgm-recv pgm-recv-int"> <input size=8 placeholder="valeur" disabled></input> </div> @@ -315,6 +325,8 @@ existe </li> </ul> + <!--}}}--> + <!--{{{ arithmetics--> <ul id="arth_cmds" class="list-group tab-pane fade library-group"> <li class="list-group-item pgm-command pgm-type-int cmd-arith-plus"> ( @@ -361,6 +373,8 @@ ) </li> </ul> + <!--}}}--> + <!--{{{ strings--> <ul id="txt_cmds" class="list-group tab-pane fade library-group"> <li class="list-group-item pgm-command pgm-type-str cmd-str-concat"> <span class="glyphicon glyphicon-paperclip"></span> @@ -372,11 +386,12 @@ </div> </li> </ul> + <!--}}}--> </div> </div> </div> <!---}}}--> - <!---{{{ program--> + <!--{{{ program--> <div class="col-xs-12 col-sm-8"> <div class="panel panel-default pgm"> <div class="panel-heading"> @@ -401,11 +416,11 @@ </ul> </div> </div> - <!---}}}--> + <!--}}}--> </div> </div> </div> - <!---{{{ mobile toolbar--> + <!--{{{ mobile toolbar--> <div class="btn-toolbar hidden" role="toolbar" id="phone-toolbar"> <div class="btn-group" role="group"> <button class="btn btn-default active" autofocus type="button" id="tool-add" @@ -430,7 +445,8 @@ </button> </div> </div> - <!---}}}--> + <!--}}}--> + <!--}}}--> <script type="text/javascript" src="js/bootstrap-native.js"></script> <script type="text/javascript" src="js/pgm_construction.js"></script> <script type="text/javascript" src="js/pgm_compiler.js"></script> -- GitLab