diff options
| author | Ron Lucke <lucke@elan-ev.de> | 2025-07-14 09:36:18 +0200 |
|---|---|---|
| committer | Ron Lucke <lucke@elan-ev.de> | 2025-07-14 09:36:18 +0200 |
| commit | 4355ded9bc56e0b06fbceffe61ddc37061cc3bc7 (patch) | |
| tree | 348493b6b0fd1286b86f213e5077413b97cf9747 /resources/assets/javascripts/lib | |
| parent | 1e59dd2dacc51b3313d7780b66d4bf72e0484f86 (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.js | 94 | ||||
| -rw-r--r-- | resources/assets/javascripts/lib/fullcalendar.js | 22 | ||||
| -rw-r--r-- | resources/assets/javascripts/lib/icon-loader.ts | 97 | ||||
| -rw-r--r-- | resources/assets/javascripts/lib/inline-editing.js | 138 | ||||
| -rw-r--r-- | resources/assets/javascripts/lib/members.js | 15 |
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) |
