--- loncom/interface/lonnavmaps.pm 2002/10/01 07:20:31 1.58
+++ loncom/interface/lonnavmaps.pm 2002/10/14 18:22:45 1.79
@@ -1,7 +1,8 @@
+
# The LearningOnline Network with CAPA
# Navigate Maps Handler
#
-# $Id: lonnavmaps.pm,v 1.58 2002/10/01 07:20:31 albertel Exp $
+# $Id: lonnavmaps.pm,v 1.79 2002/10/14 18:22:45 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;
@@ -819,9 +820,6 @@ sub new_handle {
&Apache::loncommon::no_cache($r);
$r->send_http_header;
- # Header
- $r->print(&Apache::loncommon::bodytag('Navigate Course Map','',
- ''));
# Initialize the nav map
my $navmap = Apache::lonnavmaps::navmap->new(
$ENV{"request.course.fn"}.".db",
@@ -834,6 +832,28 @@ sub new_handle {
return HTTP_NOT_ACCEPTABLE;
}
+ # Header
+ $r->print(&Apache::loncommon::bodytag('Navigate Course Map','',
+ ''));
+ $r->print('');
+ my $desc=$ENV{'course.'.$ENV{'request.course.id'}.'.description'};
+ if (defined($desc)) { $r->print("
$desc
\n"); }
+ my $date=localtime;
+ $r->print("$date
\n");
+ $r->rflush();
+ if ($navmap->{LAST_CHECK}) {
+ $r->print('
New discussion since '.
+ strftime("%A, %b %e at %I:%M %P", localtime($navmap->{LAST_CHECK})).
+ '
New message (click to open)');
+ } else {
+ $r->print('
Discussions'.
+ '
New message (click to open)
');
+ }
+ #if (($currenturl=~/^\/res/) &&
+ # ($currenturl!~/^\/res\/adm/)) {
+ # $r->print('Current Location
');
+ #}
+
# Check that it's defined
if (!($navmap->courseMapDefined())) {
$r->print('Coursemap undefined.' .
@@ -848,16 +868,43 @@ sub new_handle {
# Defines a status->color mapping, null string means don't color
my %colormap =
( $res->NETWORK_FAILURE => '',
- $res->CORRECT => '#BBFFBB',
+ $res->CORRECT => '',
$res->EXCUSED => '#BBBBFF',
- $res->PAST_DUE_ANSWER_LATER => '#FFAA00',
- $res->PAST_DUE_NO_ANSWER => '#FFAA00',
- $res->ANSWER_OPEN => '#FF00AA',
+ $res->PAST_DUE_ANSWER_LATER => '',
+ $res->PAST_DUE_NO_ANSWER => '',
+ $res->ANSWER_OPEN => '#CCFFCC',
$res->OPEN_LATER => '',
- $res->TRIES_LEFT => '#FFFF00',
- $res->INCORRECT => '#FFAA00',
- $res->OPEN => '#FFFF88',
+ $res->TRIES_LEFT => '',
+ $res->INCORRECT => '',
+ $res->OPEN => '',
$res->NOTHING_SET => '' );
+ # And a special case in the nav map; what to do when the assignment
+ # is not yet done and due in less then 24 hours
+ my $hurryUpColor = "#FFCCCC";
+
+ my %statusIconMap =
+ ( $res->NETWORK_FAILURE => '',
+ $res->NOTHING_SET => '',
+ $res->CORRECT => 'navmap.correct.gif',
+ $res->EXCUSED => 'navmap.correct.gif',
+ $res->PAST_DUE_NO_ANSWER => 'navmap.wrong.gif',
+ $res->PAST_DUE_ANSWER_LATER => 'navmap.wrong.gif',
+ $res->ANSWER_OPEN => 'navmap.wrong.gif',
+ $res->OPEN_LATER => '',
+ $res->TRIES_LEFT => 'navmap.open.gif',
+ $res->INCORRECT => 'navmap.wrong.gif',
+ $res->OPEN => 'navmap.open.gif',
+ $res->ATTEMPTED => '' );
+
+ my %iconAltTags =
+ ( 'navmap.correct.gif' => 'Correct',
+ 'navmap.wrong.gif' => 'Incorrect',
+ 'navmap.open.gif' => 'Open' );
+
+ my %condenseStatuses =
+ ( $res->NETWORK_FAILURE => 1,
+ $res->NOTHING_SET => 1,
+ $res->CORRECT => 1 );
my %filterHash;
# Figure out what we're not displaying
@@ -867,19 +914,79 @@ sub new_handle {
}
}
+ my $currenturl = $ENV{'form.postdata'};
+ $currenturl=~s/^http\:\/\///;
+ $currenturl=~s/^[^\/]+//;
+ # 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
');
+
# Begin the HTML table
- # two columns: the title/icon on the left, the info on the right
+ # four cols: resource + indent, chat+feedback, icon, text string
$r->print('
' ."\n");
- my $mapIterator = $navmap->getIterator(undef, undef, \%filterHash);
- my $curRes = $mapIterator->next();
+ my $condition = 0;
+ if ($ENV{'form.condition'}) {
+ $condition = 1;
+ }
+
+ # 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 $indentString = " ";
+ 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++;
@@ -891,20 +998,100 @@ 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()) {
+ $curRes = $mapIterator->next();
+ next; # and totally ignore this resource
+ }
if (ref($curRes) && $curRes->src()) {
- my @parts;
+ # Step one: Decide which parts to show
+ my @parts = @{$curRes->parts()};
+ my $multipart = scalar(@parts) > 1;
+ my $condensed = 0;
+
if ($curRes->is_problem()) {
- @parts = @{$curRes->parts()};
- if (scalar(@parts) > 1) { shift(@parts); }
+ # Is it multipart?
+
+ if ($multipart) {
+ # If it's multipart, see if part 0 is "open"
+ # if it is, display all parts, if it isn't,
+ # just display first
+ if (!$curRes->opendate("0")) {
+ @parts = ("0"); # just display the zero-th part
+ $condensed = 1;
+ } else {
+ # Otherwise, only display part 0 if we want to
+ # attach feedback or email information to it
+ if ($curRes->hasDiscussion() || $curRes->getFeedback()) {
+ shift @parts;
+ } else {
+ # If there's discussion or feedback, that counts
+ # as a difference, so display the parts.
+
+ # Now, we decide whether to condense the
+ # parts due to similarity
+ my $status = $curRes->status($parts[1]);
+ my $due = $curRes->duedate($parts[1]);
+ my $open = $curRes->opendate($parts[1]);
+ my $statusAllSame = 1;
+ my $dueAllSame = 1;
+ my $openAllSame = 1;
+ for (my $i = 2; $i < scalar(@parts); $i++) {
+ if ($curRes->status($parts[$i]) != $status){
+ $statusAllSame = 0;
+ }
+ if ($curRes->duedate($parts[$i]) != $due ) {
+ $dueAllSame = 0;
+ }
+ if ($curRes->opendate($parts[$i]) != $open) {
+ $openAllSame = 0;
+ }
+ }
+
+ # $allSame is true if all the statuses were
+ # the same. Now, if they are all the same and
+ # match one of the statuses to condense, or they
+ # are all open with the same due date, or they are
+ # all OPEN_LATER with the same open date, display the
+ # status of the first non-zero part (to get the 'correct'
+ # status right, since 0 is never 'correct' or 'open').
+ if (($statusAllSame && defined($condenseStatuses{$status})) ||
+ ($dueAllSame && $status == $curRes->OPEN && $statusAllSame)||
+ ($openAllSame && $status == $curRes->OPEN_LATER && $statusAllSame) ){
+ @parts = ($parts[1]);
+ $condensed = 1;
+ }
+ }
+ }
+ }
+
} 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.
+ # If this is a single part, we can at least show the correct
+ # status, but if it's multipart, we're lost.
+ if ($curRes->{RESOURCE_ERROR}) {
+ @parts = ("0");
}
+ # Step two: Show the parts
foreach my $part (@parts) {
+
+ my $deltalevel = 0; # for inserting the branch icon
+ my $nonLinkedText = ""; # unlinked stuff after title
# For each thing we're displaying...
@@ -923,17 +1110,22 @@ sub new_handle {
# If this is a new branch, label it so
# (temporary, this should be an icon w/ alt text)
if ($isNewBranch) {
- $newBranchText = "NB -> ";
+ $newBranchText = "
";
$isNewBranch = 0;
+ $deltalevel = 1;
}
# links to open and close the folders
my $linkopen = "";
my $linkclose = "";
- my $icon = "html.gif";
+ my $icon = "
";
if ($curRes->is_problem()) {
- $icon = "problem.gif";
+ if ($part eq "0" || $condensed) {
+ $icon = '
';
+ } else {
+ $icon = $indentString;
+ }
}
# Display the correct icon, link to open or shut map
@@ -941,44 +1133,136 @@ sub new_handle {
my $mapId = $curRes->map_pc();
my $nowOpen = !defined($filterHash{$mapId});
$icon = $nowOpen ?
- "folder_opened.gif" : "folder_closed.gif";
+ "navmap.folder.closed.gif" : "navmap.folder.open.gif";
+ $icon = "
";
$linkopen = "";
+ $linkopen .= "&condition=$condition&$queryAdd\">";
$linkclose = "";
-
+
}
my $colorizer = "";
if ($curRes->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\"";
}
}
- $r->print(" \n");
+ if ($curRes->randomout()) {
+ $nonLinkedText .= ' (hidden) ';
+ }
+
+ # FIRST COL: The resource indentation, branch icon, and name
+ $r->print(" |
\n");
# print indentation
- for (my $i = 0; $i < $indentLevel; $i++) {
+ for (my $i = 0; $i < $indentLevel - $deltalevel; $i++) {
$r->print($indentString);
}
- $r->print(" ${newBranchText}${linkopen} ${linkclose}\n");
+ $r->print(" ${newBranchText}${linkopen}$icon${linkclose}\n");
+
+ my $curMarkerBegin = "";
+ my $curMarkerEnd = "";
- if ($curRes->is_problem() && $part != "0") { $partLabel = " (Part $part)"; }
+ # 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) {
+ $nonLinkedText .= ' (' . $curRes->countParts() . ' parts)';
+ }
+
+ $r->print(" $curMarkerBegin$title$partLabel $curMarkerEnd $nonLinkedText");
+
+ if ($curRes->{RESOURCE_ERROR}) {
+ $r->print(&Apache::loncommon::help_open_topic ("Navmap_Host_Down",
+ 'Host down'));
+ }
+
+ my $discussionHTML = ""; my $feedbackHTML = "";
+
+ # SECOND COL: Is there text or feedback?
+ if ($curRes->hasDiscussion()) {
+ $discussionHTML = $linkopen .
+ ' ' .
+ $linkclose;
+ }
+
+ if ($curRes->getFeedback()) {
+ my $feedback = $curRes->getFeedback();
+ foreach (split(/\,/, $feedback)) {
+ if ($_) {
+ $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
+ # columns?
+ my $firstDisplayed = !$condensed && $multipart && $part eq "0";
+
+ # THIRD COL: Problem status icon
+ if ($curRes->is_problem() &&
+ !$firstDisplayed) {
+ my $icon = $statusIconMap{$curRes->status($part)};
+ my $alt = $iconAltTags{$icon};
+ if ($icon) {
+ $r->print("$linkopen $linkclose | \n");
+ } else {
+ $r->print(" | \n");
+ }
+ } else { # not problem, no icon
+ $r->print(" | \n");
+ }
- $r->print(" $title$partLabel".
- "\n");
+ # FOURTH COL: Text description
+ $r->print(" | \n");
- if ($curRes->kind() eq "res" and
- $curRes->is_problem() ) {
+ if ($curRes->kind() eq "res" &&
+ $curRes->is_problem() &&
+ !$firstDisplayed) {
$r->print (getDescription($curRes, $part));
}
+ if ($curRes->is_map() && advancedUser() && $curRes->randompick()) {
+ $r->print('(randomly select ' . $curRes->randompick() .')');
+ }
+
+ $r->print(" |
\n");
}
}
$curRes = $mapIterator->next();
@@ -986,6 +1270,8 @@ sub new_handle {
$r->print("