alchemy-chimera 1.3.0-alpha.3 → 1.3.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.
@@ -23,6 +23,100 @@ const Config = Function.inherits('Alchemy.Base', 'Alchemy.Chimera', function Con
23
23
  this.record_preview = null;
24
24
  this.record_preview_controller = null;
25
25
  this.record_preview_action = null;
26
+
27
+ // Custom row actions for this model
28
+ this.row_actions = [];
29
+ });
30
+
31
+ /**
32
+ * Add a custom row action for this model.
33
+ * Actions appear in the index table and context menu.
34
+ *
35
+ * @author Jelle De Loecker <jelle@elevenways.be>
36
+ * @since 1.3.0
37
+ * @version 1.3.0
38
+ *
39
+ * @param {Object} config
40
+ * @param {String} config.name Machine name for the action
41
+ * @param {String} config.icon Icon name (FontAwesome)
42
+ * @param {String} config.title Display title
43
+ * @param {Array} config.placement Where to show: ['row', 'context']
44
+ * @param {String} config.route Route name (e.g., 'Controller#action')
45
+ * @param {Object} config.route_params Route parameters (use $pk for record primary key)
46
+ *
47
+ * @return {Object} The action config
48
+ */
49
+ Config.setMethod(function addRowAction(config) {
50
+
51
+ if (!config || !config.name) {
52
+ throw new Error('Row action requires at least a name');
53
+ }
54
+
55
+ // Set defaults
56
+ if (!config.placement) {
57
+ config.placement = ['row', 'context'];
58
+ }
59
+
60
+ this.row_actions.push(config);
61
+
62
+ return config;
63
+ });
64
+
65
+ /**
66
+ * Get all configured row actions for a specific record.
67
+ * This resolves route URLs using the record's primary key.
68
+ *
69
+ * @author Jelle De Loecker <jelle@elevenways.be>
70
+ * @since 1.3.0
71
+ * @version 1.3.0
72
+ *
73
+ * @param {Document} record The record to create actions for
74
+ *
75
+ * @return {Array} Array of Form.Action instances
76
+ */
77
+ Config.setMethod(function getRowActionsFor(record) {
78
+
79
+ let result = [];
80
+
81
+ for (let config of this.row_actions) {
82
+ let url;
83
+
84
+ if (config.route) {
85
+ // Build route parameters, replacing $pk with actual primary key
86
+ let params = {};
87
+
88
+ if (config.route_params) {
89
+ for (let key in config.route_params) {
90
+ let value = config.route_params[key];
91
+
92
+ if (value === '$pk') {
93
+ value = record.$pk;
94
+ }
95
+
96
+ params[key] = value;
97
+ }
98
+ }
99
+
100
+ url = alchemy.routeUrl(config.route, params);
101
+
102
+ if (!url) {
103
+ log.warn('Failed to generate URL for row action', config.name, 'with route', config.route);
104
+ continue;
105
+ }
106
+ }
107
+
108
+ let action = new Classes.Alchemy.Form.Action.Url({
109
+ name : config.name,
110
+ icon : config.icon,
111
+ title : config.title,
112
+ placement : config.placement,
113
+ url : url,
114
+ });
115
+
116
+ result.push(action);
117
+ }
118
+
119
+ return result;
26
120
  });
27
121
 
