Skip to content
Permalink
Browse files

finessed time edit validation (#138)

finessed time edit validation
added visual highlight to track edit
fixed DnD during track edit
refactored delete track to jQuery
added HR after track edit form

fixes #136
  • Loading branch information
RocketMan committed Feb 8, 2020
1 parent 33279ef commit df645282737274574fc62e4547c0500f3b101cd5
Showing with 167 additions and 102 deletions.
  1. +11 −0 css/zoostyle.css
  2. +1 −0 engine/IPlaylist.php
  3. +51 −32 engine/impl/Playlist.php
  4. +1 −1 js/pled.pick.js
  5. +89 −0 js/pled.track.js
  6. +13 −68 ui/Playlists.php
  7. +1 −1 ui/UserAdmin.php
@@ -723,3 +723,14 @@ A.calLink {
.grabCursor, .grabCursor * {
cursor: move !important;
}

.invalid-input {
padding: 2px 3px 2px 3px ;
border: 2px solid #f88;
border-radius: 2px;
box-shadow: 0px 0px 2px #f88, inset 0px 0px 2px #f88;
}

.highlight {
background-color: #ffff00;
}
@@ -48,6 +48,7 @@ function getTrack($id);
function getTracks($playlist, $desc = 0);
function getTracksWithObserver($playlist, PlaylistObserver $observer, $desc = 0);
function getTrackCount($playlist);
function getTimestampWindow($playlistId);
function insertTrack($playlist, $tag, $artist, $track, $album, $label, $wantTimestamp, &$id = null);
function updateTrack($playlistId, $id, $tag, $artist, $track, $album, $label, $dateTime);
function insertTrackEntry($playlist, PlaylistEntry $entry, $wantTimestamp);
@@ -29,6 +29,10 @@
* Playlist operations
*/
class PlaylistImpl extends BaseImpl implements IPlaylist {
const TIME_FORMAT = "Y-m-d Gi"; // eg, 2019-01-01 1234
const GRACE_START = "-15 minutes";
const GRACE_END = "+30 minutes";

public function getShowdates($year, $month) {
$yearMonth = sprintf("%04d-%02d", $year, $month) . "-%";

@@ -324,52 +328,67 @@ public function getTrackCount($playlist) {
return $row?$row['count']:0;
}

public function isNowWithinShow($listRow) {
$nowDateTime = new \DateTime("now");
return $this->isWithinShow($nowDateTime, $listRow);
}

public function isDateTimeWithinShow($timeStamp, $listRow) {
$dateTime = new \DateTime($timeStamp);
return $this->isWithinShow($dateTime, $listRow);
}

// return true if "now" is within the show start/end time & date.
// NOTE: this routine must be tolerant of improperly formatted dates.
public function isWithinShow($dateTime, $listRow) {
$TIME_FORMAT = "Y-m-d Gi"; // eg, 2019-01-01 1234
$retVal = false;

try {
$timeAr = explode("-", $listRow[2]);
if (count($timeAr) == 2) {
$timeStr1 = $listRow[1] . " " . $timeAr[0];
$start = \DateTime::createFromFormat($TIME_FORMAT, $timeStr1);
$start->modify("-5 minutes");
// end time can be midnight or later
// in this case, adjust to the next day
public function getTimestampWindowInternal($playlist) {
$result = null;
if($playlist && ($showtime = $playlist['showtime'])) {
$timeAr = explode("-", $showtime);
if(count($timeAr) == 2) {
$timeStr = $playlist['showdate'] . " " . $timeAr[0];
$start = \DateTime::createFromFormat(self::TIME_FORMAT, $timeStr);
if($start) {
$start->modify(self::GRACE_START);
$end = clone $start;

// end time can be midnight or later
// in this case, adjust to the next day
if($timeAr[1] < $timeAr[0])
$end->modify("+1 day");

$end->setTime(substr($timeAr[1], 0, 2),
substr($timeAr[1], 2, 2));
$end->modify("+15 minutes");
}
if ($start && $end) {
$retVal = (($dateTime >= $start) && ($dateTime <= $end));
$end->modify(self::GRACE_END);

$result = [
"start" => $start,
"end" => $end
];
}
}
} catch (Throwable $t) {
error_log("Error: invalid date $t");
}
return $result;
}

public function getTimestampWindow($playlistId) {
$playlist = $this->getPlaylist($playlistId);
return $this->getTimestampWindowInternal($playlist);
}

public function isWithinShow($dateTime, $listRow) {
$retVal = false;
$window = $this->getTimestampWindowInternal($listRow);
if($window) {
$retVal = $dateTime >= $window['start'] &&
$dateTime <= $window['end'];
}
return $retVal;
}

// return true if "now" is within the show start/end time & date.
public function isNowWithinShow($listRow) {
$nowDateTime = new \DateTime("now");
return $this->isWithinShow($nowDateTime, $listRow);
}

public function isDateTimeWithinShow($timeStamp, $listRow) {
$dateTime = new \DateTime($timeStamp);
return $this->isWithinShow($dateTime, $listRow);
}

// insert playlist track. return following: 0 - fail, 1 - success no
// timestamp, 2 - sucess with timestamp.
public function insertTrack($playlistId, $tag, $artist, $track, $album, $label, $wantTimestamp, &$id = null) {
$row = Engine::api(IPlaylist::class)->getPlaylist($playlistId, 1);
$row = $this->getPlaylist($playlistId, 1);

// log time iff 'now' is within playlist start/end time.
$doTimestamp = $wantTimestamp && $this->isNowWithinShow($row);
@@ -424,8 +443,8 @@ public function insertTrack($playlistId, $tag, $artist, $track, $album, $label,
// of a track following a CSV import (unless dateTimeStr is within show bounds
// then use it, eg time change to an NME.
public function updateTrack($playlistId, $id, $tag, $artist, $track, $album, $label, $dateTime) {
$playlist = Engine::api(IPlaylist::class)->getPlaylist($playlistId, 1);
$trackRow = Engine::api(IPlaylist::class)->getTrack($id);
$playlist = $this->getPlaylist($playlistId, 1);
$trackRow = $this->getTrack($id);
$timestamp = $trackRow['created'];
$timeChanged = false;

@@ -32,7 +32,7 @@ $().ready(function(){
$("#active-form").addClass('zk-hidden');
$("#deleted-form").removeClass('zk-hidden');
}
$("SELECT").focus();
$("SELECT:not(#list-type-picker)").focus();
});

$("#active-list-picker").dblclick(function() {
@@ -198,6 +198,95 @@ $().ready(function(){
setAddButtonState(haveAll);
});

$("INPUT[data-date]").focusout(function() {
// if field is blank, apply no validation
var v = $(this).val();
if(v.length == 0) {
$(this).removeClass('invalid-input');
return;
}

// no time picker in webkit; be flexible in accepting input...
// ...accept am/pm time
var offset = 0;
var vlc = v.toLowerCase();
var am = vlc.indexOf('am');
var pm = vlc.indexOf('pm');
if(am > 0)
v = v.substring(0, am);
else if(pm > 0) {
v = v.substring(0, pm);
offset = 12;
}

// ...accept time without separator (e.g., 1234)
if(v.length > 2 && v.indexOf(':') < 0)
v = v.substr(0, v.length-2) + ':' + v.substr(v.length-2);

// ...accept time without leading zero (e.g., 2:34)
if(v.length < 5)
v = '0' + v;
v = v.trim();

// ...coerce am/pm to 24 hour time
if(offset > 0)
v = (v.substr(0, 2)*1 + offset) + v.substr(2);

var d = $(this).data("date");
var s = $(this).data("start");
var e = $(this).data("end");

var start = new Date(d + "T" + s);
var end = new Date(d + "T" + e);
var val = new Date(d + "T" + v);

if(!isNaN(val)) {
// val or end time can be midnight or later
// in this case, adjust to the next day
if(e < s)
end.setTime(end.getTime() + 86400000);
if(v < s)
val.setTime(val.getTime() + 86400000);
}

if(isNaN(val) || val < start || val > end) {
$(this).addClass('invalid-input');
$(this).val("").focus();
} else {
// if we massaged time for webkit, set canonical value
if($(this).val() != v)
$(this).val(v);

// if time is after midnight, set edate field to correct date
$("INPUT[name=edate]").val(val.toISOString().split('T')[0]);
$(this).removeClass('invalid-input');
}
});

$("#edit-save").click(function(){
// submit form only if time field is valid/empty
if($("INPUT[data-date].invalid-input").length == 0)
$(this).closest("FORM").submit();
});

$("#edit-delete").click(function(){
if(confirm("Delete this entry?")) {
// we need to indicate that the ' Delete ' button was pressed
var input = $("<INPUT>").attr({
type: 'hidden',
name: 'button',
value: ' Delete '
});
$(this).closest("FORM").append(input).submit();
}
});

// display highlight on track edit
if($("FORM#edit").length > 0) {
var id = $("FORM#edit INPUT[name=id]").val();
$("DIV[data-id=" + id + "]").closest("TR").addClass("highlight");
}

function moveTrack(list, fromId, toId, tr, si, rows) {
var postData = {
session: $("#track-session").val(),
@@ -140,19 +140,6 @@ private function zkTimeRangeToISOTimeAr($zkTimeRange) {
return $retVal;
}

// return array of timestamps that represent show start/end date & times
private function zkTimeRangeToISODateTimeAr($playlist) {
$retVal = ['', ''];
$date = $playlist['showdate'];
$zkTimeRange = $playlist['showtime'];
$timeAr = explode("-", $zkTimeRange);
if (count($timeAr) == 2) {
$retVal[0] = $date . ' ' . substr($timeAr[0], 0, 2) . ':' . substr($timeAr[0], 2,4) . ':00';
$retVal[1] = $date . ' ' . substr($timeAr[1], 0, 2) . ':' . substr($timeAr[1], 2,4) . ':00';
}
return $retVal;
}

// given a time string H:MM, HH:MM, or HHMM, return normalized to HHMM
// returns empty string if invalid
private function normalizeTime($t) {
@@ -348,14 +335,6 @@ public static function timeToAMPM($time) {
return strtolower(htmlentities($time));
}

public static function timestampToTime($time) {
if ($time == null || $time == '') {
return "";
} else {
return date('G:i:s', strtotime($time));
}
}

public static function timestampToDate($time) {
if ($time == null || $time == '') {
return "";
@@ -601,37 +580,6 @@ public function emitEditList() {
$this->emitEditListForm($airName, $description, $time, $date, $playlistId, null);
}

private function emitConfirm($name, $message, $action, $rtaction="") {
?>
<SCRIPT LANGUAGE="JavaScript" TYPE="text/javascript"><!--
<?php ob_start([\JSMin::class, 'minify']); ?>
function Confirm<?php echo $name; ?>()
{
<?php if($rtaction) { ?>
if(document.forms[0].<?php echo $rtaction; ?>.selectedIndex >= 0) {
action = document.forms[0].<?php echo $rtaction; ?>.options[document.forms[0].<?php echo $rtaction; ?>.selectedIndex].value;
} else {
return;
}
<?php } ?>
answer = confirm("<?php echo $message; ?>");
if(answer != 0) {
location = "<?php
echo "?$action";
if($rtaction)
echo "&$rtaction=\" + action";
else
echo "\""; ?>;
}
}
<?php
ob_end_flush();
?>
// -->
</SCRIPT>
<?php
}

private function restorePlaylist($playlist) {
Engine::api(IPlaylist::class)->restorePlaylist($playlist);
}
@@ -871,17 +819,15 @@ private function emitTrackField($tag, $seltrack, $id) {

private function emitEditForm($playlistId, $id, $album, $track) {
$entry = new PlaylistEntry($album);
$playlist = Engine::api(IPlaylist::class)->getPlaylist($playlistId, 1);
$showTimeAr = $this->zkTimeRangeToISODateTimeAr($playlist);
$startAMPM = self::timestampToAMPM($showTimeAr[0]);
$startTime = new \DateTime($showTimeAr[0]);
$endTime = new \DateTime($showTimeAr[1]);
$window = Engine::api(IPlaylist::class)->getTimestampWindow($playlistId);
$startTime = $window['start'];
$endTime = $window['end'];
$nowTime = new \DateTime("now");
$isLive = $nowTime >= $startTime && $nowTime <= $endTime;
$endTime = $isLive ? $nowTime : $endTime;
$showTimeAr[0] = self::timestampToTime($showTimeAr[0]);
$showTimeAr[1] = $endTime->format('G:i:s');
$startAMPM = $startTime->format('g:i a');
$endAMPM = $endTime->format('g:i a');
$edate = $startTime->format('Y-m-d');
$showTimeRange = "$startAMPM - $endAMPM";
$timepickerTime = $this->getTimepickerTime($entry->getCreated());
$sep = $id && $entry->isType(PlaylistEntry::TYPE_SET_SEPARATOR);
@@ -903,7 +849,9 @@ private function emitEditForm($playlistId, $id, $album, $track) {
echo "track";
break;
} ?>:</P>
<FORM ACTION="?" METHOD=POST>
<FORM ACTION="?" id='edit' METHOD=POST>
<input id='track-session' type='hidden' value='<?php echo $this->session->getSessionID(); ?>'>
<input id='track-playlist' type='hidden' value='<?php echo $playlistId; ?>'>
<TABLE>
<?php if($sep) { ?>
<INPUT TYPE=HIDDEN NAME=separator VALUE="true">
@@ -970,16 +918,16 @@ private function emitEditForm($playlistId, $id, $album, $track) {
<TR>
<TD ALIGN=RIGHT>Time:</TD>
<TD ALIGN=LEFT>
<INPUT class='timepicker' NAME=etime step='60' type='time' value="<?php echo $timepickerTime ?>" min="<?php echo $showTimeAr[0] ?>" max="<?php echo $showTimeAr[1] ?>" /> <span style='font-size:8pt;'>(<?php echo $showTimeRange ?>)</span>
<INPUT type='hidden' NAME='edate' value="<?php echo $playlist['showdate'] ?>" />
<INPUT class='timepicker' NAME=etime step='60' type='time' value="<?php echo $timepickerTime ?>" data-date='<?php echo $edate;?>' data-start='<?php echo $startTime->format('H:i');?>' data-end='<?php echo $endTime->format('H:i');?>'/> <span style='font-size:8pt;'>(<?php echo $showTimeRange ?>)</span>
<INPUT type='hidden' NAME='edate' value="<?php echo $edate;?>" />
</TD>
</TR>
<TR>
<TD>&nbsp;</TD>
<TD>
<?php if($id) { ?>
<INPUT TYPE=SUBMIT NAME=button VALUE=" Save ">&nbsp;&nbsp;&nbsp;
<INPUT TYPE=BUTTON NAME=button onClick="ConfirmDelete()" VALUE=" Delete ">
<INPUT TYPE=BUTTON NAME=button id='edit-save' VALUE=" Save ">&nbsp;&nbsp;&nbsp;
<INPUT TYPE=BUTTON NAME=button id='edit-delete' VALUE=" Delete ">
<INPUT TYPE=HIDDEN NAME=id VALUE="<?php echo $id;?>">
<?php } else { ?>
<INPUT TYPE=SUBMIT VALUE=" Next &gt;&gt; ">
@@ -992,6 +940,7 @@ private function emitEditForm($playlistId, $id, $album, $track) {
</TD>
</TR>
</TABLE>
<HR>
<?php UI::markdownHelp(); ?>
</FORM>
<SCRIPT LANGUAGE="JavaScript" TYPE="text/javascript"><!--
@@ -1004,10 +953,6 @@ private function emitEditForm($playlistId, $id, $album, $track) {
// -->
</SCRIPT>
<?php
if($id)
$this->emitConfirm("Delete",
"Delete this entry?",
"button=+Delete+&session=".$this->session->getSessionID()."&action=$this->action&playlist=$playlistId&id=$id&seq=editForm");
}

private function emitTrackForm($playlist, $id, $album, $track) {

0 comments on commit df64528

Please sign in to comment.
You can’t perform that action at this time.