plutonium 0.64.0 → 0.65.0
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.
- checksums.yaml +4 -4
- data/.claude/skills/plutonium/SKILL.md +4 -1
- data/.claude/skills/plutonium-app/SKILL.md +14 -0
- data/.claude/skills/plutonium-dashboard/SKILL.md +161 -0
- data/.claude/skills/plutonium-resource/SKILL.md +94 -14
- data/.claude/skills/plutonium-ui/SKILL.md +22 -1
- data/CHANGELOG.md +59 -0
- data/CLAUDE.md +282 -210
- data/SECURITY.md +1 -1
- data/app/assets/plutonium-charts.js +20604 -0
- data/app/assets/plutonium-charts.js.map +7 -0
- data/app/assets/plutonium-charts.min.js +41 -0
- data/app/assets/plutonium-charts.min.js.map +7 -0
- data/app/assets/plutonium.css +1 -1
- data/app/assets/plutonium.js +794 -629
- data/app/assets/plutonium.js.map +4 -4
- data/app/assets/plutonium.min.js +48 -48
- data/app/assets/plutonium.min.js.map +4 -4
- data/app/views/plutonium/_flash_alerts.html.erb +2 -2
- data/app/views/plutonium/_resource_header.html.erb +2 -2
- data/app/views/plutonium/_resource_sidebar.html.erb +11 -2
- data/app/views/plutonium/_toast.html.erb +2 -2
- data/app/views/rodauth/_login_form.html.erb +1 -1
- data/app/views/rodauth/_password_visibility.html.erb +1 -1
- data/app/views/rodauth/add_recovery_codes.html.erb +2 -2
- data/app/views/rodauth/change_login.html.erb +2 -2
- data/app/views/rodauth/create_account.html.erb +4 -4
- data/app/views/rodauth/reset_password_request.html.erb +1 -1
- data/app/views/rodauth/verify_account_resend.html.erb +1 -1
- data/app/views/rodauth/webauthn_remove.html.erb +1 -1
- data/config/brakeman.ignore +4 -4
- data/config/initializers/rabl.rb +0 -40
- data/config/locales/en/api_client.yml +22 -0
- data/config/locales/en/async.yml +34 -0
- data/config/locales/en/dashboard.yml +25 -0
- data/config/locales/en/invites.yml +37 -0
- data/config/locales/en/js.yml +40 -0
- data/config/locales/en/kanban.yml +28 -0
- data/config/locales/en/plutonium.yml +26 -0
- data/config/locales/en/positioning.yml +14 -0
- data/config/locales/en/profile.yml +29 -0
- data/config/locales/en/resource.yml +60 -0
- data/config/locales/en/ui.yml +93 -0
- data/config/locales/en/wizard.yml +46 -0
- data/docs/.vitepress/config.ts +34 -1
- data/docs/.vitepress/theme/components/HomeHero.vue +1 -1
- data/docs/.vitepress/theme/components/HomeInTheBox.vue +14 -0
- data/docs/.vitepress/theme/custom.css +34 -377
- data/docs/blog/introducing-plutonium-dashboards.md +175 -0
- data/docs/blog/introducing-plutonium-i18n.md +91 -0
- data/docs/blog/introducing-plutonium.md +4 -5
- data/docs/blog/whats-new-async-kanban-wizards.md +1 -1
- data/docs/guides/dashboards.md +300 -0
- data/docs/guides/index.md +1 -0
- data/docs/guides/kanban.md +2 -2
- data/docs/public/images/blog/dashboards-card-error.png +0 -0
- data/docs/public/images/blog/dashboards-loading.png +0 -0
- data/docs/public/images/blog/dashboards-overview.png +0 -0
- data/docs/public/images/blog/dashboards-sidebar.png +0 -0
- data/docs/public/images/blog/i18n-es-admin.png +0 -0
- data/docs/public/images/guides/dashboard-card-error.png +0 -0
- data/docs/public/images/guides/dashboard-loading.png +0 -0
- data/docs/public/images/guides/dashboard-overview.png +0 -0
- data/docs/public/images/guides/dashboard-sidebar.png +0 -0
- data/docs/reference/app/generators.md +2 -2
- data/docs/reference/app/portals.md +2 -0
- data/docs/reference/configuration.md +84 -27
- data/docs/reference/dashboard/dsl.md +123 -0
- data/docs/reference/dashboard/index.md +35 -0
- data/docs/reference/dashboard/registration.md +76 -0
- data/docs/reference/i18n.md +239 -0
- data/docs/reference/index.md +10 -1
- data/docs/reference/kanban/dsl.md +1 -1
- data/docs/reference/kanban/positioning.md +2 -2
- data/docs/reference/resource/actions.md +1 -1
- data/docs/reference/resource/definition.md +96 -18
- data/docs/reference/resource/export.md +5 -1
- data/docs/reference/resource/index.md +2 -0
- data/docs/reference/{positioning.md → resource/positioning.md} +2 -1
- data/docs/reference/resource/query.md +2 -0
- data/docs/reference/ui/assets.md +8 -0
- data/docs/reference/ui/displays.md +5 -8
- data/docs/reference/ui/index.md +1 -1
- data/docs/reference/wizard/dsl.md +2 -2
- data/esbuild.config.js +5 -2
- data/gemfiles/rails_7.gemfile.lock +2 -2
- data/gemfiles/rails_8.1.gemfile.lock +1 -1
- data/lib/active_model/validations/array_validator.rb +1 -1
- data/lib/active_model/validations/attached_validator.rb +1 -1
- data/lib/active_model/validations/url_validator.rb +2 -2
- data/lib/generators/pu/core/assets/assets_generator.rb +36 -21
- data/lib/generators/pu/core/assets/templates/postcss.config.js +1 -1
- data/lib/generators/pu/core/update/update_generator.rb +3 -2
- data/lib/generators/pu/dashboard/dashboard_generator.rb +96 -0
- data/lib/generators/pu/dashboard/templates/dashboard.rb.tt +25 -0
- data/lib/generators/pu/docker/install/install_generator.rb +58 -0
- data/lib/generators/pu/docker/install/templates/Dockerfile.dev.tt +18 -3
- data/lib/generators/pu/docker/install/templates/Dockerfile.tt +20 -5
- data/lib/generators/pu/docker/install/templates/{docker-compose.yml → docker-compose.yml.tt} +1 -1
- data/lib/generators/pu/gem/dotenv/dotenv_generator.rb +1 -1
- data/lib/generators/pu/invites/invitable_generator.rb +18 -1
- data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +0 -95
- data/lib/generators/pu/lib/plutonium_generators/concerns/js_package_manager.rb +146 -0
- data/lib/generators/pu/lib/plutonium_generators/generator.rb +1 -17
- data/lib/generators/pu/pkg/package/package_generator.rb +1 -0
- data/lib/generators/pu/pkg/package/templates/config/locales/en.yml.tt +28 -0
- data/lib/generators/pu/pkg/portal/portal_generator.rb +1 -0
- data/lib/generators/pu/pkg/portal/templates/config/locales/en.yml.tt +30 -0
- data/lib/generators/pu/res/scaffold/scaffold_generator.rb +0 -8
- data/lib/generators/pu/rodauth/concerns/feature_selector.rb +0 -18
- data/lib/plutonium/action/base.rb +27 -3
- data/lib/plutonium/action/interactive.rb +17 -4
- data/lib/plutonium/api_client/concerns/create_api_client.rb +12 -11
- data/lib/plutonium/api_client/concerns/disable_api_client.rb +3 -3
- data/lib/plutonium/auth/sequel_adapter.rb +1 -1
- data/lib/plutonium/configuration.rb +5 -0
- data/lib/plutonium/core/controller.rb +25 -1
- data/lib/plutonium/core/controllers/authorizable.rb +1 -1
- data/lib/plutonium/dashboard/base.rb +114 -0
- data/lib/plutonium/dashboard/card.rb +200 -0
- data/lib/plutonium/dashboard/controller.rb +101 -0
- data/lib/plutonium/dashboard/dsl.rb +104 -0
- data/lib/plutonium/dashboard/register.rb +40 -0
- data/lib/plutonium/dashboard/route_resolution.rb +30 -0
- data/lib/plutonium/dashboard.rb +42 -0
- data/lib/plutonium/definition/actions.rb +6 -1
- data/lib/plutonium/definition/base.rb +41 -3
- data/lib/plutonium/definition/index_views.rb +1 -1
- data/lib/plutonium/definition/positioning.rb +1 -1
- data/lib/plutonium/definition/presentable.rb +2 -2
- data/lib/plutonium/helpers/assets_helper.rb +15 -2
- data/lib/plutonium/helpers/display_helper.rb +10 -2
- data/lib/plutonium/interaction/async/context.rb +6 -8
- data/lib/plutonium/interaction/async/executor.rb +6 -7
- data/lib/plutonium/interaction/async/run.rb +2 -2
- data/lib/plutonium/interaction/async/run_definition.rb +1 -1
- data/lib/plutonium/interaction/base.rb +2 -0
- data/lib/plutonium/interaction/concerns/dispatchable.rb +15 -3
- data/lib/plutonium/invites/concerns/cancel_invite.rb +3 -3
- data/lib/plutonium/invites/concerns/invite_token.rb +25 -7
- data/lib/plutonium/invites/concerns/invite_user.rb +10 -5
- data/lib/plutonium/invites/concerns/resend_invite.rb +4 -4
- data/lib/plutonium/invites/controller.rb +11 -11
- data/lib/plutonium/kanban/column.rb +13 -3
- data/lib/plutonium/kanban/dsl.rb +7 -4
- data/lib/plutonium/models/has_cents.rb +1 -1
- data/lib/plutonium/profile/security_section.rb +10 -18
- data/lib/plutonium/query/filter.rb +10 -1
- data/lib/plutonium/query/filters/association.rb +27 -2
- data/lib/plutonium/query/filters/boolean.rb +4 -4
- data/lib/plutonium/query/filters/date.rb +4 -17
- data/lib/plutonium/query/filters/date_range.rb +2 -2
- data/lib/plutonium/query/filters/select.rb +10 -1
- data/lib/plutonium/query/filters/text.rb +9 -12
- data/lib/plutonium/railtie.rb +17 -0
- data/lib/plutonium/resource/controller.rb +10 -5
- data/lib/plutonium/resource/controllers/crud_actions.rb +17 -20
- data/lib/plutonium/resource/controllers/export_csv.rb +19 -9
- data/lib/plutonium/resource/controllers/kanban_actions.rb +16 -16
- data/lib/plutonium/resource/controllers/position_actions.rb +5 -5
- data/lib/plutonium/resource/controllers/typeahead.rb +24 -1
- data/lib/plutonium/resource/query_object.rb +30 -3
- data/lib/plutonium/routing/dashboard_registration.rb +137 -0
- data/lib/plutonium/routing/resource_registration.rb +5 -0
- data/lib/plutonium/routing/route_set_extensions.rb +2 -1
- data/lib/plutonium/translation.rb +227 -0
- data/lib/plutonium/ui/actions_dropdown.rb +2 -2
- data/lib/plutonium/ui/breadcrumbs.rb +3 -3
- data/lib/plutonium/ui/color_mode_selector.rb +1 -1
- data/lib/plutonium/ui/component/behaviour.rb +46 -0
- data/lib/plutonium/ui/component/methods.rb +4 -0
- data/lib/plutonium/ui/dashboard/board.rb +80 -0
- data/lib/plutonium/ui/dashboard/card.rb +127 -0
- data/lib/plutonium/ui/dashboard/chart.rb +43 -0
- data/lib/plutonium/ui/dashboard/custom.rb +32 -0
- data/lib/plutonium/ui/dashboard/frame.rb +25 -0
- data/lib/plutonium/ui/dashboard/metric.rb +137 -0
- data/lib/plutonium/ui/dashboard/skeleton.rb +40 -0
- data/lib/plutonium/ui/display/components/attachment.rb +1 -1
- data/lib/plutonium/ui/display/components/badge.rb +7 -1
- data/lib/plutonium/ui/display/components/boolean.rb +2 -2
- data/lib/plutonium/ui/display/resource.rb +23 -16
- data/lib/plutonium/ui/export_button.rb +3 -3
- data/lib/plutonium/ui/form/components/key_value_store.rb +5 -5
- data/lib/plutonium/ui/form/components/password.rb +2 -2
- data/lib/plutonium/ui/form/components/resource_select.rb +27 -0
- data/lib/plutonium/ui/form/components/secure_association.rb +2 -0
- data/lib/plutonium/ui/form/components/uppy.rb +11 -3
- data/lib/plutonium/ui/form/concerns/renders_nested_resource_fields.rb +1 -1
- data/lib/plutonium/ui/form/concerns/renders_repeater_row_controls.rb +5 -4
- data/lib/plutonium/ui/form/concerns/renders_structured_inputs.rb +1 -1
- data/lib/plutonium/ui/form/query.rb +8 -8
- data/lib/plutonium/ui/form/resource.rb +54 -26
- data/lib/plutonium/ui/frame_navigator_panel.rb +4 -4
- data/lib/plutonium/ui/grid/card.rb +3 -3
- data/lib/plutonium/ui/grid/resource.rb +3 -3
- data/lib/plutonium/ui/interaction/async/run_progress.rb +6 -6
- data/lib/plutonium/ui/interaction/async/running_banner.rb +2 -2
- data/lib/plutonium/ui/kanban/column.rb +6 -6
- data/lib/plutonium/ui/layout/base.rb +19 -1
- data/lib/plutonium/ui/layout/header.rb +1 -1
- data/lib/plutonium/ui/layout/icon_rail.rb +4 -4
- data/lib/plutonium/ui/layout/rodauth_layout.rb +1 -1
- data/lib/plutonium/ui/layout/sidebar.rb +1 -1
- data/lib/plutonium/ui/layout/topbar.rb +2 -2
- data/lib/plutonium/ui/modal/base.rb +4 -4
- data/lib/plutonium/ui/nav_grid_menu.rb +1 -1
- data/lib/plutonium/ui/nav_user.rb +2 -2
- data/lib/plutonium/ui/page/base.rb +10 -2
- data/lib/plutonium/ui/page/dashboard.rb +54 -0
- data/lib/plutonium/ui/page/edit.rb +3 -3
- data/lib/plutonium/ui/page/interactive_action.rb +2 -2
- data/lib/plutonium/ui/page/new.rb +3 -3
- data/lib/plutonium/ui/page/show.rb +2 -2
- data/lib/plutonium/ui/page/wizard.rb +22 -16
- data/lib/plutonium/ui/page/wizard_chooser.rb +10 -10
- data/lib/plutonium/ui/page/wizard_completed.rb +2 -2
- data/lib/plutonium/ui/skeleton_table.rb +1 -1
- data/lib/plutonium/ui/table/base.rb +2 -2
- data/lib/plutonium/ui/table/components/attachment.rb +1 -1
- data/lib/plutonium/ui/table/components/bulk_actions_toolbar.rb +19 -3
- data/lib/plutonium/ui/table/components/drag_handle.rb +3 -3
- data/lib/plutonium/ui/table/components/filter_form.rb +11 -10
- data/lib/plutonium/ui/table/components/filter_pills.rb +4 -4
- data/lib/plutonium/ui/table/components/pagy_info.rb +61 -17
- data/lib/plutonium/ui/table/components/pagy_pagination.rb +9 -5
- data/lib/plutonium/ui/table/components/row_actions_dropdown.rb +1 -1
- data/lib/plutonium/ui/table/components/scopes_bar.rb +6 -2
- data/lib/plutonium/ui/table/components/scopes_pills.rb +3 -3
- data/lib/plutonium/ui/table/components/selection_column.rb +1 -1
- data/lib/plutonium/ui/table/components/toolbar.rb +2 -2
- data/lib/plutonium/ui/table/components/view_switcher.rb +12 -8
- data/lib/plutonium/ui/table/resource.rb +53 -17
- data/lib/plutonium/ui/wizard/review.rb +7 -7
- data/lib/plutonium/ui/wizard/stepper.rb +1 -1
- data/lib/plutonium/ui/wizard/summary_display.rb +6 -0
- data/lib/plutonium/version.rb +1 -1
- data/lib/plutonium/wizard/base.rb +2 -0
- data/lib/plutonium/wizard/driving.rb +8 -2
- data/lib/plutonium/wizard/dsl.rb +4 -2
- data/lib/plutonium/wizard/field_importer.rb +1 -8
- data/lib/plutonium/wizard/review_step.rb +8 -1
- data/lib/plutonium/wizard/step.rb +14 -3
- data/package.json +4 -2
- data/plutonium.gemspec +4 -1
- data/postcss-gem-import.cjs +34 -0
- data/postcss-gem-import.js +3 -28
- data/src/css/components.css +91 -0
- data/src/css/slim_select.css +0 -38
- data/src/css/tokens.css +24 -0
- data/src/js/controllers/attachment_input_controller.js +15 -6
- data/src/js/controllers/chart_controller.js +162 -0
- data/src/js/controllers/clipboard_controller.js +3 -2
- data/src/js/controllers/dirty_form_guard_controller.js +4 -3
- data/src/js/controllers/easymde_controller.js +3 -0
- data/src/js/controllers/filter_panel_controller.js +15 -6
- data/src/js/controllers/flatpickr_controller.js +8 -0
- data/src/js/controllers/frame_refresh_controller.js +59 -0
- data/src/js/controllers/intl_tel_input_controller.js +5 -0
- data/src/js/controllers/register_controllers.js +4 -0
- data/src/js/controllers/slim_select_controller.js +79 -176
- data/src/js/core.js +9 -1
- data/src/js/i18n.js +69 -0
- data/src/js/plutonium-charts.js +15 -0
- data/src/js/turbo/turbo_confirm.js +5 -3
- metadata +64 -16
- data/.yarnrc.yml +0 -8
- data/config/initializers/hotwire_turbo_monkey_patches.rb +0 -12
- data/lib/generators/pu/field/input/input_generator.rb +0 -32
- data/lib/generators/pu/field/input/templates/input.rb.tt +0 -15
- data/lib/generators/pu/lib/plutonium_generators/concerns/config.rb +0 -39
- data/lib/generators/pu/lib/plutonium_generators/concerns/serializer.rb +0 -39
- data/lib/generators/pu/lib/plutonium_generators/installer.rb +0 -205
- data/lib/generators/pu/res/scaffold/templates/presenter.rb.tt +0 -13
- data/lib/generators/pu/res/scaffold/templates/query_object.rb.tt +0 -17
- data/public/plutonium-assets/application.js +0 -31419
- data/public/plutonium-assets/plutonium.ico +0 -0
- data/yarn.lock +0 -6858
data/src/css/tokens.css
CHANGED
|
@@ -57,6 +57,18 @@
|
|
|
57
57
|
--pu-card-bg: #ffffff;
|
|
58
58
|
--pu-card-border: #e2e8f0;
|
|
59
59
|
|
|
60
|
+
/* ===================
|
|
61
|
+
Chart Palette - series colors, read by the chart controller
|
|
62
|
+
=================== */
|
|
63
|
+
--pu-chart-1: theme(colors.primary.500);
|
|
64
|
+
--pu-chart-2: #0ea5e9;
|
|
65
|
+
--pu-chart-3: #10b981;
|
|
66
|
+
--pu-chart-4: #f59e0b;
|
|
67
|
+
--pu-chart-5: #ef4444;
|
|
68
|
+
--pu-chart-6: #8b5cf6;
|
|
69
|
+
--pu-chart-7: #ec4899;
|
|
70
|
+
--pu-chart-8: #64748b;
|
|
71
|
+
|
|
60
72
|
/* ===================
|
|
61
73
|
Shadow System - Layered, soft
|
|
62
74
|
=================== */
|
|
@@ -150,6 +162,18 @@
|
|
|
150
162
|
--pu-card-bg: #1e293b;
|
|
151
163
|
--pu-card-border: #334155;
|
|
152
164
|
|
|
165
|
+
/* ===================
|
|
166
|
+
Chart Palette (Dark) - lighter tints for contrast on dark surfaces
|
|
167
|
+
=================== */
|
|
168
|
+
--pu-chart-1: theme(colors.primary.400);
|
|
169
|
+
--pu-chart-2: #38bdf8;
|
|
170
|
+
--pu-chart-3: #34d399;
|
|
171
|
+
--pu-chart-4: #fbbf24;
|
|
172
|
+
--pu-chart-5: #f87171;
|
|
173
|
+
--pu-chart-6: #a78bfa;
|
|
174
|
+
--pu-chart-7: #f472b6;
|
|
175
|
+
--pu-chart-8: #94a3b8;
|
|
176
|
+
|
|
153
177
|
/* ===================
|
|
154
178
|
Shadow System (Dark)
|
|
155
179
|
=================== */
|
|
@@ -5,6 +5,7 @@ import Dashboard from '@uppy/dashboard'
|
|
|
5
5
|
import ImageEditor from '@uppy/image-editor'
|
|
6
6
|
import XHRUpload from '@uppy/xhr-upload'
|
|
7
7
|
import DomElement from "../support/dom_element"
|
|
8
|
+
import { t } from "../i18n.js"
|
|
8
9
|
|
|
9
10
|
// Connects to data-controller="attachment-input"
|
|
10
11
|
export default class extends Controller {
|
|
@@ -109,7 +110,7 @@ export default class extends Controller {
|
|
|
109
110
|
const dialog = this.element.closest("dialog")
|
|
110
111
|
if (dialog) dashboardOptions.target = dialog
|
|
111
112
|
|
|
112
|
-
|
|
113
|
+
const uppyOptions = {
|
|
113
114
|
restrictions: {
|
|
114
115
|
maxFileSize: this.maxFileSizeValue,
|
|
115
116
|
minFileSize: this.minFileSizeValue,
|
|
@@ -119,7 +120,14 @@ export default class extends Controller {
|
|
|
119
120
|
allowedFileTypes: this.allowedFileTypesValue,
|
|
120
121
|
requiredMetaFields: this.requiredMetaFieldsValue,
|
|
121
122
|
}
|
|
122
|
-
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Optional locale passthrough: plutonium.js.libraries.uppy.strings holds
|
|
126
|
+
// Uppy locale strings (core and Dashboard), merged over the English defaults.
|
|
127
|
+
const strings = t("plutonium.js.libraries.uppy.strings")
|
|
128
|
+
if (strings && typeof strings === "object") uppyOptions.locale = { strings }
|
|
129
|
+
|
|
130
|
+
this.uppy = new Uppy(uppyOptions)
|
|
123
131
|
.use(Dashboard, dashboardOptions)
|
|
124
132
|
.use(ImageEditor, { target: Dashboard })
|
|
125
133
|
|
|
@@ -176,7 +184,7 @@ export default class extends Controller {
|
|
|
176
184
|
const len = this.attachmentPreviewOutlets.length
|
|
177
185
|
if (len > 1) {
|
|
178
186
|
this.deleteAllTrigger.style["display"] = 'initial'
|
|
179
|
-
this.deleteAllTrigger.textContent =
|
|
187
|
+
this.deleteAllTrigger.textContent = t("plutonium.js.attachment_input.delete_all", { count: this.attachmentPreviewOutlets.length })
|
|
180
188
|
} else {
|
|
181
189
|
this.deleteAllTrigger.style["display"] = 'none'
|
|
182
190
|
}
|
|
@@ -198,7 +206,8 @@ export default class extends Controller {
|
|
|
198
206
|
}
|
|
199
207
|
|
|
200
208
|
#buildUploadTrigger() {
|
|
201
|
-
|
|
209
|
+
// One plural-keyed string: `one` for a single attachment, `other` for multiple.
|
|
210
|
+
const triggerPrompt = t("plutonium.js.attachment_input.choose_file", { count: this.multiple ? 2 : 1 })
|
|
202
211
|
this.uploadTrigger = DomElement.fromTemplate(
|
|
203
212
|
`<button type="button" class="text-gray-900 bg-white border border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-200 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700 inline-flex items-center">
|
|
204
213
|
<svg class="w-4 h-4 mr-2" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 16">
|
|
@@ -214,12 +223,12 @@ export default class extends Controller {
|
|
|
214
223
|
#buildDeleteAllTrigger() {
|
|
215
224
|
this.deleteAllTrigger = DomElement.fromTemplate(
|
|
216
225
|
`<button type="button" class="text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-red-600 dark:hover:bg-red-700 focus:outline-none dark:focus:ring-red-800 inline-flex items-center">
|
|
217
|
-
|
|
226
|
+
${t("plutonium.js.attachment_input.delete_all", { count: this.attachmentPreviewOutlets.length })}
|
|
218
227
|
</button>`,
|
|
219
228
|
false
|
|
220
229
|
)
|
|
221
230
|
this.deleteAllTrigger.addEventListener('click', () => {
|
|
222
|
-
if (confirm(
|
|
231
|
+
if (confirm(t("plutonium.js.are_you_sure"))) this.attachmentPreviewContainerOutlet.clear()
|
|
223
232
|
})
|
|
224
233
|
}
|
|
225
234
|
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { t } from "../i18n.js"
|
|
3
|
+
|
|
4
|
+
// Connects to data-controller="chart"
|
|
5
|
+
//
|
|
6
|
+
// Draws a Chartkick chart from data attributes rendered by
|
|
7
|
+
// Plutonium::UI::Dashboard::Chart:
|
|
8
|
+
//
|
|
9
|
+
// data-chart-type-value — Chartkick constructor ("LineChart", "PieChart", ...)
|
|
10
|
+
// data-chart-data-value — JSON: {label: value}, [[label, value], ...] or [{name, data}, ...]
|
|
11
|
+
// data-chart-options-value — JSON: Chartkick options passed through from the card
|
|
12
|
+
// data-chart-script-value — URL of the charts bundle, injected once per page
|
|
13
|
+
//
|
|
14
|
+
// The bundle is loaded on demand so pages without a chart never pay for
|
|
15
|
+
// Chart.js. Colors and text default to Plutonium's design tokens and are
|
|
16
|
+
// re-read when the color mode flips, so a chart matches the theme in both modes.
|
|
17
|
+
|
|
18
|
+
const PALETTE_SIZE = 8
|
|
19
|
+
let libraryPromise = null
|
|
20
|
+
|
|
21
|
+
function loadLibrary(src) {
|
|
22
|
+
if (window.Chartkick && window.Chartkick.adapters.length > 0) return Promise.resolve(window.Chartkick)
|
|
23
|
+
if (libraryPromise) return libraryPromise
|
|
24
|
+
|
|
25
|
+
libraryPromise = new Promise((resolve, reject) => {
|
|
26
|
+
const existing = document.querySelector(`script[src="${src}"]`)
|
|
27
|
+
const script = existing || document.createElement("script")
|
|
28
|
+
|
|
29
|
+
const settle = () => {
|
|
30
|
+
if (window.Chartkick) resolve(window.Chartkick)
|
|
31
|
+
else reject(new Error("[plutonium] the charts bundle loaded but defined no Chartkick"))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
script.addEventListener("load", settle, { once: true })
|
|
35
|
+
script.addEventListener("error", () => {
|
|
36
|
+
libraryPromise = null
|
|
37
|
+
reject(new Error(`[plutonium] could not load the charts bundle from ${src}`))
|
|
38
|
+
}, { once: true })
|
|
39
|
+
|
|
40
|
+
if (!existing) {
|
|
41
|
+
script.src = src
|
|
42
|
+
script.async = true
|
|
43
|
+
script.dataset.turboTrack = "reload"
|
|
44
|
+
document.head.appendChild(script)
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
return libraryPromise
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function cssVar(name) {
|
|
52
|
+
return getComputedStyle(document.documentElement).getPropertyValue(name).trim()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function themeColors() {
|
|
56
|
+
const colors = []
|
|
57
|
+
for (let i = 1; i <= PALETTE_SIZE; i++) {
|
|
58
|
+
const color = cssVar(`--pu-chart-${i}`)
|
|
59
|
+
if (color) colors.push(color)
|
|
60
|
+
}
|
|
61
|
+
return colors
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isPlainObject(value) {
|
|
65
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function deepMerge(base, extra) {
|
|
69
|
+
const out = { ...base }
|
|
70
|
+
for (const [key, value] of Object.entries(extra || {})) {
|
|
71
|
+
out[key] = isPlainObject(value) && isPlainObject(out[key]) ? deepMerge(out[key], value) : value
|
|
72
|
+
}
|
|
73
|
+
return out
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export default class extends Controller {
|
|
77
|
+
static values = {
|
|
78
|
+
type: { type: String, default: "LineChart" },
|
|
79
|
+
data: String,
|
|
80
|
+
options: Object,
|
|
81
|
+
script: String
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
connect() {
|
|
85
|
+
this.render = this.render.bind(this)
|
|
86
|
+
this.observer = new MutationObserver((mutations) => {
|
|
87
|
+
if (mutations.some((m) => m.attributeName === "class")) this.render()
|
|
88
|
+
})
|
|
89
|
+
this.observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] })
|
|
90
|
+
|
|
91
|
+
this.element.addEventListener("turbo:morph-element", this.render)
|
|
92
|
+
|
|
93
|
+
loadLibrary(this.scriptValue)
|
|
94
|
+
.then(() => this.render())
|
|
95
|
+
.catch((error) => {
|
|
96
|
+
console.error(error)
|
|
97
|
+
this.element.textContent = t("plutonium.js.charts.failed")
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
disconnect() {
|
|
102
|
+
this.observer?.disconnect()
|
|
103
|
+
this.element.removeEventListener("turbo:morph-element", this.render)
|
|
104
|
+
this.#destroy()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
dataValueChanged() { this.render() }
|
|
108
|
+
optionsValueChanged() { this.render() }
|
|
109
|
+
|
|
110
|
+
render() {
|
|
111
|
+
const Chartkick = window.Chartkick
|
|
112
|
+
if (!Chartkick || !this.element.isConnected) return
|
|
113
|
+
|
|
114
|
+
const Klass = Chartkick[this.typeValue]
|
|
115
|
+
if (!Klass) {
|
|
116
|
+
console.error(`[plutonium] unknown chart type ${this.typeValue}`)
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
this.#destroy()
|
|
121
|
+
this.element.replaceChildren()
|
|
122
|
+
this.chart = new Klass(this.element, this.#data(), this.#options())
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
#destroy() {
|
|
126
|
+
if (this.chart) {
|
|
127
|
+
this.chart.destroy()
|
|
128
|
+
this.chart = null
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
#data() {
|
|
133
|
+
try {
|
|
134
|
+
return JSON.parse(this.dataValue || "[]")
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error("[plutonium] chart data is not valid JSON", error)
|
|
137
|
+
return []
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Plutonium's defaults, with the card's own options merged over them.
|
|
142
|
+
// Axis styling only applies to cartesian charts: handing a pie chart
|
|
143
|
+
// `scales` makes Chart.js draw axes around it.
|
|
144
|
+
#options() {
|
|
145
|
+
const text = cssVar("--pu-text-muted")
|
|
146
|
+
const grid = cssVar("--pu-border")
|
|
147
|
+
const surface = cssVar("--pu-card-bg")
|
|
148
|
+
const library = { color: text }
|
|
149
|
+
|
|
150
|
+
if (this.typeValue === "PieChart") {
|
|
151
|
+
library.elements = { arc: { borderColor: surface } }
|
|
152
|
+
} else {
|
|
153
|
+
library.scales = {
|
|
154
|
+
x: { ticks: { color: text }, grid: { color: grid }, border: { color: grid } },
|
|
155
|
+
y: { ticks: { color: text }, grid: { color: grid }, border: { color: grid } }
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const defaults = { colors: themeColors(), empty: t("plutonium.js.charts.empty"), library }
|
|
160
|
+
return deepMerge(defaults, this.optionsValue)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { t } from "../i18n.js"
|
|
2
3
|
|
|
3
4
|
export default class extends Controller {
|
|
4
5
|
static targets = ["source"]
|
|
@@ -9,7 +10,7 @@ export default class extends Controller {
|
|
|
9
10
|
const originalText = button.textContent
|
|
10
11
|
|
|
11
12
|
navigator.clipboard.writeText(text).then(() => {
|
|
12
|
-
button.textContent = "
|
|
13
|
+
button.textContent = t("plutonium.js.clipboard.copied")
|
|
13
14
|
setTimeout(() => {
|
|
14
15
|
button.textContent = originalText
|
|
15
16
|
}, 2000)
|
|
@@ -17,7 +18,7 @@ export default class extends Controller {
|
|
|
17
18
|
// Fallback for browsers that don't support clipboard API
|
|
18
19
|
console.warn("Clipboard API failed, using fallback:", err)
|
|
19
20
|
this.fallbackCopy(text)
|
|
20
|
-
button.textContent = "
|
|
21
|
+
button.textContent = t("plutonium.js.clipboard.copied")
|
|
21
22
|
setTimeout(() => {
|
|
22
23
|
button.textContent = originalText
|
|
23
24
|
}, 2000)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Controller } from "@hotwired/stimulus"
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { t } from "../i18n.js";
|
|
2
3
|
|
|
3
4
|
// Connects to data-controller="dirty-form-guard"
|
|
4
5
|
// Prompts before discarding a form's unsaved changes. Two guard surfaces,
|
|
@@ -220,7 +221,7 @@ export default class extends Controller {
|
|
|
220
221
|
|
|
221
222
|
const message =
|
|
222
223
|
trigger.getAttribute("data-dirty-form-guard-leave") ||
|
|
223
|
-
"
|
|
224
|
+
t("plutonium.js.dirty_form_guard.unsaved_changes");
|
|
224
225
|
const confirmed = await this.#confirm(message);
|
|
225
226
|
if (!confirmed) return;
|
|
226
227
|
|
|
@@ -339,7 +340,7 @@ export default class extends Controller {
|
|
|
339
340
|
requestAnimationFrame(() => {
|
|
340
341
|
requestAnimationFrame(() => d.setAttribute("data-open", ""));
|
|
341
342
|
});
|
|
342
|
-
} else if (window.confirm("
|
|
343
|
+
} else if (window.confirm(t("plutonium.js.dirty_form_guard.discard_changes"))) {
|
|
343
344
|
this.forceClose = true;
|
|
344
345
|
this.dialog.dispatchEvent(new CustomEvent("modal:request-close"));
|
|
345
346
|
}
|
|
@@ -3,6 +3,9 @@ import DOMPurify from 'dompurify';
|
|
|
3
3
|
import { marked } from 'marked';
|
|
4
4
|
|
|
5
5
|
// Connects to data-controller="easymde"
|
|
6
|
+
//
|
|
7
|
+
// Not translated: EasyMDE has no locale option (its toolbar titles are
|
|
8
|
+
// per-button config), so there is no clean plutonium.js.libraries hook.
|
|
6
9
|
export default class extends Controller {
|
|
7
10
|
static targets = ["textarea"]
|
|
8
11
|
|
|
@@ -59,10 +59,20 @@ export default class extends Controller {
|
|
|
59
59
|
this._previousBodyOverflow = null
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
// Reset every input
|
|
63
|
-
//
|
|
62
|
+
// Reset every input in the FILTER form, then submit it so the collection
|
|
63
|
+
// reflects the cleared filters immediately.
|
|
64
|
+
//
|
|
65
|
+
// Scoped to #filter-form, NOT the whole controller element: the toolbar's
|
|
66
|
+
// search box is a separate <form> that lives in the same scope but before
|
|
67
|
+
// this one. Submitting that bare form (or clearing its inputs) would drop the
|
|
68
|
+
// view / sort / scope state the filter form preserves via hidden inputs —
|
|
69
|
+
// which on the kanban board bounced the user back to the table (the dropped
|
|
70
|
+
// ?view=kanban fell through to the default view).
|
|
64
71
|
clear() {
|
|
65
|
-
this.element.
|
|
72
|
+
const form = this.element.querySelector("#filter-form")
|
|
73
|
+
if (!form) return
|
|
74
|
+
|
|
75
|
+
form.querySelectorAll("input, select, textarea").forEach(input => {
|
|
66
76
|
if (input.type === "checkbox" || input.type === "radio") {
|
|
67
77
|
input.checked = false
|
|
68
78
|
} else if (input.tagName === "SELECT") {
|
|
@@ -74,13 +84,12 @@ export default class extends Controller {
|
|
|
74
84
|
}
|
|
75
85
|
})
|
|
76
86
|
|
|
77
|
-
|
|
87
|
+
form.querySelectorAll('[data-controller="flatpickr"]').forEach(input => {
|
|
78
88
|
const controller = this.application.getControllerForElementAndIdentifier(input, "flatpickr")
|
|
79
89
|
if (controller?.picker) controller.picker.clear()
|
|
80
90
|
})
|
|
81
91
|
|
|
82
|
-
|
|
83
|
-
if (form) form.requestSubmit()
|
|
92
|
+
form.requestSubmit()
|
|
84
93
|
}
|
|
85
94
|
|
|
86
95
|
get isOpen() {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus";
|
|
2
|
+
import { lookup } from "../i18n.js";
|
|
2
3
|
|
|
3
4
|
// Connects to data-controller="flatpickr"
|
|
4
5
|
export default class extends Controller {
|
|
@@ -44,6 +45,13 @@ export default class extends Controller {
|
|
|
44
45
|
#buildOptions() {
|
|
45
46
|
let options = { altInput: true };
|
|
46
47
|
|
|
48
|
+
// Optional locale passthrough: plutonium.js.libraries.flatpickr.locale is
|
|
49
|
+
// the flatpickr locale key (e.g. "de"). Loading the matching l10n bundle
|
|
50
|
+
// (flatpickr/dist/l10n/de.js) is the host app's job; without it flatpickr
|
|
51
|
+
// falls back to English.
|
|
52
|
+
const locale = lookup("plutonium.js.libraries.flatpickr.locale");
|
|
53
|
+
if (typeof locale === "string") options.locale = locale;
|
|
54
|
+
|
|
47
55
|
if (this.element.attributes.type.value == "datetime-local") {
|
|
48
56
|
options.enableTime = true;
|
|
49
57
|
} else if (this.element.attributes.type.value == "time") {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
// Connects to data-controller="frame-refresh"
|
|
4
|
+
//
|
|
5
|
+
// Reloads a `<turbo-frame src>` every `interval` seconds while the tab is
|
|
6
|
+
// visible, so a dashboard card declared with `refresh:` keeps itself current
|
|
7
|
+
// without a page reload. Pauses when the document is hidden and resumes with
|
|
8
|
+
// an immediate reload when it becomes visible again after a missed tick.
|
|
9
|
+
export default class extends Controller {
|
|
10
|
+
static values = { interval: Number }
|
|
11
|
+
|
|
12
|
+
connect() {
|
|
13
|
+
this.tick = this.tick.bind(this)
|
|
14
|
+
this.onVisibility = this.onVisibility.bind(this)
|
|
15
|
+
document.addEventListener("visibilitychange", this.onVisibility)
|
|
16
|
+
this.#start()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
disconnect() {
|
|
20
|
+
document.removeEventListener("visibilitychange", this.onVisibility)
|
|
21
|
+
this.#stop()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
intervalValueChanged() {
|
|
25
|
+
this.#stop()
|
|
26
|
+
this.#start()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
tick() {
|
|
30
|
+
if (document.hidden) {
|
|
31
|
+
this.missed = true
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
this.reload()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
reload() {
|
|
38
|
+
this.missed = false
|
|
39
|
+
if (typeof this.element.reload === "function" && this.element.getAttribute("src")) {
|
|
40
|
+
this.element.reload()
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
onVisibility() {
|
|
45
|
+
if (!document.hidden && this.missed) this.reload()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#start() {
|
|
49
|
+
if (!(this.intervalValue > 0)) return
|
|
50
|
+
this.timer = setInterval(this.tick, this.intervalValue * 1000)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#stop() {
|
|
54
|
+
if (this.timer) {
|
|
55
|
+
clearInterval(this.timer)
|
|
56
|
+
this.timer = null
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { t } from "../i18n.js"
|
|
2
3
|
|
|
3
4
|
// Connects to data-controller="intl-tel-input"
|
|
4
5
|
export default class extends Controller {
|
|
@@ -52,7 +53,11 @@ export default class extends Controller {
|
|
|
52
53
|
#buildOptions() {
|
|
53
54
|
// Defaults first; the definition-supplied `options` value (e.g.
|
|
54
55
|
// { initialCountry: "gh", strictMode: false }) is spread last so it wins.
|
|
56
|
+
// Optional locale passthrough: plutonium.js.libraries.intl_tel_input is
|
|
57
|
+
// the intl-tel-input `i18n` option (an object of strings).
|
|
58
|
+
const i18n = t("plutonium.js.libraries.intl_tel_input")
|
|
55
59
|
return {
|
|
60
|
+
...(i18n && typeof i18n === "object" ? { i18n } : {}),
|
|
56
61
|
strictMode: true,
|
|
57
62
|
hiddenInput: () => ({ phone: this.inputTarget.attributes.name.value }),
|
|
58
63
|
loadUtilsOnInit: "https://cdn.jsdelivr.net/npm/intl-tel-input@24.8.1/build/js/utils.js",
|
|
@@ -41,6 +41,8 @@ import PositionedController from "./positioned_controller.js"
|
|
|
41
41
|
import CurrencyInputController from "./currency_input_controller.js"
|
|
42
42
|
import BreadcrumbsController from "./breadcrumbs_controller.js"
|
|
43
43
|
import RunProgressController from "./run_progress_controller.js"
|
|
44
|
+
import ChartController from "./chart_controller.js"
|
|
45
|
+
import FrameRefreshController from "./frame_refresh_controller.js"
|
|
44
46
|
|
|
45
47
|
export default function (application) {
|
|
46
48
|
// Register controllers here
|
|
@@ -86,4 +88,6 @@ export default function (application) {
|
|
|
86
88
|
application.register("currency-input", CurrencyInputController)
|
|
87
89
|
application.register("breadcrumbs", BreadcrumbsController)
|
|
88
90
|
application.register("run-progress", RunProgressController)
|
|
91
|
+
application.register("chart", ChartController)
|
|
92
|
+
application.register("frame-refresh", FrameRefreshController)
|
|
89
93
|
}
|