django-smartbase-admin 1.0.14__py3-none-any.whl → 1.0.15__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. django_smartbase_admin/admin/admin_base.py +14 -14
  2. django_smartbase_admin/admin/site.py +7 -1
  3. django_smartbase_admin/admin/widgets.py +13 -1
  4. django_smartbase_admin/engine/admin_base_view.py +14 -0
  5. django_smartbase_admin/locale/sk/LC_MESSAGES/django.mo +0 -0
  6. django_smartbase_admin/locale/sk/LC_MESSAGES/django.po +22 -13
  7. django_smartbase_admin/migrations/0005_sbadminuserconfiguration.py +26 -0
  8. django_smartbase_admin/models.py +33 -0
  9. django_smartbase_admin/services/configuration.py +13 -0
  10. django_smartbase_admin/static/sb_admin/build/tailwind_config_partials/colors.js +4 -0
  11. django_smartbase_admin/static/sb_admin/css/ckeditor/ckeditor_content_dark.css +208 -0
  12. django_smartbase_admin/static/sb_admin/dist/calendar.js +1 -1
  13. django_smartbase_admin/static/sb_admin/dist/calendar_style.css +1 -1
  14. django_smartbase_admin/static/sb_admin/dist/chart.js +1 -1
  15. django_smartbase_admin/static/sb_admin/dist/main.js +1 -1
  16. django_smartbase_admin/static/sb_admin/dist/main_style.css +1 -1
  17. django_smartbase_admin/static/sb_admin/dist/table.js +1 -1
  18. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Moon.svg +3 -0
  19. django_smartbase_admin/static/sb_admin/sprites/sb_admin/Sun-one.svg +3 -0
  20. django_smartbase_admin/static/sb_admin/src/css/_base.css +1 -1
  21. django_smartbase_admin/static/sb_admin/src/css/_colors.css +257 -82
  22. django_smartbase_admin/static/sb_admin/src/css/_components.css +1 -1
  23. django_smartbase_admin/static/sb_admin/src/css/_tabulator.css +2 -2
  24. django_smartbase_admin/static/sb_admin/src/css/calendar.css +3 -3
  25. django_smartbase_admin/static/sb_admin/src/css/components/_button.css +1 -1
  26. django_smartbase_admin/static/sb_admin/src/css/components/_dropdown.css +25 -7
  27. django_smartbase_admin/static/sb_admin/src/css/components/_input.css +33 -2
  28. django_smartbase_admin/static/sb_admin/src/css/components/_modal.css +1 -1
  29. django_smartbase_admin/static/sb_admin/src/css/components/_query-builder.css +1 -7
  30. django_smartbase_admin/static/sb_admin/src/css/style.css +1 -1
  31. django_smartbase_admin/static/sb_admin/src/js/main.js +58 -15
  32. django_smartbase_admin/static/sb_admin/src/js/multiselect.js +10 -19
  33. django_smartbase_admin/static/sb_admin/src/js/utils.js +47 -23
  34. django_smartbase_admin/templates/sb_admin/filter_widgets/advanced_filters/multiple_choice_field.html +1 -1
  35. django_smartbase_admin/templates/sb_admin/filter_widgets/radio_choice_field.html +1 -1
  36. django_smartbase_admin/templates/sb_admin/navigation.html +9 -7
  37. django_smartbase_admin/templates/sb_admin/sb_admin_base_no_sidebar.html +1 -1
  38. django_smartbase_admin/templates/sb_admin/sprites/sb_admin.svg +1 -1
  39. django_smartbase_admin/templates/sb_admin/tailwind_whitelist.html +2 -1
  40. django_smartbase_admin/templates/sb_admin/widgets/{checkbox_select.html → checkbox_dropdown.html} +2 -2
  41. django_smartbase_admin/templates/sb_admin/widgets/radio.html +3 -2
  42. django_smartbase_admin/templates/sb_admin/widgets/radio_dropdown.html +30 -0
  43. django_smartbase_admin/views/user_config_view.py +35 -0
  44. {django_smartbase_admin-1.0.14.dist-info → django_smartbase_admin-1.0.15.dist-info}/METADATA +1 -1
  45. {django_smartbase_admin-1.0.14.dist-info → django_smartbase_admin-1.0.15.dist-info}/RECORD +47 -41
  46. {django_smartbase_admin-1.0.14.dist-info → django_smartbase_admin-1.0.15.dist-info}/LICENSE.md +0 -0
  47. {django_smartbase_admin-1.0.14.dist-info → django_smartbase_admin-1.0.15.dist-info}/WHEEL +0 -0