28
122
  /**
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Register Chimera's toolbar buttons
3
+ *
4
+ * @author Jelle De Loecker <jelle@elevenways.be>
5
+ * @since 0.3.0
6
+ * @version 0.3.0
7
+ */
8
+ const EditorToolbarManager = Classes.Alchemy.Widget.EditorToolbarManager;
9
+
10
+ /**
11
+ * Register model-level buttons for Chimera
12
+ *
13
+ * @author Jelle De Loecker <jelle@elevenways.be>
14
+ * @since 0.3.0
15
+ * @version 0.3.0
16
+ *
17
+ * @param {Alchemy.Widget.EditorToolbarManager} manager
18
+ * @param {string} model_name
19
+ */
20
+ EditorToolbarManager.registerModelButtonProvider(function addChimeraModelButtons(manager, model_name) {
21
+
22
+ // Only add buttons in chimera scenario
23
+ if (manager.scenario != 'chimera') {
24
+ return;
25
+ }
26
+
27
+ // Add create button for all models
28
+ manager.addTemplateToRender('buttons', 'chimera/toolbar/create_button', {
29
+ model_name: model_name,
30
+ });
31
+ });
32
+
33
+ /**
34
+ * Register document-level buttons for Chimera
35
+ *
36
+ * @author Jelle De Loecker <jelle@elevenways.be>
37
+ * @since 0.3.0
38
+ * @version 0.3.0
39
+ *
40
+ * @param {Alchemy.Widget.EditorToolbarManager} manager
41
+ * @param {Alchemy.Document} doc
42
+ * @param {Alchemy.Model} model
43
+ * @param {string} model_name
44
+ * @param {*} pk_val
45
+ */
46
+ EditorToolbarManager.registerDocumentButtonProvider(function addChimeraDocumentButtons(manager, doc, model, model_name, pk_val) {
47
+
48
+ // Edit in Chimera button (for non-chimera scenarios)
49
+ if (manager.scenario != 'chimera') {
50
+ manager.addTemplateToRender('buttons', 'chimera/toolbar/edit_in_chimera_button', {
51
+ model_name: model_name.underscore(),
52
+ record_pk: pk_val,
53
+ });
54
+ return; // No other buttons for non-chimera
55
+ }
56
+
57
+ // Preview button (chimera scenario only)
58
+ if (model.chimera?.record_preview) {
59
+ manager.addTemplateToRender('buttons', 'chimera/toolbar/preview_button', {
60
+ model_name: model_name.underscore(),
61
+ record_pk: pk_val,
62
+ });
63
+ }
64
+
65
+ // Run Task button for System.Task
66
+ if (model_name == 'System_Task' && doc.enabled && doc.type) {
67
+ manager.addTemplateToRender('buttons', 'chimera/toolbar/run_task_button', {
68
+ record_pk: pk_val,
69
+ });
70
+ }
71
+
72
+ // Monitor button for System.TaskHistory
73
+ if (model_name == 'System_TaskHistory') {
74
+ manager.addTemplateToRender('buttons', 'chimera/toolbar/monitor_button', {
75
+ record_pk: pk_val,
76
+ });
77
+ }
78
+ });
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The base Chimera Model class
3
+ * Creates the Chimera model namespace
4
+ *
5
+ * @constructor
6
+ *
7
+ * @author Jelle De Loecker <jelle@elevenways.be>
8
+ * @since 1.4.0
9
+ * @version 1.4.0
10
+ */
11
+ const Chimera = Function.inherits('Alchemy.Model.App', 'Alchemy.Model.Chimera', 'Chimera');
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The Chimera DashboardConfig Model
3
+ * Stores widget configurations for the Chimera dashboard
4
+ *
5
+ * @constructor
6
+ *
7
+ * @author Jelle De Loecker <jelle@elevenways.be>
8
+ * @since 1.3.0
9
+ * @version 1.3.0
10
+ */
11
+ const DashboardConfig = Function.inherits('Alchemy.Model.Chimera', 'DashboardConfig');
12
+
13
+ /**
14
+ * Configure the schema
15
+ *
16
+ * @author Jelle De Loecker <jelle@elevenways.be>
17
+ * @since 1.3.0
18
+ * @version 1.3.0
19
+ */
20
+ DashboardConfig.constitute(function addFields() {
21
+
22
+ // Name of this dashboard configuration
23
+ this.addField('name', 'String', {
24
+ title: 'Name',
25
+ required: true,
26
+ });
27
+
28
+ // The widget configuration
29
+ this.addField('widgets', 'Widgets', {
30
+ title: 'Widgets',
31
+ });
32
+
33
+ // Is this the default dashboard?
34
+ this.addField('is_default', 'Boolean', {
35
+ title: 'Default',
36
+ default: false,
37
+ });
38
+
39
+ // Optional: belongs to a specific user for per-user dashboards
40
+ this.belongsTo('User', {
41
+ title: 'User',
42
+ description: 'If set, this is a user-specific dashboard',
43
+ });
44
+ });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "alchemy-chimera",
3
3
  "description": "Chimera plugin for Alchemy MVC",
