mirror of https://github.com/acanas/swad-core.git
Version19.91
This commit is contained in:
parent
a7e16480de
commit
38b5bbe163
|
@ -572,9 +572,9 @@ function readMatchStdData () {
|
|||
if (objXMLHttpReqMchStd.status == 200) {
|
||||
var htmlMatch = objXMLHttpReqMchStd.responseText; // Get HTML code
|
||||
|
||||
var divMatch = document.getElementById('match'); // Access to refreshable DIV
|
||||
if (divMatch)
|
||||
divMatch.innerHTML = htmlMatch; // Update refreshable DIV
|
||||
var div = document.getElementById('match'); // Access to refreshable DIV
|
||||
if (div)
|
||||
div.innerHTML = htmlMatch; // Update DIV content
|
||||
// Global delay variable is set initially in swad-core
|
||||
setTimeout('refreshMatchStd()',delayMatch);
|
||||
}
|
||||
|
@ -608,15 +608,50 @@ function readMatchTchData () {
|
|||
var Id = objXMLHttpReqMchTch.responseText.substring(0,endOfId); // Get Id
|
||||
var htmlMatch = objXMLHttpReqMchTch.responseText.substring(endOfId + 1); // Get HTML code
|
||||
|
||||
var divMatch = document.getElementById(Id); // Access to refreshable DIV
|
||||
if (divMatch)
|
||||
divMatch.innerHTML = htmlMatch; // Update refreshable DIV
|
||||
var div = document.getElementById(Id); // Access to refreshable DIV
|
||||
if (div) {
|
||||
div.innerHTML = htmlMatch; // Update DIV content
|
||||
|
||||
// Scripts in div got via AJAX are not executed ==> execute them
|
||||
evalScriptsInElem (div);
|
||||
|
||||
// Process mathematics; see http://docs.mathjax.org/en/latest/advanced/typeset.html
|
||||
MathJax.Hub.Queue(["Typeset",MathJax.Hub,div]);
|
||||
}
|
||||
|
||||
// Global delay variable is set initially in swad-core
|
||||
setTimeout('refreshMatchTch()',delayMatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/****************** Update match control area using AJAX *********************/
|
||||
/*****************************************************************************/
|
||||
|
||||
// This function is called when user submit a form inside two parent divs
|
||||
function updateMatchTch (id,Params) {
|
||||
var objXMLHttp = false;
|
||||
|
||||
objXMLHttp = AJAXCreateObject ();
|
||||
if (objXMLHttp) {
|
||||
/* Send request to server */
|
||||
objXMLHttp.onreadystatechange = function() { // onreadystatechange must be lowercase
|
||||
if (objXMLHttp.readyState == 4) { // Check if data have been received
|
||||
if (objXMLHttp.status == 200)
|
||||
if (id) {
|
||||
var div = document.getElementById(id); // Access to DIV
|
||||
if (div)
|
||||
div.innerHTML = objXMLHttp.responseText; // Update DIV content
|
||||
}
|
||||
}
|
||||
};
|
||||
objXMLHttp.open('POST',ActionAJAX,true);
|
||||
objXMLHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||
objXMLHttp.send(Params);
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/**************** Automatic refresh of last clicks using AJAX ****************/
|
||||
/*****************************************************************************/
|
375
swad_HTML.c
375
swad_HTML.c
File diff suppressed because it is too large
Load Diff
|
@ -136,7 +136,7 @@ void HTM_INPUT_RADIO (const char *Name,bool SubmitOnClick,
|
|||
void HTM_INPUT_CHECKBOX (const char *Name,bool SubmitOnChange,
|
||||
const char *fmt,...);
|
||||
|
||||
void HTM_BUTTON_OnMouseDown_Begin (const char *Title,const char *Class);
|
||||
void HTM_BUTTON_OnMouseDown_Begin (const char *Title,const char *Class,const char *OnMouseDown);
|
||||
void HTM_BUTTON_SUBMIT_Begin (const char *Title,const char *Class,const char *OnSubmit);
|
||||
void HTM_BUTTON_BUTTON_Begin (const char *Title,const char *Class,const char *OnClick);
|
||||
void HTM_BUTTON_Animated_Begin (const char *Title,const char *Class,const char *OnClick);
|
||||
|
|
|
@ -618,12 +618,12 @@ Assessment:
|
|||
468. ActReqNewMch Put form to create a new match
|
||||
469. ActNewMch Create a new match showing first question in a new browser tab
|
||||
470. ActResMch Resume an unfinished match showing current question in a new browser tab
|
||||
NEW. ActMchCntDwn Start match countdown
|
||||
471. ActBckMch Go back when playing a match
|
||||
472. ActPlyPauMch Play/pause current match
|
||||
473. ActFwdMch Go forward when playing a match
|
||||
474. ActChgNumColMch Change number of columns when playing a match
|
||||
475. ActChgVisResMchQst Change visibility of question results when playing a match
|
||||
NEW. ActMchCntDwn Start match countdown
|
||||
476. ActRefMchTch Refresh current question when playing a match (as teacher)
|
||||
|
||||
477. ActJoiMch Show current question when playing a game (as student)
|
||||
|
@ -2158,12 +2158,12 @@ const struct Act_Actions Act_Actions[Act_NUM_ACTIONS] =
|
|||
[ActReqNewMch ] = {1670,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Gam_RequestNewMatch ,NULL},
|
||||
[ActNewMch ] = {1671,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_NEW_TAB,Mch_CreateNewMatchTch ,Mch_ResumeMatch ,NULL},
|
||||
[ActResMch ] = {1785,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_NEW_TAB,Mch_GetMatchBeingPlayed ,Mch_ResumeMatch ,NULL},
|
||||
[ActMchCntDwn ] = {1814,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_2ND_TAB,Mch_GetMatchBeingPlayed ,Mch_StartCountdown ,NULL},
|
||||
[ActBckMch ] = {1790,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_2ND_TAB,Mch_GetMatchBeingPlayed ,Mch_BackMatch ,NULL},
|
||||
[ActPlyPauMch ] = {1789,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_2ND_TAB,Mch_GetMatchBeingPlayed ,Mch_PlayPauseMatch ,NULL},
|
||||
[ActFwdMch ] = {1672,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_2ND_TAB,Mch_GetMatchBeingPlayed ,Mch_ForwardMatch ,NULL},
|
||||
[ActChgNumColMch ] = {1802,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_2ND_TAB,Mch_GetMatchBeingPlayed ,Mch_ChangeNumColsMch ,NULL},
|
||||
[ActChgVisResMchQst ] = {1794,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_2ND_TAB,Mch_GetMatchBeingPlayed ,Mch_ToggleVisibilResultsMchQst ,NULL},
|
||||
[ActMchCntDwn ] = {1814,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_AJAX_RFRESH,Mch_GetMatchBeingPlayed ,Mch_StartCountdown ,NULL},
|
||||
[ActRefMchTch ] = {1788,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_AJAX_RFRESH,Mch_GetMatchBeingPlayed ,Mch_RefreshMatchTch ,NULL},
|
||||
|
||||
[ActJoiMch ] = {1780,-1,TabUnk,ActSeeAllGam ,0x008, 0, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_NEW_TAB,Mch_GetMatchBeingPlayed ,Mch_JoinMatchAsStd ,NULL},
|
||||
|
|
|
@ -617,12 +617,12 @@ typedef signed int Act_Action_t; // Must be a signed type, because -1 is used to
|
|||
#define ActReqNewMch (ActChgCrsTT1stDay + 122)
|
||||
#define ActNewMch (ActChgCrsTT1stDay + 123)
|
||||
#define ActResMch (ActChgCrsTT1stDay + 124)
|
||||
#define ActMchCntDwn (ActChgCrsTT1stDay + 125)
|
||||
#define ActBckMch (ActChgCrsTT1stDay + 126)
|
||||
#define ActPlyPauMch (ActChgCrsTT1stDay + 127)
|
||||
#define ActFwdMch (ActChgCrsTT1stDay + 128)
|
||||
#define ActChgNumColMch (ActChgCrsTT1stDay + 129)
|
||||
#define ActChgVisResMchQst (ActChgCrsTT1stDay + 130)
|
||||
#define ActBckMch (ActChgCrsTT1stDay + 125)
|
||||
#define ActPlyPauMch (ActChgCrsTT1stDay + 126)
|
||||
#define ActFwdMch (ActChgCrsTT1stDay + 127)
|
||||
#define ActChgNumColMch (ActChgCrsTT1stDay + 128)
|
||||
#define ActChgVisResMchQst (ActChgCrsTT1stDay + 129)
|
||||
#define ActMchCntDwn (ActChgCrsTT1stDay + 130)
|
||||
#define ActRefMchTch (ActChgCrsTT1stDay + 131)
|
||||
|
||||
#define ActJoiMch (ActChgCrsTT1stDay + 132)
|
||||
|
|
|
@ -490,13 +490,15 @@ enscript -2 --landscape --color --file-align=2 --highlight --line-numbers -o - *
|
|||
En OpenSWAD:
|
||||
ps2pdf source.ps destination.pdf
|
||||
*/
|
||||
#define Log_PLATFORM_VERSION "SWAD 19.90.5 (2019-12-13)"
|
||||
#define Log_PLATFORM_VERSION "SWAD 19.91 (2019-12-13)"
|
||||
#define CSS_FILE "swad19.90.1.css"
|
||||
#define JS_FILE "swad19.90.js"
|
||||
#define JS_FILE "swad19.91.js"
|
||||
/*
|
||||
// TODO: Hacer un nuevo rol en los TFG: tutor externo (profesor de áreas no vinculadas con el centro, profesionales de empresas, etc.)
|
||||
// TODO: Impedir la creación y edición de proyectos si no son editables.
|
||||
|
||||
Version 19.91: Dec 13, 2019 Changes in match countdown.
|
||||
Code refactoring in HTML. (248513 lines)
|
||||
Version 19.90.5: Dec 13, 2019 Fixed issues in layout of links (buttons). (248432 lines)
|
||||
Version 19.90.4: Dec 13, 2019 Fixed issues in layout of statistics. (248431 lines)
|
||||
Version 19.90.3: Dec 13, 2019 Changes in match layout. (248430 lines)
|
||||
|
|
|
@ -685,12 +685,12 @@ static void Lay_WriteScriptInit (void)
|
|||
break;
|
||||
case ActNewMch:
|
||||
case ActResMch:
|
||||
case ActMchCntDwn:
|
||||
case ActBckMch:
|
||||
case ActPlyPauMch:
|
||||
case ActFwdMch:
|
||||
case ActChgNumColMch:
|
||||
case ActChgVisResMchQst:
|
||||
case ActMchCntDwn:
|
||||
RefreshMatchTch = true;
|
||||
break;
|
||||
case ActLstClk:
|
||||
|
@ -823,12 +823,12 @@ static void Lay_WriteScriptParamsAJAX (void)
|
|||
/* Parameters related with match refreshing (for teachers) */
|
||||
case ActNewMch:
|
||||
case ActResMch:
|
||||
case ActMchCntDwn:
|
||||
case ActBckMch:
|
||||
case ActPlyPauMch:
|
||||
case ActFwdMch:
|
||||
case ActChgNumColMch:
|
||||
case ActChgVisResMchQst:
|
||||
case ActMchCntDwn:
|
||||
// Handle keys in keyboard/presenter
|
||||
HTM_Txt ("document.addEventListener(\"keydown\",handleMatchKeys);\n");
|
||||
// Refresh parameters
|
||||
|
|
135
swad_match.c
135
swad_match.c
|
@ -1892,39 +1892,6 @@ void Mch_ToggleVisibilResultsMchQst (void)
|
|||
HTM_DIV_End ();
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/******************** Start match countdown (by a teacher) *******************/
|
||||
/*****************************************************************************/
|
||||
|
||||
void Mch_StartCountdown (void)
|
||||
{
|
||||
struct Match Match;
|
||||
long NewCountdown;
|
||||
|
||||
/***** Get countdown parameter ****/
|
||||
NewCountdown = Par_GetParToLong ("Countdown");
|
||||
|
||||
/***** Remove old players.
|
||||
This function must be called by a teacher
|
||||
before getting match status. *****/
|
||||
Mch_RemoveOldPlayers ();
|
||||
|
||||
/***** Get data of the match from database *****/
|
||||
Match.MchCod = Gbl.Games.MchCodBeingPlayed;
|
||||
Mch_GetDataOfMatchByCod (&Match);
|
||||
|
||||
/***** Start countdown *****/
|
||||
Match.Status.Countdown = NewCountdown;
|
||||
|
||||
/***** Update match status in database *****/
|
||||
Mch_UpdateMatchStatusInDB (&Match);
|
||||
|
||||
/***** Show current match status *****/
|
||||
HTM_DIV_Begin ("id=\"match\" class=\"MCH_CONT\"");
|
||||
Mch_ShowMatchStatusForTch (&Match);
|
||||
HTM_DIV_End ();
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/************* Show previous question in a match (by a teacher) **************/
|
||||
/*****************************************************************************/
|
||||
|
@ -2305,6 +2272,65 @@ static void Mch_WriteHourglass (struct Match *Match)
|
|||
}
|
||||
|
||||
static void Mch_PutFormCountdown (struct Match *Match,long Countdown,const char *Color)
|
||||
{
|
||||
extern const char *Txt_Countdown;
|
||||
char *OnSubmit;
|
||||
char *Class;
|
||||
bool PutForm = Match->Status.Showing != Mch_END;
|
||||
|
||||
if (PutForm)
|
||||
{
|
||||
/***** Start form *****/
|
||||
if (asprintf (&OnSubmit,"updateMatchTch('match_left',"
|
||||
"'act=%ld&ses=%s&MchCod=%ld&Countdown=%ld');"
|
||||
" return false;", // return false is necessary to not submit form
|
||||
Act_GetActCod (ActMchCntDwn),Gbl.Session.Id,
|
||||
Match->MchCod,Countdown) < 0)
|
||||
Lay_NotEnoughMemoryExit ();
|
||||
// Frm_StartFormUniqueAnchorOnSubmit (ActUnk,NULL,OnSubmit);
|
||||
|
||||
/***** Set class *****/
|
||||
if (asprintf (&Class,"BT_LINK MCH_BUTTON_ON %s",Color) < 0)
|
||||
Lay_NotEnoughMemoryExit ();
|
||||
}
|
||||
else
|
||||
{
|
||||
/***** Set class *****/
|
||||
if (asprintf (&Class,"BT_LINK_OFF MCH_BUTTON_HIDDEN %s",Color) < 0)
|
||||
Lay_NotEnoughMemoryExit ();
|
||||
}
|
||||
|
||||
/***** Put icon *****/
|
||||
HTM_DIV_Begin ("class=\"MCH_SMALLBUTTON_CONT\"");
|
||||
|
||||
if (PutForm)
|
||||
{
|
||||
HTM_BUTTON_OnMouseDown_Begin (Txt_Countdown,Class,OnSubmit);
|
||||
}
|
||||
else
|
||||
HTM_BUTTON_BUTTON_Begin (NULL,Class,NULL);
|
||||
|
||||
if (Countdown >= 0)
|
||||
HTM_TxtF (" %ld″",Countdown);
|
||||
else
|
||||
HTM_Txt (" ∞ ");
|
||||
|
||||
HTM_BUTTON_End ();
|
||||
|
||||
HTM_DIV_End ();
|
||||
|
||||
/***** Free class *****/
|
||||
free (Class);
|
||||
|
||||
/***** End form *****/
|
||||
if (PutForm)
|
||||
{
|
||||
Frm_EndForm ();
|
||||
free (OnSubmit);
|
||||
}
|
||||
}
|
||||
|
||||
static void Mch_PutFormCountdown_old (struct Match *Match,long Countdown,const char *Color)
|
||||
{
|
||||
extern const char *Txt_Countdown;
|
||||
char *Class;
|
||||
|
@ -2332,7 +2358,7 @@ static void Mch_PutFormCountdown (struct Match *Match,long Countdown,const char
|
|||
HTM_DIV_Begin ("class=\"MCH_SMALLBUTTON_CONT\"");
|
||||
|
||||
if (PutForm)
|
||||
HTM_BUTTON_OnMouseDown_Begin (Txt_Countdown,Class);
|
||||
HTM_BUTTON_OnMouseDown_Begin (Txt_Countdown,Class,NULL);
|
||||
else
|
||||
HTM_BUTTON_BUTTON_Begin (NULL,Class,NULL);
|
||||
|
||||
|
@ -2764,7 +2790,7 @@ static void Mch_PutIfAnswered (const struct Match *Match,bool Answered)
|
|||
Frm_StartForm (ActSeeMchAnsQstStd);
|
||||
Mch_PutParamMchCod (Match->MchCod); // Current match being played
|
||||
|
||||
HTM_BUTTON_OnMouseDown_Begin (Txt_View_my_answer,"BT_LINK DAT_SMALL_GREEN");
|
||||
HTM_BUTTON_OnMouseDown_Begin (Txt_View_my_answer,"BT_LINK DAT_SMALL_GREEN",NULL);
|
||||
HTM_TxtF ("<i class=\"%s\"></i>","fas fa-check-circle");
|
||||
HTM_TxtF (" %s",Txt_MATCH_QUESTION_Answered);
|
||||
HTM_BUTTON_End ();
|
||||
|
@ -2808,7 +2834,7 @@ static void Mch_PutIconToRemoveMyAnswer (const struct Match *Match)
|
|||
|
||||
/***** Put icon with link *****/
|
||||
HTM_DIV_Begin ("class=\"MCH_BIGBUTTON_CONT\"");
|
||||
HTM_BUTTON_OnMouseDown_Begin (Txt_Delete_my_answer,"BT_LINK MCH_BUTTON_ON ICO_DARKRED");
|
||||
HTM_BUTTON_OnMouseDown_Begin (Txt_Delete_my_answer,"BT_LINK MCH_BUTTON_ON ICO_DARKRED",NULL);
|
||||
HTM_Txt ("<i class=\"fas fa-trash\"></i>");
|
||||
HTM_BUTTON_End ();
|
||||
HTM_DIV_End ();
|
||||
|
@ -2976,7 +3002,7 @@ static bool Mch_ShowQuestionAndAnswersStd (const struct Match *Match,
|
|||
"",
|
||||
'A' + (char) NumOpt) < 0)
|
||||
Lay_NotEnoughMemoryExit ();
|
||||
HTM_BUTTON_OnMouseDown_Begin (NULL,Class);
|
||||
HTM_BUTTON_OnMouseDown_Begin (NULL,Class,NULL);
|
||||
HTM_TxtF ("%c",'a' + (char) NumOpt);
|
||||
HTM_BUTTON_End ();
|
||||
free (Class);
|
||||
|
@ -3450,6 +3476,37 @@ void Mch_RemoveMyQuestionAnswer (void)
|
|||
HTM_DIV_End ();
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/******************** Start match countdown (by a teacher) *******************/
|
||||
/*****************************************************************************/
|
||||
|
||||
void Mch_StartCountdown (void)
|
||||
{
|
||||
struct Match Match;
|
||||
long NewCountdown;
|
||||
|
||||
/***** Get countdown parameter ****/
|
||||
NewCountdown = Par_GetParToLong ("Countdown");
|
||||
|
||||
/***** Remove old players.
|
||||
This function must be called by a teacher
|
||||
before getting match status. *****/
|
||||
Mch_RemoveOldPlayers ();
|
||||
|
||||
/***** Get data of the match from database *****/
|
||||
Match.MchCod = Gbl.Games.MchCodBeingPlayed;
|
||||
Mch_GetDataOfMatchByCod (&Match);
|
||||
|
||||
/***** Start countdown *****/
|
||||
Match.Status.Countdown = NewCountdown;
|
||||
|
||||
/***** Update match status in database *****/
|
||||
Mch_UpdateMatchStatusInDB (&Match);
|
||||
|
||||
/***** Show current match status *****/
|
||||
Mch_ShowRefreshablePartTch (&Match);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/****************** Refresh match for a teacher via AJAX *********************/
|
||||
/*****************************************************************************/
|
||||
|
@ -3498,11 +3555,11 @@ void Mch_RefreshMatchTch (void)
|
|||
switch (WhatToRefresh)
|
||||
{
|
||||
case REFRESH_LEFT: // Refresh only left part
|
||||
HTM_TxtF ("match_left|",Cfg_TIME_TO_REFRESH_LAST_CLICKS);
|
||||
HTM_Txt ("match_left|");
|
||||
Mch_ShowRefreshablePartTch (&Match);
|
||||
break;
|
||||
case REFRESH_ALL: // Refresh the whole page
|
||||
HTM_TxtF ("match|",Cfg_TIME_TO_REFRESH_LAST_CLICKS);
|
||||
HTM_Txt ("match|");
|
||||
Mch_ShowMatchStatusForTch (&Match);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -109,7 +109,6 @@ void Mch_RemoveGroupsOfType (long GrpTypCod);
|
|||
void Mch_PlayPauseMatch (void);
|
||||
void Mch_ChangeNumColsMch (void);
|
||||
void Mch_ToggleVisibilResultsMchQst (void);
|
||||
void Mch_StartCountdown (void);
|
||||
void Mch_BackMatch (void);
|
||||
void Mch_ForwardMatch (void);
|
||||
|
||||
|
@ -122,6 +121,8 @@ bool Mch_RegisterMeAsPlayerInMatch (struct Match *Match);
|
|||
void Mch_GetMatchBeingPlayed (void);
|
||||
void Mch_JoinMatchAsStd (void);
|
||||
void Mch_RemoveMyQuestionAnswer (void);
|
||||
|
||||
void Mch_StartCountdown (void);
|
||||
void Mch_RefreshMatchTch (void);
|
||||
void Mch_RefreshMatchStd (void);
|
||||
|
||||
|
|
|
@ -1237,7 +1237,7 @@ static void TL_FormFavSha (Act_Action_t ActionGbl,Act_Action_t ActionUsr,
|
|||
Ico_PutIconLink (Icon,Title);
|
||||
Frm_EndForm ();
|
||||
|
||||
/* Free allocated memory for subquery */
|
||||
/* Free allocated memory */
|
||||
free (OnSubmit);
|
||||
}
|
||||
|
||||
|
@ -2786,7 +2786,7 @@ static void TL_FormToShowHiddenComments (Act_Action_t ActionGbl,Act_Action_t Act
|
|||
/* End form */
|
||||
Frm_EndForm ();
|
||||
|
||||
/* Free allocated memory for subquery */
|
||||
/* Free allocated memory */
|
||||
free (OnSubmit);
|
||||
|
||||
HTM_DIV_End ();
|
||||
|
|
Loading…
Reference in New Issue