--- loncom/interface/lonnavmaps.pm 2002/10/08 19:50:47 1.70
+++ loncom/interface/lonnavmaps.pm 2002/10/15 19:51:25 1.82
@@ -1,7 +1,8 @@
+
# The LearningOnline Network with CAPA
# Navigate Maps Handler
#
-# $Id: lonnavmaps.pm,v 1.70 2002/10/08 19:50:47 bowersj2 Exp $
+# $Id: lonnavmaps.pm,v 1.82 2002/10/15 19:51:25 bowersj2 Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -44,8 +45,8 @@ use strict;
use Apache::Constants qw(:common :http);
use Apache::lonnet();
use Apache::loncommon();
-use HTML::TokeParser;
use GDBM_File;
+use POSIX qw (floor strftime);
# -------------------------------------------------------------- Module Globals
my %hash;
@@ -822,7 +823,7 @@ sub new_handle {
# Initialize the nav map
my $navmap = Apache::lonnavmaps::navmap->new(
$ENV{"request.course.fn"}.".db",
- $ENV{"request.course.fn"}."_parms.db", 1);
+ $ENV{"request.course.fn"}."_parms.db", 1, 1);
if (!defined($navmap)) {
@@ -842,7 +843,7 @@ sub new_handle {
$r->rflush();
if ($navmap->{LAST_CHECK}) {
$r->print(' New discussion since '.
- timeToHumanString($navmap->{LAST_CHECK}).
+ strftime("%A, %b %e at %I:%M %P", localtime($navmap->{LAST_CHECK})).
'
New message (click to open)
');
} else {
$r->print(' Discussions'.
@@ -916,7 +917,13 @@ sub new_handle {
my $currenturl = $ENV{'form.postdata'};
$currenturl=~s/^http\:\/\///;
$currenturl=~s/^[^\/]+//;
- my $queryAdd = "postdata=" . &Apache::lonnet::escape($currenturl);
+ # alreadyHere allows us to only open the maps necessary to view
+ # the current location once, while at the same time remembering
+ # the current location. Without that check, the user would never
+ # be able to close those maps; the user would close it, and the
+ # currenturl scan would re-open it.
+ my $queryAdd = "postdata=" . &Apache::lonnet::escape($currenturl) .
+ "&alreadyHere=1";
$r->print('Show All Resources
');
@@ -929,17 +936,57 @@ sub new_handle {
$condition = 1;
}
- my $mapIterator = $navmap->getIterator(undef, undef, \%filterHash, $condition);
- my $curRes = $mapIterator->next();
+ # This needs to be updated to use symbs from the remote,
+ # instead of uris. The changes to this and the main rendering
+ # loop should be obvious.
+ # Here's a simple example of the iterator.
+ # If there is a current resource
+ if ($currenturl && !$ENV{'form.alreadyHere'}) {
+ # Give me every resource...
+ my $mapIterator = $navmap->getIterator(undef, undef, {}, 1);
+ my $found != 0;
+ my $depth = 1;
+ $mapIterator->next(); # discard the first BEGIN_MAP
+ my $curRes = $mapIterator->next();
+
+ while ($depth > 0 && !$found) {
+ if (ref($curRes) && $curRes->src() eq $currenturl) {
+ # If this is the correct resource, be sure to
+ # show it by making sure the containing maps
+ # are open.
+
+ my $mapStack = $mapIterator->getStack();
+ for my $map (@{$mapStack}) {
+ if ($condition) {
+ undef $filterHash{$map->map_pc()};
+ } else {
+ $filterHash{$map->map_pc()} = 1;
+ }
+ }
+ $found = 1;
+ }
+ $curRes = $mapIterator->next();
+ }
+ }
+
undef $res; # so we don't accidentally use it later
- my $indentLevel = -1;
+ my $indentLevel = 0;
my $indentString = "";
my $isNewBranch = 0;
my $now = time();
my $in24Hours = $now + 24 * 60 * 60;
+ my $depth = 1;
+ my $displayedHereMarker = 0;
+
+ # We know the first thing is a BEGIN_MAP (see "$self->{STARTED}"
+ # code in iterator->next), so ignore the first one
+ my $mapIterator = $navmap->getIterator(undef, undef, \%filterHash,
+ $condition);
+ $mapIterator->next();
+ my $curRes = $mapIterator->next();
- while ($curRes != $mapIterator->END_NAV_MAP) {
+ while ($depth > 0) {
if ($curRes == $mapIterator->BEGIN_MAP() ||
$curRes == $mapIterator->BEGIN_BRANCH()) {
$indentLevel++;
@@ -951,6 +998,12 @@ sub new_handle {
if ($curRes == $mapIterator->BEGIN_BRANCH()) {
$isNewBranch = 1;
}
+ if ($curRes == $mapIterator->BEGIN_MAP()) {
+ $depth++;
+ }
+ if ($curRes == $mapIterator->END_MAP()) {
+ $depth--;
+ }
# Is this resource being blotted out?
if (ref($curRes) && !advancedUser() && $curRes->randomout()) {
@@ -1024,7 +1077,7 @@ sub new_handle {
} else {
@parts[0] = "0"; # this is to get past foreach loop below
# you can consider a non-problem resource as a resource
- # with only one part without loss
+ # with only one part without loss
}
# Display one part, in event of network error.
@@ -1051,6 +1104,10 @@ sub new_handle {
'symb='.&Apache::lonnet::escape($curRes->symb()).
'"';
my $title = $curRes->title();
+ if (!$title) {
+ $title = $curRes->src();
+ $title = substr ($title, rindex($title, "/") + 1);
+ }
my $partLabel = "";
my $newBranchText = "";
@@ -1078,9 +1135,10 @@ sub new_handle {
# Display the correct icon, link to open or shut map
if ($curRes->is_map()) {
my $mapId = $curRes->map_pc();
- my $nowOpen = !defined($filterHash{$mapId});
+ my $nowOpen = (!defined($filterHash{$mapId}));
+ if ($condition) {$nowOpen = !$nowOpen;}
$icon = $nowOpen ?
- "folder_closed.gif" : "folder_opened.gif";
+ "navmap.folder.closed.gif" : "navmap.folder.open.gif";
$icon = "";
$linkopen = "is_problem()) {
my $status = $curRes->status($part);
my $color = $colormap{$status};
+
+ # Special case in the navmaps: If in less then
+ # 24 hours, give it a bit of urgency
+ if ($status == $curRes->OPEN() && $curRes->duedate() &&
+ $curRes->duedate() < time()+(24*60*60) &&
+ $curRes->duedate() > time()) {
+ $color = $hurryUpColor;
+ }
+ # Special case: If this is the last try, and there is
+ # more then one available, give a bit of urgency
+ my $tries = $curRes->tries($part);
+ my $maxtries = $curRes->maxtries($part);
+ if ($tries && $maxtries && $maxtries > 1 &&
+ $maxtries - $tries == 1) {
+ $color = $hurryUpColor;
+ }
if ($color ne "") {
$colorizer = "bgcolor=\"$color\"";
}
@@ -1105,7 +1179,7 @@ sub new_handle {
}
# FIRST COL: The resource indentation, branch icon, and name
- $r->print(" \n");
+ $r->print(" \n");
# print indentation
for (my $i = 0; $i < $indentLevel - $deltalevel; $i++) {
@@ -1114,15 +1188,25 @@ sub new_handle {
$r->print(" ${newBranchText}${linkopen}$icon${linkclose}\n");
+ my $curMarkerBegin = "";
+ my $curMarkerEnd = "";
+
+ # Is this the current resource?
+ if ($curRes->src() eq $currenturl && !$displayedHereMarker) {
+ $curMarkerBegin = '> ';
+ $curMarkerEnd = ' <';
+ $displayedHereMarker = 1;
+ }
+
if ($curRes->is_problem() && $part ne "0" && !$condensed) {
$partLabel = " (Part $part)";
$title = "";
}
- if ($multipart && $condensed) {
+ if ($multipart && $condensed && $curRes->countParts > 1) {
$nonLinkedText .= ' (' . $curRes->countParts() . ' parts)';
}
- $r->print(" $title$partLabel $nonLinkedText");
+ $r->print(" $curMarkerBegin$title$partLabel $curMarkerEnd $nonLinkedText");
if ($curRes->{RESOURCE_ERROR}) {
$r->print(&Apache::loncommon::help_open_topic ("Navmap_Host_Down",
@@ -1150,7 +1234,7 @@ sub new_handle {
}
}
- $r->print(" $discussionHTML$feedbackHTML ");
+ $r->print("$discussionHTML$feedbackHTML ");
# Is this the first displayed part of a multi-part problem
# that has not been condensed, so we should suppress these two
@@ -1163,7 +1247,7 @@ sub new_handle {
my $icon = $statusIconMap{$curRes->status($part)};
my $alt = $iconAltTags{$icon};
if ($icon) {
- $r->print("$linkopen$linkclose \n");
+ $r->print("$linkopen$linkclose \n");
} else {
$r->print("\n");
}
@@ -1172,7 +1256,7 @@ sub new_handle {
}
# FOURTH COL: Text description
- $r->print(" \n");
+ $r->print(" \n");
if ($curRes->kind() eq "res" &&
$curRes->is_problem() &&
@@ -1258,20 +1342,20 @@ sub getDescription {
return "Not currently assigned.";
}
if ($status == $res->OPEN_LATER) {
- return "Opens: " . timeToHumanString($res->opendate($part));
+ return "Open " . timeToHumanString($res->opendate($part));
}
if ($status == $res->OPEN) {
- if ($res->duedate()) {
- return "Due: $status " . timeToHumanString($res->duedate($part));
+ if ($res->duedate($part)) {
+ return "Due " . timeToHumanString($res->duedate($part));
} else {
return "Open, no due date";
}
}
if ($status == $res->PAST_DUE_ANSWER_LATER) {
- return "Answer opens: " . timeToHumanString($res->answerdate($part));
+ return "Answer open " . timeToHumanString($res->answerdate($part));
}
if ($status == $res->PAST_DUE_NO_ANSWER) {
- return "Was Due: " . timeToHumanString($res->duedate($part));
+ return "Was due " . timeToHumanString($res->duedate($part));
}
if ($status == $res->ANSWER_OPEN) {
return "Answer available";
@@ -1283,11 +1367,17 @@ sub getDescription {
return "Not yet graded.";
}
if ($status == $res->TRIES_LEFT) {
- my $tries = $res->tries();
- my $maxtries = $res->maxtries();
- my $triesString = "($tries of $maxtries tries used)";
+ my $tries = $res->tries($part);
+ my $maxtries = $res->maxtries($part);
+ my $triesString = "";
+ if ($tries && $maxtries) {
+ $triesString = "($tries of $maxtries tries used)";
+ if ($maxtries > 1 && $maxtries - $tries == 1) {
+ $triesString = "$triesString";
+ }
+ }
if ($res->duedate()) {
- return "Due: $status " . timeToHumanString($res->duedate($part)) .
+ return "Due " . timeToHumanString($res->duedate($part)) .
" $triesString";
} else {
return "No due date $triesString";
@@ -1299,16 +1389,108 @@ sub advancedUser {
return $ENV{'user.adv'};
}
-# I want to change this into something more human-friendly. For
-# now, this is a simple call to localtime. The final function
-# probably belongs in loncommon.
+
+# timeToHumanString takes a time number and converts it to a
+# human-readable representation, meant to be used in the following
+# manner:
+# print "Due $timestring"
+# print "Open $timestring"
+# print "Answer available $timestring"
+# Very, very, very, VERY English-only... goodness help a localizer on
+# this func...
sub timeToHumanString {
my ($time) = @_;
# zero, '0' and blank are bad times
- if ($time) {
- return localtime($time);
- } else {
- return 'Never';
+ if (!$time) {
+ return 'never';
+ }
+
+ my $now = time();
+
+ my @time = localtime($time);
+ my @now = localtime($now);
+
+ # Positive = future
+ my $delta = $time - $now;
+
+ my $minute = 60;
+ my $hour = 60 * $minute;
+ my $day = 24 * $hour;
+ my $week = 7 * $day;
+ my $inPast = 0;
+
+ # Logic in comments:
+ # Is it now? (extremely unlikely)
+ if ( $delta == 0 ) {
+ return "this instant";
+ }
+
+ if ($delta < 0) {
+ $inPast = 1;
+ $delta = -$delta;
+ }
+
+ # Is it in the future?
+ if ( $delta > 0 ) {
+ # Is it less then a minute away?
+ my $tense = $inPast ? " ago" : "";
+ my $prefix = $inPast ? "" : "in ";
+ if ( $delta < $minute ) {
+ if ($delta == 1) { return "${prefix}1 second$tense"; }
+ return "$prefix$delta seconds$tense";
+ }
+
+ # Is it less then an hour away?
+ if ( $delta < $hour ) {
+ # If so, use minutes
+ my $minutes = floor($delta / 60);
+ if ($minutes == 1) { return "${prefix}1 minute$tense"; }
+ return "$prefix$minutes minutes$tense";
+ }
+
+ # Is it less then 24 hours away? If so,
+ # display hours + minutes
+ if ( $delta < $hour * 24) {
+ my $hours = floor($delta / $hour);
+ my $minutes = floor(($delta % $hour) / $minute);
+ my $hourString = "$hours hours";
+ my $minuteString = ", $minutes minutes";
+ if ($hours == 1) {
+ $hourString = "1 hour";
+ }
+ if ($minutes == 1) {
+ $minuteString = ", 1 minute";
+ }
+ if ($minutes == 0) {
+ $minuteString = "";
+ }
+ return "$prefix$hourString$minuteString$tense";
+ }
+
+ # Less then 5 days away, display day of the week and
+ # HH:MM
+ if ( $delta < $day * 5 ) {
+ my $timeStr = strftime("%A at %I:%M %P", localtime($time));
+ $timeStr =~ s/12:00 am/midnight/;
+ $timeStr =~ s/12:00 pm/noon/;
+ return ($inPast ? "last " : "next ") .
+ $timeStr;
+ }
+
+ # Is it this year?
+ if ( $time[5] == $now[5]) {
+ # Return on Month Day, HH:MM meridian
+ my $timeStr = strftime("on %A, %b %e at %I:%M %P", localtime($time));
+ $timeStr =~ s/12:00 am/midnight/;
+ $timeStr =~ s/12:00 pm/noon/;
+ return $timeStr;
+ }
+
+ # Not this year, so show the year
+ my $timeStr = strftime("on %A, %b %e %G at %I:%M %P", localtime($time));
+ $timeStr =~ s/12:00 am/midnight/;
+ $timeStr =~ s/12:00 pm/noon/;
+ return $timeStr;
}
}
@@ -2400,25 +2582,27 @@ sub extractParts {
$self->{PARTS} = [];
- # Retrieve part count
- my $metadata = &Apache::lonnet::metadata($self->src(), 'allpossiblekeys');
- if (!$metadata) {
- $self->{RESOURCE_ERROR} = 1;
- $self->{PARTS} = [];
- return;
- }
-
- foreach (split(/\,/,$metadata)) {
- if ($_ =~ /^parameter\_(.*)\_opendate$/) {
- push @{$self->{PARTS}}, $1;
+ # Retrieve part count, if this is a problem
+ if ($self->is_problem()) {
+ my $metadata = &Apache::lonnet::metadata($self->src(), 'allpossiblekeys');
+ if (!$metadata) {
+ $self->{RESOURCE_ERROR} = 1;
+ $self->{PARTS} = [];
+ return;
+ }
+
+ foreach (split(/\,/,$metadata)) {
+ if ($_ =~ /^parameter\_(.*)\_opendate$/) {
+ push @{$self->{PARTS}}, $1;
+ }
}
+
+
+ # Is this possible to do in one line? - Jeremy
+ my @sortedParts = sort @{$self->{PARTS}};
+ $self->{PARTS} = \@sortedParts;
}
-
- # Is this possible to do in one line? - Jeremy
- my @sortedParts = sort @{$self->{PARTS}};
- $self->{PARTS} = \@sortedParts;
-
return;
}
@@ -2667,7 +2851,7 @@ sub status {
# If it's WRONG...
if ($completionStatus == INCORRECT || $completionStatus == INCORRECT_BY_OVERRIDE) {
# and there are TRIES LEFT:
- if ($self->tries() < $self->maxtries()) {
+ if ($self->tries($part) < $self->maxtries($part) || !$self->maxtries($part)) {
return TRIES_LEFT;
}
return INCORRECT; # otherwise, return orange; student can't fix this