aboutsummaryrefslogtreecommitdiff
path: root/resources/assets/javascripts/lib
diff options
context:
space:
mode:
authorRon Lucke <lucke@elan-ev.de>2025-07-14 09:36:18 +0200
committerRon Lucke <lucke@elan-ev.de>2025-07-14 09:36:18 +0200
commit4355ded9bc56e0b06fbceffe61ddc37061cc3bc7 (patch)
tree348493b6b0fd1286b86f213e5077413b97cf9747 /resources/assets/javascripts/lib
parent1e59dd2dacc51b3313d7780b66d4bf72e0484f86 (diff)
Color-Themes-System, fixes #5361
Closes #5361 Merge request studip/studip!4038
Diffstat (limited to 'resources/assets/javascripts/lib')
-rw-r--r--resources/assets/javascripts/lib/course_wizard.js94
-rw-r--r--resources/assets/javascripts/lib/fullcalendar.js22
-rw-r--r--resources/assets/javascripts/lib/icon-loader.ts97
-rw-r--r--resources/assets/javascripts/lib/inline-editing.js138
-rw-r--r--resources/assets/javascripts/lib/members.js15
5 files changed, 159 insertions, 207 deletions
diff --git a/resources/assets/javascripts/lib/course_wizard.js b/resources/assets/javascripts/lib/course_wizard.js
index ebd821f..af63737 100644
--- a/resources/assets/javascripts/lib/course_wizard.js
+++ b/resources/assets/javascripts/lib/course_wizard.js
@@ -13,28 +13,25 @@ const CourseWizard = {
addParticipatingInst: function(id, name) {
// Check if already set.
if ($('input[name="participating[' + id + ']"]').length == 0) {
- var wrapper = $('<div>').addClass('institute');
+ const wrapper = $('<div>').addClass('institute');
$('#wizard-participating')
.children('div.description')
.removeClass('hidden-js');
- var input = $('<input>')
+ const input = $('<input>')
.attr('type', 'hidden')
.attr('name', 'participating[' + id + ']')
.attr('id', id)
.attr('value', '1');
- var trash = $('<input>')
- .attr('type', 'image')
- .attr('src', STUDIP.ASSETS_URL + 'images/icons/blue/trash.svg')
- .attr('name', 'remove_participating[' + id + ']')
- .attr('value', '1')
- .attr('onclick', "return STUDIP.CourseWizard.removeParticipatingInst('" + id + "')")
- .addClass('text-bottom')
- .css({
- width: 16,
- height: 16
- });
+ const trash = $('<button>')
+ .attr('type', 'button')
+ .attr('name', 'remove_participating[' + id + ']')
+ .attr('value', '1')
+ .addClass('btn-icon btn-icon--trash btn-icon--inline')
+ .on('click', function () {
+ return STUDIP.CourseWizard.removeParticipatingInst(id);
+ });
wrapper.append(input);
- var nametext = $('<span>')
+ const nametext = $('<span>')
.html(name)
.text();
wrapper.append(nametext);
@@ -72,27 +69,25 @@ const CourseWizard = {
addPerson: function(id, name, inputName, elClass, elId, otherInput) {
// Check if already set.
if ($('input[name="' + inputName + '[' + id + ']"]').length == 0) {
- var wrapper = $('<div>').addClass(elClass);
+ const wrapper = $('<div>').addClass(elClass);
$('#' + elId)
.children('div.description')
.removeClass('hidden-js');
- var input = $('<input>')
+ const input = $('<input>')
.attr('type', 'hidden')
.attr('name', inputName + '[' + id + ']')
.attr('id', id)
.attr('value', '1');
- var trash = $('<input>')
- .attr('type', 'image')
- .attr('src', STUDIP.ASSETS_URL + 'images/icons/blue/trash.svg')
+ const trash = $('<button>')
+ .attr('type', 'button')
.attr('name', 'remove_' + elClass + '[' + id + ']')
.attr('value', '1')
- .attr('onclick', "return STUDIP.CourseWizard.removePerson('" + id + "')")
- .css({
- width: 16,
- height: 16
+ .addClass('btn-icon btn-icon--trash btn-icon--inline')
+ .on('click', function () {
+ return STUDIP.CourseWizard.removePerson(id);
});
wrapper.append(input);
- var nametext = $('<span>')
+ const nametext = $('<span>')
.html(name)
.text();
wrapper.append(nametext);
@@ -314,13 +309,14 @@ const CourseWizard = {
.attr('name', 'studyareas[]')
.attr('value', items[i].id);
node.children('ul').before(input);
- var unassign = $('<input>')
- .attr('type', 'image')
+ const unassign = $('<button>')
+ .attr('type', 'button')
.attr('name', 'unassign[' + items[i].id + ']')
- .attr('src', STUDIP.ASSETS_URL + 'images/icons/blue/trash.svg')
- .attr('width', '16')
- .height('height', '16')
- .attr('onclick', "return STUDIP.CourseWizard.unassignNode('" + items[i].id + "')");
+ .attr('value', '1')
+ .addClass('btn-icon btn-icon--trash btn-icon--inline')
+ .on('click', function () {
+ return STUDIP.CourseWizard.unassignNode(items[i].id);
+ });
node.children('input[name="studyareas[]"]').before(unassign);
}
}
@@ -344,38 +340,37 @@ const CourseWizard = {
* @returns {*|jQuery}
*/
createTreeNode: function(values, assignable, selected) {
- let item = $('<li/>');
+ const item = $('<li/>');
// Node in "All study areas" tree.
if (assignable) {
item.addClass('sem-tree-' + values.id);
- var assign = $('<input>')
- .attr('type', 'image')
+ const assign = $('<button>')
+ .attr('type', 'button')
.attr('name', 'assign[' + values.id + ']')
- .attr('src', STUDIP.ASSETS_URL + 'images/icons/yellow/arr_2left.svg')
- .attr('width', '16')
- .height('height', '16')
+ .attr('value', '1')
+ .addClass('btn-icon btn-icon--add btn-icon--inline')
.attr('onclick', "return STUDIP.CourseWizard.assignNode('" + values.id + "')");
if (values.assignable) {
item.append(assign);
item.append(document.createTextNode(' '));
}
if (values.has_children) {
- var input = $('<input>')
+ const input = $('<input>')
.attr('type', 'checkbox')
.attr('id', values.id);
- var label = $('<label>')
+ const label = $('<label>')
.addClass('undecorated')
.attr('for', values.id)
.attr('onclick', "return STUDIP.CourseWizard.getTreeChildren('" + values.id + "', true)");
// Build link for opening the current node.
- var link = $('div#studyareas').data('forward-url');
+ let link = $('div#studyareas').data('forward-url');
if (link.indexOf('?') > -1) {
link += '&open_node=' + values.id;
} else {
link += '?open_node=' + values.id;
}
- var openLink = $('<a>').attr('href', link);
+ const openLink = $('<a>').attr('href', link);
openLink.html(
$('<div/>')
.text(values.name)
@@ -413,17 +408,16 @@ const CourseWizard = {
.html()
);
if ((!values.has_children || values.assignable) && selected) {
- var unassign = $('<input>')
- .attr('type', 'image')
- .attr('name', 'unassign[' + values.id + ']')
- .attr('src', STUDIP.ASSETS_URL + 'images/icons/blue/trash.svg')
- .attr('width', '16')
- .height('height', '16')
+ const unassign = $('<button>')
+ .attr('type', 'button')
+ .attr('name', 'assiunassigngn[' + values.id + ']')
+ .attr('value', '1')
+ .addClass('btn-icon btn-icon--trash btn-icon--inline')
.attr('onclick', "return STUDIP.CourseWizard.unassignNode('" + values.id + "')");
item.append(unassign);
}
if (values.assignable && selected) {
- input = $('<input>')
+ const input = $('<input>')
.attr('type', 'hidden')
.attr('name', 'studyareas[]')
.attr('value', values.id);
@@ -452,7 +446,7 @@ const CourseWizard = {
var items = $.parseJSON(data);
CourseWizard.buildPartialTree(items, false, id);
$('.sem-tree-assigned-root').removeClass('hidden-js');
- $('input[name="assign[' + id + ']"]').hide();
+ $('button[name="assign[' + id + ']"]').hide();
$('svg[name="assign[' + id + ']"]').hide();
},
error: function(xhr, status, error) {
@@ -471,12 +465,12 @@ const CourseWizard = {
var target = $('li.sem-tree-assigned-' + id);
if (target.children('ul').children('li').length > 0) {
target.children('input[name="studyareas[]"]').remove();
- target.children('input[name="unassign[' + id + ']"]').remove();
+ target.children('button[name="unassign[' + id + ']"]').remove();
target.children('a').remove();
} else {
CourseWizard.cleanupAssignTree(target);
}
- $('input[name="assign[' + id + ']"]').show();
+ $('button[name="assign[' + id + ']"]').show();
$('svg[name="assign[' + id + ']"]').show();
return false;
},
diff --git a/resources/assets/javascripts/lib/fullcalendar.js b/resources/assets/javascripts/lib/fullcalendar.js
index 350c72f..e1174bd 100644
--- a/resources/assets/javascripts/lib/fullcalendar.js
+++ b/resources/assets/javascripts/lib/fullcalendar.js
@@ -600,23 +600,17 @@ class Fullcalendar
if (event.extendedProps.icon) {
//Check if the icon is already an URL or just the name of an icon.
- let icon_url = '';
+ let iconUrl = '';
if (event.extendedProps.icon.includes('://')) {
//The icon already is an URL.
- icon_url = event.extendedProps.icon;
+ iconUrl = event.extendedProps.icon;
} else {
- //The icon is just referenced by its name.
- icon_url = `${STUDIP.ASSETS_URL}images/icons/${iconColor}/${event.extendedProps.icon}.svg`
+ //The icon is just referenced by its name. We do not need a specific color here, background-color is currentColor.
+ iconUrl = `${STUDIP.ASSETS_URL}images/icons/black/${event.extendedProps.icon}.svg`
}
- $(eventElement).find('.fc-title').prepend(
- $('<img>').attr('src', icon_url)
- .css({
- verticalAlign: 'text-bottom',
- marginRight: '3px',
- width: 14,
- height: 14
- })
- );
+ const $title = $(eventElement).find('.fc-title');
+ $title.addClass('has-icon');
+ $title.css('--icon-url', `url('${iconUrl}')`);
}
},
eventSourceSuccess: function(content) {
@@ -690,7 +684,7 @@ class Fullcalendar
$('<a>').attr('href', url).text(renderInfo.resource.title)
);
} else if ($("*[data-fullcalendar='1']").hasClass('institute-plan') && renderInfo.resource.id > 0) {
- let icon = '<img class="text-bottom icon-role-clickable icon-shape-edit" width="20" height="20" src="' + STUDIP.URLHelper.getURL('assets/images/icons/blue/edit.svg') + '" alt="edit">';
+ const icon = '<span class="btn-icon btn-icon--edit icon-role-clickable" aria-label="edit"></span>';
$(renderInfo.el).append(
'<a href="'
+ STUDIP.URLHelper.getURL('dispatch.php/admin/courseplanning/rename_column/'
diff --git a/resources/assets/javascripts/lib/icon-loader.ts b/resources/assets/javascripts/lib/icon-loader.ts
new file mode 100644
index 0000000..3ccdbd6
--- /dev/null
+++ b/resources/assets/javascripts/lib/icon-loader.ts
@@ -0,0 +1,97 @@
+type CacheOption = 'off' | 'session' | 'local';
+
+class IconLoader
+{
+ readonly #cacheKey: string = 'studip/svg-icons';
+
+ #baseUrl: string;
+ #useCache: CacheOption = 'off';
+
+ #cache: Map<string, string>;
+ #promises: Map<string, Promise<string>>;
+
+ constructor(baseUrl: string, useCache: CacheOption = 'off')
+ {
+ this.#baseUrl = baseUrl;
+ this.#useCache = useCache;
+
+ this.#cache = new Map<string, string>(this.#initialState());
+ this.#promises = new Map<string, Promise<string>>();
+ }
+
+ async load(shape: string): Promise<string>
+ {
+ if (this.#cache.has(shape)) {
+ return this.#cache.get(shape)!;
+ }
+
+ if (this.#promises.has(shape)) {
+ return this.#promises.get(shape)!;
+ }
+
+ const url = `${this.#baseUrl}images/icons/blue/${shape}.svg`;
+
+ const promise = (async () => {
+ try {
+ const response = await fetch(url);
+ if (!response.ok) {
+ return '';
+ }
+ let svg = await response.text();
+
+ svg = svg.replace(/fill="(?!none)[^"]+"/g, 'fill="currentColor"');
+ svg = svg.replace(/(width|height)="[^"]+"/g, '');
+
+ this.store(shape, svg);
+
+ return svg;
+ } catch(error) {
+ console.error(`IconLoader: Fehler beim Laden von ${shape}`, error);
+ return '';
+ } finally {
+ this.#promises.delete(shape);
+ }
+ })();
+
+ this.#promises.set(shape, promise);
+
+ return promise;
+ }
+
+ store(shape: string, svg: string): void
+ {
+ this.#cache.set(shape, svg);
+
+ this.#getStorage()?.setItem(
+ this.#cacheKey,
+ JSON.stringify([...this.#cache])
+ )
+ }
+
+ #getStorage(): Storage|null
+ {
+ if (this.#useCache === 'off') {
+ return null;
+ }
+ return this.#useCache === 'session' ? sessionStorage : localStorage;
+ }
+
+ #initialState(): [string, string][]
+ {
+ const cached = this.#getStorage()?.getItem(this.#cacheKey);
+ if (!cached) {
+ return [];
+ }
+
+ try {
+ return JSON.parse(cached);
+ } catch {
+ return [];
+ }
+ }
+}
+
+const defaultLoader = new IconLoader(window.STUDIP.ASSETS_URL, 'session');
+
+export default defaultLoader;
+export { IconLoader };
diff --git a/resources/assets/javascripts/lib/inline-editing.js b/resources/assets/javascripts/lib/inline-editing.js
deleted file mode 100644
index b1f025f..0000000
--- a/resources/assets/javascripts/lib/inline-editing.js
+++ /dev/null
@@ -1,138 +0,0 @@
-class InlineEditing
-{
- static init(element) {
- if (!element) {
- return;
- }
-
- var text = jQuery(element).text().trim();
-
- var icon_path = STUDIP.ASSETS_URL + '/images/icons/blue/NAME.svg';
- var input_type = jQuery(element).data('input-type').toLowerCase();
- var input_name = jQuery(element).data('input-name');
- var icon_size = jQuery(element).data('icon-size');
- if (!icon_size) {
- icon_size = '20px';
- }
-
- //Build the display container:
- var text_container = jQuery('<span class="text"></span>');
- jQuery(text_container).text(text);
- var icon_container = jQuery('<div></div>');
- var icon_element = jQuery('<img class="edit-button"></img>');
- jQuery(icon_element).attr('width', icon_size);
- jQuery(icon_element).attr('height', icon_size);
- jQuery(icon_element).attr('src', icon_path.replace('NAME', 'edit'));
- jQuery(icon_container).append(icon_element);
- var display_container = jQuery(
- '<div class="display-container"></div>'
- );
- jQuery(display_container).append(text_container);
- jQuery(display_container).append(icon_container);
-
- var input_field = undefined;
- var edit_icons_container = undefined;
- var accept_icon = jQuery('<img class="save-button"></img>');
- jQuery(accept_icon).attr('width', icon_size);
- jQuery(accept_icon).attr('height', icon_size);
- jQuery(accept_icon).attr('src', icon_path.replace('NAME', 'accept'));
- var abort_icon = jQuery('<img class="abort-button"></img>');
- jQuery(abort_icon).attr('width', icon_size);
- jQuery(abort_icon).attr('height', icon_size);
- jQuery(abort_icon).attr('src', icon_path.replace('NAME', 'decline'));
-
- if (input_type == 'textarea') {
- input_field = jQuery('<textarea class="input-field"></textarea>');
- jQuery(input_field).attr('name', input_name);
- jQuery(input_field).text(text);
- edit_icons_container = jQuery('<div></div>');
- } else {
- input_field = jQuery('<input class="input-field">');
- jQuery(input_field).attr('type', input_type);
- jQuery(input_field).attr('name', input_name);
- jQuery(input_field).val(text);
- edit_icons_container = jQuery('<span></span>');
- }
- jQuery(edit_icons_container).append(accept_icon);
- jQuery(edit_icons_container).append(abort_icon);
-
- var edit_container = jQuery('<div class="edit-container invisible"></div>');
- jQuery(edit_container).append(input_field);
- jQuery(edit_container).append(edit_icons_container);
-
- jQuery(element).empty();
- jQuery(element).append(display_container);
- jQuery(element).append(edit_container);
- }
-
-
- static activate(element) {
- var container = jQuery(element).parents('[data-inline-editing]');
- if (!container) {
- return;
- }
-
- jQuery(container).children('.display-container').addClass('invisible');
- jQuery(container).children('.edit-container').removeClass('invisible');
- }
-
-
- static save(element) {
- var container = jQuery(element).parents('[data-inline-editing]');
- if (!container) {
- return;
- }
- var ajax_url = jQuery(container).data('inline-editing');
-
- var text_field = jQuery(container).find('.text')[0];
- if (!text_field) {
- return;
- }
- var input_field = jQuery(container).find('.input-field')[0];
- if (!input_field) {
- return;
- }
- var input_name = jQuery(container).data('input-name');
- var input_value = jQuery(input_field).val();
- var data = {
- quiet: 1
- };
- data[input_name] = input_value;
-
- jQuery.ajax(
- {
- url: ajax_url,
- method: 'POST',
- data: data
- }
- ).done(
- function() {
- jQuery(text_field).text(input_value);
- jQuery(container).find('.edit-container').addClass('invisible');
- jQuery(container).find('.display-container').removeClass('invisible');
- }
- ).fail(
- function(data) {
- jQuery(input_field).css('border-color', 'red');
- if (data) {
- jQuery(container).find('.error-message').val(data);
- }
- }
- );
- }
-
-
- static abort(element) {
- var container = jQuery(element).parents('[data-inline-editing]');
- if (!container) {
- return;
- }
-
- jQuery(container).children('.edit-container').addClass('invisible');
- jQuery(container).children('.display-container').removeClass('invisible');
-
- }
-}
-
-
-export default InlineEditing;
diff --git a/resources/assets/javascripts/lib/members.js b/resources/assets/javascripts/lib/members.js
index 3fbdfb9..1630c71 100644
--- a/resources/assets/javascripts/lib/members.js
+++ b/resources/assets/javascripts/lib/members.js
@@ -1,13 +1,18 @@
const Members = {
addPersonToSelection: function(userId, name) {
- var target = $('#persons-to-add'),
- newEl = $('<li>').html(
+ const target = $('#persons-to-add');
+ let newEl = $('<li>').html(
$('<span>')
.html(name)
.text()
- ),
- input = $('<input type="hidden" name="users[]">').val(userId),
- remove = $('<img>').attr('src', STUDIP.ASSETS_URL + 'images/icons/blue/trash.svg');
+ );
+ let input = $('<input type="hidden" name="users[]">').val(userId);
+ let remove = $('<button>')
+ .addClass('btn-icon btn-icon--trash btn-icon--inline')
+ .attr('type', 'button')
+ .on('click', function () {
+ $(this).parent().remove();
+ });
remove.on('click', function() {
$(this)