mensa 0.9.4 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 63364b3ef1004b6f44cb7188209c0924553d7e141f52062df2c4e06bf55cca41
4
- data.tar.gz: d213f088d9a5f07af9f3502318f23095a0654fb7ea0738a7012c520b0fe5ad21
3
+ metadata.gz: 486efef62711b15401ec2a9704476bcd3768a7c856c2d21e050b478e23ada619
4
+ data.tar.gz: 710ba8aa3365038772f309bd74fd13c7271c1a67068615c13c33fac6700ae804
5
5
  SHA512:
6
- metadata.gz: 7a0aa6a1675642a482b9d616370755f0cd46b315997d3082ac4ecf4d510116f32d235909cf28a3f7e123b427f88dc3f68c3ccd04a2a09dd5f8515fae7d4e4bca
7
- data.tar.gz: c89e70746c216146cbaceb589e485dc43a63c37d98f92ad8c420e021eae40f2c04af90130bacd9c90a68b698b0cb57386ff993ce75285638b31361db5dbdb141
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/mensa"
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
- - ../..:/workspaces
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
@@ -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
@@ -22,3 +22,6 @@ test/dummy/config/tailwind.config.js
22
22
  **/**/.DS_Store
23
23
  mise.toml
24
24
  todo.md
25
+
26
+ # Written by .devcontainer/initialize.sh
27
+ .devcontainer/.env
data/.shipkit.yml CHANGED
@@ -1,2 +1,2 @@
1
1
  ---
2
- previous_version: 0.9.3
2
+ previous_version: 0.9.4
@@ -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
- if (Object.keys(order).length > 0) {
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
- // Show save/reset if any persistent state exists (order, column layout, filters, search)
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 ||
@@ -609,6 +598,21 @@ export default class FilterPillListComponentController extends ApplicationContro
609
598
  );
610
599
  }
611
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
+
612
616
  // Clears all dirty-state localStorage keys so the view is clean after save.
613
617
  clearPersistedDirtyState() {
614
618
  this.writeStorage(this.filtersStorageKey, null);
@@ -831,6 +835,15 @@ export default class FilterPillListComponentController extends ApplicationContro
831
835
  }
832
836
  }
833
837
 
838
+ _updateUnsavedState() {
839
+ if (
840
+ this.hasMensaTableOutlet &&
841
+ typeof this.mensaTableOutlet.updateUnsavedState === "function"
842
+ ) {
843
+ this.mensaTableOutlet.updateUnsavedState();
844
+ }
845
+ }
846
+
834
847
  _notifyUnsavedState() {
835
848
  if (
836
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();
@@ -106,17 +106,14 @@ module Mensa
106
106
  end
107
107
 
108
108
  def active_filters
109
- (config[:filters] || {}).filter_map do |column_name, filter_config|
110
- col = column(column_name)
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
- definition = config.dig(:columns, col.name, :filter)
116
- definition = {} unless definition.is_a?(Hash)
117
- requested = (filter_config || {}).to_h.symbolize_keys.slice(:value, :operator)
118
- Mensa::Filter.new(column: col, config: definition.merge(requested), table: self)
119
- end
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
@@ -1,3 +1,3 @@
1
1
  module Mensa
2
- VERSION = "0.9.4"
2
+ VERSION = "0.9.5"
3
3
  end
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
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"