aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/admin/tree.php6
-rw-r--r--lib/classes/JsonApi/Schemas/TreeNodeCourse.php21
-rw-r--r--resources/assets/stylesheets/scss/contentbar.scss2
-rw-r--r--resources/assets/stylesheets/scss/tree.scss36
-rw-r--r--resources/vue/components/tree/StudipTree.vue68
-rw-r--r--resources/vue/components/tree/StudipTreeList.vue80
-rw-r--r--resources/vue/components/tree/StudipTreeNode.vue19
-rw-r--r--resources/vue/components/tree/StudipTreeTable.vue92
-rw-r--r--resources/vue/components/tree/TreeCourseTable.vue69
-rw-r--r--resources/vue/components/tree/TreeExportWidget.vue4
-rw-r--r--resources/vue/mixins/TreeMixin.js16
-rw-r--r--resources/vue/store/TreeStore.js12
12 files changed, 198 insertions, 227 deletions
diff --git a/app/controllers/admin/tree.php b/app/controllers/admin/tree.php
index 5d22775..9bce2ea 100644
--- a/app/controllers/admin/tree.php
+++ b/app/controllers/admin/tree.php
@@ -28,6 +28,9 @@ class Admin_TreeController extends AuthenticatedController
'visible-children-only' => false,
'with-courses' => true,
])
+ ->withVuexStore('TreeStore', 'treestore', [
+ 'SET_SEMESTER' => $this->semester,
+ ])
);
}
@@ -59,6 +62,9 @@ class Admin_TreeController extends AuthenticatedController
'with-course-assign' => true,
'with-courses' => true,
])
+ ->withVuexStore('TreeStore', 'treestore', [
+ 'SET_SEMESTER' => $this->semester,
+ ])
);
}
diff --git a/lib/classes/JsonApi/Schemas/TreeNodeCourse.php b/lib/classes/JsonApi/Schemas/TreeNodeCourse.php
index 85f4226..331fc17 100644
--- a/lib/classes/JsonApi/Schemas/TreeNodeCourse.php
+++ b/lib/classes/JsonApi/Schemas/TreeNodeCourse.php
@@ -34,8 +34,29 @@ final class TreeNodeCourse extends SchemaProvider
);
}
+ /**
+ * @param Model $resource
+ */
public function getRelationships($resource, ContextInterface $context): iterable
{
return [];
}
+
+ /**
+ * @param Model $resource
+ */
+ public function hasResourceMeta($resource): bool
+ {
+ $schema = $this->schemaContainer->getSchema($resource->getCourse());
+ return $schema->hasResourceMeta($resource->getCourse());
+ }
+
+ /**
+ * @param Model $resource
+ */
+ public function getResourceMeta($resource)
+ {
+ $schema = $this->schemaContainer->getSchema($resource->getCourse());
+ return $schema->getResourceMeta($resource->getCourse());
+ }
}
diff --git a/resources/assets/stylesheets/scss/contentbar.scss b/resources/assets/stylesheets/scss/contentbar.scss
index b582ec5..72a8f78 100644
--- a/resources/assets/stylesheets/scss/contentbar.scss
+++ b/resources/assets/stylesheets/scss/contentbar.scss
@@ -15,7 +15,7 @@
.contentbar-wrapper-left {
display: flex;
- max-width: calc(100% - 130px);
+ flex: 1 0 calc(100% - 130px);
.contentbar-breadcrumb {
font-size: 1.25em;
diff --git a/resources/assets/stylesheets/scss/tree.scss b/resources/assets/stylesheets/scss/tree.scss
index 1d700f9..1c46031 100644
--- a/resources/assets/stylesheets/scss/tree.scss
+++ b/resources/assets/stylesheets/scss/tree.scss
@@ -14,7 +14,7 @@ $tree-outline: 1px solid var(--light-gray-color-40);
}
.contentbar {
- display: relative;
+ display: flex;
.contentbar-wrapper-right {
display: inherit;
@@ -308,23 +308,6 @@ $tree-outline: 1px solid var(--light-gray-color-40);
}
}
}
-
- table {
- tr {
- td {
- line-height: 24px;
- padding: 10px;
- vertical-align: top;
-
- a {
- img {
- margin-right: 5px;
- vertical-align: bottom;
- }
- }
- }
- }
- }
}
/* Display as table */
@@ -371,6 +354,23 @@ $tree-outline: 1px solid var(--light-gray-color-40);
}
}
+ .studip-tree-courses-table {
+ tr {
+ td {
+ line-height: 24px;
+ padding: 10px;
+ vertical-align: top;
+
+ a {
+ img {
+ margin-right: 5px;
+ vertical-align: bottom;
+ }
+ }
+ }
+ }
+ }
+
.studip-tree-course-path {
font-size: 0.9em;
list-style: none;
diff --git a/resources/vue/components/tree/StudipTree.vue b/resources/vue/components/tree/StudipTree.vue
index 7e0d599..c0a1d82 100644
--- a/resources/vue/components/tree/StudipTree.vue
+++ b/resources/vue/components/tree/StudipTree.vue
@@ -19,19 +19,44 @@
:with-export="withExport"
:show-structure-as-navigation="showStructureAsNavigation"
:assignable="assignable"
- :with-course-assign="withCourseAssign"
+ :page="currentPage"
@change-current-node="changeCurrentNode"
></component>
+
+ <tree-course-table :courses="courses">
+ <template #pagination
+ v-if="totalCourseCount > limit"
+ >
+ <studip-pagination :items-per-page="limit"
+ :total-items="totalCourseCount"
+ v-model:current-offset="currentPage"
+ />
+ </template>
+ </tree-course-table>
</div>
<div v-else class="studip-tree">
<tree-search-result :search-config="searchConfig"></tree-search-result>
</div>
+
+ <Teleport v-if="showExport && courses.length > 0" to="#export-widget" name="sidebar-export">
+ <tree-export-widget :title="$gettext('Veranstaltungen exportieren')"
+ :url="exportUrl"
+ :export-data="courses"
+ />
+ </Teleport>
+
+ <Teleport v-if="withCourseAssign" to="#assign-widget" name="sidebar-assign-courses">
+ <assign-link-widget v-if="courses.length > 0" :node="currentNode" :courses="courses"></assign-link-widget>
+ </Teleport>
+
<Teleport v-if="withSearch" to="#search-widget" name="sidebar-search">
- <search-widget v-if="currentNode" :min-length="3" ref="searchWidget"></search-widget>
+ <search-widget v-if="currentNode" :min-length="3" ref="searchWidget" />
</Teleport>
+
<Teleport v-if="!editable && !isSearching && !isLoading && currentNode"
to="#views-widget"
- name="sidebar-views">
+ name="sidebar-views"
+ >
<studip-tree-view-widget :config="viewConfig" />
</Teleport>
</div>
@@ -48,17 +73,25 @@ import StudipTreeList from './StudipTreeList.vue';
import StudipTreeTable from './StudipTreeTable.vue';
import StudipTreeNode from './StudipTreeNode.vue';
import TreeSearchResult from './TreeSearchResult.vue';
+import TreeCourseTable from "./TreeCourseTable.vue";
+import StudipPagination from "../StudipPagination.vue";
+import TreeExportWidget from "./TreeExportWidget.vue";
+import AssignLinkWidget from "./AssignLinkWidget.vue";
export default {
name: 'StudipTree',
components: {
- TreeSearchResult,
+ AssignLinkWidget,
SearchWidget,
- StudipTreeViewWidget,
+ StudipPagination,
StudipProgressIndicator,
StudipTreeList,
+ StudipTreeNode,
StudipTreeTable,
- StudipTreeNode
+ StudipTreeViewWidget,
+ TreeCourseTable,
+ TreeExportWidget,
+ TreeSearchResult
},
mixins: [ TreeMixin ],
props: {
@@ -153,8 +186,11 @@ export default {
},
data() {
return {
+ courses: [],
+
nodeId: this.startId,
startNode: null,
+ currentPage: 0,
currentNode: this.startNode,
loaded: false,
isLoading: false,
@@ -165,6 +201,12 @@ export default {
}
},
computed: {
+ exportUrl() {
+ return STUDIP.URLHelper.getURL('dispatch.php/tree/export_csv');
+ },
+ showExport() {
+ return this.withExport && document.getElementById('export-widget');
+ },
viewComponent() {
if (this.startNode && this.viewType === 'list') {
return StudipTreeList;
@@ -193,9 +235,6 @@ export default {
document.getElementById('tree-breadcrumb-' + node.attributes.id)?.focus();
});
},
- exportUrl() {
- return STUDIP.URLHelper.getURL('dispatch.php/tree/export_csv');
- },
injectSearchterm(targetId, searchterm) {
const form = document.getElementById(targetId).querySelector('form');
let input = form.querySelector('input[type="hidden"][name="search"]');
@@ -262,6 +301,17 @@ export default {
document.getElementById('semclass-selector-searchterm')?.remove();
this.isSearching = false;
});
+ },
+ watch: {
+ async currentNode(node) {
+ if (this.withCourses) {
+ this.courses = await this.fetchNodeCourses(node.id);
+ }
+ },
+ currentPage(current) {
+ this.updateOffset(current);
+ }
}
+
}
</script>
diff --git a/resources/vue/components/tree/StudipTreeList.vue b/resources/vue/components/tree/StudipTreeList.vue
index d96c235..6db7232 100644
--- a/resources/vue/components/tree/StudipTreeList.vue
+++ b/resources/vue/components/tree/StudipTreeList.vue
@@ -73,86 +73,20 @@
</button>
</span>
</section>
- <table v-if="courses.length > 0" class="default">
- <caption>{{ $gettext('Veranstaltungen') }}</caption>
- <colgroup>
- <col>
- <col>
- </colgroup>
- <thead>
- <tr v-if="totalCourseCount > limit">
- <td colspan="2">
- <studip-pagination :items-per-page="limit"
- :total-items="totalCourseCount"
- v-model:current-offset="page"
- />
- </td>
- </tr>
- <tr>
- <th>{{ $gettext('Name') }}</th>
- <th>{{ $gettext('Information') }}</th>
- </tr>
- </thead>
- <tbody>
- <tr v-for="(course) in courses" :key="course.id" class="studip-tree-child studip-tree-course">
- <td>
- <a :href="courseUrl(course.id)" tabindex="0"
- :title="$gettext(
- 'Zur Veranstaltung %{ title }',
- { title: course.attributes.title },
- true
- )">
- <studip-icon shape="seminar" :size="26"></studip-icon>
- <template v-if="course.attributes['course-number']">
- {{ course.attributes['course-number'] }}
- </template>
- {{ course.attributes.title }}
- </a>
- <div :id="'course-dates-' + course.id" class="course-dates"></div>
- </td>
- <td>
- <tree-course-details :course="course"></tree-course-details>
- </td>
- </tr>
- </tbody>
- <tfoot v-if="totalCourseCount > limit">
- <tr>
- <td colspan="2">
- <studip-pagination :items-per-page="limit"
- :total-items="totalCourseCount"
- v-model:current-offset="page"
- />
- </td>
- </tr>
- </tfoot>
- </table>
- <Teleport v-if="showExport" to="#export-widget" name="sidebar-export">
- <tree-export-widget v-if="courses.length > 0"
- :title="$gettext('Veranstaltungen exportieren')" :url="exportUrl()"
- :export-data="courses"></tree-export-widget>
- </Teleport>
- <Teleport v-if="withCourseAssign" to="#assign-widget" name="sidebar-assign-courses">
- <assign-link-widget v-if="courses.length > 0" :node="currentNode" :courses="courses"></assign-link-widget>
- </Teleport>
</article>
</template>
<script>
import draggable from 'vuedraggable';
import { TreeMixin } from '../../mixins/TreeMixin';
-import TreeExportWidget from './TreeExportWidget.vue';
import TreeBreadcrumb from './TreeBreadcrumb.vue';
import TreeNodeTile from './TreeNodeTile.vue';
import StudipProgressIndicator from '../StudipProgressIndicator.vue';
-import TreeCourseDetails from './TreeCourseDetails.vue';
-import AssignLinkWidget from './AssignLinkWidget.vue';
-import StudipPagination from '../StudipPagination.vue';
export default {
name: 'StudipTreeList',
components: {
- draggable, StudipProgressIndicator, TreeExportWidget, TreeBreadcrumb, TreeNodeTile, TreeCourseDetails,
- AssignLinkWidget, StudipPagination
+ draggable, StudipProgressIndicator, TreeBreadcrumb, TreeNodeTile
},
mixins: [ TreeMixin ],
emits: ['change-current-node', 'sort-tree-children'],
@@ -197,14 +131,10 @@ export default {
type: Boolean,
default: false
},
- withCourseAssign: {
- type: Boolean,
- default: false
- },
showStructureAsNavigation: {
type: Boolean,
default: false
- }
+ },
},
data() {
return {
@@ -292,12 +222,6 @@ export default {
if (this.withCourses) {
this.courses = await this.fetchNodeCourses(this.currentNode.id);
-// , 0, this.semester, this.semClass)
-// .then(courses => {
-// // this.totalCourseCount = courses.data.meta.page.total;
-// // this.offset = 0;
-// // this.courses = courses.data.data;
-// });
}
this.globalOn('open-tree-node', node => {
diff --git a/resources/vue/components/tree/StudipTreeNode.vue b/resources/vue/components/tree/StudipTreeNode.vue
index 78691c8..1abeeae 100644
--- a/resources/vue/components/tree/StudipTreeNode.vue
+++ b/resources/vue/components/tree/StudipTreeNode.vue
@@ -130,7 +130,6 @@ export default {
isLoading: false,
childrenLoaded: false,
children: [],
- semester: 'all',
openState: this.isOpen,
theAncestors: this.ancestors,
assignedCourses: 0,
@@ -155,12 +154,14 @@ export default {
}, 500);
return config;
});
- this.getNodeChildren(this.node, this.visibleChildrenOnly)
- .then(response => {
- this.isLoading = false;
- this.children = response.data.data;
- this.childrenLoaded = true;
- });
+ this.fetchNodeChildren({
+ id: this.node.id,
+ visibleChildrenOnly: this.visibleChildrenOnly,
+ }).then(children => {
+ this.isLoading = false;
+ this.children = children;
+ this.childrenLoaded = true;
+ });
axios.interceptors.request.eject(loadingIndicator);
}
},
@@ -242,8 +243,8 @@ export default {
if (this.ancestors.length === 0) {
for (const open of this.openNodes) {
- this.getNode(open).then((response) => {
- const haystack = response.data.data.attributes.ancestors?.map(element => {
+ this.fetchNode(open).then((node) => {
+ const haystack = node.attributes.ancestors?.map(element => {
return element.classname + '_' + element.id;
});
if (haystack) {
diff --git a/resources/vue/components/tree/StudipTreeTable.vue b/resources/vue/components/tree/StudipTreeTable.vue
index 759768e..97b0e0a 100644
--- a/resources/vue/components/tree/StudipTreeTable.vue
+++ b/resources/vue/components/tree/StudipTreeTable.vue
@@ -80,98 +80,21 @@
</template>
</draggable>
</table>
-
- <table v-if="courses.length > 0" class="default">
- <colgroup>
- <col style="width: 20px">
- <col style="width: 30px">
- <col>
- <col style="width: 40%">
- </colgroup>
- <thead>
- <tr v-if="totalCourseCount > limit">
- <td colspan="4">
- <studip-pagination :items-per-page="limit"
- :total-items="totalCourseCount"
- v-model:current-offset="page"
- />
- </td>
- </tr>
- <tr>
- <th></th>
- <th>{{ $gettext('Typ') }}</th>
- <th>{{ $gettext('Name') }}</th>
- <th>{{ $gettext('Information') }}</th>
- </tr>
- </thead>
- <tbody role="listbox">
- <tr v-for="(course) in courses" :key="course.id" class="studip-tree-child studip-tree-course">
- <td></td>
- <td>
- <studip-icon shape="seminar" :size="26"></studip-icon>
- </td>
- <td>
- <a :href="courseUrl(course.id)" tabindex="0"
- :title="$gettext(
- 'Zur Veranstaltung %{ title }',
- { title: course.attributes.title },
- true
- )"
- >
- <template v-if="course.attributes['course-number']">
- {{ course.attributes['course-number'] }}
- </template>
- {{ course.attributes.title }}
- </a>
- <div :id="'course-dates-' + course.id" class="course-dates"></div>
- </td>
- <td :colspan="editable ? 2 : null">
- <tree-course-details :course="course"></tree-course-details>
- </td>
- </tr>
- </tbody>
- <tfoot v-if="totalCourseCount > limit">
- <tr>
- <td colspan="4">
- <studip-pagination :items-per-page="limit"
- :total-items="totalCourseCount"
- v-model:current-offset="page"
- />
- </td>
- </tr>
- </tfoot>
- </table>
-
- <Teleport v-if="showExport" to="#export-widget" name="sidebar-export">
- <tree-export-widget v-if="courses.length > 0" :title="$gettext('Download des Ergebnisses')" :url="exportUrl()"
- :export-data="courses"></tree-export-widget>
- </Teleport>
- <Teleport v-if="withCourseAssign" to="#assign-widget" name="sidebar-assign-courses">
- <assign-link-widget v-if="courses.length > 0" :node="currentNode" :courses="courses"></assign-link-widget>
- </Teleport>
</article>
</template>
<script>
import draggable from 'vuedraggable';
import { TreeMixin } from '../../mixins/TreeMixin';
-import TreeExportWidget from './TreeExportWidget.vue';
import TreeBreadcrumb from './TreeBreadcrumb.vue';
import StudipProgressIndicator from '../StudipProgressIndicator.vue';
-import AssignLinkWidget from "./AssignLinkWidget.vue";
-import StudipPagination from "../StudipPagination.vue";
import StudipTreeTableRows from "./StudipTreeTableRows.vue";
-import TreeCourseDetails from "./TreeCourseDetails.vue";
-import StudipIcon from "../StudipIcon.vue";
export default {
name: 'StudipTreeTable',
components: {
- StudipIcon, TreeCourseDetails,
StudipTreeTableRows,
- StudipPagination,
- draggable, TreeExportWidget, StudipProgressIndicator, TreeBreadcrumb,
- AssignLinkWidget
+ draggable, StudipProgressIndicator, TreeBreadcrumb
},
mixins: [ TreeMixin ],
emits: ['change-current-node', 'sort-tree-children'],
@@ -204,10 +127,6 @@ export default {
type: Boolean,
default: false
},
- withExport: {
- type: Boolean,
- default: false
- },
withChildren: {
type: Boolean,
default: true
@@ -216,10 +135,6 @@ export default {
type: Boolean,
default: false
},
- withCourseAssign: {
- type: Boolean,
- default: false
- },
showStructureAsNavigation: {
type: Boolean,
default: false
@@ -238,11 +153,6 @@ export default {
showingAllCourses: false
}
},
- computed: {
- showExport() {
- return this.withExport && document.getElementById('export-widget');
- }
- },
methods: {
dropChild() {
this.updateSorting(this.currentNode.id, this.children);
diff --git a/resources/vue/components/tree/TreeCourseTable.vue b/resources/vue/components/tree/TreeCourseTable.vue
new file mode 100644
index 0000000..6a1f1b4
--- /dev/null
+++ b/resources/vue/components/tree/TreeCourseTable.vue
@@ -0,0 +1,69 @@
+<template>
+ <table v-if="courses.length > 0"
+ class="default studip-tree-courses-table"
+ >
+ <colgroup>
+ <col>
+ <col>
+ </colgroup>
+ <thead>
+ <tr v-if="$slots.pagination">
+ <td colspan="3">
+ <slot name="pagination"></slot>
+ </td>
+ </tr>
+ <tr>
+ <th>{{ $gettext('Name') }}</th>
+ <th>{{ $gettext('Information') }}</th>
+ </tr>
+ </thead>
+ <tbody role="listbox">
+ <tr v-for="(course) in courses" :key="course.id" class="studip-tree-child studip-tree-course">
+ <td>
+ <a :href="courseUrl(course.id)" tabindex="0"
+ :title="$gettext(
+ 'Zur Veranstaltung %{ title }',
+ { title: course.attributes.title },
+ true
+ )"
+ >
+ <studip-icon shape="seminar" :size="26"></studip-icon>
+
+ <template v-if="course.attributes['course-number']">
+ {{ course.attributes['course-number'] }}
+ </template>
+ {{ course.attributes.title }}
+ </a>
+ <div :id="'course-dates-' + course.id" class="course-dates"></div>
+ </td>
+ <td>
+ <tree-course-details :course="course"></tree-course-details>
+ </td>
+ </tr>
+ </tbody>
+ <tfoot v-if="$slots.pagination">
+ <tr>
+ <td colspan="3">
+ <slot name="pagination"></slot>
+ </td>
+ </tr>
+ </tfoot>
+ </table>
+</template>
+<script>
+import StudipIcon from "../StudipIcon.vue";
+import TreeCourseDetails from "./TreeCourseDetails.vue";
+
+export default {
+ name: 'TreeCourseTable',
+ components: {TreeCourseDetails, StudipIcon},
+ props: {
+ courses: Array,
+ },
+ methods: {
+ courseUrl(courseId) {
+ return STUDIP.URLHelper.getURL('dispatch.php/course/details/index/' + courseId)
+ }
+ }
+}
+</script>
diff --git a/resources/vue/components/tree/TreeExportWidget.vue b/resources/vue/components/tree/TreeExportWidget.vue
index 61500f5..bb47c67 100644
--- a/resources/vue/components/tree/TreeExportWidget.vue
+++ b/resources/vue/components/tree/TreeExportWidget.vue
@@ -3,7 +3,9 @@
<template #content>
<form class="sidebar-export">
<studip-icon shape="export"></studip-icon>
- <a :href="url" :title="title" @click.prevent="createExport()">{{ title }}</a>
+ <a :href="url" :title="title" @click.prevent="createExport()">
+ {{ title }}
+ </a>
</form>
</template>
</sidebar-widget>
diff --git a/resources/vue/mixins/TreeMixin.js b/resources/vue/mixins/TreeMixin.js
index 43ef469..72bc235 100644
--- a/resources/vue/mixins/TreeMixin.js
+++ b/resources/vue/mixins/TreeMixin.js
@@ -11,8 +11,7 @@ export const TreeMixin = {
data() {
return {
currentNode: null,
- showProgressIndicatorTimeout: 500,
- page: 0
+ showProgressIndicatorTimeout: 500
};
},
computed: {
@@ -28,7 +27,7 @@ export const TreeMixin = {
]),
totalCourseCount() {
- return this.getNodeCoursesTotal(this.currentNode.id);
+ return this.getNodeCoursesTotal(this.currentNode?.id);
}
},
methods: {
@@ -84,15 +83,9 @@ export const TreeMixin = {
nodeUrl(node_id, semester = null ) {
return STUDIP.URLHelper.getURL('', { node_id, semester })
},
- courseUrl(courseId) {
- return STUDIP.URLHelper.getURL('dispatch.php/course/details/index/' + courseId)
- },
profileUrl(username) {
return STUDIP.URLHelper.getURL('dispatch.php/profile', { username })
},
- exportUrl() {
- return STUDIP.URLHelper.getURL('dispatch.php/tree/export_csv');
- },
editNode(editUrl, id) {
STUDIP.Dialog.fromURL(
editUrl + '/' + id,
@@ -129,10 +122,5 @@ export const TreeMixin = {
page
});
}
- },
- watch: {
- page(current) {
- this.updateOffset(current);
- }
}
}
diff --git a/resources/vue/store/TreeStore.js b/resources/vue/store/TreeStore.js
index 9d9cf00..4de27e9 100644
--- a/resources/vue/store/TreeStore.js
+++ b/resources/vue/store/TreeStore.js
@@ -40,11 +40,11 @@ class DataRequest
return DataRequest.#promises[index];
}
- const promise = DataRequest.#apiRequest(request.path, request.parameters);
- DataRequest.#promises[index] = promise;
- return promise.then(handler).finally(() => {
- delete DataRequest.#promises[index];
- });
+ return DataRequest.#promises[index] = DataRequest.#apiRequest(request.path, request.parameters)
+ .then(handler)
+ .finally(() => {
+ delete DataRequest.#promises[index];
+ });
}
}
@@ -61,7 +61,7 @@ export default {
isLoading: false,
semesterId: 'all',
semClass: 0,
- viewType: 'tree'
+ viewType: 'table'
}),
getters: {
getNode: (state) => (id) => {