4
- "version": "1.3.0-alpha.3",
4
+ "version": "1.3.0",
5
5
  "author": "Jelle De Loecker <jelle@elevenways.be>",
6
6
  "keywords": [
7
7
  "alchemy",
@@ -11,10 +11,10 @@
11
11
  ],
12
12
  "repository": "11ways/alchemy-chimera",
13
13
  "peerDependencies": {
14
- "alchemy-acl" : "~0.9.0||~0.9.0-alpha",
14
+ "alchemy-acl" : ">=0.9.0||>=0.9.0-alpha",
15
15
  "alchemymvc" : ">=1.4.0||>=1.4.0-alpha",
16
- "alchemy-form" : "~0.3.0||~0.3.0-alpha",
17
- "alchemy-widget" : "~0.3.0||~0.3.0-alpha",
16
+ "alchemy-form" : ">=0.3.0||>=0.3.0-alpha",
17
+ "alchemy-widget" : ">=0.3.0||>=0.3.0-alpha",
18
18
  "alchemy-styleboost" : ">=0.5.0||>=0.5.0-alpha"
19
19
  },
20
20
  "license": "MIT",
@@ -1,7 +1,8 @@
1
1
  {% extend "layouts/chimera_body" %}
2
2
 
3
3
  {% block "main" %}
4
- <al-widgets>
5
-
6
- </al-widgets>
4
+ <al-widgets
5
+ #record={% dashboard_config %}
6
+ #field="widgets"
7
+ ></al-widgets>
7
8
  {% /block %}
