mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-07-21 08:52:21 +00:00
created FhcBase.js plugin which coordinates time orderly setup between Alerts, Phrasen & Api Plugins. These have slightly rewritten Adaption as BaseAlerts.js, BasePhrase.js & BaseApi.js which have internal ready states activated once their required dependencies have been injected. Added await/async to several handlers to guarantee initialized cross dependencies in all plugins; While testing new Plugin Setup found a few legacy Instances of $fhcApi in Cis4 Studium.js that were only still working because plugins/Phrasen is still importing the legacyApi. Fixed that code and the corresponding errors in Studium.php that where now alerted into ui! wow! Also added FhcBase Plugin to all apps inside fhc-core/public/js/apps;
This commit is contained in:
@@ -29,7 +29,7 @@ class Studium extends FHCAPI_Controller
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct([
|
||||
'getStudienAllSemester'=> self::PERM_LOGGED,
|
||||
'getAllStudienSemester'=> self::PERM_LOGGED,
|
||||
'getStudiengaengeForStudienSemester'=> self::PERM_LOGGED,
|
||||
'getStudienplaeneBySemester'=> self::PERM_LOGGED,
|
||||
'getLvEvaluierungInfo'=> self::PERM_LOGGED,
|
||||
@@ -51,7 +51,7 @@ class Studium extends FHCAPI_Controller
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
// Public methods
|
||||
|
||||
public function getStudienAllSemester(){
|
||||
public function getAllStudienSemester(){
|
||||
|
||||
$parameter_studiensemester = $this->input->get('studiensemester',true);
|
||||
$parameter_studiengang = $this->input->get('studiengang',true);
|
||||
@@ -59,24 +59,26 @@ class Studium extends FHCAPI_Controller
|
||||
$parameter_studienplan = $this->input->get('studienplan',true);
|
||||
|
||||
$aktuelles_studiensemester = current($this->getDataOrTerminateWithError($this->StudiensemesterModel->getAktOrNextSemester()));
|
||||
|
||||
|
||||
if($this->getDataOrTerminateWithError($this->StudentModel->isStudent(getAuthUID()))){
|
||||
$studentLehrverband =$this->StudentlehrverbandModel->loadWhere(["student_uid" => getAuthUID(), "studiensemester_kurzbz" => $aktuelles_studiensemester->studiensemester_kurzbz]);
|
||||
$studentLehrverband = current($this->getDataOrTerminateWithError($studentLehrverband));
|
||||
$lv_result =$this->StudentlehrverbandModel->loadWhere([
|
||||
"student_uid" => getAuthUID(),
|
||||
"studiensemester_kurzbz" => $aktuelles_studiensemester->studiensemester_kurzbz
|
||||
]);
|
||||
$lv_data = $this->getDataOrTerminateWithError($lv_result);
|
||||
|
||||
if ($studentLehrverband = current($lv_data)) {
|
||||
$student_studiensemester = $studentLehrverband->studiensemester_kurzbz;
|
||||
$student_studiengang = $studentLehrverband->studiengang_kz;
|
||||
$student_semester = $studentLehrverband->semester;
|
||||
}
|
||||
|
||||
$student_studiensemester = $studentLehrverband->studiensemester_kurzbz;
|
||||
$student_studiengang = $studentLehrverband->studiengang_kz;
|
||||
$student_semester = $studentLehrverband->semester;
|
||||
$student_studienplan = $this->getStudienPlanFromPrestudentStatus(getAuthPersonId())->studienplan_id;
|
||||
|
||||
if(!isset($parameter_studiensemester))
|
||||
$parameter_studiensemester = $student_studiensemester;
|
||||
if(!isset($parameter_studiengang))
|
||||
$parameter_studiengang = $student_studiengang;
|
||||
if(!isset($parameter_semester))
|
||||
$parameter_semester = $student_semester;
|
||||
if(!isset($parameter_studienplan))
|
||||
$parameter_studienplan = $student_studienplan;
|
||||
|
||||
$parameter_studiensemester = $parameter_studiensemester ?? $student_studiensemester;
|
||||
$parameter_studiengang = $parameter_studiengang ?? $student_studiengang;
|
||||
$parameter_semester = $parameter_semester ?? $student_semester;
|
||||
$parameter_studienplan = $parameter_studienplan ?? $student_studienplan;
|
||||
}
|
||||
|
||||
if(isset($parameter_studiensemester)){
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
export default {
|
||||
getAllStudienSemester(studiensemester=undefined, studiengang=undefined, semester=undefined, studienplan=undefined) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: '/api/frontend/v1/Studium/getAllStudienSemester',
|
||||
params: {studiensemester, studiengang, semester, studienplan}
|
||||
};
|
||||
},
|
||||
getStudiengaengeForStudienSemester(studiensemester) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: `/api/frontend/v1/Studium/getStudiengaengeForStudienSemester/${studiensemester}`,
|
||||
};
|
||||
},
|
||||
getStudienplaeneBySemester(studiengang, studiensemester) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: `/api/frontend/v1/Studium/getStudienplaeneBySemester`,
|
||||
params: {
|
||||
studiengang,
|
||||
studiensemester,
|
||||
}
|
||||
};
|
||||
},
|
||||
getLvPlanForStudiensemester(studiensemester_kurzbz, lvid) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: `/api/frontend/v1/LvPlan/getLvPlanForStudiensemester/${studiensemester}/${lvid}`,
|
||||
};
|
||||
},
|
||||
getLvEvaluierungInfo(studiensemester_kurzbz, lvid) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: `/api/frontend/v1/Studium/getLvEvaluierungInfo/${studiensemester_kurzbz}/${lvid}`,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1,9 +1,9 @@
|
||||
import FhcSearchbar from "../components/searchbar/searchbar.js";
|
||||
import CisMenu from "../components/Cis/Menu.js";
|
||||
import PluginsPhrasen from '../plugins/Phrasen.js';
|
||||
import ApiSearchbar from '../api/factory/searchbar.js';
|
||||
import Theme from "../plugins/Theme.js";
|
||||
|
||||
import FhcBase from '../plugins/FhcBase/FhcBase.js';
|
||||
// import PluginsPhrasen from '../plugins/Phrasen.js';
|
||||
const app = Vue.createApp({
|
||||
name: 'CisApp',
|
||||
components: {
|
||||
@@ -148,6 +148,7 @@ app.use(primevue.config.default, {
|
||||
tooltip: 8000
|
||||
}
|
||||
})
|
||||
app.use(PluginsPhrasen);
|
||||
// app.use(PluginsPhrasen);
|
||||
app.use(FhcBase);
|
||||
app.use(Theme);
|
||||
app.mount('#cis-header');
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PluginsPhrasen from '../../plugins/Phrasen.js';
|
||||
import {setScrollbarWidth} from "../../helpers/CssVarCalcHelpers.js";
|
||||
import FhcBase from "../../plugins/FhcBase/FhcBase.js";
|
||||
|
||||
const app = Vue.createApp({
|
||||
name: 'DocumentsApp',
|
||||
@@ -89,5 +90,5 @@ const app = Vue.createApp({
|
||||
|
||||
setScrollbarWidth();
|
||||
|
||||
app.use(PluginsPhrasen);
|
||||
app.use(FhcBase);
|
||||
app.mount('#content');
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import ProfilUpdateView from "../../components/Cis/ProfilUpdate/ProfilUpdateView.js";
|
||||
import PluginsPhrasen from "../../plugins/Phrasen.js";
|
||||
|
||||
import ApiProfilUpdate from '../../api/factory/profilUpdate.js';
|
||||
import FhcBase from "../../plugins/FhcBase/FhcBase.js";
|
||||
|
||||
// TODO: sobald in verwendung den vue router pfad zu ProfilUpdateView definieren und diese app in component auslagern
|
||||
const app = Vue.createApp({
|
||||
@@ -34,4 +33,4 @@ const app = Vue.createApp({
|
||||
});
|
||||
},
|
||||
});
|
||||
app.use(PluginsPhrasen).mount("#content");
|
||||
app.use(FhcBase).mount("#content");
|
||||
@@ -1,6 +1,6 @@
|
||||
import {CoreNavigationCmpt} from '../../components/navigation/Navigation.js';
|
||||
import DashboardAdmin from '../../components/Dashboard/Admin.js';
|
||||
import PluginsPhrasen from '../../plugins/Phrasen.js';
|
||||
import FhcBase from "../../plugins/FhcBase/FhcBase.js";
|
||||
|
||||
const app = Vue.createApp({
|
||||
name: 'AdminApp',
|
||||
@@ -12,5 +12,5 @@ const app = Vue.createApp({
|
||||
DashboardAdmin
|
||||
}
|
||||
});
|
||||
app.use(PluginsPhrasen);
|
||||
app.use(FhcBase);
|
||||
app.mount('#main');
|
||||
@@ -1,5 +1,6 @@
|
||||
import FhcDashboard from '../../components/Dashboard/Dashboard.js';
|
||||
import PluginsPhrasen from '../../plugins/Phrasen.js';
|
||||
import FhcBase from '../../plugins/FhcBase/FhcBase.js';
|
||||
|
||||
import Theme from '../../plugins/Theme.js';
|
||||
import contrast from '../../directives/contrast.js';
|
||||
import {setScrollbarWidth} from "../../helpers/CssVarCalcHelpers.js";
|
||||
@@ -330,7 +331,7 @@ app.use(primevue.config.default, {
|
||||
}
|
||||
})
|
||||
app.directive('tooltip', primevue.tooltip);
|
||||
app.use(PluginsPhrasen);
|
||||
app.use(FhcBase);
|
||||
app.use(Theme);
|
||||
app.directive('contrast', contrast);
|
||||
app.mount('#fhccontent');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {CoreNavigationCmpt} from '../components/navigation/Navigation.js';
|
||||
import DashboardAdmin from '../components/Dashboard/Admin.js';
|
||||
import Phrases from "../plugin/Phrasen.js"
|
||||
import FhcBase from "../plugins/FhcBase/FhcBase.js";
|
||||
|
||||
Vue.createApp({
|
||||
name: 'DashboardAdminApp',
|
||||
@@ -13,4 +13,4 @@ Vue.createApp({
|
||||
},
|
||||
mounted() {
|
||||
}
|
||||
}).use(Phrases).mount('#main');
|
||||
}).use(FhcBase).mount('#main');
|
||||
@@ -1,5 +1,5 @@
|
||||
import LVVerwaltung from "../components/LVVerwaltung/LVVerwaltung.js";
|
||||
import Phrasen from "../plugins/Phrasen.js";
|
||||
import FhcBase from "../plugins/FhcBase/FhcBase.js";
|
||||
|
||||
const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
|
||||
|
||||
@@ -98,5 +98,5 @@ app
|
||||
overlay: 1100
|
||||
}
|
||||
})
|
||||
.use(Phrasen)
|
||||
.use(FhcBase)
|
||||
.mount('#main');
|
||||
|
||||
@@ -21,7 +21,7 @@ import {LogsViewerTabulatorEventHandlers} from './TabulatorSetup.js';
|
||||
import {CoreFilterCmpt} from '../../components/filter/Filter.js';
|
||||
import {CoreNavigationCmpt} from '../../components/navigation/Navigation.js';
|
||||
|
||||
import PluginsPhrasen from '../../plugins/Phrasen.js';
|
||||
import FhcBase from "../../plugins/FhcBase/FhcBase.js";
|
||||
|
||||
const logsViewerApp = Vue.createApp({
|
||||
data: function() {
|
||||
@@ -42,5 +42,5 @@ const logsViewerApp = Vue.createApp({
|
||||
}
|
||||
});
|
||||
|
||||
logsViewerApp.use(PluginsPhrasen).mount('#main');
|
||||
logsViewerApp.use(FhcBase).mount('#main');
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//TODO Manu
|
||||
//use this instead of Nachrichten.js
|
||||
import NewMessage from "../../components/Messages/Details/NewMessage/NewDiv.js";
|
||||
import Phrasen from "../../plugin/Phrasen.js";
|
||||
import FhcBase from "../../plugins/FhcBase/FhcBase.js";
|
||||
|
||||
const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
|
||||
|
||||
@@ -23,5 +23,5 @@ app
|
||||
overlay: 1100
|
||||
}
|
||||
})
|
||||
.use(Phrasen)
|
||||
.use(FhcBase)
|
||||
.mount('#main');
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import NewMessage from "../components/Messages/Details/NewMessage/NewDiv.js";
|
||||
|
||||
import Phrasen from "../plugins/Phrasen.js";
|
||||
import FhcBase from "../plugins/FhcBase/FhcBase.js";
|
||||
|
||||
const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
|
||||
|
||||
@@ -23,5 +22,5 @@ app
|
||||
overlay: 1100
|
||||
}
|
||||
})
|
||||
.use(Phrasen)
|
||||
.use(FhcBase)
|
||||
.mount('#main');
|
||||
@@ -16,10 +16,7 @@
|
||||
*/
|
||||
|
||||
import FhcStudentenverwaltung from "../components/Stv/Studentenverwaltung.js";
|
||||
import fhcapifactory from "./api/fhcapifactory.js";
|
||||
|
||||
import PluginsPhrasen from "../plugins/Phrasen.js";
|
||||
|
||||
import FhcBase from "../plugins/FhcBase/FhcBase.js";
|
||||
|
||||
const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
|
||||
|
||||
@@ -217,5 +214,5 @@ app
|
||||
overlay: 1100
|
||||
}
|
||||
})
|
||||
.use(PluginsPhrasen)
|
||||
.use(FhcBase)
|
||||
.mount('#main');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import StudierendenantragAntrag from "../../components/Studierendenantrag/Antrag.js";
|
||||
import StudierendenantragStatus from "../../components/Studierendenantrag/Status.js";
|
||||
import StudierendenantragInfoblock from "../../components/Studierendenantrag/Infoblock.js";
|
||||
import PluginsPhrasen from '../../plugins/Phrasen.js';
|
||||
import FhcBase from "../../plugins/FhcBase/FhcBase.js";
|
||||
|
||||
const app = Vue.createApp({
|
||||
name: 'AntragApp',
|
||||
@@ -21,5 +21,5 @@ const app = Vue.createApp({
|
||||
}
|
||||
});
|
||||
app
|
||||
.use(PluginsPhrasen)
|
||||
.use(FhcBase)
|
||||
.mount('#wrapper');
|
||||
@@ -1,5 +1,5 @@
|
||||
import StudierendenantragLeitung from '../../../components/Studierendenantrag/Leitung.js';
|
||||
import PluginsPhrasen from '../../../plugins/Phrasen.js';
|
||||
import FhcBase from "../../../plugins/FhcBase/FhcBase.js";
|
||||
|
||||
const app = Vue.createApp({
|
||||
name: 'LeitungApp',
|
||||
@@ -8,6 +8,6 @@ const app = Vue.createApp({
|
||||
}
|
||||
});
|
||||
app
|
||||
.use(PluginsPhrasen)
|
||||
.use(FhcBase)
|
||||
.use(primevue.config.default,{zIndex: {overlay: 9999}})
|
||||
.mount('#wrapper');
|
||||
@@ -1,5 +1,5 @@
|
||||
import LvZuweisung from '../../../components/Studierendenantrag/Lvzuweisung.js';
|
||||
import PluginsPhrasen from '../../../plugins/Phrasen.js';
|
||||
import FhcBase from "../../../plugins/FhcBase/FhcBase.js";
|
||||
|
||||
const app = Vue.createApp({
|
||||
name: 'LvzuweisungApp',
|
||||
@@ -13,5 +13,5 @@ const app = Vue.createApp({
|
||||
}
|
||||
});
|
||||
app
|
||||
.use(PluginsPhrasen)
|
||||
.use(FhcBase)
|
||||
.mount('#wrapper');
|
||||
@@ -1,5 +1,5 @@
|
||||
import LvPopup from '../../../components/Studierendenantrag/Leitung/LvPopup.js';
|
||||
import PluginsPhrasen from '../../../plugins/Phrasen.js';
|
||||
import FhcBase from "../../../plugins/FhcBase/FhcBase.js";
|
||||
|
||||
const app = Vue.createApp({
|
||||
name: 'StudentApp',
|
||||
@@ -8,5 +8,5 @@ const app = Vue.createApp({
|
||||
}
|
||||
});
|
||||
app
|
||||
.use(PluginsPhrasen)
|
||||
.use(FhcBase)
|
||||
.mount('#wrapper');
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
import LvTemplateUebersicht from '../../../lehre/lvplanung/LvTemplateUebersicht.js';
|
||||
import {CoreNavigationCmpt} from '../../../components/navigation/Navigation.js';
|
||||
import PluginsPhrasen from "../../../plugins/Phrasen.js";
|
||||
import FhcBase from "../../../plugins/FhcBase/FhcBase.js";
|
||||
|
||||
|
||||
const lvTemplatesApp = Vue.createApp({
|
||||
@@ -30,5 +30,5 @@ const lvTemplatesApp = Vue.createApp({
|
||||
|
||||
lvTemplatesApp
|
||||
.use(primevue.config.default,{zIndex: {overlay: 9999}})
|
||||
.use(PluginsPhrasen)
|
||||
.use(FhcBase)
|
||||
.mount('#main')
|
||||
@@ -3,7 +3,6 @@ import FhcSearchbar from "../searchbar/searchbar.js";
|
||||
import CisSprachen from "./Sprachen.js"
|
||||
import ThemeSwitch from "./ThemeSwitch.js";
|
||||
import ApiCisMenu from '../../api/factory/cis/menu.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CisMenuEntry,
|
||||
|
||||
@@ -2,9 +2,10 @@ import BsModal from "../../Bootstrap/Modal.js";
|
||||
import LvMenu from "./LvMenu.js";
|
||||
|
||||
import ApiAddons from '../../../api/factory/addons.js';
|
||||
import ApiStudium from '../../../api/factory/studium.js';
|
||||
|
||||
export default {
|
||||
|
||||
name: "LvUebersicht",
|
||||
props:{
|
||||
event:{
|
||||
type:Object,
|
||||
@@ -63,7 +64,7 @@ export default {
|
||||
|
||||
// check lv evaluierung info
|
||||
if (this.studium_studiensemester) {
|
||||
this.$fhcApi.factory.studium.getLvEvaluierungInfo(this.studium_studiensemester, this.event.lehreinheit_id ?? this.event.lehrveranstaltung_id)
|
||||
this.$api.call(ApiStudium.getLvEvaluierungInfo(this.studium_studiensemester, this.event.lehreinheit_id ?? this.event.lehrveranstaltung_id))
|
||||
.then(data => data.data)
|
||||
.then(res => {
|
||||
this.lvEvaluierungMessage = res.message;
|
||||
@@ -72,7 +73,7 @@ export default {
|
||||
|
||||
// check if the lv has lvplan entries for this studiensemester
|
||||
if (this.studiensemester && this.event) {
|
||||
return this.$fhcApi.factory.studium.getLvPlanForStudiensemester(this.studiensemester, this.event.lehreinheit_id ?? this.event.lehrveranstaltung_id)
|
||||
this.$api.call(ApiStudium.getLvPlanForStudiensemester(this.studiensemester, this.event.lehreinheit_id ?? this.event.lehrveranstaltung_id))
|
||||
.then(data => data.data)
|
||||
.then(res => {
|
||||
if (Array.isArray(res) && res.length > 0) {
|
||||
|
||||
@@ -31,7 +31,7 @@ export default {
|
||||
this.event.lektor.slice(0, 3).map(lektor => lektor.kurzbz).join("\n")
|
||||
+ "\n" + this.$p.t('lehre/weitereLektoren', [this.event.lektor.length - 3])
|
||||
].join(": "));
|
||||
} else {console.log(this.event.lektor);
|
||||
} else {
|
||||
tooltipArray.push([
|
||||
this.$p.t('lehre/lektor'),
|
||||
this.event.lektor.map(lektor => lektor.kurzbz).join("\n")
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import LvUebersicht from "../Mylv/LvUebersicht.js";
|
||||
|
||||
import ApiStudium from '../../../api/factory/studium.js';
|
||||
|
||||
export default {
|
||||
name: "Studium",
|
||||
data(){
|
||||
return {
|
||||
studienSemester :[],
|
||||
@@ -97,28 +98,28 @@ export default {
|
||||
return value;
|
||||
},
|
||||
changeSelectedStudienSemester(studiensemester_kurzbz) {
|
||||
this.$fhcApi.factory.studium.getAllStudienSemester(studiensemester_kurzbz, this.selectedStudiengang, this.selectedSemester, this.selectedStudienordnung)
|
||||
this.$api.call(ApiStudium.getAllStudienSemester(studiensemester_kurzbz, this.selectedStudiengang, this.selectedSemester, this.selectedStudienordnung))
|
||||
.then(data => data.data)
|
||||
.then(res => {
|
||||
this.extractPropertyValues(res);
|
||||
})
|
||||
},
|
||||
changeSelectedStudienGang(studiengang_kz) {
|
||||
this.$fhcApi.factory.studium.getAllStudienSemester(this.selectedStudiensemester, studiengang_kz, this.selectedSemester, this.selectedStudienordnung)
|
||||
this.$api.call(ApiStudium.getAllStudienSemester(this.selectedStudiensemester, studiengang_kz, this.selectedSemester, this.selectedStudienordnung))
|
||||
.then(data => data.data)
|
||||
.then(res => {
|
||||
this.extractPropertyValues(res);
|
||||
})
|
||||
},
|
||||
changeSelectedSemester(semester) {
|
||||
this.$fhcApi.factory.studium.getAllStudienSemester(this.selectedStudiensemester, this.selectedStudiengang, semester, this.selectedStudienordnung)
|
||||
this.$api.call(ApiStudium.getAllStudienSemester(this.selectedStudiensemester, this.selectedStudiengang, semester, this.selectedStudienordnung))
|
||||
.then(data => data.data)
|
||||
.then(res => {
|
||||
this.extractPropertyValues(res);
|
||||
})
|
||||
},
|
||||
changeSelectedStudienPlan(studienplan_id) {
|
||||
this.$fhcApi.factory.studium.getAllStudienSemester(this.selectedStudiensemester, this.selectedStudiengang, this.selectedSemester, studienplan_id)
|
||||
this.$api.call(ApiStudium.getAllStudienSemester(this.selectedStudiensemester, this.selectedStudiengang, this.selectedSemester, studienplan_id))
|
||||
.then(data => data.data)
|
||||
.then(res => {
|
||||
this.extractPropertyValues(res);
|
||||
@@ -256,8 +257,8 @@ export default {
|
||||
const studienordnung = JSON.parse(this.getDataFromLocalStorage("studienordnung")) ?? undefined;
|
||||
|
||||
// only fetch default data if no data is stored in the local storage
|
||||
|
||||
this.$fhcApi.factory.studium.getAllStudienSemester(studiensemester, studiengang, semester, studienordnung)
|
||||
|
||||
this.$api.call(ApiStudium.getAllStudienSemester(studiensemester, studiengang, semester, studienordnung))
|
||||
.then(data => data.data)
|
||||
.then(res => {
|
||||
this.extractPropertyValues(res);
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
import PvConfig from "../../../../index.ci.php/public/js/components/primevue/config/config.esm.min.js";
|
||||
import PvToast from "../../../../index.ci.php/public/js/components/primevue/toast/toast.esm.min.js";
|
||||
import PvConfirm from "../../../../index.ci.php/public/js/components/primevue/confirmdialog/confirmdialog.esm.min.js";
|
||||
import PvConfirmationService from "../../../../index.ci.php/public/js/components/primevue/confirmationservice/confirmationservice.esm.min.js";
|
||||
import {CoreRESTClient} from "../../RESTClient.js";
|
||||
|
||||
export default {
|
||||
init(app, $p) {
|
||||
let resolveReady;
|
||||
const readyPromise = new Promise(resolve => { resolveReady = resolve; });
|
||||
|
||||
const helperAppContainer = document.createElement('div');
|
||||
document.body.appendChild(helperAppContainer);
|
||||
|
||||
const helperApp = Vue.createApp({
|
||||
name: "FhcAlertApp",
|
||||
components: { PvToast, PvConfirm },
|
||||
methods: {
|
||||
mailToUrl(slotProps) {
|
||||
let mailTo = FHC_JS_DATA_STORAGE_OBJECT.systemerror_mailto;
|
||||
let subject = 'Meldung%20Systemfehler';
|
||||
let body = `
|
||||
Danke, dass Sie uns den Fehler melden. %0D%0A %0D%0A
|
||||
Bitte beschreiben Sie uns ausführlich das Problem.%0D%0A
|
||||
Bsp: Ich habe X ausgewählt und Y angelegt. Beim Speichern erhielt ich die Fehlermeldung. [Optional: Ich habe den Browser Z verwendet.]%0D%0A
|
||||
-----------------------------------------------------------------------------------------------------------------------------------%0D%0A
|
||||
PROBLEM: ... %0D%0A %0D%0A %0D%0A
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------------------------------%0D%0A
|
||||
Fehler URL: ` + FHC_JS_DATA_STORAGE_OBJECT.called_path + '/' + FHC_JS_DATA_STORAGE_OBJECT.called_method + `%0D%0A
|
||||
Fehler Meldung: ` + slotProps.message.detail + `%0D%0A
|
||||
-----------------------------------------------------------------------------------------------------------------------------------%0D%0A %0D%0A
|
||||
Wir kümmern uns um eine rasche Behebung des Problems!`
|
||||
|
||||
return "mailto:" + mailTo + "?subject=" + subject + "&body=" + body;
|
||||
},
|
||||
openMessagecard(e) {
|
||||
bootstrap.Collapse.getOrCreateInstance(e.target.getAttribute('href')).toggle();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showmaillink() { return FHC_JS_DATA_STORAGE_OBJECT.systemerror_mailto !== ''; }
|
||||
},
|
||||
template: /* html */`
|
||||
<pv-toast ref="toast" class="fhc-alert" :base-z-index="99999">
|
||||
<template #message="{ message }">
|
||||
<!--span :class="slotProps.iconClass"></span-->
|
||||
<div class="p-toast-message-text">
|
||||
<span class="p-toast-summary">{{ message.summary }}</span>
|
||||
<div v-if="message.detail && message.html" class="p-toast-detail" v-html="message.detail"></div>
|
||||
<div v-else-if="message.detail" class="p-toast-detail">{{ message.detail }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</pv-toast>
|
||||
<pv-toast ref="alert" class="fhc-alert" :base-z-index="99999" position="center">
|
||||
<template #message="slotProps">
|
||||
<i class="fa fa-circle-exclamation fa-2xl mt-3"></i>
|
||||
<div class="p-toast-message-text">
|
||||
<span class="p-toast-summary">{{slotProps.message.summary}}</span>
|
||||
<div class="p-toast-detail my-3">Sorry! Ein interner technischer Fehler ist aufgetreten.</div>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<a
|
||||
class="align-bottom flex-fill me-2"
|
||||
data-bs-toggle="collapse"
|
||||
:href="'#fhcAlertCollapseMessageCard' + slotProps.message.id"
|
||||
role="button"
|
||||
aria-expanded="false"
|
||||
:aria-controls="'fhcAlertCollapseMessageCard' + slotProps.message.id"
|
||||
@click="openMessagecard"
|
||||
>
|
||||
Fehler anzeigen
|
||||
</a>
|
||||
<a
|
||||
v-if="showmaillink"
|
||||
class="btn btn-primary flex-fill"
|
||||
target="_blank"
|
||||
:href="mailToUrl(slotProps)"
|
||||
>
|
||||
Fehler melden
|
||||
</a>
|
||||
</div>
|
||||
<div ref="messageCard" :id="'fhcAlertCollapseMessageCard' + slotProps.message.id" class="collapse mt-3">
|
||||
<div class="card card-body text-body small alertCollapseText">
|
||||
{{slotProps.message.detail}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</pv-toast>
|
||||
<pv-confirm group="fhcAlertConfirm"></pv-confirm>`
|
||||
});
|
||||
|
||||
// Link the helper app to the main app's phrasen system
|
||||
helperApp.config.globalProperties.$p = $p;
|
||||
helperApp.use(PvConfig);
|
||||
helperApp.use(PvConfirmationService);
|
||||
|
||||
const helperAppInstance = helperApp.mount(helperAppContainer);
|
||||
|
||||
const $fhcAlert = {
|
||||
// Internal storage for cross-plugin dependencies
|
||||
deps: { $p },
|
||||
ready: readyPromise,
|
||||
// Method used by FhcBase to inject the API later
|
||||
setDeps(deps) {
|
||||
Object.assign(this.deps, deps);
|
||||
// Alert is ready when both Phrases and API are present
|
||||
if (this.deps.$p && this.deps.$api) resolveReady();
|
||||
},
|
||||
|
||||
alertSuccess(message) {
|
||||
if (Array.isArray(message))
|
||||
return message.forEach(this.alertSuccess);
|
||||
helperAppInstance.$refs.toast.add({ severity: 'success', summary: 'Info', detail: message, life: 1000});
|
||||
},
|
||||
alertInfo(message) {
|
||||
if (Array.isArray(message))
|
||||
return message.forEach(this.alertInfo);
|
||||
helperAppInstance.$refs.toast.add({ severity: 'info', summary: 'Info', detail: message, life: 3000 });
|
||||
},
|
||||
alertWarning(message) {
|
||||
if (Array.isArray(message))
|
||||
return message.forEach(this.alertWarning);
|
||||
helperAppInstance.$refs.toast.add({ severity: 'warn', summary: 'Achtung', detail: message});
|
||||
},
|
||||
alertError(message) {
|
||||
if (Array.isArray(message))
|
||||
return message.forEach(this.alertError);
|
||||
helperAppInstance.$refs.toast.add({ severity: 'error', summary: 'Achtung', detail: message });
|
||||
},
|
||||
async alertSystemError(message) {
|
||||
//TODO(Manu) for translation of content of template: restructure in data
|
||||
//and update definitions with translations
|
||||
|
||||
await this.ready;
|
||||
|
||||
if (Array.isArray(message))
|
||||
return message.forEach(this.alertSystemError);
|
||||
helperAppInstance.$refs.alert.add({
|
||||
severity: 'error',
|
||||
summary: Vue.computed(() => this.deps.$p.t('alert/systemerror')),
|
||||
detail: message});
|
||||
},
|
||||
async confirmDelete() {
|
||||
await this.ready;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
helperAppInstance.$confirm.require({
|
||||
group: 'fhcAlertConfirm',
|
||||
header: Vue.computed(() => this.deps.$p.t('alert/attention')),
|
||||
message: Vue.computed(() => this.deps.$p.t('alert/confirm_delete')),
|
||||
acceptLabel: Vue.computed(() => this.deps.$p.t('ui/loeschen')),
|
||||
acceptClass: 'p-button-danger',
|
||||
rejectLabel: Vue.computed(() => this.deps.$p.t('ui/abbrechen')),
|
||||
rejectClass: 'p-button-secondary',
|
||||
accept() {
|
||||
resolve(true);
|
||||
},
|
||||
reject() {
|
||||
resolve(false);
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
confirm(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
helperAppInstance.$confirm.require({
|
||||
group: options?.group ?? 'fhcAlertConfirm',
|
||||
header: options?.header ?? Vue.computed(() => this.deps.$p.t('alert/attention')),
|
||||
message: options?.message ?? '',
|
||||
acceptLabel: options?.acceptLabel ?? 'Ok',
|
||||
acceptClass: options?.acceptClass ?? 'btn btn-primary',
|
||||
rejectLabel: options?.rejectLabel ?? Vue.computed(() => this.deps.$p.t('ui/abbrechen')),
|
||||
rejectClass: options?.rejectClass ?? 'btn btn-outline-secondary',
|
||||
accept() {
|
||||
resolve(true);
|
||||
},
|
||||
reject() {
|
||||
resolve(false);
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
alertDefault(severity, title, message, sticky = false, html = false) {
|
||||
let options = { severity: severity, summary: title, detail: message, html };
|
||||
|
||||
if (!sticky)
|
||||
options.life = 3000;
|
||||
|
||||
helperAppInstance.$refs.toast.add(options);
|
||||
},
|
||||
alertMultiple(messageArray, severity = 'info', title = 'Info', sticky = false, html = false){
|
||||
// Check, if array has only string values
|
||||
if (messageArray.every(message => typeof message === 'string')) {
|
||||
messageArray.forEach(message => this.alertDefault(severity, title, message, sticky, html));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
handleSystemError(error) {
|
||||
// don't show an error message to the user if the error was an aborted request
|
||||
if(error.hasOwnProperty('name') && error.name.toLowerCase() === "AbortError".toLowerCase())
|
||||
return;
|
||||
|
||||
// Error is string
|
||||
if (typeof error === 'string')
|
||||
return this.alertSystemError(error);
|
||||
|
||||
// Error is array of strings
|
||||
if (Array.isArray(error) && error.every(err => typeof err === 'string'))
|
||||
return error.every(this.alertSystemError);
|
||||
|
||||
// Error has been handled already
|
||||
if (error.hasOwnProperty('handled') && error.handled)
|
||||
return;
|
||||
|
||||
// Error is object
|
||||
if (typeof error === 'object' && error !== null) {
|
||||
let errMsg = '';
|
||||
|
||||
|
||||
if (error.hasOwnProperty('response') && error.response?.data?.retval)
|
||||
errMsg += 'Error Message: ' + (error.response.data.retval.message || error.response.data.retval) + '\r\n';
|
||||
else if (error.hasOwnProperty('message'))
|
||||
errMsg += 'Error Message: ' + error.message.toUpperCase() + '\r\n';
|
||||
|
||||
if (error.hasOwnProperty('config') && error.config.hasOwnProperty('url'))
|
||||
errMsg += 'Error ConfigURL: ' + error.config.url + '\r\n';
|
||||
|
||||
if (error.hasOwnProperty('stack'))
|
||||
errMsg += 'Error Stack: ' + error.stack + '\r\n';
|
||||
|
||||
// Fallback object error message
|
||||
if (errMsg == '')
|
||||
errMsg = 'Error Message: ' + JSON.stringify(error) + '\r\n';
|
||||
|
||||
errMsg += 'Error Controller Path: ' + FHC_JS_DATA_STORAGE_OBJECT.called_path + '/' + FHC_JS_DATA_STORAGE_OBJECT.called_method;
|
||||
|
||||
return this.alertSystemError(errMsg);
|
||||
}
|
||||
|
||||
// Fallback
|
||||
this.alertSystemError('alertSystemError throws Generic Error\r\nError Controller Path: ' + FHC_JS_DATA_STORAGE_OBJECT.called_path + '/' + FHC_JS_DATA_STORAGE_OBJECT.called_method);
|
||||
},
|
||||
handleSystemMessage(message) {
|
||||
// Message is string
|
||||
if (typeof message === 'string')
|
||||
return this.alertWarning(message);
|
||||
|
||||
// Message is array of strings
|
||||
if (Array.isArray(message)) {
|
||||
// If Array has only Strings
|
||||
if (message.every(msg => typeof msg === 'string'))
|
||||
return message.every(this.alertWarning);
|
||||
|
||||
// If Array has only Objects
|
||||
if (message.every(msg => typeof msg === 'object') && msg !== null) {
|
||||
return message.every(msg => {
|
||||
if (msg.hasOwnProperty('data') && msg.data.hasOwnProperty('retval')) {
|
||||
this.alertWarning(JSON.stringify(msg.data.retval));
|
||||
} else {
|
||||
this.alertSystemError(JSON.stringify(msg));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Message is Object with data property
|
||||
if (typeof message === 'object' && message !== null){
|
||||
if (message.hasOwnProperty('data') && message.data.hasOwnProperty('retval')) {
|
||||
// NOTE(chris): changed: alertSystemError => alertWarning
|
||||
this.alertWarning(JSON.stringify(message.data.retval));
|
||||
} else {
|
||||
this.alertSystemError(JSON.stringify(message));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback
|
||||
this.alertSystemError('alertSystemError throws Generic Error\r\nError Controller Path: ' + FHC_JS_DATA_STORAGE_OBJECT.called_path + '/' + FHC_JS_DATA_STORAGE_OBJECT.called_method);
|
||||
},
|
||||
resetFormValidation(form) {
|
||||
const event = new Event('fhc-form-reset');
|
||||
form.querySelectorAll(['[data-fhc-form-validate],[data-fhc-form-error]']).forEach(el => el.dispatchEvent(event));
|
||||
/*const alert = form.querySelector('div.alert.alert-danger[role="alert"]');
|
||||
if (alert) {
|
||||
alert.innerHTML = '';
|
||||
alert.classList.add('d-none');
|
||||
}
|
||||
form.querySelectorAll('.invalid-feedback').forEach(n => n.remove());
|
||||
form.querySelectorAll('.is-invalid').forEach(n => n.classList.remove('is-invalid'));
|
||||
form.querySelectorAll('.is-valid').forEach(n => n.classList.remove('is-valid'));*/
|
||||
},
|
||||
handleFormValidation(error, form) {
|
||||
if (form === undefined) {
|
||||
if (error && error.nodeType === Node.ELEMENT_NODE)
|
||||
return err => this.handleFormValidation(err, error);
|
||||
} else {
|
||||
if (error?.response?.status == 400) {
|
||||
let errors = CoreRESTClient.getError(error.response.data);
|
||||
if (typeof errors !== "object")
|
||||
errors = error.response.data;
|
||||
|
||||
// NOTE(chris): reset form validation
|
||||
this.resetFormValidation(form);
|
||||
|
||||
// NOTE(chris): set form input validation
|
||||
const notFound = Object.entries(errors).filter(([key, detail]) => {
|
||||
const input = form.querySelector('[data-fhc-form-validate="' + key + '"]');
|
||||
if (!input)
|
||||
return true;
|
||||
|
||||
input.dispatchEvent(new CustomEvent('fhc-form-invalidate', {detail}));
|
||||
|
||||
/*const input = form.querySelector('[name="' + key + '"]');
|
||||
if (!input)
|
||||
return true;
|
||||
input.classList.add('is-invalid');
|
||||
const feedback = document.createElement('div');
|
||||
feedback.classList.add('invalid-feedback');
|
||||
feedback.innerHTML = detail;
|
||||
input.after(feedback);*/
|
||||
return false;
|
||||
}).map(arr => arr[1]);
|
||||
|
||||
|
||||
//const alert = form.querySelector('div.alert.alert-danger[role="alert"]');
|
||||
const alert = form.querySelector('[data-fhc-form-error]');
|
||||
if (alert && notFound.length) {
|
||||
alert.dispatchEvent(new CustomEvent('fhc-form-error', {detail: notFound}));
|
||||
/*notFound.forEach(txt => {
|
||||
const p = document.createElement('p');
|
||||
p.innerHTML = txt;
|
||||
alert.append(p);
|
||||
});
|
||||
|
||||
if (notFound.length) {
|
||||
alert.lastChild.classList.add('mb-0');
|
||||
alert.classList.remove('d-none');
|
||||
}*/
|
||||
} else {
|
||||
notFound.forEach(this.alertError);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (error?.response?.status == 400) {
|
||||
let errors = CoreRESTClient.getError(error.response.data);
|
||||
this.alertError((typeof errors === 'object') ? Object.values(errors) : errors);
|
||||
} else {
|
||||
this.handleSystemError(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return $fhcAlert;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,589 @@
|
||||
/**
|
||||
* BaseApi.js - Contains the original api plugin methods call, get, post, etc.
|
||||
* Dependencies like phrasen($p) or alert($fhcAlert) are optional and can be injected at initialization
|
||||
* or at a later stage.
|
||||
*/
|
||||
export class BaseApi {
|
||||
constructor(deps = {}) {
|
||||
// optional dependencies like { $fhcAlert: ..., $p: ... }
|
||||
this.deps = deps;
|
||||
|
||||
this.resolveReady = null;
|
||||
this.ready = new Promise(resolve => { this.resolveReady = resolve; });
|
||||
|
||||
// If deps were passed in constructor, resolve immediately
|
||||
if (this.deps.$fhcAlert && this.deps.$p) this.resolveReady();
|
||||
|
||||
this.setupDefaultConfig()
|
||||
|
||||
this.axiosInstance = axios.create({
|
||||
timeout: 500000,
|
||||
baseURL: FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + "/"
|
||||
});
|
||||
|
||||
this.setupInterceptors();
|
||||
}
|
||||
|
||||
// === public functions START ===
|
||||
|
||||
// in case of api instantiation before vue app mount and later usage via fhcBase plugin
|
||||
setDependencies(deps) {
|
||||
Object.assign(this.deps, deps);
|
||||
if (this.deps.$fhcAlert && this.deps.$p) this.resolveReady();
|
||||
}
|
||||
|
||||
getUri(url) {
|
||||
return this.axiosInstance.getUri({ url });
|
||||
}
|
||||
|
||||
get(form, uri, params, config) {
|
||||
[uri, params, config] = this.get_config(form, uri, params, config);
|
||||
if (params) {
|
||||
config = config ? { ...config, params } : { params };
|
||||
}
|
||||
return this.axiosInstance.get(uri, config);
|
||||
}
|
||||
|
||||
post(form, uri, data, config) {
|
||||
[uri, data, config] = this.get_config(form, uri, data, config);
|
||||
return this.axiosInstance.post(uri, data, config);
|
||||
}
|
||||
|
||||
getErrorHandler(config) {
|
||||
return this.get_error_handler(config);
|
||||
}
|
||||
|
||||
async call(factory, configoverwrite, form) {
|
||||
if (Array.isArray(factory)) {
|
||||
return Promise.allSettled(factory.map((config, index) => {
|
||||
if (!Array.isArray(config)) config = ['#' + index, config];
|
||||
return this.call(config[1], { errorHeader: config[0], errorHandling: false });
|
||||
})).then(result => {
|
||||
const [ , , config ] = this.get_config(form, undefined, undefined, configoverwrite || {});
|
||||
const errorConfig = this.get_error_handler(config);
|
||||
|
||||
if (!errorConfig.success && !errorConfig.fail) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const typedErrors = {};
|
||||
for (let res of result) {
|
||||
const [ allowed, item ] = res.status === 'fulfilled'
|
||||
? [ errorConfig.success, res.value ]
|
||||
: [ errorConfig.fail, res.reason ];
|
||||
if (!allowed)
|
||||
return;
|
||||
|
||||
const errors = this.popHandleableErrors(errorConfig, this.get_error_list(item));
|
||||
|
||||
for (let type in errors) {
|
||||
if (!typedErrors[type])
|
||||
typedErrors[type] = {
|
||||
[item.config.errorHeader]: errors[type]
|
||||
};
|
||||
else
|
||||
typedErrors[type][item.config.errorHeader] = errors[type];
|
||||
}
|
||||
};
|
||||
|
||||
for (let errType in typedErrors) {
|
||||
errorConfig.handler[errType](typedErrors[errType]);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
let { method = 'get', url, params, config } = factory;
|
||||
if (configoverwrite !== undefined) config = configoverwrite;
|
||||
method = method.toLowerCase();
|
||||
|
||||
return method === 'post' ? this.post(form, url, params, config) : this.get(form, url, params, config);
|
||||
}
|
||||
|
||||
// === public functions END ===
|
||||
|
||||
// === private functions START ===
|
||||
|
||||
setupInterceptors() {
|
||||
|
||||
this.axiosInstance.interceptors.request.use(config => {
|
||||
if (config.method != 'post' || !config.data)
|
||||
return config;
|
||||
|
||||
if (config.data instanceof FormData)
|
||||
return config;
|
||||
|
||||
if (!Object.values(config.data).every(item => {
|
||||
if (item instanceof FileList)
|
||||
return false;
|
||||
if (Array.isArray(item))
|
||||
return item.every(i => !(i instanceof File));
|
||||
return true;
|
||||
})) {
|
||||
const newData = Object.entries(config.data).reduce((nd, [key, item]) => {
|
||||
if (item instanceof FileList) {
|
||||
for (const file of item)
|
||||
nd.FormData.append(key + (item.length > 1 ? '[]' : ''), file);
|
||||
} else if (Array.isArray(item)) {
|
||||
if (item.every(i => !(i instanceof File))) {
|
||||
nd.jsondata[key] = item;
|
||||
} else {
|
||||
item.forEach(file => nd.FormData.append(key + (item.length > 1 ? '[]' : ''), file));
|
||||
}
|
||||
} else {
|
||||
nd.jsondata[key] = item;
|
||||
}
|
||||
return nd;
|
||||
}, {
|
||||
FormData: new FormData(),
|
||||
jsondata: {}
|
||||
});
|
||||
newData.FormData.append('_jsondata', JSON.stringify(newData.jsondata));
|
||||
config.data = newData.FormData;
|
||||
}
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
this.axiosInstance.interceptors.response.use(
|
||||
response => {
|
||||
if (response.config?.errorHandling == 'off'
|
||||
|| response.config?.errorHandling === false
|
||||
|| response.config?.errorHandling == 'fail')
|
||||
return this.clean_return_value(response);
|
||||
|
||||
// NOTE(chris): loop through errors
|
||||
if (response.data.errors)
|
||||
response.data.errors = response.data.errors.filter(
|
||||
err => (response.config[err.type + 'ErrorHandler'] || this.DEFAULT_ERROR_CONFIG.handler[err.type])(err, response.config)
|
||||
);
|
||||
|
||||
return this.clean_return_value(response);
|
||||
},
|
||||
error => {
|
||||
if (error.code == 'ERR_CANCELED')
|
||||
return Promise.reject({ handled: true, ...error });
|
||||
|
||||
const errorConfig = this.get_error_handler(error.config);
|
||||
|
||||
if (!errorConfig.fail)
|
||||
return Promise.reject(error);
|
||||
|
||||
const remaining = this.get_error_list(error);
|
||||
|
||||
const errors = this.popHandleableErrors(errorConfig, remaining);
|
||||
|
||||
for (let type in errors) {
|
||||
errorConfig.handler[type](errors[type]);
|
||||
}
|
||||
|
||||
if (remaining.length)
|
||||
return Promise.reject(error);
|
||||
|
||||
return Promise.reject({ handled: true, ...error });
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
setupDefaultConfig() {
|
||||
|
||||
this.DEFAULT_ERROR_CONFIG = {
|
||||
success: true,
|
||||
fail: true,
|
||||
combine: {
|
||||
form: ['validation', 'general'],
|
||||
toast: ['validation', 'general', 'not_found', 'site_failed']
|
||||
},
|
||||
handler: {
|
||||
form: (form, errors) => {
|
||||
form.clearValidation();
|
||||
errors.forEach(err => form.setFeedback(
|
||||
false,
|
||||
err.messages || err.message
|
||||
));
|
||||
},
|
||||
toast: async (errors) => {
|
||||
await this.ready;
|
||||
|
||||
async function _format_toast(errors) {
|
||||
errors = errors.reduce((result, err) => {
|
||||
switch (err.type) {
|
||||
case 'not_found':
|
||||
case 'site_failed':
|
||||
if (err.message)
|
||||
result[err.message] = [err.url];
|
||||
else
|
||||
result._default = [err.url];
|
||||
break;
|
||||
case 'general':
|
||||
if (!result._default)
|
||||
result._default = [];
|
||||
result._default.push(err.message);
|
||||
break;
|
||||
case 'validation':
|
||||
Object.entries(err.messages)
|
||||
.forEach(([field, msg]) => {
|
||||
if (!result[field])
|
||||
result[field] = [];
|
||||
if (Array.isArray(msg))
|
||||
result[field].push(...msg);
|
||||
else
|
||||
result[field].push(msg);
|
||||
});
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}, {});
|
||||
let counter = 0;
|
||||
const msgs = await Promise.all(Object.entries(errors)
|
||||
.sort((a, b) => ['_default'].indexOf(b[0]) - ['_default'].indexOf(a[0])) // sort _default first
|
||||
.map(async ([field, msgs]) => {
|
||||
if (field == '_default') {
|
||||
await this.deps.$p.loadCategory('dashboard');
|
||||
const general = this.deps.$p.t('dashboard/general');
|
||||
field = '<dt class="d-none">' + general + '</dt>';
|
||||
} else {
|
||||
field = '<dt>' + field + '</dt>';
|
||||
}
|
||||
counter += msgs.length;
|
||||
return field
|
||||
+ '<dd>'
|
||||
+ msgs.join('</dd><dd>')
|
||||
+ '</dd>';
|
||||
}));
|
||||
return {
|
||||
counter,
|
||||
msgs
|
||||
}
|
||||
}
|
||||
|
||||
let counter, msgs;
|
||||
if (Array.isArray(errors)) {
|
||||
({ counter, msgs } = await _format_toast(errors));
|
||||
} else {
|
||||
({ counter, msgs } = await Object.entries(errors)
|
||||
.reduce(async (res, [title, errs]) => {
|
||||
const result = await res;
|
||||
const { counter, msgs } = await _format_toast(errs);
|
||||
result.counter += counter;
|
||||
result.msgs.push('<dt>'
|
||||
+ title
|
||||
+ '</dt><dd><dl>'
|
||||
+ msgs.join('')
|
||||
+ '</dl></dd>');
|
||||
return result;
|
||||
}, Promise.resolve({ counter: 0, msgs: []})));
|
||||
}
|
||||
|
||||
await this.deps.$p.loadCategory('ui');
|
||||
const n_errors = this.deps.$p.t('ui/n_errors', { n: counter });
|
||||
|
||||
this.deps.$fhcAlert.alertDefault(
|
||||
'error',
|
||||
n_errors,
|
||||
'<dl>' + msgs.join('') + '</dl>',
|
||||
true,
|
||||
true
|
||||
);
|
||||
},
|
||||
php: async (errors) => {
|
||||
await this.ready;
|
||||
|
||||
this._send_array_or_object(errors, (error, title) => {
|
||||
var message = '';
|
||||
message += 'Message: ' + error.message + '\n\n';
|
||||
message += 'Filename: ' + error.filename + '\n';
|
||||
message += 'Line Number: ' + error.line + '\n';
|
||||
if (error.backtrace && error.backtrace.length) {
|
||||
message += '\nBacktrace: ';
|
||||
error.backtrace.forEach(err => {
|
||||
message += '\n\tFile: ' + err.file + '\n';
|
||||
message += '\tLine: ' + err.line + '\n';
|
||||
message += '\tFunction: ' + err.function + '\n';
|
||||
});
|
||||
}
|
||||
switch (error.severity) {
|
||||
case 'Warning':
|
||||
case 'Core Warning':
|
||||
case 'Compile Warning':
|
||||
case 'User Warning':
|
||||
if (title)
|
||||
title += ': PHP ' + error.severity;
|
||||
else
|
||||
title = 'PHP ' + error.severity;
|
||||
this.deps.$fhcAlert.alertDefault('warn', title, message, true);
|
||||
break;
|
||||
case 'Notice':
|
||||
case 'User Notice':
|
||||
case 'Runtime Notice':
|
||||
if (title)
|
||||
title += ': PHP ' + error.severity;
|
||||
else
|
||||
title = 'PHP ' + error.severity;
|
||||
this.deps.$fhcAlert.alertDefault('info', title, message, true);
|
||||
break;
|
||||
default:
|
||||
message = 'Type: PHP ' + error.severity + '\n\n' + message;
|
||||
if (title)
|
||||
message = title + '\n\n' + message;
|
||||
this.deps.$fhcAlert.alertSystemError(message);
|
||||
break;
|
||||
}
|
||||
});
|
||||
},
|
||||
exception: async (errors) => {
|
||||
await this.ready;
|
||||
|
||||
this._send_array_or_object(errors, (error, title) => {
|
||||
var message = '';
|
||||
if (title)
|
||||
message += title + '\n\n';
|
||||
message += 'Type: ' + error.class + '\n\n';
|
||||
message += 'Message: ' + error.message + '\n\n';
|
||||
message += 'Filename: ' + error.filename + '\n';
|
||||
message += 'Line Number: ' + error.line + '\n';
|
||||
if (error.backtrace && error.backtrace.length) {
|
||||
message += '\nBacktrace: ';
|
||||
error.backtrace.forEach(err => {
|
||||
message += '\n\tFile: ' + err.file + '\n';
|
||||
message += '\tLine: ' + err.line + '\n';
|
||||
message += '\tFunction: ' + err.function + '\n';
|
||||
});
|
||||
}
|
||||
this.deps.$fhcAlert.alertSystemError(message);
|
||||
});
|
||||
},
|
||||
db: async (errors) => {
|
||||
await this.ready;
|
||||
|
||||
this._send_array_or_object(errors, (error, title) => {
|
||||
var message = '';
|
||||
if (title)
|
||||
message += title + '\n\n';
|
||||
if (error.heading !== undefined)
|
||||
message += error.heading + '\n\n';
|
||||
if (error.code !== undefined)
|
||||
message += 'Code: ' + error.code + '\n\n';
|
||||
if (error.sql !== undefined)
|
||||
message += 'SQL: ' + error.sql + '\n\n';
|
||||
if (error.message !== undefined)
|
||||
message += 'Message: ' + error.message + '\n\n';
|
||||
else if (error.messages !== undefined)
|
||||
message += 'Messages: ' + error.messages.join('\n\t') + '\n\n';
|
||||
if (error.filename !== undefined)
|
||||
message += 'Filename: ' + error.filename + '\n';
|
||||
if (error.line !== undefined)
|
||||
message += 'Line Number: ' + error.line + '\n';
|
||||
|
||||
this.deps.$fhcAlert.alertSystemError(message);
|
||||
});
|
||||
},
|
||||
auth: async (errors) => {
|
||||
await this.ready;
|
||||
|
||||
this._send_array_or_object(errors, (error, title) => {
|
||||
if (title)
|
||||
title += ': ' + error.message;
|
||||
else
|
||||
title = error.message;
|
||||
|
||||
var message = '';
|
||||
message += 'Controller name: ' + error.controller + '\n';
|
||||
message += 'Method name: ' + error.method + '\n';
|
||||
message += 'Required permissions: ' + error.required_permissions;
|
||||
|
||||
this.deps.$fhcAlert.alertDefault(
|
||||
'error',
|
||||
title,
|
||||
message,
|
||||
true
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
_send_array_or_object(errors, func) {
|
||||
if (!errors) return;
|
||||
|
||||
if (Array.isArray(errors)) {
|
||||
errors.forEach(error => func(error));
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle Single Error Object
|
||||
if (errors.type) {
|
||||
func(errors);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle Category Container
|
||||
Object.entries(errors).forEach(([title, value]) => {
|
||||
const errorList = Array.isArray(value) ? value : [value];
|
||||
|
||||
errorList.forEach(error => {
|
||||
if (error && typeof error === 'object') {
|
||||
func(error, title);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
get_config(form, uri, data, config) {
|
||||
if (typeof form == 'string' && config === undefined) {
|
||||
[uri, data, config] = [form, uri, data];
|
||||
form = undefined;
|
||||
} else if (form) {
|
||||
if (typeof form != 'object')
|
||||
throw new TypeError('Parameter 1 of _get_config must be an object or a string');
|
||||
if (uri === undefined && data === undefined && config === undefined) {
|
||||
config = form;
|
||||
form = undefined;
|
||||
}
|
||||
}
|
||||
if (form) {
|
||||
// NOTE(chris): check if form is fhc-form
|
||||
if (!form.clearValidation || !form.setFeedback)
|
||||
throw new TypeError("'form' is not a Form Component");
|
||||
|
||||
form = {
|
||||
clearValidation: form.clearValidation,
|
||||
setFeedback: form.setFeedback
|
||||
};
|
||||
|
||||
if (config)
|
||||
config.form = form;
|
||||
else
|
||||
config = {form};
|
||||
}
|
||||
|
||||
return [uri, data, config];
|
||||
}
|
||||
|
||||
clean_return_value(response) {
|
||||
if (typeof response.data === 'string' || response.data instanceof String)
|
||||
return this.clean_return_value({ data: response });
|
||||
|
||||
const result = response.data;
|
||||
delete response.data;
|
||||
if (!result)
|
||||
return {meta: {response}, data: null};
|
||||
if (!result.meta)
|
||||
result.meta = { response };
|
||||
else
|
||||
result.meta.response = response;
|
||||
return result;
|
||||
}
|
||||
|
||||
merge_error_config(config) {
|
||||
if (config === false || config === 'off')
|
||||
return { ...this.DEFAULT_ERROR_CONFIG, success: false, fail: false };
|
||||
|
||||
if (!config || config === true)
|
||||
return { ...this.DEFAULT_ERROR_CONFIG };
|
||||
|
||||
if (config === 'success')
|
||||
return { ...this.DEFAULT_ERROR_CONFIG, fail: false };
|
||||
|
||||
if (config === 'fail')
|
||||
return { ...this.DEFAULT_ERROR_CONFIG, success: false };
|
||||
|
||||
const { success, fail, handler, combine } = config;
|
||||
|
||||
config = { ...this.DEFAULT_ERROR_CONFIG };
|
||||
|
||||
Object.entries({ fail, success }).forEach(([key, value]) => {
|
||||
if (value !== undefined)
|
||||
config[key] = value;
|
||||
});
|
||||
Object.entries({ handler, combine }).forEach(([key, value]) => {
|
||||
if (value !== undefined)
|
||||
config[key] = { ...config[key], ...value };
|
||||
});
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
get_error_handler(config) {
|
||||
const result = this.merge_error_config(config?.errorHandling);
|
||||
|
||||
if (!config?.form) {
|
||||
result.combine = { ...result.combine, form: [] };
|
||||
} else {
|
||||
const formHandler = result.handler.form;
|
||||
result.handler = { ...result.handler, form: errors => formHandler(config.form, errors) };
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
get_error_list(error) {
|
||||
if (error.response) {
|
||||
if (error.response.status == 404) {
|
||||
return [{
|
||||
type: 'not_found',
|
||||
message: error.message,
|
||||
url: error.request.responseURL
|
||||
}];
|
||||
} else {
|
||||
if (error.response.data.errors == undefined) return [];
|
||||
return error.response.data.errors;
|
||||
}
|
||||
} else if (error.request) {
|
||||
return [{
|
||||
type: 'site_failed',
|
||||
message: error.message,
|
||||
url: error.request.responseURL
|
||||
}];
|
||||
} else {
|
||||
return [{
|
||||
type: 'script',
|
||||
message: error.message
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
popHandleableErrors(errorHandling, errors) {
|
||||
const result = {};
|
||||
const copy = [];
|
||||
|
||||
if (errors == undefined) return {};
|
||||
|
||||
while (errors.length)
|
||||
copy.push(errors.pop());
|
||||
for (let error of copy) {
|
||||
let type = error.type;
|
||||
let newType = null;
|
||||
for (let t in errorHandling.combine) {
|
||||
let newTypeCombinesType = errorHandling
|
||||
.combine[t]
|
||||
.includes(type);
|
||||
let newTypeHasHandler = errorHandling.handler[t];
|
||||
if (newTypeCombinesType && newTypeHasHandler) {
|
||||
newType = t;
|
||||
if (newType == 'form')
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (newType)
|
||||
type = newType;
|
||||
const handler = errorHandling.handler[type];
|
||||
if (handler) {
|
||||
if (!result[type])
|
||||
result[type] = [];
|
||||
if (Array.isArray(error))
|
||||
result[type].push(...error);
|
||||
else
|
||||
result[type].push(error);
|
||||
continue;
|
||||
}
|
||||
errors.push(error);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// === private functions END ===
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import ApiPhrasen from '../../api/factory/phrasen.js';
|
||||
|
||||
const categories = Vue.reactive({});
|
||||
const loadingModules = {};
|
||||
let user_language = Vue.ref(FHC_JS_DATA_STORAGE_OBJECT.user_language);
|
||||
|
||||
export const user_locale = Vue.computed(() => {
|
||||
if (!user_language.value) return null;
|
||||
return FHC_JS_DATA_STORAGE_OBJECT.server_languages.find(l => l.sprache == user_language.value).LC_Time;
|
||||
});
|
||||
|
||||
function extractCategory(obj, category) {
|
||||
return obj.filter(e => e.category == category).reduce((res, elem) => {
|
||||
if (!res[elem.phrase]) res[elem.phrase] = elem.text;
|
||||
return res;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function getValueForLoadedPhrase(category, phrase, params) {
|
||||
let result = categories[category][phrase];
|
||||
if (!result)
|
||||
return '<< PHRASE ' + phrase + '>>';
|
||||
if (params)
|
||||
result = result.replace(/\{([^}]*)\}/g, (match, p1) => params[p1] === undefined ? match : params[p1]);
|
||||
return result;
|
||||
}
|
||||
|
||||
export default {
|
||||
init(app) {
|
||||
// Create a controller to resolve the promise later
|
||||
let resolveReady;
|
||||
const readyPromise = new Promise(resolve => { resolveReady = resolve; });
|
||||
|
||||
const $p = {
|
||||
deps: {},
|
||||
ready: readyPromise,
|
||||
setDeps(deps) {
|
||||
Object.assign(this.deps, deps);
|
||||
// Once we have the API, we are ready to load data
|
||||
if (this.deps.$api) resolveReady();
|
||||
},
|
||||
user_language,
|
||||
user_locale,
|
||||
|
||||
async loadCategory(category) {
|
||||
if (Array.isArray(category))
|
||||
return Promise.all(category.map(cat => this.loadCategory(cat)));
|
||||
|
||||
// 2. SAFETY: Check if API is available via deps
|
||||
await this.ready;
|
||||
|
||||
if (!loadingModules[category])
|
||||
loadingModules[category] = this.deps.$api
|
||||
.call(ApiPhrasen.loadCategory(category))
|
||||
.then(res => res?.data ? extractCategory(res.data, category) : {})
|
||||
.then(res => {
|
||||
categories[category] = res;
|
||||
});
|
||||
return loadingModules[category];
|
||||
},
|
||||
t_ref(category, phrase, params) {
|
||||
console.warn('deprecated');
|
||||
return Vue.computed(() => this.t(category, phrase, params));
|
||||
},
|
||||
|
||||
t(category, phrase, params) {
|
||||
if (params === undefined && (
|
||||
(Array.isArray(category) && category.length == 2) ||
|
||||
(category.split && category.split('/').length == 2))
|
||||
) {
|
||||
params = phrase;
|
||||
[category, phrase] = category.split ? category.split('/') : category;
|
||||
}
|
||||
if (phrase === undefined) {
|
||||
console.error('invalid input', category, phrase, params);
|
||||
return '';
|
||||
}
|
||||
let val = Vue.computed(() => {
|
||||
if (!categories[category])
|
||||
return '';
|
||||
return getValueForLoadedPhrase(category, phrase, params);
|
||||
});
|
||||
if (!categories[category])
|
||||
this.loadCategory(category);
|
||||
return val.value;
|
||||
},
|
||||
|
||||
async setLanguage(language) {
|
||||
await this.ready;
|
||||
const catArray = Object.keys(categories);
|
||||
return this.deps.$api.call(ApiPhrasen.setLanguage(catArray, language)).then(res => {
|
||||
res.data.forEach(row => { categories[row.category][row.phrase] = row.text; });
|
||||
user_language.value = language;
|
||||
return res;
|
||||
});
|
||||
}
|
||||
};
|
||||
return $p;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import Phrasen from './BasePhrasen.js';
|
||||
import Alert from './BaseAlert.js';
|
||||
import { BaseApi } from './BaseApi.js';
|
||||
|
||||
export default {
|
||||
|
||||
install(app, options = {}) {
|
||||
// init in order
|
||||
const $p = Phrasen.init(app);
|
||||
const $fhcAlert = Alert.init(app, $p);
|
||||
|
||||
// try to reuse existing CoreRESTClient api instance if one has been active since before
|
||||
// fhcBase Plugin install
|
||||
let $api = app.config.globalProperties.$api;
|
||||
if (!($api instanceof BaseApi)) {
|
||||
$api = new BaseApi({ $fhcAlert, $p }, options);
|
||||
} else {
|
||||
// If api existed pre-app install
|
||||
$api.setDependencies({ $fhcAlert, $p });
|
||||
}
|
||||
|
||||
// set ready promise for awaiting async functions
|
||||
$p.setDeps({ $api, $fhcAlert });
|
||||
$fhcAlert.setDeps({ $api });
|
||||
|
||||
// globalProperties Binding & provide
|
||||
app.config.globalProperties.$p = $p;
|
||||
app.config.globalProperties.$fhcAlert = $fhcAlert;
|
||||
app.config.globalProperties.$api = $api;
|
||||
app.config.globalProperties.$fhcApi = $api;
|
||||
app.provide('$api', $api);
|
||||
app.provide('$fhcApi', $api);
|
||||
app.provide('$p', $p);
|
||||
app.provide('$fhcAlert', $fhcAlert);
|
||||
}
|
||||
|
||||
};
|
||||
Reference in New Issue
Block a user