diff --git a/blocks/ORB/New.pm b/blocks/ORB/New.pm new file mode 100644 index 0000000..4f2ed2a --- /dev/null +++ b/blocks/ORB/New.pm @@ -0,0 +1,279 @@ +## @file +# This file contains the implementation of the new page. +# +# @author Chris Page <chris@starforge.co.uk> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# 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 +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. + +## @class +package ORB::New; + +use strict; +use parent qw(ORB); # This class extends the ORB block class +use experimental qw(smartmatch); +use v5.14; + +# How many ingredient rows should appear in the empty form? +use constant DEFAULT_INGREDIENT_COUNT => 5; + + +## @method private $ _build_timereq($seconds) +# Given a time requirement in seconds, generate a string representing +# the time required in the form "X days, Y hours and Z minues", +# optionally dropping parts of the string depending on whether +# X, Y, or Z are zero. +# +# @param seconds The number of seconds required to make the recipe. +# @return A string representing the seconds. +sub _build_timereq { + my $self = shift; + my $seconds = shift; + + my $days = int($seconds / (24 * 60 * 60)); + my $hours = ($seconds / (60 * 60)) % 24; + my $mins = ($seconds / 60) % 60; + + # localisation needed... + my @parts = (); + push(@parts, "$days days") if($days); + push(@parts, "$hours hours") if($hours); + push(@parts, "$mins minutes") if($mins); + + my $count = scalar(@parts); + if($count == 3) { + return $parts[0].", ".$parts[1]." and ".$parts[2]; + } elsif($count == 2) { + return $parts[0]." and ".$parts[1]; + } elsif($count == 1) { + return $parts[0]; + } + + return ""; +} + + +sub _build_temptypes { + my $self = shift; + my $default = shift; + + # Supported types are in the column enum list + my $tempenum = $self -> get_enum_values($self -> {"settings"} -> {"database"} -> {"recipes"}, "temptype"); + return $tempenum + unless(ref($tempenum) eq "ARRAY"); + + # convert to something build_optionlist will understand + map { $_ = { name => $_, value => $_ } } @{$tempenum}; + + return $self -> {"template"} -> build_optionlist($tempenum, $default); +} + + +sub _get_units { + my $self = shift; + + return $self -> {"units"} + if($self -> {"units"}); + + $self -> {"units"} = [ + { value => "None", name => "None" }, + @{ $self -> {"system"} -> {"entities"} -> {"units"} -> as_options(1) } + ]; + + return $self -> {"units"}; +} + + +sub _build_ingredients { + my $self = shift; + my $args = shift; + my $units = shift; + my $preps = shift; + + my @ingreds = (); + + # If any ingredients are present in the argument list, push them into templated strings + if($args -> {"ingredients"} && scalar(@{$args -> {"ingredients"}})) { + foreach my $ingred (@{$args -> {"ingredients"}}) { + # Ensure we never try to deal with undef elements in the array + next unless($ingred); + + # Which template to use depends on whether this is a separator + my $template = $ingred -> {"separator"} ? "new/separator.tem" : "new/ingredient.tem"; + + my $unitopts = $self -> {"template"} -> build_optionlist($units, $args -> {"units"}); + my $prepopts = $self -> {"template"} -> build_optionlist($preps, $args -> {"prep"}); + + push(@ingreds, + $self -> {"template"} -> load_template($template, + { "%(quantity)s" => $ingred -> {"quantity"}, + "%(name)s" => $ingred -> {"name"}, + "%(notes)s" => $ingred -> {"notes"}, + "%(units)s" => $unitopts, + "%(preps)s" => $prepopts, + })); + } + + # if the ingredient list is empty, generate some empties + } else { + # Only need to calculate these once for the empty ingredients + my $unitopts = $self -> {"template"} -> build_optionlist($units); + my $prepopts = $self -> {"template"} -> build_optionlist($preps); + + for(my $i = 0; $i < DEFAULT_INGREDIENT_COUNT; ++$i) { + push(@ingreds, + $self -> {"template"} -> load_template("new/ingredient.tem", + { "%(quantity)s" => "", + "%(name)s" => "", + "%(notes)s" => "", + "%(units)s" => $unitopts, + "%(preps)s" => $prepopts, + })); + } + } + + return join("", @ingreds); +} + + +sub _generate_new { + my $self = shift; + my ($args, $errors); + + if($errors) { + $self -> log("new", "Errors detected in addition: $errors"); + + my $errorlist = $self -> {"template"} -> load_template("error/error_list.tem", {"%(message)s" => "{L_NEW_ERRORS}", + "%(errors)s" => $errors }); + $errors = $self -> {"template"} -> load_template("error/page_error.tem", { "%(message)s" => $errorlist }); + } + + # Prebuild arrays for units and prep methods + my $units = $self -> _get_units(); + my $preps = $self -> {"system"} -> {"entities"} -> {"prep"} -> as_options(1); + + # And convert them to optionlists for the later template call + my $unitopts = $self -> {"template"} -> build_optionlist($units); + my $prepopts = $self -> {"template"} -> build_optionlist($preps); + + # Build the list of ingredients + my $ingredients = $self -> _build_ingredients($args, $units, $preps); + + # Build up the type and status data + my $typeopts = $self -> {"template"} -> build_optionlist($self -> {"system"} -> {"entities"} -> {"types"} -> as_options(), + $args -> {"type"}); + + my $statusopts = $self -> {"template"} -> build_optionlist($self -> {"system"} -> {"entities"} -> {"states"} -> as_options(0, visible => {value => 1}), + $args -> {"status"}); + + # Convert the time fields + my ($timemins, $timesecs) = ("", 0); + if($args -> {"timemins"}) { + $timesecs = $args -> {"timemins"} * 60; + $timemins = $self -> _build_timereq($timesecs); + } + + # And squirt out the page content + my $body = $self -> {"template"} -> load_template("new/content.tem", + { + "%(errors)s" => $errors, + "%(name)s" => $args -> {"name"} // "", + "%(source)s" => $args -> {"source"} // "", + "%(yield)s" => $args -> {"yield"} // "", + "%(timereq)s" => $args -> {"timereq"} // "", + "%(timemins)s" => $timemins, + "%(timesecs)s" => $timesecs, + "%(temp)s" => $args -> {"temp"} // "", + "%(temptypes)s" => $self -> _build_temptypes($args -> {"temptype"}), + "%(types)s" => $typeopts, + "%(units)s" => $unitopts, + "%(preps)s" => $prepopts, + "%(status)s" => $statusopts, + "%(ingreds)s" => $ingredients, + "%(method)s" => $args -> {"method"} // "", + "%(notes)s" => $args -> {"notes"} // "", + }); + + return ($self -> {"template"} -> replace_langvar("NEW_TITLE"), + $body, + $self -> {"template"} -> load_template("new/extrahead.tem"), + $self -> {"template"} -> load_template("new/extrajs.tem")); +} + + +# ============================================================================ +# UI handler/dispatcher functions + +## @method private @ _fatal_error($error) +# Generate the tile and content for an error page. +# +# @param error A string containing the error message to display +# @return The title of the error page and an error message to place in the page. +sub _fatal_error { + my $self = shift; + my $error = shift; + + return ("{L_VIEW_ERROR_FATAL}", + $self -> {"template"} -> load_template("error/page_error.tem", + { "%(message)s" => $error, + "%(url-logout)s" => $self -> build_url(block => "login", pathinfo => ["signout"]) + }) + ); +} + + +## @method private $ _dispatch_ui() +# Implements the core behaviour dispatcher for non-api functions. This will +# inspect the state of the pathinfo and invoke the appropriate handler +# function to generate content for the user. +# +# @return A string containing the page HTML. +sub _dispatch_ui { + my $self = shift; + + my ($title, $body, $extrahead, $extrajs) = $self -> _generate_new(); + + # Done generating the page content, return the filled in page template + return $self -> generate_orb_page(title => $title, + content => $body, + extrahead => $extrahead, + extrajs => $extrajs, + active => '-', + doclink => 'summary'); +} + + +# ============================================================================ +# Module interface functions + +## @method $ page_display() +# Generate the page content for this module. +sub page_display { + my $self = shift; + + # Is this an API call, or a normal page operation? + my $apiop = $self -> is_api_operation(); + if(defined($apiop)) { + # API call - dispatch to appropriate handler. + given($apiop) { + default { + return $self -> api_response($self -> api_errorhash('bad_op', + $self -> {"template"} -> replace_langvar("API_BAD_OP"))) + } + } + } else { + return $self -> _dispatch_ui(); + } +} + +1; \ No newline at end of file diff --git a/lang/en/new.lang b/lang/en/new.lang new file mode 100644 index 0000000..da9c0fa --- /dev/null +++ b/lang/en/new.lang @@ -0,0 +1,47 @@ +NEW_TITLE = Create Recipe + +NEW_NAME = Name +NEW_NAME_DOC = The name of the recipe +NEW_NAME_PH = Recipe name + +NEW_SOURCE = Source +NEW_SOURCE_DOC = Information about the source this recipe was based on +NEW_SOURCE_PH = http://source.url + +NEW_YIELD = Yield +NEW_YIELD_DOC = How many servings does this recipe make? +NEW_YIELD_PH = X servings + +NEW_PREPINFO = Prep info +NEW_PREPINFO_DOC = How much time each step of the recipe take? +NEW_PREPINFO_PH = 10 min prep + 20 min cook + +NEW_TIMEREQ = Time required +NEW_TIMEREQ_DOC = How long does this recipe take in total? +NEW_TIMEREQ_PH = 1 hour 10 minutes + +NEW_OVENTEMP = Oven preheat +NEW_OVENTEMP_DOC = Initial oven temperature (show changes in method) +NEW_OVENTEMP_PH = None + +NEW_TYPE = Type +NEW_STATUS = Status +NEW_TAGS = Tags + +NEW_ADD_SEP = Add Separator +NEW_ADD_INGRED = Add Ingredient +NEW_ADD_INGRED5 = Add 5 Ingredients +NEW_ADD_INGRED10 = Add 10 Ingredients + + +NEW_INGREDIENTS = Ingredients +NEW_ING_QUANT_PH = Quantity +NEW_ING_ING_PH = Ingredient +NEW_ING_NOTE_PH = Notes +NEW_ING_SEP_PH = Separator text +NEW_ING_DELETE = Delete + +NEW_METHOD = Method +NEW_NOTES = Notes + +NEW_CREATE = Add Recipe diff --git a/lang/en/validate.lang b/lang/en/validate.lang index ccc5797..5fc48dd 100755 --- a/lang/en/validate.lang +++ b/lang/en/validate.lang @@ -16,3 +16,5 @@ BLOCK_VALIDATE_RANGEMAX = The value provided for '***field***' is out of range BLOCK_ERROR_TITLE = Fatal System Error BLOCK_ERROR_SUMMARY = The system has encountered an unrecoverable error. BLOCK_ERROR_TEXT = A serious error has been encountered while processing your request. The following information was generated by the system, please contact moodlesupport@cs.man.ac.uk about this, including this error and a description of what you were doing when it happened!