@@ -0,0 +1,32 @@
1
+ {% include "layouts/chimera_basics" %}
2
+
3
+ {% block "page-actions" %}
4
+ {% if system_task %}
5
+ <a
6
+ !Route="Chimera.Editor#edit"
7
+ +model="System_Task"
8
+ +pk={% system_task.$pk %}
9
+ class="btn"
10
+ >
11
+ <al-icon icon-name="pencil"></al-icon>
12
+ {%t "edit-task" %}
13
+ </a>
14
+ {% /if %}
15
+
16
+ <a
17
+ !Route="Chimera.Editor#index"
18
+ +model="System_TaskHistory"
19
+ class="btn"
20
+ >
21
+ <al-icon icon-name="list"></al-icon>
22
+ {%t "task-history" %}
23
+ </a>
24
+ {% /block %}
25
+
26
+ {% block "main" %}
27
+ <chimera-task-monitor
28
+ task-history-id={% task_history_id %}
29
+ #history_doc={% history_doc %}
30
+ #system_task={% system_task %}
31
+ ></chimera-task-monitor>
32
+ {% /block %}
File without changes
@@ -0,0 +1,26 @@
1
+ <chimera-dashboard-button
2
+ class="btn btn-toolbar"
3
+ action="customize"
4
+ title="Create your personal dashboard"
5
+ state="default"
6
+ >
7
+ <al-state state-name="default">
8
+ <al-icon icon-name="user-pen"></al-icon>
9
+ {%t "customize" %}
10
+ </al-state>
11
+
12
+ <al-state state-name="busy">
13
+ <al-icon icon-name="spinner" icon-flags="spin"></al-icon>
14
+ {%t "working" %}
15
+ </al-state>
16
+
17
+ <al-state state-name="done">
18
+ <al-icon icon-name="badge-check" icon-flags="beat"></al-icon>
19
+ {%t "done" %}
20
+ </al-state>
21
+
22
+ <al-state state-name="error">
23
+ <al-icon icon-name="skull" icon-flags="shake"></al-icon>
24
+ {%t "error" %}
25
+ </al-state>
26
+ </chimera-dashboard-button>
@@ -0,0 +1,8 @@
1
+ <a
2
+ class="btn btn-monitor"
3
+ !Route="Chimera.Editor#taskMonitor"
4
+ +task_history_id={% record_pk %}
5
+ >
6
+ <al-icon icon-name="chart-line"></al-icon>
7
+ {%t "monitor" %}
8
+ </a>
@@ -0,0 +1,26 @@
1
+ <chimera-dashboard-button
2
+ class="btn btn-toolbar"
3
+ action="reset"
4
+ title="Reset to default dashboard"
5
+ state="default"
6
+ >
7
+ <al-state state-name="default">
8
+ <al-icon icon-name="rotate-left"></al-icon>
9
+ {%t "reset" %}
10
+ </al-state>
11
+
12
+ <al-state state-name="busy">
13
+ <al-icon icon-name="spinner" icon-flags="spin"></al-icon>
14
+ {%t "working" %}
15
+ </al-state>
16
+
17
+ <al-state state-name="done">
18
+ <al-icon icon-name="badge-check" icon-flags="beat"></al-icon>
19
+ {%t "done" %}
20
+ </al-state>
21
+
22
+ <al-state state-name="error">
23
+ <al-icon icon-name="skull" icon-flags="shake"></al-icon>
24
+ {%t "error" %}
25
+ </al-state>
26
+ </chimera-dashboard-button>
@@ -0,0 +1,25 @@
1
+ <chimera-run-task-button
2
+ class="btn btn-run-task"
3
+ task-id={% record_pk %}
4
+ state="default"
5
+ >
6
+ <al-state state-name="default">
7
+ <al-icon icon-style="duotone" icon-name="play"></al-icon>
8
+ {%t "run-task" %}
9
+ </al-state>
10
+
11
+ <al-state state-name="busy">
12
+ <al-icon icon-style="duotone" icon-name="spinner" icon-flags="spin"></al-icon>
13
+ {%t "starting" %}
14
+ </al-state>
15
+
16
+ <al-state state-name="done">
17
+ <al-icon icon-style="duotone" icon-name="badge-check" icon-flags="beat"></al-icon>
18
+ {%t "started" %}
19
+ </al-state>
20
+
21
+ <al-state state-name="error">
22
+ <al-icon icon-style="duotone" icon-name="skull" icon-flags="shake"></al-icon>
23
+ {%t "error" %}
24
+ </al-state>
25
+ </chimera-run-task-button>
@@ -0,0 +1,80 @@
1
+ <div class="task-monitor-container">
2
+ <div class="task-monitor-header">
3
+ <div class="task-info">
4
+ <h2 class="task-title">
5
+ {% if self.system_task %}
6
+ {{ self.system_task.title || self.history_doc.type }}
7
+ {% else %}
8
+ {{ self.history_doc.type }}
9
+ {% /if %}
10
+ </h2>
11
+ <div class="task-type">{{ self.history_doc.type }}</div>
12
+ </div>
13
+
14
+ <div class="task-meta">
15
+ <div class="meta-item">
16
+ <span class="meta-label">{%t "status" %}</span>
17
+ <span class="task-status status-pending">{%t "task-status-pending" %}</span>
18
+ </div>
19
+ <div class="meta-item">
20
+ <span class="meta-label">{%t "elapsed" %}</span>
21
+ <span class="elapsed-time">-</span>
22
+ </div>
23
+ <div class="meta-item">
24
+ <span class="meta-label">{%t "progress" %}</span>
25
+ <span class="progress-text">-</span>
26
+ </div>
27
+ </div>
28
+ </div>
29
+
30
+ <div class="task-error" style="display: none;"></div>
31
+
32
+ <div class="progress-container">
33
+ <div class="progress-track">
34
+ <div class="progress-bar" style="width: 0%;"></div>
35
+ </div>
36
+ </div>
37
+
38
+ <div class="task-controls">
39
+ <button class="btn btn-pause" type="button" style="display: none;">
40
+ <al-icon icon-name="pause"></al-icon>
41
+ {%t "pause" %}
42
+ </button>
43
+ <button class="btn btn-resume" type="button" style="display: none;">
44
+ <al-icon icon-name="play"></al-icon>
45
+ {%t "resume" %}
46
+ </button>
47
+ <button class="btn btn-stop btn-danger" type="button" style="display: none;">
48
+ <al-icon icon-name="stop"></al-icon>
49
+ {%t "stop" %}
50
+ </button>
51
+ </div>
52
+
53
+ <div class="task-logs">
54
+ <h3 class="logs-title">{%t "output" %}</h3>
55
+ <div class="log-output"></div>
56
+ </div>
57
+
58
+ <div class="task-timestamps">
59
+ <div class="timestamp-item">
60
+ <span class="timestamp-label">{%t "started-at" %}</span>
61
+ <span class="timestamp-value">
62
+ {% if self.history_doc.started_at %}
63
+ {{ self.history_doc.started_at.format('Y-m-d H:i:s') }}
64
+ {% else %}
65
+ -
66
+ {% /if %}
67
+ </span>
68
+ </div>
69
+ <div class="timestamp-item">
70
+ <span class="timestamp-label">{%t "ended-at" %}</span>
71
+ <span class="timestamp-value ended-at-value">
72
+ {% if self.history_doc.ended_at %}
73
+ {{ self.history_doc.ended_at.format('Y-m-d H:i:s') }}
74
+ {% else %}
75
+ -
76
+ {% /if %}
77
+ </span>
78
+ </div>
79
+ </div>
80
+ </div>
@@ -34,11 +34,53 @@
34
34
  ></al-user-avatar-group>
