Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/assets/javascripts/folio/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//= require folio/input/date_time
//= require folio/input/embed
//= require folio/input/multiselect
//= require folio/input/ordered_multiselect
//= require folio/input/numeral
//= require folio/input/phone
//= require folio/input/redactor
Expand Down
793 changes: 793 additions & 0 deletions app/assets/javascripts/folio/input/ordered_multiselect.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions app/assets/stylesheets/folio/_input.sass
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
@import "./input/date_time"
@import "./input/embed"
@import "./input/multiselect"
@import "./input/ordered_multiselect"
@import "./input/phone"
@import "./input/tiptap"
@import "./input/url"
158 changes: 158 additions & 0 deletions app/assets/stylesheets/folio/input/_ordered_multiselect.sass
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
.f-input-ordered-multiselect
+font-size($input-font-size)
+border-radius($input-border-radius, 0)
+box-shadow($input-box-shadow)
background: $input-bg
color: $input-color
border: $input-border-width solid $input-border-color
padding: $input-padding-y $input-padding-x
font-family: $input-font-family
font-weight: $input-font-weight
line-height: $input-line-height
min-height: 49px
position: relative

.form-group-invalid &
border-color: var(--#{$prefix}form-invalid-border-color)

// Tom Select overrides
.ts-wrapper
border: 0

&.focus .ts-control
box-shadow: none !important
border-color: transparent

.ts-control
border: 0
box-shadow: none !important
background: transparent
padding: 0

> input
&::placeholder
color: $input-placeholder-color

.ts-dropdown
z-index: 102

.no-results
padding: $input-padding-y $input-padding-x
color: $text-muted
cursor: default
pointer-events: none
text-align: center

// --- Selected items list ---

&__list
margin-bottom: $grid-gutter-half / 2
max-height: 300px
overflow-y: auto
-webkit-overflow-scrolling: touch

&__item
display: flex
align-items: flex-start
padding: 2px 0

&--flash
animation: f-input-ordered-multiselect-flash 0.6s ease

@keyframes f-input-ordered-multiselect-flash
0%
background-color: rgba($success, 0.2)
100%
background-color: transparent

&__item-handle
cursor: grab
opacity: 0.3
user-select: none
flex-shrink: 0
display: flex
align-items: center
margin-right: $grid-gutter-half / 2

&:hover
opacity: 0.6

&__item-label
flex: 1
min-width: 0

&__item-usage,
&__option-usage
display: block
font-size: $font-size-sm
color: $text-muted
white-space: nowrap
overflow: hidden
text-overflow: ellipsis

&__item-actions,
&__option-actions
display: flex
align-items: center
align-self: stretch
gap: 2px
margin-left: $grid-gutter-half / 2
flex-shrink: 0

&__item-action,
&__option-action
padding: 2px 4px
line-height: 1
cursor: pointer
display: flex
align-items: center

&--danger
color: $danger

&__item-rename-input,
&__option-edit-input
width: 100%
border: 0
outline: none
background: transparent
font: inherit
padding: 0
box-shadow: none

&:focus
box-shadow: none

// --- Sortable ---

&__sortable-placeholder
height: 34px
background: $light-gray
border: 1px dashed $medium-gray
border-radius: $border-radius

// --- Dropdown option with actions ---

&__option-with-actions
display: flex
align-items: flex-start
width: 100%

&__option-label
flex: 1
min-width: 0

// No usage labels variant — center-align items vertically
.f-input-ordered-multiselect--no-usage
.f-input-ordered-multiselect__item,
.f-input-ordered-multiselect__option-with-actions
align-items: center

// Not sortable variant
.f-input-ordered-multiselect--not-sortable
.f-input-ordered-multiselect__item
padding-left: 0

.f-input-ordered-multiselect__item-handle
display: none

87 changes: 86 additions & 1 deletion app/controllers/folio/console/api/autocompletes_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -311,13 +311,15 @@ def react_select
text = record.to_console_label
text = "#{text} – #{record.class.model_name.human}" if show_model_names

{
hash = {
id: record.id,
text:,
label: text,
value: Folio::Console::StiHelper.sti_record_to_select_value(record),
type: klass.to_s
}
hash[:usage_labels] = record.usage_labels_for_warning if record.respond_to?(:usage_labels_for_warning)
hash
end

render json: { data: response, meta: meta_from_pagy(pagination) }
Expand Down Expand Up @@ -413,6 +415,89 @@ def react_select
end
end

def react_select_create
klass = params.require(:class_name).safe_constantize

if klass && klass < ActiveRecord::Base
record = klass.new
record.title = params.require(:label)
record.site = Folio::Current.site if record.respond_to?(:site=)
authorize! :create, record

if record.save
render json: {
data: {
id: record.id,
text: record.to_console_label,
label: record.to_console_label,
value: record.id.to_s,
type: record.class.to_s
}
}
else
render json: { error: record.errors.full_messages.join(", ") }, status: :unprocessable_entity
end
else
render json: { error: "Invalid class" }, status: :unprocessable_entity
end
end

def react_select_update
klass = params.require(:class_name).safe_constantize
id = params.require(:id)
label = params.require(:label)

if klass && klass < ActiveRecord::Base
record = klass.find(id)
authorize! :update, record
record.title = label

if record.save
render json: {
data: {
id: record.id,
text: record.to_console_label,
label: record.to_console_label,
value: record.id.to_s,
type: record.class.to_s
}
}
else
render json: { error: record.errors.full_messages.join(", ") }, status: :unprocessable_entity
end
else
render json: { error: "Invalid class" }, status: :unprocessable_entity
end
end

def react_select_destroy
klass = params.require(:class_name).safe_constantize
id = params.require(:id)

if klass && klass < ActiveRecord::Base
record = klass.find(id)
authorize! :destroy, record

usage_count = record.respond_to?(:usage_count_for_warning) ? record.usage_count_for_warning : 0
usage_labels = record.respond_to?(:usage_labels_for_warning) ? record.usage_labels_for_warning : []

if params[:confirmed] == "true"
record.destroy!
render json: { data: { id: record.id, destroyed: true } }
else
render json: {
data: {
id: record.id,
usage_count: usage_count,
usage_labels: usage_labels,
}
}
end
else
render json: { error: "Invalid class" }, status: :unprocessable_entity
end
end

private
def apply_ordered_for_folio_console_selects(scope, klass)
return [scope, false] unless klass.respond_to?(:ordered_for_folio_console_selects)
Expand Down
Loading