diff --git a/app/assets/javascripts/components/course_labels_search_bar.ts b/app/assets/javascripts/components/course_labels_search_bar.ts deleted file mode 100644 index 917ddef405..0000000000 --- a/app/assets/javascripts/components/course_labels_search_bar.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { customElement, property } from "lit/decorators.js"; -import { html, TemplateResult } from "lit"; -import { Option } from "components/datalist_input"; -import "components/datalist_input"; -import { i18n } from "i18n/i18n"; -import { DodonaElement } from "components/meta/dodona_element"; -/** - * This component represents a list of the selected course labels - * - * @element d-course-labels - * @prop {string[]} Labels - the labels of a user in a certain course - */ -@customElement("d-course-labels") -export class CourseLabelTokens extends DodonaElement { - @property({ type: Array }) - labels: string[]; - - processClick(e: Event, label: string): void { - this.labels.splice(this.labels.indexOf(label), 1); - this.requestUpdate(); - } - - render(): TemplateResult { - if (!this.labels) { - return html``; - } - - return html` - ${ this.labels.map( label => html` - - ${label} - this.processClick(e, label)}>× - - - `)} - - `; - } -} - -/** - * This component represents a search bar for course labels, also showing a list of already selected labels. - * It allows for searching existing labels, or creating new ones, and adding them to the user of a certain course - * - * @element d-course-labels-search-bar - * - * @prop {{id: number, name: string}[]} Labels - all the labels already used in a course - * @prop {string[]} SelectedLabels - the labels that have been added to the user - */ -@customElement("d-course-labels-search-bar") -export class CourseLabelsSearchBar extends DodonaElement { - @property({ type: Array }) - labels: {id: number, name: string}[]; - - @property({ type: Array }) - selected_labels: string[]; - - @property({ state: true }) - selected_label: string; - - @property({ state: true }) - filter: string; - - get options(): Option[] { - return this.labels.map(i => ({ label: i.name, value: i.id.toString() })); - } - - addLabel(): void { - const selectedLabel = this.selected_label.trim(); - if (selectedLabel.length > 0) { - const newSelectedLabels = this.selected_labels.slice(); - if (!this.selected_labels.includes(selectedLabel)) { - newSelectedLabels.push(selectedLabel); - this.selected_labels = newSelectedLabels; - } - this.filter = ""; - } - } - - handleInput(e: CustomEvent): void { - this.selected_label = e.detail.label; - this.filter = e.detail.label; - if (e.detail.value) { - this.addLabel(); - } - } - - render(): TemplateResult { - return html` -
- -
- this.handleInput(e)} - placeholder="${i18n.t("js.course_labels_search_bar.placeholder")}" - > - ${i18n.t("js.course_labels_search_bar.add")} -
- ${i18n.t("js.course_labels_search_bar.edit_explanation")} -
- `; - } -} diff --git a/app/assets/javascripts/components/labels_search_bar.ts b/app/assets/javascripts/components/labels_search_bar.ts new file mode 100644 index 0000000000..42bf862777 --- /dev/null +++ b/app/assets/javascripts/components/labels_search_bar.ts @@ -0,0 +1,124 @@ +import { customElement, property } from "lit/decorators.js"; +import { html, TemplateResult } from "lit"; +import { Option } from "components/datalist_input"; +import "components/datalist_input"; +import { i18n } from "i18n/i18n"; +import { DodonaElement } from "components/meta/dodona_element"; +/** + * This component represents a list of the selected labels + * + * @element d-course-labels + * @prop {string[]} labels - the labels of a user in a certain course + * @prop {string} name - the name of the input field (used in form submit) + */ +@customElement("d-labels") +export class LabelTokens extends DodonaElement { + @property({ type: Array }) + labels: string[]; + @property({ type: String }) + name: string; + + removeLabel(label: string): void { + this.dispatchEvent(new CustomEvent("remove-label", { + detail: { + value: label, + }, + })); + } + + render(): TemplateResult { + if (!this.labels) { + return html``; + } + + return html` + ${ this.labels.length > 0 ? html` +
+ ${ this.labels.map( label => html` + ${label} + this.removeLabel(label)}>× + + `)} +
+ ` : html`` } + + `; + } +} + +/** + * This component represents a search bar for labels, also showing a list of already selected labels. + * It allows for searching existing labels, or creating new ones, and adding them to the user of a certain course + * + * @element d-labels-search-bar + * + * @prop {{id: number, name: string}[]} labels - all the labels already used in a course + * @prop {string[]} selected_labels - the labels that have been added to the user + * @prop {string} Name - the name of the input field (used in form submit) + */ +@customElement("d-labels-search-bar") +export class LabelsSearchBar extends DodonaElement { + @property({ type: Array }) + labels: {id: number, name: string}[]; + @property({ type: Array }) + selected_labels: string[]; + @property({ type: String }) + name: string; + + @property({ state: true }) + selected_label: string; + @property({ state: true }) + filter: string; + + get options(): Option[] { + return this.labels + .filter(i => !this.selected_labels.includes(i.name)) + .map(i => ({ label: i.name, value: i.id.toString() })); + } + + addLabel(): void { + const selectedLabel = this.selected_label.trim(); + if (selectedLabel.length > 0 && !this.selected_labels.includes(selectedLabel)) { + this.selected_labels = [...this.selected_labels, selectedLabel]; + this.filter = ""; + } + } + + handleInput(e: CustomEvent): void { + this.selected_label = e.detail.label; + this.filter = e.detail.label; + } + + handleKeyDown(e: KeyboardEvent): void { + if (e.key === "Enter" || e.key === "Tab") { + this.addLabel(); + } + } + + removeLabel(label: string): void { + this.selected_labels = this.selected_labels.filter(i => i !== label); + } + + render(): TemplateResult { + return html` +
+ this.removeLabel(e.detail.value)} + > +
+ this.handleInput(e)} + @keydown=${e => this.handleKeyDown(e)} + placeholder="${i18n.t("js.labels_search_bar.placeholder")}" + > + ${i18n.t("js.labels_search_bar.add")} +
+ ${i18n.t("js.labels_search_bar.edit_explanation")} +
+ `; + } +} diff --git a/app/assets/javascripts/i18n/translations.json b/app/assets/javascripts/i18n/translations.json index ed2910a8e5..75631ffce4 100644 --- a/app/assets/javascripts/i18n/translations.json +++ b/app/assets/javascripts/i18n/translations.json @@ -181,11 +181,6 @@ "course-removed-failed": "Removing course failed", "course-removed-success": "Course removed", "course_labels": "Member labels", - "course_labels_search_bar": { - "add": "Add", - "edit_explanation": "Add an existing userlabel or create a new one.", - "placeholder": "Label" - }, "courses": "Courses", "ctimeseries_desc": "This graph shows the evolution of the percentage of students that correctly solved each exercise.", "ctimeseries_title": "Users with at least one correct submission", @@ -241,6 +236,11 @@ "judges": "Judges", "label-undeletable": "This label can't be deleted because it was set in the dirconfig file of a parent directory of the learning activity.", "labels": "Labels", + "labels_search_bar": { + "add": "Add", + "edit_explanation": "Add an existing label or create a new one.", + "placeholder": "Label" + }, "loading": "Loading...", "machine_annotation": { "external_url": "more information" @@ -706,11 +706,6 @@ "course-removed-failed": "Cursus verwijderen mislukt", "course-removed-success": "Cursus verwijderd", "course_labels": "Gebruikerlabels", - "course_labels_search_bar": { - "add": "Toevoegen", - "edit_explanation": "Voeg een bestaand gebruikerlabel toe of maak een nieuwe aan.", - "placeholder": "Label" - }, "courses": "Cursussen", "ctimeseries_desc": "Deze grafiek geeft de evolutie weer van het percentage studenten dat een oefening correct had.", "ctimeseries_title": "Gebruikers met minstens één correcte oplossing", @@ -766,6 +761,11 @@ "judges": "Judges", "label-undeletable": "Dit label kan niet verwijderd worden omdat het werd ingesteld in het dirconfig bestand van een bovenliggende map.", "labels": "Labels", + "labels_search_bar": { + "add": "Toevoegen", + "edit_explanation": "Voeg een bestaand label toe of maak een nieuwe aan.", + "placeholder": "Label" + }, "loading": "Aan het laden...", "machine_annotation": { "external_url": "meer informatie" diff --git a/app/assets/stylesheets/components/courselabel-edit.css.scss b/app/assets/stylesheets/components/courselabel-edit.css.scss index de68774da0..8aeea6e609 100644 --- a/app/assets/stylesheets/components/courselabel-edit.css.scss +++ b/app/assets/stylesheets/components/courselabel-edit.css.scss @@ -1,6 +1,14 @@ -d-course-labels-search-bar { - .labels-searchbar-group { - padding-top: 10px; +d-labels-search-bar { + .labels { + margin-bottom: 10px; + } + + d-datalist-input { + flex: auto; + + input { + height: 33.5px; + } } .add-button { diff --git a/app/javascript/packs/course_membership.js b/app/javascript/packs/course_membership.js deleted file mode 100644 index 2d7dd0ade7..0000000000 --- a/app/javascript/packs/course_membership.js +++ /dev/null @@ -1 +0,0 @@ -import "components/course_labels_search_bar"; diff --git a/app/javascript/packs/label_search_bar.js b/app/javascript/packs/label_search_bar.js new file mode 100644 index 0000000000..c307f168a3 --- /dev/null +++ b/app/javascript/packs/label_search_bar.js @@ -0,0 +1 @@ +import "components/labels_search_bar"; diff --git a/app/views/activities/_form.html.erb b/app/views/activities/_form.html.erb index 310f41a821..c931346e67 100644 --- a/app/views/activities/_form.html.erb +++ b/app/views/activities/_form.html.erb @@ -1,3 +1,6 @@ +<% content_for :javascripts do %> + <% javascript_include_tag 'label_search_bar' %> +<% end %> <%= form_for(activity.becomes(Activity), :url => activity_scoped_path(activity: activity, course: @course, series: @series), :html => {:class => 'form-horizontal'}) do |f| %> <% if activity.errors.any? %>
@@ -23,8 +26,13 @@
<%= f.label :labels, :class => "col-sm-3 col-12 col-form-label" %> -
<%= f.text_field :labels, class: 'form-control', disable: !f.permission?(:labels), value: activity.labels.map(&:name).join(','), placeholder: t(".labels") %>
- <%= t '.labels_delimiter' %> +
+ +
diff --git a/app/views/course_members/_form.html.erb b/app/views/course_members/_form.html.erb index 6afdde7460..061e08dd5d 100644 --- a/app/views/course_members/_form.html.erb +++ b/app/views/course_members/_form.html.erb @@ -1,5 +1,5 @@ <% content_for :javascripts do %> - <% javascript_include_tag 'course_membership' %> + <% javascript_include_tag 'label_search_bar' %> <% end %> <%= form_for(course_membership, :url => course_member_path(course, user), :html => {:class => 'form-horizontal'}) do |f| %> <% if course_membership.errors.any? %> @@ -16,10 +16,11 @@
<%= f.label :course_labels, :class => 'col-sm-2 col-10 col-form-label' %>
- + name="course_membership[course_labels]" + >
diff --git a/config/locales/js/en.yml b/config/locales/js/en.yml index 9ea3f2d159..d4bab5d56f 100644 --- a/config/locales/js/en.yml +++ b/config/locales/js/en.yml @@ -297,10 +297,10 @@ en: sign_in_search_bar: institution_search: "Type to search for your institution" log_in: Sign in - course_labels_search_bar: + labels_search_bar: placeholder: "Label" add: "Add" - edit_explanation: "Add an existing userlabel or create a new one." + edit_explanation: "Add an existing label or create a new one." progress_bar: series-admin-progress: key: ".singular |||| .plural" diff --git a/config/locales/js/nl.yml b/config/locales/js/nl.yml index 519846d864..81d7882eb9 100644 --- a/config/locales/js/nl.yml +++ b/config/locales/js/nl.yml @@ -297,10 +297,10 @@ nl: sign_in_search_bar: institution_search: "Typ om jouw school te vinden" log_in: Aanmelden - course_labels_search_bar: + labels_search_bar: placeholder: "Label" add: "Toevoegen" - edit_explanation: "Voeg een bestaand gebruikerlabel toe of maak een nieuwe aan." + edit_explanation: "Voeg een bestaand label toe of maak een nieuwe aan." progress_bar: series-admin-progress: key: ".singular |||| .plural"