@@ -27,12 +27,13 @@ import Range from "./range"
27
27
  import Sorting from "./sorting"
28
28
  import Autocomplete from "./autocomplete"
29
29
  import ChoicesJS from "./choices"
30
- import {setCookie} from "./utils"
30
+ import {setCookie, setDropdownLabel} from "./utils"
31
31
  import Multiselect from "./multiselect"
32
32
 
33
33
  class Main {
34
34
  constructor() {
35
35
  document.body.classList.add('js-ready')
36
+ this.handleColorSchemeChange()
36
37
 
37
38
  const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
38
39
  tooltipTriggerList.map((tooltipTriggerEl) => {
@@ -104,6 +105,22 @@ class Main {
104
105
  this.handleLocationHashFromTabs()
105
106
  }
106
107
 
108
+ handleColorSchemeChange() {
109
+ const picker = document.querySelector('.js-color-scheme-picker')
110
+ if(!picker) {
111
+ return
112
+ }
113
+ picker.addEventListener('change', (e)=>{
114
+ if(e.target.value) {
115
+ document.documentElement.setAttribute('data-theme', e.target.value)
116
+ this.switchCKEditorTheme(e.target.value)
117
+ return
118
+ }
119
+ document.documentElement.removeAttribute('data-theme')
120
+ })
121
+ this.switchCKEditorTheme(document.documentElement.dataset.theme)
122
+ }
123
+
107
124
  initInlines(target) {
108
125
  target = target || document
109
126
  const inlineGroups = target.querySelectorAll('.inline-group')
@@ -173,7 +190,7 @@ class Main {
173
190
  } else {
174
191
  offset = [0, 8]
175
192
  }
176
- return new Dropdown(dropdownToggleEl, {
193
+ const dropdown = new Dropdown(dropdownToggleEl, {
177
194
  autoClose: 'outside',
178
195
  offset: offset,
179
196
  popperConfig(defaultBsPopperConfig) {
@@ -184,6 +201,14 @@ class Main {
184
201
  return {...defaultBsPopperConfig, ...elementConf, strategy: 'fixed'}
185
202
  }
186
203
  })
204
+ const dropdownWrapper = dropdownToggleEl.closest('.js-dropdown-wrapper')
205
+ if(dropdownWrapper) {
206
+ const dropdownLabelEl = dropdownWrapper.querySelector('.js-dropdown-label')
207
+ dropdown._menu.addEventListener('change', ()=>{
208
+ setDropdownLabel(dropdown._menu, dropdownLabelEl)
209
+ })
210
+ }
211
+ return dropdown
187
212
  })
188
213
  }
189
214
 
@@ -304,28 +329,46 @@ class Main {
304
329
  })
305
330
  }
306
331
 
307
- initCKEditor(target) {
332
+ initCKEditor(target, config, force=false) {
308
333
  if (!window.CKEDITOR) {
309
334
  return
310
335
  }
311
336
  target = target || document
312
337
  target.querySelectorAll('textarea[data-type="ckeditortype"]').forEach((textarea) => {
313
- const id = textarea.id
314
- if (!id) {
315
- return
316
- }
317
- if(
318
- textarea.getAttribute("data-processed") == "0" &&
319
- textarea.id.indexOf("__prefix__") == -1
320
- ) {
321
- if(window.CKEDITOR.instances[id]) {
322
- window.CKEDITOR.instances[id].destroy(true)
323
- }
324
- window.CKEDITOR.replace(id, JSON.parse(textarea.getAttribute("data-config")))
338
+ if( force || (textarea.getAttribute("data-processed") == "0" && textarea.id.indexOf("__prefix__") == -1)) {
339
+ this.reinitCKEditor(textarea, config)
325
340
  }
326
341
  })
327
342
  }
328
343
 
344
+ reinitCKEditor(textarea, config) {
345
+ const id = textarea.id
346
+ if (!id) {
347
+ return
348
+ }
349
+ if(window.CKEDITOR.instances[id]) {
350
+ window.CKEDITOR.instances[id].destroy(true)
351
+ }
352
+ config = config || {}
353
+ const new_config = {...JSON.parse(textarea.getAttribute("data-config")), ...config}
354
+ window.CKEDITOR.replace(id, new_config)
355
+ }
356
+
357
+ switchCKEditorTheme(colorScheme) {
358
+ if(!window.CKEDITOR) {
359
+ return
360
+ }
361
+ let dark = colorScheme === 'dark'
362
+ if(colorScheme === 'auto') {
363
+ dark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
364
+ }
365
+ if(dark) {
366
+ this.initCKEditor(document, {'contentsCss': '/static/sb_admin/css/ckeditor/ckeditor_content_dark.css', uiColor: '#000000'}, true)
367
+ return
368
+ }
369
+ this.initCKEditor(document, {'contentsCss':window.CKEDITOR.config.contentsCss}, true)
370
+ }
371
+
329
372
  clearFilter(inputId) {
330
373
  const fieldElem = document.querySelector(`#${inputId}`)
331
374
  fieldElem.value = ''
@@ -1,3 +1,5 @@
1
+ import {setDropdownLabel} from "./utils"
2
+
1
3
  export default class Multiselect {
2
4
  constructor(selector_override, options_override, target) {
3
5
  target = target || document
@@ -9,9 +11,13 @@ export default class Multiselect {
9
11
  document.addEventListener('change', e => {
10
12
  const wrapperEl = e.target.closest(this.wrapperSelector)
11
13
  const multiselectInput = wrapperEl?.querySelector(selector)
14
+ if(!wrapperEl || !multiselectInput) {
15
+ // not filter widget
16
+ return
17
+ }
12
18
  const isCheckboxClicked = e.target.type === 'checkbox'
13
19
  const selectAllEl = wrapperEl?.querySelector(`.${selectAllClass}`)
14
- if(multiselectInput && isCheckboxClicked) {
20
+ if(isCheckboxClicked) {
15
21
  const checkboxes = Array.from(this.getCheckboxes(multiselectInput))
16
22
  if(e.target.classList.contains(selectAllClass)) {
17
23
  checkboxes.forEach(el => {
@@ -79,35 +85,20 @@ export default class Multiselect {
79
85
  })
80
86
  }
81
87
 
82
-
83
- setLabel(wrapper, valueEl) {
84
- let labels = []
85
- wrapper.querySelectorAll('input[type="checkbox"]').forEach(el => {
86
- if (el.checked) {
87
- labels.push(document.querySelector(`label[for="${el.id}"]`).innerText)
88
- }
89
- })
90
- valueEl.innerHTML = labels.join(',')
91
- }
92
-
93
88
  clearAll(wrapper, valueEl) {
94
89
  wrapper.querySelectorAll('input[type="checkbox"]').forEach(el => {
95
90
  el.checked = false
96
91
  })
97
- this.setLabel(wrapper, valueEl)
92
+ setDropdownLabel(wrapper, valueEl)
98
93
  }
99
94
 
100
95
 
101
96
  initDetailMultiselect(wrapper) {
102
- const valueEl = wrapper.querySelector('.js-value')
103
97
  const clearEl = wrapper.querySelector('.js-clear')
104
98
 
105
- wrapper.addEventListener('change', () => {
106
- this.setLabel(wrapper, valueEl)
107
- })
108
99
  clearEl?.addEventListener('click', () => {
109
- this.clearAll(wrapper, valueEl)
100
+ this.clearAll(wrapper)
110
101
  })
111
- this.setLabel(wrapper, valueEl)
102
+ setDropdownLabel(wrapper)
112
103
  }
113
104
  }
@@ -66,6 +66,52 @@ export const filterInputValueChangeListener = (inputSelector, callbackFunction)
66
66
  })
67
67
  }
68
68
 
69
+ const getResultLabel = (valueOrObject, separator=', ') => {
70
+ const labelArray = []
71
+ const entries = Object.values(valueOrObject)
72
+ let hasMaxEntries = false
73
+ for (let [index, item] of entries.entries()) {
74
+ if (index === window.sb_admin_const.MULTISELECT_FILTER_MAX_CHOICES_SHOWN) {
75
+ break
76
+ }
77
+ if(entries.length > 1 && item.value === window.sb_admin_const.SELECT_ALL_KEYWORD) {
78
+ continue
79
+ }
80
+ if (index === window.sb_admin_const.MULTISELECT_FILTER_MAX_CHOICES_SHOWN - 2 && entries[index + 2]) {
81
+ labelArray.push(item.label)
82
+ hasMaxEntries = true
83
+ break
84
+ }
85
+ labelArray.push(item.label)
86
+ }
87
+ let resultLabel = labelArray.join(separator)
88
+ if(hasMaxEntries) {
89
+ resultLabel = resultLabel.substring(0, resultLabel.length)
90
+ resultLabel += `... +${entries.length - window.sb_admin_const.MULTISELECT_FILTER_MAX_CHOICES_SHOWN + 1}`
91
+ }
92
+ return resultLabel
93
+ }
94
+
95
+ export const setDropdownLabel = (dropdownMenuEl, dropdownLabelEl) => {
96
+ if(!dropdownMenuEl) {
97
+ return
98
+ }
99
+ if(!dropdownLabelEl) {
100
+ dropdownLabelEl = dropdownMenuEl.querySelector('.js-dropdown-label')
101
+ }
102
+ if(!dropdownLabelEl) {
103
+ return
104
+ }
105
+ const fields = Array.from(dropdownMenuEl.querySelectorAll('input[type="checkbox"]:checked, input[type="radio"]:checked')).map(el => {
106
+ const label = el.parentElement.querySelector(`label[for="${el.id}"]`)
107
+ return {
108
+ 'value': el.value,
109
+ 'label': label?label.innerHTML:''
110
+ }
111
+ })
112
+ dropdownLabelEl.innerHTML = getResultLabel(fields)
113
+ }
114
+
69
115
  export const filterInputValueChangedUtil = (field) => {
70
116
  const filterId = field.dataset.filterId || field.id
71
117
  const separator = field.dataset.labelSeparator || ', '
@@ -88,29 +134,7 @@ export const filterInputValueChangedUtil = (field) => {
88
134
  return valueElem
89
135
  }
90
136
  if (typeof valueOrObject === 'object') {
91
- const labelArray = []
92
- const entries = Object.values(valueOrObject)
93
- let hasMaxEntries = false
94
- for (let [index, item] of entries.entries()) {
95
- if (index === window.sb_admin_const.MULTISELECT_FILTER_MAX_CHOICES_SHOWN) {
96
- break
97
- }
98
- if(entries.length > 1 && item.value === window.sb_admin_const.SELECT_ALL_KEYWORD) {
99
- continue
100
- }
101
- if (index === window.sb_admin_const.MULTISELECT_FILTER_MAX_CHOICES_SHOWN - 2 && entries[index + 2]) {
102
- labelArray.push(item.label)
103
- hasMaxEntries = true
104
- break
105
- }
106
- labelArray.push(item.label)
107
- }
108
- let resultLabel = labelArray.join(separator)
109
- if(hasMaxEntries) {
110
- resultLabel = resultLabel.substring(0, resultLabel.length)
111
- resultLabel += `... +${entries.length - window.sb_admin_const.MULTISELECT_FILTER_MAX_CHOICES_SHOWN + 1}`
112
- }
113
- valueElem.innerHTML = resultLabel
137
+ valueElem.innerHTML = getResultLabel(valueOrObject, separator)
114
138
  } else {
115
139
  try {
116
140
  // select
@@ -7,7 +7,7 @@
7
7
  data-bs-auto-close="outside"
8
8
  class="btn !rounded px-10 font-normal w-full"
9
9
  >
10
- <span id="{{ filter_widget.input_id }}-value" class="js-value"></span>
10
+ <span id="{{ filter_widget.input_id }}-value"></span>
11
11
  <svg class="ml-8">
12
12
  <use xlink:href="#Down"></use>
13
13
  </svg>
@@ -7,7 +7,7 @@
7
7
  {% for choice in filter_widget.choices %}
8
8
  <li>
9
9
  <div class="relative">
10
- <input onchange="document.getElementById('{{ filter_widget.input_id }}').value = event.target.value;document.getElementById('{{ filter_widget.input_id }}').dispatchEvent(new Event('change'))" type="radio" value="{{ choice.0 }}" name="{{ filter_widget.input_name }}_dummy" class="radio" id="{{ filter_widget.input_id }}_{{ choice.0 }}" {% if filter_widget.get_default_value == choice.0 %} checked{% endif %} data-label="{{ choice.1 }}">
10
+ <input onchange="document.getElementById('{{ filter_widget.input_id }}').value = event.target.value;document.getElementById('{{ filter_widget.input_id }}').dispatchEvent(new Event('change'))" type="radio" value="{{ choice.0 }}" name="{{ filter_widget.input_name }}_dummy" class="radio radio-list" id="{{ filter_widget.input_id }}_{{ choice.0 }}" {% if filter_widget.get_default_value == choice.0 %} checked{% endif %} data-label="{{ choice.1 }}">
11
11
  <label for="{{ filter_widget.input_id }}_{{ choice.0 }}">
12
12
  {{ choice.1|safe }}
13
13
  </label>
@@ -148,16 +148,18 @@
148
148
  </svg>
149
149
  {% trans 'Change password' %}
150
150
  </li>
151
- {% if request_data.global_filter_instance %}
152
- <li class="p-12 text-dark-900">
153
- {% trans 'Choose domain' %}
151
+ {% block color_scheme %}
152
+ <li class="border-t border-dark-100 pt-8">
153
+ <form hx-post="{% url 'sb_admin:color_scheme' %}" hx-trigger="change" hx-swap="none" id="color-schema-form" class="js-color-scheme-picker">
154
+ {{ color_scheme_form.color_scheme }}
155
+ </form>
154
156
  </li>
155
- <li>
157
+ {% endblock %}
158
+ {% if request_data.global_filter_instance %}
159
+ <li class="border-t border-dark-100 pt-8">
156
160
  <form hx-post="{% url 'sb_admin:global_filter' %}" hx-trigger="change" hx-swap="none" id="global_filter_form">
157
161
  {% for field in request_data.global_filter_instance %}
158
- <div class="relative">
159
- {{ field }}
160
- </div>
162
+ {{ field }}
161
163
  {{ field.errors }}
162
164
  {% endfor %}
163
165
  </form>
@@ -1,7 +1,7 @@
1
1
  {% load static %}
2
2
 
3
3
  <!DOCTYPE html>
4
- <html lang="{{ request.LANGUAGE_CODE }}" data-theme="light" data-color-scheme="light">
4
+ <html lang="{{ request.LANGUAGE_CODE }}" data-theme="{{ user_config.color_scheme }}">
5
5
  <head>
6
6
  {% include 'sb_admin/fonts.html' %}
7
7
  <meta name="robots" content="noindex"/>