***error*** + +BLOCK_ERROR_BADENUM = Unable to fetch enum values from ***table***.***col***: ***errstr*** \ No newline at end of file diff --git a/templates/default/css/new.css b/templates/default/css/new.css new file mode 100644 index 0000000..6645f65 --- /dev/null +++ b/templates/default/css/new.css @@ -0,0 +1,27 @@ + +#ingredients { + margin: 0px; + padding: 0px; +} + +#ingredients li { + list-style: none; + margin-bottom: 0.5rem; + padding-left: 1rem; + background-repeat: no-repeat; + background-image: url('../images/draghandle.png'); + background-position: left center; +} + +#ingredients li input, +#ingredients li select, +#ingredients li button +{ + margin: 0px; + height: auto; +} + +#ingredients .ui-state-highlight { + height: calc(2.75rem - 2px); + background-image: none; +} diff --git a/templates/default/images/draghandle.png b/templates/default/images/draghandle.png new file mode 100755 index 0000000..21708a8 Binary files /dev/null and b/templates/default/images/draghandle.png differ diff --git a/templates/default/images/draghandle.xcf b/templates/default/images/draghandle.xcf new file mode 100755 index 0000000..24cc474 Binary files /dev/null and b/templates/default/images/draghandle.xcf differ diff --git a/templates/default/js/new.js b/templates/default/js/new.js new file mode 100644 index 0000000..a103be2 --- /dev/null +++ b/templates/default/js/new.js @@ -0,0 +1,74 @@ + + +function add_separator() +{ + var $new = $('#templates li.separator').clone(true); + $new.hide().appendTo($('#ingredients')).fadeIn(300); +} + + +function add_ingredient(count) +{ + for(i = 0; i < count; ++i) { + var $new = $('#templates li.ingred').clone(true); + $new.hide().appendTo($('#ingredients')).fadeIn(300); + + $new.find(".ingredient").autocomplete({ + source: api.ingredients, + minLength: 2 + }); + + } +} + + +$(function() { + $('#timemins').timeDurationPicker({ + lang: 'en_US', + seconds: false, + minutes: true, + hours: true, + days: true, + months: false, + years: false, + onSelect: function(element, seconds, humanDuration) { + $('#timemins').val(humanDuration); + $('#timesecs').val(seconds); + console.log(seconds, humanDuration); + } + }); + + $('#tags').select2({ + theme: "foundation", + tags: true, + tokenSeparators: [','], + minimumInputLength: 2, + multiple: true, + ajax: { + delay: 250, + dataType: 'json', + url: api.tags + } + }); + + CKEDITOR.replace('method'); + CKEDITOR.replace('notes'); + + $('#ingredients').sortable({ + placeholder: "ui-state-highlight" + }); + + $("#ingredients .ingredient").autocomplete({ + source: api.ingredients, + minLength: 2 + }); + + // Handle addition of separators and ingredients + $('#addsep').on('click', function() { add_separator(); }); + $('.adding').on('click', function() { add_ingredient($(this).data('count')); }); + + // Handle removal of separators and ingredients + $('.deletectrl').on('click', function() { + $(this).parents('li').fadeOut(300, function() { $(this).remove(); }); + }); +}); diff --git a/templates/default/new/content.tem b/templates/default/new/content.tem new file mode 100644 index 0000000..38a4169 --- /dev/null +++ b/templates/default/new/content.tem @@ -0,0 +1,142 @@ +%(errors)s +
+
+

{L_NEW_TITLE}

+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+
+ +
+ +
+ + + + Show menu + + +
+ +
+ +
+
+ +
+ +
+ +
+
+
+ diff --git a/templates/default/new/extrahead.tem b/templates/default/new/extrahead.tem new file mode 100644 index 0000000..ab77799 --- /dev/null +++ b/templates/default/new/extrahead.tem @@ -0,0 +1 @@ + diff --git a/templates/default/new/extrajs.tem b/templates/default/new/extrajs.tem new file mode 100644 index 0000000..46c6e28 --- /dev/null +++ b/templates/default/new/extrajs.tem @@ -0,0 +1,8 @@ + + + + + - + - +