diff --git a/application/controllers/Cis/Auth.php b/application/controllers/Cis/Auth.php index 67267ebf6..87ef5d3da 100644 --- a/application/controllers/Cis/Auth.php +++ b/application/controllers/Cis/Auth.php @@ -40,7 +40,7 @@ class Auth extends FHC_Controller if ($this->form_validation->run()) { - redirect($this->authlib->getLandingPage('/CisVue/Dashboard')); + redirect($this->authlib->getLandingPage('/Cis4')); } else { diff --git a/application/controllers/CisVue/Dashboard.php b/application/controllers/CisVue/Dashboard.php deleted file mode 100644 index ee830cb8b..000000000 --- a/application/controllers/CisVue/Dashboard.php +++ /dev/null @@ -1,43 +0,0 @@ - 'dashboard/benutzer:r' - ) - ); - } - - // ----------------------------------------------------------------------------------------------------------------- - // Public methods - - /** - * @return void - */ - public function index() - { - - $this->load->model('person/Person_model','PersonModel'); - $personData = getData($this->PersonModel->getByUid(getAuthUID()))[0]; - - $viewData = array( - 'uid' => getAuthUID(), - 'name' => $personData->vorname, - 'person_id' => $personData->person_id - ); - - $this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData]); - - } -} \ No newline at end of file diff --git a/application/controllers/api/frontend/v1/dashboard/Board.php b/application/controllers/api/frontend/v1/dashboard/Board.php index c50fec128..fdded61e3 100644 --- a/application/controllers/api/frontend/v1/dashboard/Board.php +++ b/application/controllers/api/frontend/v1/dashboard/Board.php @@ -40,11 +40,32 @@ class Board extends FHCAPI_Controller public function list() { + $this->DashboardModel->addSelect('dashboard_id'); + $this->DashboardModel->addSelect('dashboard_kurzbz'); + $this->DashboardModel->addSelect('tbl_dashboard.beschreibung'); + $this->DashboardModel->addSelect("( + SELECT json_agg(w.*) + FROM dashboard.tbl_widget w + JOIN dashboard.tbl_dashboard_widget dw + USING(widget_id) + WHERE dw.dashboard_id=tbl_dashboard.dashboard_id + ) AS \"widgetSetup\""); + $result = $this->DashboardModel->load(); $data = $this->getDataOrTerminateWithError($result); - $this->terminateWithSuccess($result); + $data = array_map(function ($dashboard) { + $tmpSetups = json_decode($dashboard->widgetSetup); + $tmpSetups = array_map(function ($widget) { + $widget->setup->file = absoluteJsImportUrl($widget->setup->file); + return $widget; + }, $tmpSetups); + $dashboard->widgetSetup = $tmpSetups; + return $dashboard; + }, $data); + + $this->terminateWithSuccess($data); } public function create() @@ -82,7 +103,7 @@ class Board extends FHCAPI_Controller $data = $this->getDataOrTerminateWithError($result); - $this->terminateWithSuccess($result); + $this->terminateWithSuccess($data); } public function delete() @@ -116,6 +137,6 @@ class Board extends FHCAPI_Controller $data = $this->getDataOrTerminateWithError($result); - $this->terminateWithSuccess($result); + $this->terminateWithSuccess($data); } } diff --git a/application/controllers/api/frontend/v1/dashboard/Preset.php b/application/controllers/api/frontend/v1/dashboard/Preset.php index 5983d9660..d9be307cf 100644 --- a/application/controllers/api/frontend/v1/dashboard/Preset.php +++ b/application/controllers/api/frontend/v1/dashboard/Preset.php @@ -120,10 +120,7 @@ class Preset extends FHCAPI_Controller $conf = $this->dashboardlib->getPreset($db, $funktion); if ($conf) { $preset = json_decode($conf->preset, true); - if (!isset($preset[$funktion]) || !isset($preset[$funktion]['widgets'])) - $result[$funktion] = []; - else - $result[$funktion] = $preset[$funktion]['widgets']; + $result[$funktion] = $preset; } else { $result[$funktion] = []; } @@ -154,7 +151,7 @@ class Preset extends FHCAPI_Controller $preset_decoded = json_decode($preset->preset, true); - $this->dashboardlib->addWidgetsToWidgets($preset_decoded, $dashboard_kurzbz, $funktion_kurzbz, [$widget]); + $preset_decoded[$widget['widgetid']] = $widget; $preset->preset = json_encode($preset_decoded); @@ -186,8 +183,10 @@ class Preset extends FHCAPI_Controller $preset_decoded = json_decode($preset->preset, true); - if (!$this->dashboardlib->removeWidgetFromWidgets($preset_decoded, $funktion_kurzbz, $widgetid)) + if (!isset($preset_decoded[$widgetid])) show_404(); + + unset($preset_decoded[$widgetid]); $preset->preset = json_encode($preset_decoded); diff --git a/application/controllers/api/frontend/v1/dashboard/User.php b/application/controllers/api/frontend/v1/dashboard/User.php index 9d020649e..e603573ed 100644 --- a/application/controllers/api/frontend/v1/dashboard/User.php +++ b/application/controllers/api/frontend/v1/dashboard/User.php @@ -48,25 +48,9 @@ class User extends FHCAPI_Controller $uid = $this->authlib->getAuthObj()->username; - /*$mergedconfig = $this->dashboardlib->getMergedConfig($dashboard->dashboard_id, $uid); + $mergedconfig = $this->dashboardlib->getMergedUserConfig($dashboard->dashboard_id, $uid); - $this->terminateWithSuccess([ - 'general' => call_user_func_array( - 'array_merge_recursive', - $mergedconfig - ) - ]);*/ - $defaultconfig = $this->dashboardlib->getDefaultConfig($dashboard->dashboard_id); - $userconfig = $this->dashboardlib->getUserConfig($dashboard->dashboard_id, $uid); - - $defaultconfig_squashed = $defaultconfig ? call_user_func_array('array_replace_recursive', $defaultconfig) : []; - $userconfig_squashed = $userconfig ? call_user_func_array('array_replace_recursive', $userconfig) : []; - - $mergedconfig = array_replace_recursive($defaultconfig_squashed, $userconfig_squashed); - - $this->terminateWithSuccess([ - DashboardLib::SECTION_IF_FUNKTION_KURZBZ_IS_NULL => $mergedconfig - ]); + $this->terminateWithSuccess($mergedconfig); } public function addWidget() @@ -86,26 +70,15 @@ class User extends FHCAPI_Controller if (!isset($widget['widgetid'])) $widget['widgetid'] = $this->dashboardlib->generateWidgetId($dashboard_kurzbz); + if (isset($widget['source'])) + unset($widget['source']); + $override = $this->dashboardlib->getOverrideOrCreateEmptyOverride($dashboard_kurzbz, $uid); - + $override_decoded = json_decode($override->override, true); - if (!isset($override_decoded['general']) || !is_array($override_decoded['general'])) - $override_decoded['general'] = []; + $override_decoded[$widget['widgetid']] = $widget; - if (!isset($override_decoded['general']['widgets'])) - $override_decoded['general']['widgets'] = []; - - $override_decoded['general']['widgets'][$widget['widgetid']] = $widget; - - // NOTE(chris): remove doubles in other funktionen - foreach ($override_decoded as $funktion => $array) { - if ($funktion == 'general') - continue; - if (isset($array['widgets']) && isset($array['widgets'][$widget['widgetid']])) - unset($override_decoded[$funktion]['widgets'][$widget['widgetid']]); - } - $override->override = json_encode($override_decoded); $result = $this->dashboardlib->insertOrUpdateOverride($override); @@ -135,18 +108,10 @@ class User extends FHCAPI_Controller $override_decoded = json_decode($override->override, true); - foreach (array_keys($override_decoded) as $k) { - if (!isset($override_decoded[$k]["widgets"])) { - unset($override_decoded[$k]); - continue; - } - if (isset($override_decoded[$k]["widgets"][$widget_id])) { - unset($override_decoded[$k]["widgets"][$widget_id]); - } - if (!$override_decoded[$k]["widgets"]) { - unset($override_decoded[$k]); - } - } + if (!isset($override_decoded[$widget_id])) + show_404(); + + unset($override_decoded[$widget_id]); $override->override = json_encode($override_decoded); diff --git a/application/libraries/dashboard/DashboardLib.php b/application/libraries/dashboard/DashboardLib.php index 1c3983108..c9838f0e7 100644 --- a/application/libraries/dashboard/DashboardLib.php +++ b/application/libraries/dashboard/DashboardLib.php @@ -37,7 +37,9 @@ class DashboardLib public function getDashboardByKurzbz($dashboard_kurzbz) { - $result = $this->_ci->DashboardModel->getDashboardByKurzbz($dashboard_kurzbz); + $result = $this->_ci->DashboardModel->loadWhere([ + 'dashboard_kurzbz' => $dashboard_kurzbz + ]); if (hasData($result)) { @@ -47,17 +49,21 @@ class DashboardLib return null; } - public function getMergedConfig($dashboard_id, $uid) + public function getMergedUserConfig($dashboard_id, $uid) { - $defaultconfig = $this->getDefaultConfig($dashboard_id); - $userconfig = $this->getUserConfig($dashboard_id, $uid); + $defaultconfig = $this->getUserBaseConfig($dashboard_id); + $userconfig = $this->getUserOverrideConfig($dashboard_id, $uid); - $mergedconfig = array_replace_recursive($defaultconfig, $userconfig); + $sourceconfig = array_map(function ($value) { + return ['source' => $value['source']]; + }, $defaultconfig); + + $mergedconfig = array_replace_recursive($defaultconfig, $userconfig, $sourceconfig); return $mergedconfig; } - public function getDefaultConfig($dashboard_id) + protected function getUserBaseConfig($dashboard_id) { $funktion_kurzbzs = []; $rights = $this->_ci->permissionlib->getAccessRights(); @@ -87,7 +93,11 @@ class DashboardLib $preset = json_decode($presetobj->preset, true); if (null !== $preset) { - $defaultconfig = array_replace_recursive($defaultconfig, $preset); + $preset = array_map(function ($value) use ($presetobj) { + $value['source'] = $presetobj->funktion_kurzbz ?: self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL; + return $value; + }, $preset); + $defaultconfig = array_merge_recursive($defaultconfig, $preset); } } } @@ -95,7 +105,7 @@ class DashboardLib return $defaultconfig; } - public function getUserConfig($dashboard_id, $uid) + protected function getUserOverrideConfig($dashboard_id, $uid) { $res_userconfig = $this->_ci->DashboardOverrideModel->getOverride($dashboard_id, $uid); @@ -124,7 +134,7 @@ class DashboardLib $emptyoverride = new stdClass(); $emptyoverride->dashboard_id = $dashboard->dashboard_id; $emptyoverride->uid = $uid; - $emptyoverride->override = '{"' . self::USEROVERRIDE_SECTION . '": {"widgets":{}}, "custom": { "widgets" : {}}}'; + $emptyoverride->override = '[]'; return $emptyoverride; } @@ -143,8 +153,7 @@ class DashboardLib $emptypreset = new stdClass(); $emptypreset->dashboard_id = $dashboard->dashboard_id; $emptypreset->funktion_kurzbz = $funktion_kurzbz; - $section = ($funktion_kurzbz !== null) ? $funktion_kurzbz : self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL; - $emptypreset->preset = '{"' . $section . '": { "widgets" : {}},"custom": { "widgets" : {}}}'; + $emptypreset->preset = '[]'; return $emptypreset; } @@ -209,44 +218,4 @@ class DashboardLib return $result; } - - public function addWidgetsToWidgets(&$widgets, $dashboard_kurzbz, $section, $addwigets) - { - foreach ($addwigets as $widget) - { - if(!isset($widget['widgetid'])) - { - $widget['widgetid'] = $this->generateWidgetId($dashboard_kurzbz); - } - $this->addWidgetToWidgets($widgets, $section, $widget, $widget['widgetid']); - } - } - - public function addWidgetToWidgets(&$widgets, $section, $widget, $widgetid) - { - $section = ($section !== null) ? $section : self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL; - if (!isset($widgets[$section]) || !isset($widgets[$section]["widgets"]) || !is_array($widgets[$section])) - { - $widgets[$section] = array(); - $widgets[$section]["widgets"] = array(); - } - - $widgets[$section]["widgets"][$widgetid] = $widget; - } - - public function removeWidgetFromWidgets(&$widgets, $section, $widgetid) - { - $section = ($section !== null) ? $section : self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL; - if (isset($widgets[$section]) && isset($widgets[$section]["widgets"][$widgetid])) - { - unset($widgets[$section]["widgets"][$widgetid]); - if(empty($widgets[$section]["widgets"]) && $section !== self::USEROVERRIDE_SECTION) { - unset($widgets[$section]); - } - return true; - } - else { - return false; - } - } } diff --git a/application/models/dashboard/Dashboard_model.php b/application/models/dashboard/Dashboard_model.php index 88946ed83..78f6b1100 100644 --- a/application/models/dashboard/Dashboard_model.php +++ b/application/models/dashboard/Dashboard_model.php @@ -11,15 +11,4 @@ class Dashboard_model extends DB_Model $this->dbTable = 'dashboard.tbl_dashboard'; $this->pk = 'dashboard_id'; } - - - /** - * Get Dashboard by kurzbz. - * @param string dashboard_kurzbz - * @return array - */ - public function getDashboardByKurzbz($dashboard_kurzbz) - { - return $this->loadWhere(array('dashboard_kurzbz' => $dashboard_kurzbz)); - } } diff --git a/application/views/CisRouterView/CisRouterView.php b/application/views/CisRouterView/CisRouterView.php index 37c18e39c..8c637492a 100644 --- a/application/views/CisRouterView/CisRouterView.php +++ b/application/views/CisRouterView/CisRouterView.php @@ -41,7 +41,7 @@ $includesArray = array( 'vendor/moment/luxonjs/luxon.min.js' ), 'customJSModules' => array( - 'public/js/apps/Dashboard/Fhc.js', + 'public/js/apps/Cis.js', ), ); diff --git a/application/views/templates/CISVUE-Footer.php b/application/views/templates/CISVUE-Footer.php index d7c1de24c..eae2a94ff 100644 --- a/application/views/templates/CISVUE-Footer.php +++ b/application/views/templates/CISVUE-Footer.php @@ -6,7 +6,7 @@ $includesArray = array( 'fontawesome6' => true, 'axios027' => true, 'customJSModules' => array_merge([ - 'public/js/apps/Cis.js' + 'public/js/apps/Cis/Menu.js' ], $customJSModules ?? []), 'customCSSs' => array_merge([ 'public/css/Cis4/Cis.css' diff --git a/application/views/templates/CISVUE-Header.php b/application/views/templates/CISVUE-Header.php index 804a43821..d98cbc9cd 100644 --- a/application/views/templates/CISVUE-Header.php +++ b/application/views/templates/CISVUE-Header.php @@ -8,7 +8,7 @@ $includesArray = array( 'axios027' => true, 'primevue3' => true, 'customJSModules' => array_merge([ - 'public/js/apps/Cis.js' + 'public/js/apps/Cis/Menu.js' ], $customJSModules ?? []), 'customCSSs' => array_merge([ 'public/css/Cis4/Cis.css', diff --git a/composer.json b/composer.json index c1f4506c6..ce6fc71e1 100644 --- a/composer.json +++ b/composer.json @@ -70,6 +70,18 @@ } } }, + { + "type": "package", + "package": { + "name": "drag-drop-touch-js/dragdroptouch", + "version": "2.0.3", + "source": { + "url": "https://github.com/drag-drop-touch-js/dragdroptouch.git", + "type": "git", + "reference": "master" + } + } + }, { "type": "package", "package": { @@ -452,6 +464,8 @@ "easyrdf/easyrdf": "0.9.*", + "drag-drop-touch-js/dragdroptouch": "*", + "fgelinas/timepicker": "0.3.3", "fortawesome/font-awesome4": "4.7.*", "fortawesome/font-awesome6": "6.1.*", diff --git a/composer.lock b/composer.lock index 7525b7f5b..212eea3be 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f4f0af4586f46f97d8b6092c1ac0fb3a", + "content-hash": "869cbc35bd1ba90ab90934fcb41b0f51", "packages": [ { "name": "afarkas/html5shiv", @@ -804,6 +804,16 @@ "abandoned": true, "time": "2018-03-09T06:07:41+00:00" }, + { + "name": "drag-drop-touch-js/dragdroptouch", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/drag-drop-touch-js/dragdroptouch.git", + "reference": "master" + }, + "type": "library" + }, { "name": "easyrdf/easyrdf", "version": "0.9.1", diff --git a/public/css/components/dashboard.css b/public/css/components/dashboard.css index 88d136b55..eb9f0a4b1 100644 --- a/public/css/components/dashboard.css +++ b/public/css/components/dashboard.css @@ -2,7 +2,7 @@ @import './dashboard/news.css'; @import './dashboard/LvPlan.css'; -:root{ +:root { --fhc-dashboard-danger: var(--fhc-danger, #842029); --fhc-dashboard-grid-size: 4; --fhc-dashboard-link: var(--fhc-link, #0a57ca); @@ -17,22 +17,16 @@ --fhc-dashboard-section-info-color-hover: var(--fhc-primary-highlight, #005585); } -@media(max-width: 577px) { - :root { - --fhc-dashboard-grid-size: 1; - } -} - -.core-dashboard a{ +.core-dashboard a { color: var(--fhc-dashboard-link); } -@media (max-width: 576px){ +@media (max-width: 576px) { .widget-icon { max-height: 250px; object-fit: cover; } - .widget-icon-container{ + .widget-icon-container { max-width: 250px; margin-left: auto; margin-right: auto; @@ -46,27 +40,36 @@ background-repeat: no-repeat; background-position: center; background-size: cover; - cursor:pointer; + cursor: pointer; } -.dashboard-section > .newGridRow{ - position:absolute; - width:20px; - height:20px; - padding:0; - bottom:0; - left:50%; - transform:translate(-50%, 50%); +.dashboard-section.edit-active { + /** + * replaces margin for extra row + * 10% equals 0.1 of 100% + * 1rem equals the padding of pb-3 that is overwritten here + */ + padding-bottom: calc(10% / var(--fhc-dashboard-grid-size) + 1rem) !important; +} + +.dashboard-section > .newGridRow { + position: absolute; + width: 20px; + height: 20px; + padding: 0; + bottom: 0; + left: 50%; + transform: translate(-50%, 50%); background-color: var(--fhc-dashboard-gridrow-background); } .newGridRow:hover { - color:white; + color: white; background-color: var(--fhc-dashboard-gridrow-background-highlight); } .empty-tile-hover:hover { - background-image: url('data:image/svg+xml;utf8,'); + background-image: url('data:image/svg+xml;utf8,'); } .alert-danger .form-check-input:checked { @@ -74,16 +77,6 @@ background-color: var(--fhc-dashboard-danger); } -:root { - --fhc-dashboard-grid-size: 4; -} - -@media(max-width: 1400px) { - :root { - --fhc-dashboard-grid-size: 4; - } -} - @media(max-width: 1200px) { :root { --fhc-dashboard-grid-size: 3; @@ -105,6 +98,7 @@ @media(max-width: 577px) { :root { --fhc-dashboard-grid-size: 1; + --fhc-dg-item-py: .75rem; } } @@ -132,50 +126,64 @@ cursor: move !important; } -.draggedItem { +.drop-grid-item-resize > .dashboard-item, +.drop-grid-item-move > .dashboard-item { height: 100%; width: 100%; background-color: var(--fhc-dashboard-draggeditem-background); position: relative; } -.dashboard-item-overlay{ +.drop-grid-item-resize > .dashboard-item > *, +.drop-grid-item-move > .dashboard-item > * { + display: none; +} + +.drop-grid-item-sizechanged > .dashboard-item, +.drop-grid-item-move > .dashboard-item { background-color: var(--fhc-dashboard-item-overlay-background); } -.dashboard-item-overlay::before{ - position:absolute; - content:""; - top:0.25rem; - left:0.25rem; - right:0.25rem; - bottom:0.25rem; - border:4px dashed var(--fhc-dashboard-item-overly-border-color); - opacity: 0.5; +.drop-grid-item-sizechanged > .dashboard-item::before, +.drop-grid-item-move > .dashboard-item::before { + position: absolute; + content: ""; + top: .25rem; + left: .25rem; + right: .25rem; + bottom: .25rem; + border: 4px dashed var(--fhc-dashboard-item-overly-border-color); + opacity: .5; } -#deleteBookmark i{ +.drop-grid-item-oversized > .dashboard-item { + /* Bootstrap: border-danger */ + --bs-border-opacity: 1; + border-color: rgba(var(--bs-danger-rgb), var(--bs-border-opacity)) !important; +} + +#deleteBookmark i { color: var(--fhc-dashboard-danger); } -.pin:hover{ +.pin:hover { cursor: pointer; } -.pin[pinned]:hover{ +.pin[pinned]:hover { color: var(--fhc-dashboard-pin-pinned-hover-color); } -.section-info{ +.section-info { color: var(--fhc-dashboard-section-info-color); - cursor:pointer; + cursor: pointer; } .section-info:hover { color: var(--fhc-dashboard-section-info-color-hover); } -.denied-dragging-animation { +.drop-grid-item-blocker [pinned='true'] { animation: wiggle 0.5s linear; color: var(--fhc-dashboard-denied-dragging-animation-color) !important; } @@ -204,13 +212,13 @@ } -.hiddenWidget{ +.hidden-widget { background: var(--fhc-disabled-background); opacity: 40%; } -.hiddenWidget .card, -.hiddenWidget .card-body, -.hiddenWidget .card-body *{ +.hidden-widget .card, +.hidden-widget .card-body, +.hidden-widget .card-body * { background: inherit !important; } diff --git a/public/js/apps/Cis.js b/public/js/apps/Cis.js index ce148834e..f83f5c5a9 100644 --- a/public/js/apps/Cis.js +++ b/public/js/apps/Cis.js @@ -1,211 +1,336 @@ -import FhcSearchbar from "../components/searchbar/searchbar.js"; -import CisMenu from "../components/Cis/Menu.js"; -import PluginsPhrasen from "../plugins/Phrasen.js"; -import Theme from "../plugins/Theme.js"; +import FhcDashboard from '../components/Dashboard/Dashboard.js'; +import PluginsPhrasen from '../plugins/Phrasen.js'; +import Theme from '../plugins/Theme.js'; +import contrast from '../directives/contrast.js'; +import {setScrollbarWidth} from "../helpers/CssVarCalcHelpers.js"; +import LvPlan from "../components/Cis/LvPlan/Lehrveranstaltung.js"; +import MyLvPlan from "../components/Cis/LvPlan/Personal.js"; +import MylvStudent from "../components/Cis/Mylv/Student.js"; +import Profil from "../components/Cis/Profil/Profil.js"; +import Raumsuche from "../components/Cis/Raumsuche/Raumsuche.js"; +import CmsNews from "../components/Cis/Cms/News.js"; +import CmsContent from "../components/Cis/Cms/Content.js"; +import Info from "../components/Cis/Mylv/Semester/Studiengang/Lv/Info.js"; +import RoomInformation, {DEFAULT_MODE_RAUMINFO} from "../components/Cis/Mylv/RoomInformation.js"; +import AbgabetoolStudent from "../components/Cis/Abgabetool/AbgabetoolStudent.js"; +import AbgabetoolMitarbeiter from "../components/Cis/Abgabetool/AbgabetoolMitarbeiter.js"; +import AbgabetoolAssistenz from "../components/Cis/Abgabetool/AbgabetoolAssistenz.js"; +import DeadlineOverview from "../components/Cis/Abgabetool/DeadlineOverview.js"; +import Studium from "../components/Cis/Studium/Studium.js"; +import StgOrgLvPlan from "../components/Cis/LvPlan/StgOrg.js"; +import OtherLvPlan from "../components/Cis/LvPlan/OtherLvPlan.js"; -import ApiSearchbar from "../api/factory/searchbar.js"; -import ApiLvPlan from "../api/factory/lvPlan.js"; +import ApiRouteInfo from '../api/factory/routeinfo.js'; +import {capitalize} from "../helpers/StringHelpers.js"; + +const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router; + +const router = VueRouter.createRouter({ + history: VueRouter.createWebHistory(`/${ciPath}`), + routes: [ + { + path: `/Cis/Studium`, + name: 'Studium', + component: Studium, + props: true + }, + { + path: `/Cis/Profil/View/:uid`, + name: 'ProfilView', + component: Profil, + props: true + }, + { + path: `/Cis/Profil`, + name: 'Profil', + component: Profil, + props: true + }, + { + path: `/Cis/Abgabetool/Student/:student_uid_prop?`, + name: 'AbgabetoolStudent', + component: AbgabetoolStudent, + props: true + }, + { + path: `/Cis/Abgabetool/Mitarbeiter`, + name: 'AbgabetoolMitarbeiter', + component: AbgabetoolMitarbeiter, + props: true + }, + { + path: `/Cis/Abgabetool/Assistenz/:stg_kz_prop?`, + name: 'AbgabetoolAssistenz', + component: AbgabetoolAssistenz, + props: true + }, + { + path: `/Cis/Abgabetool/Deadlines/:person_uid_prop?`, + name: 'DeadlineOverview', + component: DeadlineOverview, + props: true + }, + { + path: `/Cis/Raumsuche`, + name: 'Raumsuche', + component: Raumsuche, + props: true + }, + // Redirect old links to new format + { + path: "/CisVue/Cms/getRoomInformation/:ort_kurzbz", + name: "RoomInformationOld", + component: RoomInformation, + redirect: (to) => { + return { // redirect to longer Rauminfo url and map params + name: "RoomInformation", + params: { // in this case always populate other params since they are not optional + ort_kurzbz: to.params.ort_kurzbz, + mode: DEFAULT_MODE_RAUMINFO, + focus_date: new Date().toISOString().split("T")[0] + }, + }; + }, + }, + { + path: `/CisVue/Cms/getRoomInformation/:mode/:focus_date/:ort_kurzbz`, + name: 'RoomInformation', + component: RoomInformation, + props: (route) => { // validate and set mode/focus date if for some reason missing + const validModes = ["Month", "Week", "Day"]; + + // check mode string + const mode = route.params.mode && + validModes.includes(route.params.mode.charAt(0).toUpperCase() + route.params.mode.slice(1).toLowerCase()) + ? route.params.mode.charAt(0).toUpperCase() + route.params.mode.slice(1).toLowerCase() + : DEFAULT_MODE_RAUMINFO; + + // default to today date if not provided + const d = new Date(route.params.focus_date) + const focus_date = !isNaN(d) ? route.params.focus_date : new Date().toISOString().split("T")[0]; + + // for consistency reasons format the props into one object but actually use a new name to we dont collide with + // existing viewData declaration written from codeigniter 3 into routerview tag + return { + propsViewData: { + mode, + focus_date, + ort_kurzbz: route.params.ort_kurzbz + } + }; + }, + beforeEnter: (to, from, next) => { + // missing mode or focus_date -> set defaults + if (!to.params.mode || !to.params.focus_date) { + next({ + name: "RoomInformation", + params: { + mode: to.params.mode || DEFAULT_MODE_RAUMINFO, + focus_date: to.params.focus_date || new Date().toISOString().split("T")[0], + ort_kurzbz: route.params.ort_kurzbz + } + }); + } else { + next(); + } + } + }, + { + path: `/CisVue/Cms/Content/:content_id`, + name: 'Content', + component: CmsContent, + props: true + }, + { + path: `/CisVue/Cms/News`, + name: 'News', + component: CmsNews, + props: true + }, + { + path: `/Cis/MyLv/:studiensemester?`, + name: 'MyLv', + component: MylvStudent, + props: true, + }, + { + path: `/Cis/MyLv/Info/:studien_semester/:lehrveranstaltung_id`, + name: 'LvInfo', + component: Info, + props: true + }, + // Redirect old links to new format + { + // only trigger on first param being numeric to avoid paths like "LvPlan/Month" entering here + path: "/Cis/LvPlan/:lv_id(\\d+)", + name: "LvPlanOld", + component: LvPlan, + redirect(to) { + const route = Vue.unref(router.currentRoute); + const { mode, focus_date } = route.params; // keep mode and focus_date if available + return { // redirect to longer LvPlan url and map params + name: "LvPlan", + params: { + mode, + focus_date, + lv_id: to.params.lv_id + }, + }; + }, + }, + { + path: `/Cis/LvPlan/:mode?/:focus_date?/:lv_id?`, + name: 'LvPlan', + component: LvPlan, + props(route) { + return { + propsViewData: route.params + }; + } + }, + { + path: `/Cis/MyLvPlan/:mode?/:focus_date?`, + name: 'MyLvPlan', + component: MyLvPlan, + props(route) { + return { + propsViewData: route.params + }; + } + }, + { + path: `/Cis/StgOrgLvPlan/:mode?/:focus_date?/:stgkz?/:sem?/:verband?/:gruppe?`, + name: 'StgOrgLvPlan', + component: StgOrgLvPlan, + props(route) { + return { + propsViewData: route.params + }; + } + }, + { + path: `/Cis/OtherLvPlan/:otherUid/:mode?/:focus_date?`, + name: "OtherLvPlan", + component: OtherLvPlan, + props(route) { + return { + propsViewData: route.params + }; + } + }, + { + path: `/Cis4`, + name: 'Cis4', + component: FhcDashboard, + props: {dashboard: 'CIS'}, + }, + { + path: `/`, + name: 'FhcDashboard', + component: FhcDashboard, + props: {dashboard: 'CIS'}, + }, + { + path: '/:pathMatch(.*)*', + name: 'Fallback', + component: FhcDashboard, + props: {dashboard: 'CIS'}, + redirect: () => { + return { + name: "Cis4", + params: { + dashboard: 'CIS' + }, + }; + }, + }, + ] +}) const app = Vue.createApp({ - name: "CisApp", - components: { - FhcSearchbar, - CisMenu, + name: 'CisApp', + data: () => ({ + appSideMenuEntries: {}, + renderers: null, + }), + components: {}, + computed: { + isMobile() { + const smallScreen = window.matchMedia("(max-width: 767px)").matches; + const touchCapable = ("ontouchstart" in window) || navigator.maxTouchPoints > 0; + return smallScreen;// && touchCapable; + } }, - data: function () { - return { - searchbaroptions: { - origin: "cis", - cssclass: "", - calcheightonly: true, - types: { - employee: Vue.computed(() => - this.$p.t("search/type_employee"), - ), - student: Vue.computed(() => - this.$p.t("search/type_student"), - ), - room: Vue.computed(() => this.$p.t("search/type_room")), - organisationunit: Vue.computed(() => - this.$p.t("search/type_organisationunit"), - ), - cms: Vue.computed(() => this.$p.t("search/type_cms")), - dms: Vue.computed(() => this.$p.t("search/type_dms")), - }, - actions: { - employee: { - defaultaction: { - type: "link", - action: function (data) { - return ( - FHC_JS_DATA_STORAGE_OBJECT.app_root + - FHC_JS_DATA_STORAGE_OBJECT.ci_router + - "/Cis/Profil/View/" + - data.uid - ); - }, - }, - childactions: [], - }, - student: { - defaultaction: { - type: "link", - action: function (data) { - return ( - FHC_JS_DATA_STORAGE_OBJECT.app_root + - FHC_JS_DATA_STORAGE_OBJECT.ci_router + - "/Cis/Profil/View/" + - data.uid - ); - }, - }, - childactions: [], - }, - room: { - defaultaction: { - type: "link", - renderif: function (data) { - if (data.content_id === null) { - return false; - } - return true; - }, - action: function (data) { - const link = - FHC_JS_DATA_STORAGE_OBJECT.app_root + - FHC_JS_DATA_STORAGE_OBJECT.ci_router + - "/CisVue/Cms/content/" + - data.content_id; - return link; - }, - }, - childactions: [ - { - label: "LV-Plan", - icon: "fas fa-bookmark", - type: "link", - action: function (data) { - const link = - FHC_JS_DATA_STORAGE_OBJECT.app_root + - FHC_JS_DATA_STORAGE_OBJECT.ci_router + - "/CisVue/Cms/getRoomInformation/" + - data.ort_kurzbz; - return link; - }, - }, - { - label: "Rauminformation", - icon: "fas fa-info-circle", - type: "link", - renderif: function (data) { - if (data.content_id === null) { - return false; - } - return true; - }, - action: function (data) { - const link = - FHC_JS_DATA_STORAGE_OBJECT.app_root + - FHC_JS_DATA_STORAGE_OBJECT.ci_router + - "/CisVue/Cms/content/" + - data.content_id; - return link; - }, - }, - ], - }, - organisationunit: { - defaultaction: { - type: "link", - renderif: function (data) { - if (data.mailgroup) { - return true; - } - return false; - }, - action: function (data) { - const link = "mailto:" + data.mailgroup; - return link; - }, - }, - childactions: [], - }, - cms: { - defaultaction: { - type: "link", - action: function (data) { - const link = - FHC_JS_DATA_STORAGE_OBJECT.app_root + - FHC_JS_DATA_STORAGE_OBJECT.ci_router + - "/CisVue/Cms/content/" + - data.content_id; - return link; - }, - }, - childactions: [], - }, - dms: { - defaultaction: { - type: "link", - action: function (data) { - const link = - FHC_JS_DATA_STORAGE_OBJECT.app_root + - "cms/dms.php?id=" + - data.dms_id; - return link; - }, - }, - childactions: [], - }, - }, - }, - }; + provide() { + return { // provide injectable & watchable language property + language: Vue.computed(() => this.$p.user_language), + isMobile: this.isMobile + } }, methods: { - searchfunction: function (searchsettings) { - return this.$api.call(ApiSearchbar.searchCis(searchsettings)); + isInternalRoute(href) { + const internalBase = window.location.origin + return href.startsWith(internalBase); }, - }, - async mounted() { - const openOtherLvPlanAction = { - label: Vue.computed(() => this.$p.t("lehre/stundenplan")), - icon: "fas fa-calendar-days", - type: "link", - action: function (data) { - const uid = JSON.parse(data.data).uid; - const link = - FHC_JS_DATA_STORAGE_OBJECT.app_root + - FHC_JS_DATA_STORAGE_OBJECT.ci_router + - "/Cis/OtherLvPlan/" + - uid; - return link; - }, - }; - let checkPermissionOtherLvPlanResult = await this.$api.call( - ApiLvPlan.checkPermissionOtherLvPlan(), - ); - if ( - checkPermissionOtherLvPlanResult.meta.status === "success" && - checkPermissionOtherLvPlanResult.data - ) { - this.searchbaroptions.actions.employee.childactions.push( - openOtherLvPlanAction, - ); - this.searchbaroptions.actions.student.childactions.push( - openOtherLvPlanAction, - ); + handleClick(event) { + const target = event.target.closest('a'); + + if(target?.id == 'skiplink') return + if (target && this.isInternalRoute(target.href)) { + const url = new URL(target.href) + + const path = url.pathname + const base = this.$router.options.history.base + const route = path.replace(base, '') || '/' + + // let click event propagate normally if we dont route internally + const res = this.$router.resolve(route) + if(!res?.matched?.length || res.name === 'Fallback') return + + event.preventDefault(); // Prevent browser navigation + + if(this.isMobile) { // toggle the menu + const navMain = document.getElementById('nav-main'); + // fix unwanted toggle from off to on for some links on mobile + if(navMain.classList.contains('show')){ + document.getElementById('nav-main-btn').click(); + } + } + + this.$router.push(route); + + } } }, + mounted() { + document.addEventListener('click', this.handleClick); + + }, + beforeUnmount() { + document.removeEventListener('click', this.handleClick); + }, }); +// kind of a bandaid for bad css on some pages to avoid horizontal scroll +setScrollbarWidth(); +app.config.globalProperties.$capitalize = capitalize; + +FhcApps.router.makeExtendable(router); FhcApps.makeExtendable(app); +app.use(router); app.use(primevue.config.default, { zIndex: { overlay: 9000, - tooltip: 8000, - }, -}); + tooltip: 8000 + } +}) +app.directive('tooltip', primevue.tooltip); app.use(PluginsPhrasen); app.use(Theme); -app.mount("#cis-header"); +app.directive('contrast', contrast); +app.mount('#fhccontent'); + +router.afterEach((to, from, failure) => { + app.config.globalProperties.$api.call(ApiRouteInfo.info('cis4', to.fullPath)); +}); diff --git a/public/js/apps/Cis/Menu.js b/public/js/apps/Cis/Menu.js new file mode 100644 index 000000000..5c03ac128 --- /dev/null +++ b/public/js/apps/Cis/Menu.js @@ -0,0 +1,156 @@ +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"; + +const app = Vue.createApp({ + name: 'CisMenuApp', + components: { + FhcSearchbar, + CisMenu + }, + data: function() { + return { + searchbaroptions: { + origin: "cis", + cssclass: "", + calcheightonly: true, + types: { + employee: Vue.computed(() => this.$p.t("search/type_employee")), + student: Vue.computed(() => this.$p.t("search/type_student")), + room: Vue.computed(() => this.$p.t("search/type_room")), + organisationunit: Vue.computed(() => this.$p.t("search/type_organisationunit")), + cms: Vue.computed(() => this.$p.t("search/type_cms")), + dms: Vue.computed(() => this.$p.t("search/type_dms")) + }, + actions: { + employee: { + defaultaction: { + type: "link", + action: function(data) { + return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router+ + "/Cis/Profil/View/"+data.uid; + } + }, + childactions: [] + }, + student: { + defaultaction: { + type: "link", + action: function (data) { + return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + + "/Cis/Profil/View/" + data.uid; + + } + }, + childactions: [] + }, + room: { + defaultaction: { + type: "link", + renderif: function(data) { + if(data.content_id === null){ + return false; + } + return true; + }, + action: function(data) { + const link= FHC_JS_DATA_STORAGE_OBJECT.app_root + + FHC_JS_DATA_STORAGE_OBJECT.ci_router + + '/CisVue/Cms/content/' + data.content_id; + return link; + } + }, + childactions: [ + { + label: "LV-Plan", + icon: "fas fa-bookmark", + type: "link", + action: function(data) { + const link = FHC_JS_DATA_STORAGE_OBJECT.app_root + + FHC_JS_DATA_STORAGE_OBJECT.ci_router + + '/CisVue/Cms/getRoomInformation/' + data.ort_kurzbz; + return link; + } + }, + { + label: "Rauminformation", + icon: "fas fa-info-circle", + type: "link", + renderif: function(data) { + if(data.content_id === null){ + return false; + } + return true; + }, + action: function(data) { + const link= FHC_JS_DATA_STORAGE_OBJECT.app_root + + FHC_JS_DATA_STORAGE_OBJECT.ci_router + + '/CisVue/Cms/content/' + data.content_id; + return link; + } + }, + ] + }, + organisationunit: { + defaultaction: { + type: "link", + renderif: function(data) { + if(data.mailgroup) { + return true; + } + return false; + }, + action: function(data) { + const link = 'mailto:' + data.mailgroup; + return link; + } + }, + childactions: [] + }, + cms: { + defaultaction: { + type: "link", + action: function (data) { + const link = FHC_JS_DATA_STORAGE_OBJECT.app_root + + FHC_JS_DATA_STORAGE_OBJECT.ci_router + + '/CisVue/Cms/content/' + data.content_id; + return link; + } + }, + childactions: [] + }, + dms: { + defaultaction: { + type: "link", + action: function (data) { + const link = FHC_JS_DATA_STORAGE_OBJECT.app_root + + 'cms/dms.php?id=' + data.dms_id; + return link; + } + }, + childactions: [] + } + } + } + }; + }, + methods: { + searchfunction: function(searchsettings) { + return this.$api.call(ApiSearchbar.searchCis(searchsettings)); + } + } +}); + +FhcApps.makeExtendable(app); + +app.use(primevue.config.default, { + zIndex: { + overlay: 9000, + tooltip: 8000 + } +}) +app.use(PluginsPhrasen); +app.use(Theme); +app.mount('#cis-header'); diff --git a/public/js/apps/Dashboard/Admin.js b/public/js/apps/Dashboard/Admin.js index 32909a50a..685e1bc9c 100644 --- a/public/js/apps/Dashboard/Admin.js +++ b/public/js/apps/Dashboard/Admin.js @@ -3,13 +3,10 @@ import DashboardAdmin from '../../components/Dashboard/Admin.js'; import PluginsPhrasen from '../../plugins/Phrasen.js'; -import ApiRenderers from '../../api/factory/renderers.js'; - const app = Vue.createApp({ name: 'DashboardAdminApp', data: () => ({ - appSideMenuEntries: {}, - renderers: null + appSideMenuEntries: {} }), components: { CoreNavigationCmpt, @@ -17,49 +14,8 @@ const app = Vue.createApp({ }, provide() { return { - // TODO(chris): move those two into the components that need it - renderers: Vue.computed(() => this.renderers), timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone }; - }, - created() { - this.$api - .call(ApiRenderers.loadRenderers()) - .then(res => { - for (let rendertype of Object.keys(res.data)) { - let modalTitle = null; - let modalContent = null; - let calendarEvent = null; - if (res.data[rendertype].modalTitle) - modalTitle = Vue.markRaw(Vue.defineAsyncComponent(() => import(res.data[rendertype].modalTitle))); - if (res.data[rendertype].modalContent) - modalContent = Vue.markRaw(Vue.defineAsyncComponent(() => import(res.data[rendertype].modalContent))); - if (res.data[rendertype].calendarEvent) - calendarEvent = Vue.markRaw(Vue.defineAsyncComponent(() => import(res.data[rendertype].calendarEvent))); - - if (res.data[rendertype].calendarEventStyles) { - var head = document.head; - if (!head.querySelector(`link[href="${res.data[rendertype].calendarEventStyles}"]`)) { - var link = document.createElement("link"); - link.type = "text/css"; - link.rel = "stylesheet"; - link.href = res.data[rendertype].calendarEventStyles; - head.appendChild(link); - } - } - - if (this.renderers === null) { - this.renderers = {}; - } - if (!this.renderers[rendertype]) { - this.renderers[rendertype] = {} - } - this.renderers[rendertype].modalTitle = modalTitle; - this.renderers[rendertype].modalContent = modalContent; - this.renderers[rendertype].calendarEvent = calendarEvent; - } - }) - .catch(this.$fhcAlert.handleSystemErrors); } }); app.use(PluginsPhrasen); diff --git a/public/js/apps/Dashboard/Fhc.js b/public/js/apps/Dashboard/Fhc.js deleted file mode 100644 index b2b7752df..000000000 --- a/public/js/apps/Dashboard/Fhc.js +++ /dev/null @@ -1,377 +0,0 @@ -import FhcDashboard from '../../components/Dashboard/Dashboard.js'; -import PluginsPhrasen from '../../plugins/Phrasen.js'; -import Theme from '../../plugins/Theme.js'; -import contrast from '../../directives/contrast.js'; -import {setScrollbarWidth} from "../../helpers/CssVarCalcHelpers.js"; -import LvPlan from "../../components/Cis/LvPlan/Lehrveranstaltung.js"; -import MyLvPlan from "../../components/Cis/LvPlan/Personal.js"; -import MylvStudent from "../../components/Cis/Mylv/Student.js"; -import Profil from "../../components/Cis/Profil/Profil.js"; -import Raumsuche from "../../components/Cis/Raumsuche/Raumsuche.js"; -import CmsNews from "../../components/Cis/Cms/News.js"; -import CmsContent from "../../components/Cis/Cms/Content.js"; -import Info from "../../components/Cis/Mylv/Semester/Studiengang/Lv/Info.js"; -import RoomInformation, {DEFAULT_MODE_RAUMINFO} from "../../components/Cis/Mylv/RoomInformation.js"; -import AbgabetoolStudent from "../../components/Cis/Abgabetool/AbgabetoolStudent.js"; -import AbgabetoolMitarbeiter from "../../components/Cis/Abgabetool/AbgabetoolMitarbeiter.js"; -import AbgabetoolAssistenz from "../../components/Cis/Abgabetool/AbgabetoolAssistenz.js"; -import DeadlineOverview from "../../components/Cis/Abgabetool/DeadlineOverview.js"; -import Studium from "../../components/Cis/Studium/Studium.js"; -import StgOrgLvPlan from "../../components/Cis/LvPlan/StgOrg.js"; -import OtherLvPlan from "../../components/Cis/LvPlan/OtherLvPlan.js"; - -import ApiRenderers from '../../api/factory/renderers.js'; -import ApiRouteInfo from '../../api/factory/routeinfo.js'; -import {capitalize} from "../../helpers/StringHelpers.js"; - -const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router; - -const router = VueRouter.createRouter({ - history: VueRouter.createWebHistory(`/${ciPath}`), - routes: [ - { - path: `/Cis/Studium`, - name: 'Studium', - component: Studium, - props: true - }, - { - path: `/Cis/Profil/View/:uid`, - name: 'ProfilView', - component: Profil, - props: true - }, - { - path: `/Cis/Profil`, - name: 'Profil', - component: Profil, - props: true - }, - { - path: `/Cis/Abgabetool/Student/:student_uid_prop?`, - name: 'AbgabetoolStudent', - component: AbgabetoolStudent, - props: true - }, - { - path: `/Cis/Abgabetool/Mitarbeiter`, - name: 'AbgabetoolMitarbeiter', - component: AbgabetoolMitarbeiter, - props: true - }, - { - path: `/Cis/Abgabetool/Assistenz/:stg_kz_prop?`, - name: 'AbgabetoolAssistenz', - component: AbgabetoolAssistenz, - props: true - }, - { - path: `/Cis/Abgabetool/Deadlines/:person_uid_prop?`, - name: 'DeadlineOverview', - component: DeadlineOverview, - props: true - }, - { - path: `/Cis/Raumsuche`, - name: 'Raumsuche', - component: Raumsuche, - props: true - }, - // Redirect old links to new format - { - path: "/CisVue/Cms/getRoomInformation/:ort_kurzbz", - name: "RoomInformationOld", - component: RoomInformation, - redirect: (to) => { - return { // redirect to longer Rauminfo url and map params - name: "RoomInformation", - params: { // in this case always populate other params since they are not optional - ort_kurzbz: to.params.ort_kurzbz, - mode: DEFAULT_MODE_RAUMINFO, - focus_date: new Date().toISOString().split("T")[0] - }, - }; - }, - }, - { - path: `/CisVue/Cms/getRoomInformation/:mode/:focus_date/:ort_kurzbz`, - name: 'RoomInformation', - component: RoomInformation, - props: (route) => { // validate and set mode/focus date if for some reason missing - const validModes = ["Month", "Week", "Day"]; - - // check mode string - const mode = route.params.mode && - validModes.includes(route.params.mode.charAt(0).toUpperCase() + route.params.mode.slice(1).toLowerCase()) - ? route.params.mode.charAt(0).toUpperCase() + route.params.mode.slice(1).toLowerCase() - : DEFAULT_MODE_RAUMINFO; - - // default to today date if not provided - const d = new Date(route.params.focus_date) - const focus_date = !isNaN(d) ? route.params.focus_date : new Date().toISOString().split("T")[0]; - - // for consistency reasons format the props into one object but actually use a new name to we dont collide with - // existing viewData declaration written from codeigniter 3 into routerview tag - return { - propsViewData: { - mode, - focus_date, - ort_kurzbz: route.params.ort_kurzbz - } - }; - }, - beforeEnter: (to, from, next) => { - // missing mode or focus_date -> set defaults - if (!to.params.mode || !to.params.focus_date) { - next({ - name: "RoomInformation", - params: { - mode: to.params.mode || DEFAULT_MODE_RAUMINFO, - focus_date: to.params.focus_date || new Date().toISOString().split("T")[0], - ort_kurzbz: route.params.ort_kurzbz - } - }); - } else { - next(); - } - } - }, - { - path: `/CisVue/Cms/Content/:content_id`, - name: 'Content', - component: CmsContent, - props: true - }, - { - path: `/CisVue/Cms/News`, - name: 'News', - component: CmsNews, - props: true - }, - { - path: `/Cis/MyLv/:studiensemester?`, - name: 'MyLv', - component: MylvStudent, - props: true, - }, - { - path: `/Cis/MyLv/Info/:studien_semester/:lehrveranstaltung_id`, - name: 'LvInfo', - component: Info, - props: true - }, - // Redirect old links to new format - { - // only trigger on first param being numeric to avoid paths like "LvPlan/Month" entering here - path: "/Cis/LvPlan/:lv_id(\\d+)", - name: "LvPlanOld", - component: LvPlan, - redirect(to) { - const route = Vue.unref(router.currentRoute); - const { mode, focus_date } = route.params; // keep mode and focus_date if available - return { // redirect to longer LvPlan url and map params - name: "LvPlan", - params: { - mode, - focus_date, - lv_id: to.params.lv_id - }, - }; - }, - }, - { - path: `/Cis/LvPlan/:mode?/:focus_date?/:lv_id?`, - name: 'LvPlan', - component: LvPlan, - props(route) { - return { - propsViewData: route.params - }; - } - }, - { - path: `/Cis/MyLvPlan/:mode?/:focus_date?`, - name: 'MyLvPlan', - component: MyLvPlan, - props(route) { - return { - propsViewData: route.params - }; - } - }, - { - path: `/Cis/StgOrgLvPlan/:mode?/:focus_date?/:stgkz?/:sem?/:verband?/:gruppe?`, - name: 'StgOrgLvPlan', - component: StgOrgLvPlan, - props(route) { - return { - propsViewData: route.params - }; - } - }, - { - path: `/Cis/OtherLvPlan/:otherUid/:mode?/:focus_date?`, - name: "OtherLvPlan", - component: OtherLvPlan, - props(route) { - return { - propsViewData: route.params - }; - } - }, - { - path: `/Cis4`, - name: 'Cis4', - component: FhcDashboard, - props: {dashboard: 'CIS'}, - }, - { - path: `/`, - name: 'FhcDashboard', - component: FhcDashboard, - props: {dashboard: 'CIS'}, - }, - { - path: '/:pathMatch(.*)*', - name: 'Fallback', - component: FhcDashboard, - props: {dashboard: 'CIS'}, - redirect: () => { - return { - name: "Cis4", - params: { - dashboard: 'CIS' - }, - }; - }, - }, - ] -}) - -const app = Vue.createApp({ - name: 'FhcApp', - data: () => ({ - appSideMenuEntries: {}, - renderers: null, - }), - components: {}, - computed: { - isMobile() { - const smallScreen = window.matchMedia("(max-width: 767px)").matches; - const touchCapable = ("ontouchstart" in window) || navigator.maxTouchPoints > 0; - return smallScreen;// && touchCapable; - } - }, - provide() { - return { // provide injectable & watchable language property - language: Vue.computed(() => this.$p.user_language), - renderers: Vue.computed(() => this.renderers), - isMobile: this.isMobile - } - }, - methods: { - isInternalRoute(href) { - const internalBase = window.location.origin - return href.startsWith(internalBase); - }, - handleClick(event) { - const target = event.target.closest('a'); - - if(target?.id == 'skiplink') return - if (target && this.isInternalRoute(target.href)) { - const url = new URL(target.href) - - const path = url.pathname - const base = this.$router.options.history.base - const route = path.replace(base, '') || '/' - - // let click event propagate normally if we dont route internally - const res = this.$router.resolve(route) - if(!res?.matched?.length || res.name === 'Fallback') return - - event.preventDefault(); // Prevent browser navigation - - if(this.isMobile) { // toggle the menu - const navMain = document.getElementById('nav-main'); - // fix unwanted toggle from off to on for some links on mobile - if(navMain.classList.contains('show')){ - document.getElementById('nav-main-btn').click(); - } - } - - this.$router.push(route); - - } - } - }, - async created(){ - await this.$api - .call(ApiRenderers.loadRenderers()) - .then(res => res.data) - .then(data => { - for (let rendertype of Object.keys(data)) { - let modalTitle = null; - let modalContent = null; - let calendarEvent = null; - if (data[rendertype].modalTitle) - modalTitle = Vue.markRaw(Vue.defineAsyncComponent(() => import(data[rendertype].modalTitle))); - if (data[rendertype].modalContent) - modalContent = Vue.markRaw(Vue.defineAsyncComponent(() => import(data[rendertype].modalContent))); - if (data[rendertype].calendarEvent) - calendarEvent = Vue.markRaw(Vue.defineAsyncComponent(() => import(data[rendertype].calendarEvent))); - - if (data[rendertype].calendarEventStyles){ - var head = document.head; - if(!head.querySelector(`link[href="${data[rendertype].calendarEventStyles}"]`)){ - var link = document.createElement("link"); - link.type = "text/css"; - link.rel = "stylesheet"; - link.href = data[rendertype].calendarEventStyles; - head.appendChild(link); - } - } - - if(this.renderers === null) { - this.renderers = {}; - } - if (!this.renderers[rendertype]) { - this.renderers[rendertype] = {} - } - this.renderers[rendertype].modalTitle = modalTitle; - this.renderers[rendertype].modalContent = modalContent; - this.renderers[rendertype].calendarEvent = calendarEvent; - } - }); - }, - mounted() { - document.addEventListener('click', this.handleClick); - - }, - beforeUnmount() { - document.removeEventListener('click', this.handleClick); - }, -}); - -// kind of a bandaid for bad css on some pages to avoid horizontal scroll -setScrollbarWidth(); -app.config.globalProperties.$capitalize = capitalize; - -FhcApps.router.makeExtendable(router); -FhcApps.makeExtendable(app); - -app.use(router); -app.use(primevue.config.default, { - zIndex: { - overlay: 9000, - tooltip: 8000 - } -}) -app.directive('tooltip', primevue.tooltip); -app.use(PluginsPhrasen); -app.use(Theme); -app.directive('contrast', contrast); -app.mount('#fhccontent'); - -router.afterEach((to, from, failure) => { - app.config.globalProperties.$api.call(ApiRouteInfo.info('cis4', to.fullPath)); -}); \ No newline at end of file diff --git a/public/js/apps/DashboardAdmin_DEPR.js b/public/js/apps/DashboardAdmin_DEPR.js deleted file mode 100644 index 05b438798..000000000 --- a/public/js/apps/DashboardAdmin_DEPR.js +++ /dev/null @@ -1,16 +0,0 @@ -import {CoreNavigationCmpt} from '../components/navigation/Navigation.js'; -import DashboardAdmin from '../components/Dashboard/Admin.js'; -import Phrases from "../plugin/Phrasen.js" - -Vue.createApp({ - name: 'DashboardAdminApp', - data: () => ({ - appSideMenuEntries: {} - }), - components: { - CoreNavigationCmpt, - DashboardAdmin - }, - mounted() { - } -}).use(Phrases).mount('#main'); \ No newline at end of file diff --git a/public/js/components/Calendar/Base/DragAndDrop.js b/public/js/components/Calendar/Base/DragAndDrop.js index 631a792a8..fb6cb1848 100644 --- a/public/js/components/Calendar/Base/DragAndDrop.js +++ b/public/js/components/Calendar/Base/DragAndDrop.js @@ -20,7 +20,8 @@ export default { }, inject: { mode: "mode", - dropableEvents: "dropableEvents" + dropableEvents: "dropableEvents", + timezone: "timezone" }, props: { events: Array, diff --git a/public/js/components/Calendar/LvPlan.js b/public/js/components/Calendar/LvPlan.js index 6b4257853..60de7d171 100644 --- a/public/js/components/Calendar/LvPlan.js +++ b/public/js/components/Calendar/LvPlan.js @@ -3,6 +3,7 @@ import FhcCalendar from "./Base.js"; import ApiLvPlan from '../../api/factory/lvPlan.js'; import { useEventLoader } from '../../composables/EventLoader.js'; +import { useRenderers } from '../../composables/Renderers.js'; import ModeDay from './Mode/Day.js'; import ModeWeek from './Mode/Week.js'; @@ -13,14 +14,7 @@ export default { components: { FhcCalendar }, - inject: [ - "renderers" - ], props: { - timezone: { - type: String, - required: true - }, date: { type: [Date, String, Number, luxon.DateTime], default: luxon.DateTime.local() @@ -41,6 +35,7 @@ export default { ], data() { return { + timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone, modes: { day: Vue.markRaw(ModeDay), week: Vue.markRaw(ModeWeek), @@ -102,11 +97,14 @@ export default { context.emit('update:lv', newValue); }); + const { renderers } = useRenderers(); + return { rangeInterval, events, lv, - reset + reset, + renderers }; }, created() { diff --git a/public/js/components/Calendar/Widget.js b/public/js/components/Calendar/Widget.js index f9d641d4e..54109268e 100644 --- a/public/js/components/Calendar/Widget.js +++ b/public/js/components/Calendar/Widget.js @@ -1,6 +1,7 @@ import FhcCalendar from "./Base.js"; import { useEventLoader } from '../../composables/EventLoader.js'; +import { useRenderers } from '../../composables/Renderers.js'; import ModeList from '../Calendar/Mode/List.js'; @@ -9,22 +10,17 @@ export default { components: { FhcCalendar }, - inject: [ - "renderers" - ], props: { - timezone: { - type: String, - required: true - }, getPromiseFunc: { type: Function, required: true } }, data() { + const timezone = FHC_JS_DATA_STORAGE_OBJECT.timezone; return { - now: luxon.DateTime.now().setZone(this.timezone), + timezone, + now: luxon.DateTime.now().setZone(timezone), modes: { list: Vue.markRaw(ModeList) }, @@ -59,10 +55,12 @@ export default { const rangeInterval = Vue.ref(null); const { events } = useEventLoader(rangeInterval, props.getPromiseFunc); + const { renderers } = useRenderers(); return { rangeInterval, - events + events, + renderers }; }, template: /* html */` diff --git a/public/js/components/Cis/LvPlan/Lehrveranstaltung.js b/public/js/components/Cis/LvPlan/Lehrveranstaltung.js index 4364a5df4..1e74ad93a 100644 --- a/public/js/components/Cis/LvPlan/Lehrveranstaltung.js +++ b/public/js/components/Cis/LvPlan/Lehrveranstaltung.js @@ -22,7 +22,7 @@ export default { computed:{ currentDay() { if (!this.propsViewData?.focus_date || isNaN(new Date(this.propsViewData?.focus_date))) - return luxon.DateTime.now().setZone(this.viewData.timezone).toISODate(); + return luxon.DateTime.now().setZone(FHC_JS_DATA_STORAGE_OBJECT.timezone).toISODate(); return this.propsViewData?.focus_date; }, currentMode() { @@ -95,7 +95,6 @@ export default { this.dashboards[this.current] ? this.dashboards[this.current].widgetSetup : null) + widgetsSetup: Vue.computed(() => this.dashboard ? this.dashboard.widgetSetup : null) }; }, data() { @@ -34,33 +33,32 @@ export default { methods: { dashboardAdd() { let _name = ''; - BsPrompt.popup('New Dashboard name').then( - name => { - _name = name; + BsPrompt + .popup('New Dashboard name') + .then(dashboard_kurzbz => { const params = { - dashboard_kurzbz: name + dashboard_kurzbz }; return this.$api .call(ApiDashboardBoard.add(params)) - .then(response =>{ + .then(response => { this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave')); let newDashboard = { dashboard_id: response.data, - dashboard_kurzbz: _name, + dashboard_kurzbz, beschreibung: '' }; this.dashboards.push(newDashboard); this.current = newDashboard.dashboard_id; }) .catch(this.$fhcAlert.handleSystemError); - }); + }); }, dashboardUpdate(dashboard) { - return this.$api + this.$api .call(ApiDashboardBoard.update(dashboard)) - .then(response =>{ - + .then(response => { this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave')); let old = this.dashboards.find(el => el.dashboard_id == dashboard.dashboard_id); @@ -70,73 +68,122 @@ export default { .catch(this.$fhcAlert.handleSystemError); }, dashboardDelete(dashboard_id) { - return this.$api + this.$api .call(ApiDashboardBoard.delete(dashboard_id)) .then(response => { this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successDelete')); - }) - .catch(this.$fhcAlert.handleSystemError) - .finally(() => { this.current = -1; this.dashboards = this.dashboards.filter(el => el.dashboard_id != dashboard_id); - }); + }) + .catch(this.$fhcAlert.handleSystemError); }, assignWidgets(widgets) { this.widgets = widgets; - /*while (this.widgets.length) - this.widgets.pop(); - for (var i in widgets) - this.widgets.push(widgets[i]);*/ } }, created() { this.$api .call(ApiDashboardBoard.list()) .then(result => { - this.dashboards = result.data.retval; - for (const dashboard of this.dashboards) { - this.$api - .call(ApiDashboardWidget.list(dashboard.dashboard_id)) - .then(res => { - dashboard.widgetSetup = res.data; - }) - .catch(this.$fhcAlert.handleSystemError); - } + this.dashboards = result.data; }) .catch(this.$fhcAlert.handleSystemError); }, - template: `
- + template: /* html */` +
- - + - +
-
- +
+
-
- +
+
-
- +
+
diff --git a/public/js/components/Dashboard/Admin/Edit.js b/public/js/components/Dashboard/Admin/Edit.js index c40d91183..426dea7b0 100644 --- a/public/js/components/Dashboard/Admin/Edit.js +++ b/public/js/components/Dashboard/Admin/Edit.js @@ -1,15 +1,15 @@ import BsConfirm from '../../Bootstrap/Confirm.js'; export default { - emits: [ - "change", - "delete" - ], props: { dashboard_id: Number, dashboard_kurzbz: String, beschreibung: String }, + emits: [ + "change", + "delete" + ], data() { return { kurzbz: this.dashboard_kurzbz, @@ -18,22 +18,43 @@ export default { }, methods: { sendDelete() { - BsConfirm.popup(this.$p.t('ui', 'confirm_delete') + " " + this.$p.t('ui', 'deleteInfo')) - .then(() => this.$emit('delete', this.dashboard_id)).catch(); + BsConfirm + .popup(this.$p.t('ui', 'confirm_delete') + " " + this.$p.t('ui', 'deleteInfo')) + .then(() => this.$emit('delete', this.dashboard_id)) + .catch(); } }, - template: `
+ template: /* html */` +
- +
- +
- - + +
` } diff --git a/public/js/components/Dashboard/Admin/Presets.js b/public/js/components/Dashboard/Admin/Presets.js index ef1c06e00..63e8b99e0 100644 --- a/public/js/components/Dashboard/Admin/Presets.js +++ b/public/js/components/Dashboard/Admin/Presets.js @@ -12,18 +12,27 @@ export default { dashboard: String, widgets: Array }, - data: () => ({ - funktionen: {}, - sections: [], - tmpLoading: '' - }), + data() { + return { + funktionen: {}, + sections: [], + selectedFunktionen: [], + abortController: null + }; + }, computed: { pickerWidgets() { return this.widgets.filter(widget => widget.allowed); } }, + watch: { + dashboard() { + this.loadSections(); + this.loadFunktionen(); + } + }, methods: { - widgetAdd(section_name, widget) { + widgetAdd(widget, section_name) { this.$refs.widgetpicker.getWidget().then(widget_id => { widget.widget = widget_id; widget.id = 'loading_' + String((new Date()).valueOf()); @@ -64,22 +73,26 @@ export default { }) .catch(() => {}); }, - widgetUpdate(section_name, payload) { - payload = payload[section_name]; + widgetUpdate(payload, section_name) { for (var k in payload) { const section = this.sections.find(section => section.name == section_name); for (var wid in section.widgets) { if (section.widgets[wid].id == k) { payload[k] = ObjectUtils.mergeDeep(section.widgets[wid], payload[k]); // NOTE(chris): remove internal props - for (var prop of ['_x', '_y', '_w', '_h', 'index', 'id']) + for (var prop of ['_x', '_y', '_w', '_h', 'index', 'id', 'custom']) if (payload[k][prop]) delete payload[k][prop]; break; } } + if (payload[k].place) { + Object.values(payload[k].place).forEach(place => { + if (place.pinned === false) + delete place.pinned; + }); + } payload[k].widgetid = k; - delete payload[k].custom; } this.$api .call(Object.entries(payload).map(([key, widget]) => [ @@ -106,7 +119,7 @@ export default { }) .catch(this.$fhcAlert.handleSystemError); }, - widgetRemove(section_name, id) { + widgetRemove(id, section_name) { const params = { db: this.dashboard, funktion_kurzbz: section_name, @@ -122,21 +135,22 @@ export default { }) .catch(this.$fhcAlert.handleSystemError); }, - loadSections(evt) { - let funktionen = Array.from(evt.target.querySelectorAll("option:checked"),e=>e.value); - this.sections = []; - this.tmpLoading = funktionen.join('###'); - + loadSections() { const params = { db: this.dashboard, - funktionen + funktionen: this.selectedFunktionen }; + if (this.abortController) + this.abortController.abort(); + this.abortController = new AbortController(); + const signal = this.abortController.signal; + + this.sections = []; + return this.$api - .call(ApiDashboardPreset.getBatch(params)) + .call(ApiDashboardPreset.getBatch(params), { signal }) .then(result => { - if (this.tmpLoading !== funktionen.join('###')) - return; // NOTE(chris): prevent race condition for (var section in result.data) { let widgets = []; for (var wid in result.data[section]) { @@ -151,7 +165,6 @@ export default { } }) .catch(this.$fhcAlert.handleSystemError); - }, loadFunktionen() { this.$api @@ -165,17 +178,17 @@ export default { created() { this.loadFunktionen(); }, - watch: { - dashboard() { - // TODO(chris): this should be done without a watcher - this.loadSections({target:this.$refs.funktionenList}); - this.loadFunktionen(); - } - }, - template: `
+ template: /* html */` +
-
- +
- +
` } diff --git a/public/js/components/Dashboard/Admin/Widgets.js b/public/js/components/Dashboard/Admin/Widgets.js index a0c7b2139..ddf0517f9 100644 --- a/public/js/components/Dashboard/Admin/Widgets.js +++ b/public/js/components/Dashboard/Admin/Widgets.js @@ -1,14 +1,14 @@ import ApiDashboardWidget from "../../../api/factory/dashboard/widget.js"; export default { - emits: [ - "change", - "assignWidgets" - ], props: { dashboard_id: Number, widgets: Array }, + emits: [ + "change", + "assignWidgets" + ], methods: { sendChange(widget_id) { let allow = !this.widgets.find(el => el.widget_id == widget_id).allowed; @@ -29,11 +29,27 @@ export default { }) .catch(this.$fhcAlert.handleSystemError); }, - template: ` + template: /* html */`
-
- - +
+ +
` } diff --git a/public/js/components/Dashboard/Dashboard.js b/public/js/components/Dashboard/Dashboard.js index e92e34f29..64d4de71d 100644 --- a/public/js/components/Dashboard/Dashboard.js +++ b/public/js/components/Dashboard/Dashboard.js @@ -21,7 +21,7 @@ export default { type: Object, required: true, validator(value) { - return value && value.name && value.timezone + return value && value.name } } }, @@ -35,14 +35,12 @@ export default { }, provide() { return { - editMode: Vue.computed(()=>this.editMode), - widgetsSetup: Vue.computed(() => this.widgetsSetup), - timezone: Vue.computed(() => this.viewData.timezone) + editMode: Vue.computed(() => this.editMode), + widgetsSetup: Vue.computed(() => this.widgetsSetup) } }, methods: { - widgetAdd(section_name, widget) { - // TODO(chris): remove section_name? (change order of params => get rid of it) + widgetAdd(widget) { this.$refs.widgetpicker .getWidget() .then(widget_id => { @@ -64,19 +62,24 @@ export default { }) .catch(() => {}); }, - widgetUpdate(section_name, payload) { - payload = payload[section_name]; + widgetUpdate(payload) { for (var k in payload) { for (var wid in this.widgets) { if (this.widgets[wid].id == k) { payload[k] = ObjectUtils.mergeDeep(this.widgets[wid], payload[k]); // NOTE(chris): remove internal props - for (var prop of ['_x','_y','_w','_h','index','id','preset']) + for (var prop of ['_x', '_y', '_w', '_h', 'index', 'id', 'preset']) if (payload[k][prop]) delete payload[k][prop]; break; } } + if (payload[k].place) { + Object.values(payload[k].place).forEach(place => { + if (place.pinned === false) + delete place.pinned; + }); + } payload[k].widgetid = k; } this.$api @@ -113,7 +116,7 @@ export default { }) .catch(this.$fhcAlert.handleSystemError); }, - widgetRemove(section_name, id) { + widgetRemove(id) { this.$api .call(ApiDashboardUser.removeWidget(this.dashboard, id)) .then(() => { @@ -138,8 +141,8 @@ export default { const widgets = []; const remove = []; - for (var wid in res.data.general.widgets) { - let widget = res.data.general.widgets[wid]; + for (var wid in res.data) { + let widget = res.data[wid]; widget.id = wid; if (widget.custom || widget.preset) { widgets.push(widget); @@ -149,19 +152,33 @@ export default { } } - remove.forEach(wid => this.widgetRemove('general', wid)); + remove.forEach(wid => this.widgetRemove(wid)); this.widgets = widgets; }) .catch(this.$fhcAlert.handleSystemError); }, - template: ` + template: /* html */`

{{ $p.t('global/personalGreeting', [ viewData?.name ]) }} - +

- - + +
` } diff --git a/public/js/components/Dashboard/Item.js b/public/js/components/Dashboard/Item.js index 626ae7024..3cd674c25 100644 --- a/public/js/components/Dashboard/Item.js +++ b/public/js/components/Dashboard/Item.js @@ -1,7 +1,13 @@ import BsModal from "../Bootstrap/Modal.js"; -import { useCachedWidgetLoader } from "../../composables/Dashboard/CachedWidgetLoader.js"; import HeightTransition from "../Tranistion/HeightTransition.js"; +import { enableDragDropTouch } from "../../../../vendor/drag-drop-touch-js/dragdroptouch/dist/drag-drop-touch.esm.min.js"; + +if (!document.dragDropTouchActive) { + enableDragDropTouch(); + document.dragDropTouchActive = true; +} + export default { name: 'Item', components: { @@ -11,18 +17,14 @@ export default { data: () => ({ component: "", arguments: null, - target: false, - widget: null, tmpConfig: {}, isLoading: false, hasConfig: false, - sharedData: null, + sharedData: null }), emits: [ "change", "remove", - "dragstart", - "resizestart", "configOpened", "configClosed", "pinItem", @@ -30,41 +32,85 @@ export default { ], props: [ "id", - "widgetID", "config", "width", "height", "custom", "hidden", "editMode", - "loading", + "loading", // widget got added and is waiting for backend to save in db "item_data", "place", - "setup", - "dragstate", - "resizeOverlay", - "additionalRow" + "widgetTemplate", + "source" ], computed: { - maxHeight(){ - return this.setup?.height?.max; - }, - maxWidth(){ - if (Object.prototype.toString.call(this.setup?.width) == "[object Number]"){ - return this.setup?.width; + sourceInfoTooltip() { + switch (this.source) { + case null: + return ''; + case 'general': + return this.$p.t('dashboard', 'widgetFromGeneralSection'); + case 'custom': + return this.$p.t('dashboard', 'widgetFromCustomSection'); + default: + return this.$p.t('dashboard', 'widgetFromFunktionSection', [this.source]); } - return this.setup?.width?.max; }, - minHeight() { - return this.setup?.height?.min; + isResizeableHorizontal() { + if (this.widgetTemplate.setup.width === undefined) + return true; + + if (Object.prototype.toString.call(this.widgetTemplate.setup.width) == "[object Number]") + return false; + + if (this.widgetTemplate.setup.width.min === undefined) { + if (this.widgetTemplate.setup.width.max === undefined) + return true; + return this.widgetTemplate.setup.width.max > 1; + } + + if (this.widgetTemplate.setup.width.max === undefined) + return true; + + return this.widgetTemplate.setup.width.max > this.widgetTemplate.setup.width.min; }, - minWidth() { - return this.setup?.width?.min; + isResizeableVertical() { + if (this.widgetTemplate.setup.height === undefined) + return true; + + if (Object.prototype.toString.call(this.widgetTemplate.setup.height) == "[object Number]") + return false; + + if (this.widgetTemplate.setup.height.min === undefined) { + if (this.widgetTemplate.setup.height.max === undefined) + return true; + return this.widgetTemplate.setup.height.max > 1; + } + + if (this.widgetTemplate.setup.height.max === undefined) + return true; + + return this.widgetTemplate.setup.height.max > this.widgetTemplate.setup.height.min; }, - isResizeable(){ - return this.maxWidth >1 || this.maxHeight >1; + isResizeable() { + return this.isResizeableVertical || this.isResizeableHorizontal; }, - isPinned(){ + resizeClasses() { + const classes = { + icon: 'fa-up-right-and-down-left-from-center mirror-x', + button: 'cursor-nw-resize' + }; + if (!this.isResizeableHorizontal) { + classes.icon = 'fa-up-down pe-2'; + classes.button = 'cursor-ns-resize'; + } else if (!this.isResizeableVertical) { + classes.icon = 'fa-left-right pe-2'; + classes.button = 'cursor-ew-resize'; + } + return classes; + }, + isPinned() { return this.place?.pinned ? true : false; }, ready() { @@ -80,16 +126,16 @@ export default { } }, methods: { - unpin(){ + unpin() { // Unpinning is only possible in edit mode - if(!this.editMode) + if (!this.editMode) return; - let result = { item: this.item_data, x: this.item_data.x, y: this.item_data.y }; + let result = { item: this.item_data, pinned: false }; this.$emit('unPinItem', [result]); }, - pinItem(){ - let result = { item: this.item_data, x: this.item_data.x, y: this.item_data.y}; - this.$emit('pinItem',[result]); + pinItem() { + let result = { item: this.item_data, pinned: true }; + this.$emit('pinItem', [result]); }, getWidgetC4Link(widget) { return (FHC_JS_DATA_STORAGE_OBJECT.app_root + @@ -101,22 +147,6 @@ export default { handleHideBsModal() { this.$emit('configClosed') }, - mouseDown(e) { - this.target = e.target; - }, - startDrag(e) { - if (this.$refs.dragHandle.contains(this.target)) { - this.$emit("dragstart", e); - } else if ( - this.isResizeable && - this.$refs.resizeHandle.contains(this.target) - ) { - if (this.isResizeable) this.$emit("resizestart", e); - else e.preventDefault(); - } else { - e.preventDefault(); - } - }, openConfig() { this.tmpConfig = { ...this.arguments }; this.$refs.config.show(); @@ -135,111 +165,242 @@ export default { }, sendChangeConfig(config) { for (var k in config) { - if (this.widget.arguments[k] == config[k]) { - delete config[k]; + if (this.widgetTemplate.arguments[k] == config[k]) { + delete config[k]; } } this.$emit("change", config); }, + async initializeComponent() { + if ( + this.widgetTemplate + && this.widgetTemplate.setup + && this.widgetTemplate.widget_id + && this.widgetTemplate.arguments + ) { + let component = (await import(this.widgetTemplate.setup.file)).default; + this.$options.components["widget" + this.widgetTemplate.widget_id] = component; + this.component = "widget" + this.widgetTemplate.widget_id; + this.arguments = { ...this.widgetTemplate.arguments, ...this.config }; + this.tmpConfig = { ...this.arguments }; + } + } }, watch: { config() { - this.arguments = { ...this.widget?.arguments, ...this.config }; + this.arguments = { ...this.widgetTemplate?.arguments, ...this.config }; this.tmpConfig = { ...this.arguments }; this.$refs.config && this.$refs.config.hide(); this.isLoading = false; }, + widgetTemplate() { + this.initializeComponent(); + } }, - setup() { - const { actions } = useCachedWidgetLoader(); - return { - loadWidget: actions.load - }; - }, - async created() { - this.widget = await this.loadWidget(this.id); - let component = (await import(this.widget.setup.file)).default; - this.$options.components["widget" + this.widget.widget_id] = component; - this.component = "widget" + this.widget.widget_id; - this.arguments = { ...this.widget.arguments, ...this.config }; - this.tmpConfig = { ...this.arguments }; + created() { + this.initializeComponent(); }, template: /*html*/ ` -
-
+
+
-
-
-
-
- - - - {{ widget.setup.name }} - - - - - - - - - - -
- -
-
-
- -
- -
-
- - - - - - -