mensa 0.9.3 → 0.9.5
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/.devcontainer/devcontainer.json +6 -1
- data/.devcontainer/docker-compose.yml +8 -2
- data/.devcontainer/initialize.sh +22 -0
- data/.devcontainer/postStart.sh +2 -0
- data/.gitignore +3 -0
- data/.shipkit.yml +1 -1
- data/Gemfile.lock +1 -1
- data/app/components/mensa/filter_pill_list/component.html.erb +1 -0
- data/app/components/mensa/filter_pill_list/component_controller.js +32 -21
- data/app/components/mensa/table/component_controller.js +13 -0
- data/app/components/mensa/views/component.css +1 -1
- data/app/components/mensa/views/component.html.erb +2 -1
- data/app/components/mensa/views/component_controller.js +1 -1
- data/app/tables/mensa/base.rb +29 -10
- data/lib/mensa/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 486efef62711b15401ec2a9704476bcd3768a7c856c2d21e050b478e23ada619
|
|
4
|
+
data.tar.gz: 710ba8aa3365038772f309bd74fd13c7271c1a67068615c13c33fac6700ae804
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cbb1d6a9a8a4bee3f1ef4ea7db1dddf44cee1d00432ab59bbed77be3ba506bcf5c586c3e33d799b54dce3cfc1275968b6f303346b62b5597867b94b888e3d042
|
|
7
|
+
data.tar.gz: 4e6818cc69caed0ffc36163b69aa604d1d593eb144c684c4e71866d73a79879809acb5ff0cd1a627efbcb0c4c8636b2a103f06dd410f2ef0a13460f0597b6465
|
|
@@ -24,11 +24,16 @@
|
|
|
24
24
|
"source=/run/host-services/ssh-auth.sock,target=/ssh-agent,type=bind,consistency=cached",
|
|
25
25
|
"source=${localEnv:HOME}/.gitconfig,target=/home/vscode/.gitconfig,type=bind,consistency=cached"
|
|
26
26
|
],
|
|
27
|
+
"initializeCommand": ".devcontainer/initialize.sh",
|
|
27
28
|
"postStartCommand": ".devcontainer/postStart.sh",
|
|
28
29
|
"containerEnv": {
|
|
29
30
|
"VISUAL": "vi",
|
|
30
31
|
"SSH_AUTH_SOCK": "/ssh-agent"
|
|
31
32
|
},
|
|
33
|
+
// Credentials for the GitHub Packages gem source in the Gemfile
|
|
34
|
+
"remoteEnv": {
|
|
35
|
+
"BUNDLE_RUBYGEMS__PKG__GITHUB__COM": "${localEnv:BUNDLE_RUBYGEMS__PKG__GITHUB__COM}"
|
|
36
|
+
},
|
|
32
37
|
"customizations": {},
|
|
33
|
-
"workspaceFolder": "/workspaces
|
|
38
|
+
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}"
|
|
34
39
|
}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
name: mensa
|
|
1
|
+
name: ${COMPOSE_PROJECT_NAME:-mensa}
|
|
2
2
|
|
|
3
3
|
services:
|
|
4
4
|
app:
|
|
5
5
|
image: ghcr.io/rails/devcontainer/images/ruby:4.0.5
|
|
6
6
|
|
|
7
7
|
volumes:
|
|
8
|
-
|
|
8
|
+
# .env is written by initialize.sh, the defaults are for the main checkout
|
|
9
|
+
- ${WORKSPACES_ROOT:-../..}:/workspaces
|
|
10
|
+
# A worktree's .git file points here by absolute path
|
|
11
|
+
- ${GIT_COMMON_DIR:-../.git}:${GIT_COMMON_DIR:-/workspaces/mensa/.git}
|
|
12
|
+
|
|
13
|
+
environment:
|
|
14
|
+
CAPYBARA_APP_HOST: http://app.${COMPOSE_PROJECT_NAME:-mensa}.orb.local
|
|
9
15
|
|
|
10
16
|
# Overrides default command so things don't shut down after the process ends.
|
|
11
17
|
command: sleep infinity
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Runs on the host before the containers start, see initializeCommand.
|
|
3
|
+
# Writes the .env docker compose reads, so a git worktree gets its own
|
|
4
|
+
# containers and can use git inside them.
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
cd "$(dirname "$0")/.."
|
|
8
|
+
checkout="$(pwd -P)"
|
|
9
|
+
name="$(basename "$checkout")"
|
|
10
|
+
|
|
11
|
+
if [ -f .git ]; then
|
|
12
|
+
# A worktree: its .git file points into the main checkout's .git directory
|
|
13
|
+
project="mensa-$(echo "$name" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9_-\n' '-')"
|
|
14
|
+
else
|
|
15
|
+
project="mensa"
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
cat > .devcontainer/.env <<EOF
|
|
19
|
+
COMPOSE_PROJECT_NAME=$project
|
|
20
|
+
WORKSPACES_ROOT=$(dirname "$checkout")
|
|
21
|
+
GIT_COMMON_DIR=$(cd "$(git rev-parse --git-common-dir)" && pwd -P)
|
|
22
|
+
EOF
|
data/.devcontainer/postStart.sh
CHANGED
|
@@ -11,3 +11,5 @@ sudo chmod 666 /ssh-agent
|
|
|
11
11
|
"$MISE" exec -- bundle install
|
|
12
12
|
"$MISE" exec --cd test/dummy -- rake db:prepare
|
|
13
13
|
"$MISE" exec --cd test/dummy -- rake tailwindcss:config
|
|
14
|
+
# Propshaft only serves app/assets/builds when it exists at boot
|
|
15
|
+
"$MISE" exec --cd test/dummy -- rake tailwindcss:build
|
data/.gitignore
CHANGED
data/.shipkit.yml
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
---
|
|
2
|
-
previous_version: 0.9.
|
|
2
|
+
previous_version: 0.9.4
|
data/Gemfile.lock
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
data-mensa-table-target="filterList"
|
|
8
8
|
data-mensa-filter-pill-list-table-name-value="<%= table.name %>"
|
|
9
9
|
data-mensa-filter-pill-list-storage-key-value="<%= table.storage_key %>"
|
|
10
|
+
data-mensa-filter-pill-list-filters-customized-value="<%= table.filters_customized? %>"
|
|
10
11
|
data-mensa-filter-pill-list-mensa-table-outlet=".mensa-table#table-<%= table.table_id %>"
|
|
11
12
|
data-mensa-filter-pill-list-mensa-filter-pill-outlet=".mensa-filter-pill"
|
|
12
13
|
data-mensa-filter-pill-list-mensa-add-filter-outlet="[data-controller=mensa-add-filter]">
|
|
@@ -9,6 +9,7 @@ export default class FilterPillListComponentController extends ApplicationContro
|
|
|
9
9
|
supportsViews: Boolean,
|
|
10
10
|
tableName: String,
|
|
11
11
|
storageKey: String,
|
|
12
|
+
filtersCustomized: Boolean,
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
connect() {
|
|
@@ -180,7 +181,10 @@ export default class FilterPillListComponentController extends ApplicationContro
|
|
|
180
181
|
page: "",
|
|
181
182
|
};
|
|
182
183
|
this.persistState(state);
|
|
183
|
-
// The selected view brings its own grouping
|
|
184
|
+
// The selected view brings its own filters and grouping. Stored filters
|
|
185
|
+
// hold the previous view's pills (its defaults included), which would
|
|
186
|
+
// otherwise be replayed onto this view when the page is visited again.
|
|
187
|
+
this.writeStorage(this.filtersStorageKey, null);
|
|
184
188
|
this.clearPersistedGrouping();
|
|
185
189
|
this.setSearchField("");
|
|
186
190
|
this.updateSearchPlaceholder();
|
|
@@ -210,9 +214,7 @@ export default class FilterPillListComponentController extends ApplicationContro
|
|
|
210
214
|
const order = this.parseOrderParams(url.searchParams);
|
|
211
215
|
this.persistOrder(order);
|
|
212
216
|
|
|
213
|
-
|
|
214
|
-
this._notifyUnsavedState();
|
|
215
|
-
}
|
|
217
|
+
this._updateUnsavedState();
|
|
216
218
|
}
|
|
217
219
|
|
|
218
220
|
currentRequestState() {
|
|
@@ -286,21 +288,8 @@ export default class FilterPillListComponentController extends ApplicationContro
|
|
|
286
288
|
const page = this.loadPage();
|
|
287
289
|
const order = this.loadOrder();
|
|
288
290
|
const columnOrder = this.loadColumnOrder();
|
|
289
|
-
const hiddenColumns = this.loadHiddenColumns();
|
|
290
291
|
|
|
291
|
-
|
|
292
|
-
if (
|
|
293
|
-
Object.keys(filters).length > 0 ||
|
|
294
|
-
filtersProvided ||
|
|
295
|
-
query.length > 0 ||
|
|
296
|
-
Object.keys(order).length > 0 ||
|
|
297
|
-
columnOrder.length > 0 ||
|
|
298
|
-
hiddenColumns.length > 0 ||
|
|
299
|
-
this.loadGroupBy() !== null ||
|
|
300
|
-
this.loadAggregates() !== null
|
|
301
|
-
) {
|
|
302
|
-
this._notifyUnsavedState();
|
|
303
|
-
}
|
|
292
|
+
this._updateUnsavedState();
|
|
304
293
|
|
|
305
294
|
const hasFilterOrSearch =
|
|
306
295
|
Object.keys(filters).length > 0 ||
|
|
@@ -592,9 +581,7 @@ export default class FilterPillListComponentController extends ApplicationContro
|
|
|
592
581
|
`[data-mensa-views-target="view"][data-view-id="${view}"]`,
|
|
593
582
|
);
|
|
594
583
|
if (viewEl) {
|
|
595
|
-
const name =
|
|
596
|
-
viewEl.dataset.viewName ||
|
|
597
|
-
viewEl.querySelector("span")?.textContent?.trim();
|
|
584
|
+
const name = viewEl.dataset.viewName;
|
|
598
585
|
if (name) triggerLabel.textContent = name;
|
|
599
586
|
}
|
|
600
587
|
}
|
|
@@ -611,6 +598,21 @@ export default class FilterPillListComponentController extends ApplicationContro
|
|
|
611
598
|
);
|
|
612
599
|
}
|
|
613
600
|
|
|
601
|
+
// Whether the table differs from the selected view, so there's something
|
|
602
|
+
// to save or reset. The server tells whether the rendered filters differ,
|
|
603
|
+
// as stored filters can equal the view's own.
|
|
604
|
+
hasUnsavedState() {
|
|
605
|
+
return (
|
|
606
|
+
this.filtersCustomizedValue ||
|
|
607
|
+
this.loadQuery().length > 0 ||
|
|
608
|
+
Object.keys(this.loadOrder()).length > 0 ||
|
|
609
|
+
this.loadColumnOrder().length > 0 ||
|
|
610
|
+
this.loadHiddenColumns().length > 0 ||
|
|
611
|
+
this.loadGroupBy() !== null ||
|
|
612
|
+
this.loadAggregates() !== null
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
|
|
614
616
|
// Clears all dirty-state localStorage keys so the view is clean after save.
|
|
615
617
|
clearPersistedDirtyState() {
|
|
616
618
|
this.writeStorage(this.filtersStorageKey, null);
|
|
@@ -833,6 +835,15 @@ export default class FilterPillListComponentController extends ApplicationContro
|
|
|
833
835
|
}
|
|
834
836
|
}
|
|
835
837
|
|
|
838
|
+
_updateUnsavedState() {
|
|
839
|
+
if (
|
|
840
|
+
this.hasMensaTableOutlet &&
|
|
841
|
+
typeof this.mensaTableOutlet.updateUnsavedState === "function"
|
|
842
|
+
) {
|
|
843
|
+
this.mensaTableOutlet.updateUnsavedState();
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
836
847
|
_notifyUnsavedState() {
|
|
837
848
|
if (
|
|
838
849
|
this.hasMensaTableOutlet &&
|
|
@@ -185,6 +185,17 @@ export default class TableComponentController extends ApplicationController {
|
|
|
185
185
|
this.showSaveReset();
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
// Shows the save/reset buttons only while something differs from the view,
|
|
189
|
+
// so they disappear again once a change is undone.
|
|
190
|
+
updateUnsavedState() {
|
|
191
|
+
if (!this.hasMensaFilterPillListOutlet) return;
|
|
192
|
+
if (this.mensaFilterPillListOutlet.hasUnsavedState()) {
|
|
193
|
+
this.notifyUnsavedState();
|
|
194
|
+
} else {
|
|
195
|
+
this.hideSaveReset();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
188
199
|
// --- Cancel / Reset ---
|
|
189
200
|
|
|
190
201
|
// Resets all filters, search, and column customisation to the active view's clean state.
|
|
@@ -400,6 +411,8 @@ export default class TableComponentController extends ApplicationController {
|
|
|
400
411
|
// Called when the filter-pill-list outlet connects (including after turbo-stream re-renders).
|
|
401
412
|
// Re-applied on every connect so late-arriving pills (from restoreState) get the right state.
|
|
402
413
|
mensaFilterPillListOutletConnected() {
|
|
414
|
+
// The pill list is rendered again after every change
|
|
415
|
+
this.updateUnsavedState();
|
|
403
416
|
const hasViewFilters = this._hasViewFilterPills();
|
|
404
417
|
if (hasViewFilters) {
|
|
405
418
|
const visible = this._loadViewFiltersVisible();
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
<% is_user_view = view.is_a?(Mensa::TableView) %>
|
|
23
23
|
<li class="mensa-table__views__option" role="option" data-view-id="<%= view.id %>" data-view-name="<%= view.name %>" data-mensa-views-target="view">
|
|
24
24
|
<button class="mensa-table__views__option-btn" type="button" data-action="mensa-views#select" data-view-id="<%= view.id %>">
|
|
25
|
-
<i
|
|
25
|
+
<%# The state is on the span: FontAwesome swaps the <i> for an <svg> asynchronously, copying classes that may be stale by then %>
|
|
26
|
+
<span class="mensa-table__views__option-check<%= " invisible" unless is_selected %>"><i class="<%= Mensa.config.icons[:views_option_selected] %>"></i></span>
|
|
26
27
|
<span><%= view.name %></span>
|
|
27
28
|
</button>
|
|
28
29
|
<% if is_user_view %>
|
|
@@ -42,7 +42,7 @@ export default class ViewsComponentController extends ApplicationController {
|
|
|
42
42
|
check?.classList.toggle("invisible", !isThis);
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
const viewName =
|
|
45
|
+
const viewName = selected.closest("[data-mensa-views-target='view']")?.dataset.viewName || "";
|
|
46
46
|
if (this.hasTriggerLabelTarget) {
|
|
47
47
|
this.triggerLabelTarget.textContent = viewName;
|
|
48
48
|
}
|
data/app/tables/mensa/base.rb
CHANGED
|
@@ -106,17 +106,14 @@ module Mensa
|
|
|
106
106
|
end
|
|
107
107
|
|
|
108
108
|
def active_filters
|
|
109
|
-
(config[:filters]
|
|
110
|
-
|
|
111
|
-
# Columns without SQL can only be filtered with a custom scope, skip
|
|
112
|
-
# filters on them, e.g. from browser state saved earlier
|
|
113
|
-
next if col.nil? || !(col.sql? || col.filter?)
|
|
109
|
+
filters_for(config[:filters])
|
|
110
|
+
end
|
|
114
111
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
112
|
+
# True when the filters differ from the selected view's, so there is
|
|
113
|
+
# something to save or reset
|
|
114
|
+
def filters_customized?
|
|
115
|
+
view_filters = (table_view&.config || {}).deep_symbolize_keys[:filters]
|
|
116
|
+
comparable_filters(active_filters) != comparable_filters(filters_for(view_filters))
|
|
120
117
|
end
|
|
121
118
|
|
|
122
119
|
def actions?
|
|
@@ -273,6 +270,28 @@ module Mensa
|
|
|
273
270
|
|
|
274
271
|
private
|
|
275
272
|
|
|
273
|
+
def filters_for(filters)
|
|
274
|
+
(filters || {}).filter_map do |column_name, filter_config|
|
|
275
|
+
col = column(column_name)
|
|
276
|
+
# Columns without SQL can only be filtered with a custom scope, skip
|
|
277
|
+
# filters on them, e.g. from browser state saved earlier
|
|
278
|
+
next if col.nil? || !(col.sql? || col.filter?)
|
|
279
|
+
|
|
280
|
+
definition = config.dig(:columns, col.name, :filter)
|
|
281
|
+
definition = {} unless definition.is_a?(Hash)
|
|
282
|
+
requested = (filter_config || {}).to_h.symbolize_keys.slice(:value, :operator)
|
|
283
|
+
Mensa::Filter.new(column: col, config: definition.merge(requested), table: self)
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Operator and value per column, the value only when the operator takes one
|
|
288
|
+
def comparable_filters(filters)
|
|
289
|
+
filters.to_h do |filter|
|
|
290
|
+
value = Array(filter.value).map(&:to_s).reject(&:blank?).sort if filter.operator_with_value?
|
|
291
|
+
[filter.column.name, [filter.operator, value]]
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
276
295
|
def ensure_internal_columns_for_joined_associations
|
|
277
296
|
return if @internal_columns_ensured
|
|
278
297
|
|
data/lib/mensa/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mensa
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.9.
|
|
4
|
+
version: 0.9.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tom de Grunt
|
|
@@ -167,6 +167,7 @@ files:
|
|
|
167
167
|
- ".devcontainer/devcontainer-lock.json"
|
|
168
168
|
- ".devcontainer/devcontainer.json"
|
|
169
169
|
- ".devcontainer/docker-compose.yml"
|
|
170
|
+
- ".devcontainer/initialize.sh"
|
|
170
171
|
- ".devcontainer/postStart.sh"
|
|
171
172
|
- ".gem_release.yml"
|
|
172
173
|
- ".github/workflows/gem-push.yml"
|