35
35
  </div>
36
36
 
37
- <div slot="right">
38
- <div data-area="buttons">
37
+ <div slot="right">
38
+ <al-button class="start-edit" state="ready">
39
+ <al-icon icon-name="pencil"></al-icon>
40
+ {%t "start-editing" %}
41
+ </al-button>
39
42
 
40
- </div>
41
- </div>
43
+ <al-button class="stop-and-save" state="ready">
44
+ <al-state state-name="saving">
45
+ <al-icon icon-name="spinner" icon-flags="spin"></al-icon>
46
+ {%t "saving" %}
47
+ </al-state>
48
+ <al-state state-name="saving-before-stop">
49
+ <al-icon icon-name="spinner" icon-flags="spin"></al-icon>
50
+ {%t "saving" %}
51
+ </al-state>
52
+ <al-state state-name="saved">
53
+ <al-icon icon-name="badge-check" icon-flags="beat"></al-icon>
54
+ {%t "saved" %}
55
+ </al-state>
56
+ <al-state state-name="ready">
57
+ <al-icon icon-name="floppy-disk-circle-arrow-right"></al-icon>
58
+ {%t "save-and-stop-editing" %}
59
+ </al-state>
60
+ </al-button>
61
+
62
+ <al-button class="stop-edit" state="ready">
63
+ <al-icon icon-name="pencil-slash"></al-icon>
64
+ {%t "stop-editing" %}
65
+ </al-button>
66
+
67
+ <al-button class="save-all" state="ready">
68
+ <al-state state-name="saving">
69
+ <al-icon icon-name="spinner" icon-flags="spin"></al-icon>
70
+ {%t "saving" %}
71
+ </al-state>
72
+ <al-state state-name="saved">
73
+ <al-icon icon-name="badge-check" icon-flags="beat"></al-icon>
74
+ {%t "saved" %}
75
+ </al-state>
76
+ <al-state state-name="ready">
77
+ <al-icon icon-name="floppy-disk"></al-icon>
78
+ {%t "save-all" %}
79
+ </al-state>
80
+ </al-button>
81
+
82
+ <div data-area="buttons"></div>
83
+ </div>
42
84
  </al-editor-toolbar>
43
85
 
44
86
  {#