rori 0.1.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.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +25 -0
  3. data/LICENSE +21 -0
  4. data/README.md +279 -0
  5. data/app/assets/stylesheets/rori/_init.css +79 -0
  6. data/app/assets/stylesheets/rori/layout.css +162 -0
  7. data/app/assets/stylesheets/rori/menubar.css +117 -0
  8. data/app/assets/stylesheets/rori/notifications.css +120 -0
  9. data/app/assets/stylesheets/rori/palette.css +114 -0
  10. data/app/assets/stylesheets/rori/shortcuts.css +110 -0
  11. data/app/assets/stylesheets/rori/terminal.css +98 -0
  12. data/app/assets/stylesheets/rori/themes.css +156 -0
  13. data/app/assets/stylesheets/rori/typing_badge.css +56 -0
  14. data/app/assets/stylesheets/rori/wallpaper.css +52 -0
  15. data/app/assets/stylesheets/rori/window.css +174 -0
  16. data/app/controllers/concerns/rori/windowed.rb +15 -0
  17. data/app/controllers/rori/commands/bars_controller.rb +9 -0
  18. data/app/controllers/rori/commands/runs_controller.rb +22 -0
  19. data/app/controllers/rori/commands/themes_controller.rb +9 -0
  20. data/app/controllers/rori/commands/ui_controller.rb +9 -0
  21. data/app/controllers/rori/commands/wallpapers_controller.rb +9 -0
  22. data/app/controllers/rori/commands_controller.rb +7 -0
  23. data/app/controllers/rori/desktops_controller.rb +7 -0
  24. data/app/helpers/rori/assets_helper.rb +9 -0
  25. data/app/helpers/rori/keymap_helper.rb +50 -0
  26. data/app/helpers/rori/prefs_helper.rb +13 -0
  27. data/app/helpers/rori/theme_helper.rb +14 -0
  28. data/app/helpers/rori/wallpaper_helper.rb +19 -0
  29. data/app/helpers/rori/window_helper.rb +36 -0
  30. data/app/javascript/rori/clock_controller.js +18 -0
  31. data/app/javascript/rori/command_index.js +84 -0
  32. data/app/javascript/rori/fuzzy.js +50 -0
  33. data/app/javascript/rori/index.js +21 -0
  34. data/app/javascript/rori/notifications_controller.js +45 -0
  35. data/app/javascript/rori/palette_controller.js +307 -0
  36. data/app/javascript/rori/prefs_controller.js +68 -0
  37. data/app/javascript/rori/rori_controller.js +996 -0
  38. data/app/javascript/rori/terminal_controller.js +198 -0
  39. data/app/javascript/rori/theme_controller.js +35 -0
  40. data/app/javascript/rori/wallpaper_controller.js +67 -0
  41. data/app/models/rori/command.rb +105 -0
  42. data/app/models/rori/notification.rb +20 -0
  43. data/app/models/rori/theme/color.rb +30 -0
  44. data/app/models/rori/theme.rb +87 -0
  45. data/app/models/rori/wallpaper.rb +54 -0
  46. data/app/views/layouts/rori/_head.html.haml +2 -0
  47. data/app/views/layouts/rori/shell.html.haml +97 -0
  48. data/app/views/layouts/rori/window.html.haml +10 -0
  49. data/app/views/rori/_menubar.html.haml +14 -0
  50. data/app/views/rori/_notification.html.haml +9 -0
  51. data/app/views/rori/_notifications.html.haml +10 -0
  52. data/app/views/rori/_palette.html.haml +13 -0
  53. data/app/views/rori/_shortcuts.html.haml +47 -0
  54. data/app/views/rori/_terminal.html.haml +9 -0
  55. data/app/views/rori/_wallpaper.html.haml +7 -0
  56. data/app/views/rori/_wallpaper_credit.html.haml +3 -0
  57. data/app/views/rori/_window.html.haml +15 -0
  58. data/app/views/rori/commands/_command.html.haml +17 -0
  59. data/app/views/rori/commands/index.html.haml +5 -0
  60. data/app/views/rori/desktops/show.html.haml +1 -0
  61. data/config/importmap.rb +3 -0
  62. data/config/locales/rori.en.yml +133 -0
  63. data/config/routes/rori.rb +13 -0
  64. data/lib/generators/rori/install/install_generator.rb +67 -0
  65. data/lib/generators/rori/install/templates/rori.rb.tt +22 -0
  66. data/lib/rori/engine.rb +19 -0
  67. data/lib/rori/version.rb +3 -0
  68. data/lib/rori.rb +180 -0
  69. data/lib/tasks/rori.rake +9 -0
  70. data/vendor/themes/LICENSE +25 -0
  71. data/vendor/themes/ghostty/Catppuccin Latte +22 -0
  72. data/vendor/themes/ghostty/Catppuccin Mocha +22 -0
  73. data/vendor/themes/ghostty/Everforest Dark Med +22 -0
  74. data/vendor/themes/ghostty/Flexoki Light +22 -0
  75. data/vendor/themes/ghostty/GitHub Dark Dimmed +22 -0
  76. data/vendor/themes/ghostty/Gruvbox Material Dark +22 -0
  77. data/vendor/themes/ghostty/Kanagawa Wave +22 -0
  78. data/vendor/themes/ghostty/Melange Dark +22 -0
  79. data/vendor/themes/ghostty/Nord +22 -0
  80. data/vendor/themes/ghostty/Rose Pine Dawn +22 -0
  81. data/vendor/themes/ghostty/Rose Pine Moon +22 -0
  82. data/vendor/themes/ghostty/TokyoNight Storm +22 -0
  83. data/vendor/themes/ghostty/Tomorrow Night Eighties +22 -0
  84. data/vendor/themes/ghostty/Zenbones Light +22 -0
  85. data/vendor/wallpapers/unsplash.yml +23 -0
  86. metadata +219 -0
@@ -0,0 +1,97 @@
1
+ -# The desk shell: full page loads render the requested page as the first
2
+ -# window. Apps add app-wide <head> tags (icons…) by overriding the
3
+ -# layouts/rori/_head partial, and per page with `content_for :head`.
4
+ !!!
5
+ %html{ lang: I18n.locale, data: { theme: current_theme&.slug } }
6
+ %head
7
+ %title= windowed? ? window_title : Rori.app_name
8
+ %meta{ name: "viewport", content: "width=device-width,initial-scale=1" }
9
+ %meta{ name: "apple-mobile-web-app-capable", content: "yes" }
10
+ %meta{ name: "application-name", content: Rori.app_name }
11
+ %meta{ name: "mobile-web-app-capable", content: "yes" }
12
+ -# Turbo's hover-prefetch cache is keyed by URL alone and hands its response
13
+ -# to the next fetch of that URL, whatever frame asks. Window frames have
14
+ -# generated ids, so a page prefetched for `_top` or another window arrives
15
+ -# without the requesting frame and the window shows "missing frame".
16
+ %meta{ name: "turbo-prefetch", content: "false" }
17
+ = csrf_meta_tags
18
+ = csp_meta_tag
19
+ = render "layouts/rori/head"
20
+ = yield :head
21
+ = stylesheet_link_tag :app, "data-turbo-track": "reload"
22
+ = rori_stylesheet_link_tags "data-turbo-track": "reload"
23
+ = rori_local_themes_style_tag
24
+ = javascript_importmap_tags
25
+
26
+ %body{ class: rori_body_classes,
27
+ style: ("--rori-wallpaper: #{rori_wallpaper.css_image}" if rori_wallpaper),
28
+ data: { controller: "rori rori-palette rori-theme rori-wallpaper rori-terminal rori-prefs rori-notifications",
29
+ rori_notifications_url_value: rori_commands_runs_path,
30
+ rori_prefs_bars_cookie_value: Rori.bars_cookie,
31
+ rori_prefs_bars_value: rori_bars,
32
+ rori_prefs_hint_cookie_value: Rori.hint_cookie,
33
+ rori_theme_cookie_value: Rori.theme_cookie,
34
+ rori_wallpaper_cookie_value: Rori.wallpaper_cookie,
35
+ rori_wallpaper_mode_value: rori_wallpaper_mode,
36
+ rori_wallpaper_pin_cookie_value: Rori.wallpaper_pin_cookie,
37
+ rori_wallpaper_pool_value: (Rori::Wallpaper.all.to_json if rori_wallpaper),
38
+ rori_wallpaper_current_value: rori_wallpaper&.id,
39
+ rori_wallpaper_pinned_value: rori_wallpaper_pinned?.to_s,
40
+ rori_terminal_root_value: rori_commands_path,
41
+ rori_terminal_key_value: Rori.terminal_key,
42
+ rori_terminal_prompt_value: t("rori.terminal.prompt"),
43
+ rori_terminal_messages_value: t("rori.terminal.messages").to_json,
44
+ rori_palette_root_value: rori_commands_path,
45
+ rori_palette_confirm_value: t("rori.palette.confirm"),
46
+ rori_root_url_value: root_path,
47
+ rori_load_error_value: t("rori.window.load_error"),
48
+ rori_workspace_group_value: t("rori.commands.workspaces.group"),
49
+ rori_new_workspace_label_value: t("rori.commands.workspaces.new"),
50
+ rori_keymap_value: Rori.resolved_keymap(rori_modifier).to_json,
51
+ rori_native_value: rori_native?.to_s,
52
+ rori_hover_keymap_value: Rori.resolved_hover_keymap.to_json,
53
+ rori_hover_timeout_value: (Rori.hover_timeout * 1000).round,
54
+ rori_discard_prompt_value: t("rori.window.discard"),
55
+ action: ["keydown@window->rori#keydown",
56
+ "keydown@window->rori-palette#keydown",
57
+ "resize@window->rori#layout",
58
+ "pagehide@window->rori#save",
59
+ "message->rori#streamMessage:capture",
60
+ "turbo:before-stream-render@document->rori#streamRender",
61
+ "turbo:before-visit@document->rori#interceptVisit",
62
+ "keydown@window->rori-terminal#keydown",
63
+ "rori-command:run->rori#command",
64
+ "rori-command:source->rori#commandSource",
65
+ "rori-command:run->rori-theme#run",
66
+ "rori-command:preview->rori-theme#preview",
67
+ "rori-command:closed->rori-theme#revert",
68
+ "rori-command:run->rori-wallpaper#run",
69
+ "rori-command:preview->rori-wallpaper#preview",
70
+ "rori-command:closed->rori-wallpaper#revert",
71
+ "rori-command:run->rori-prefs#run",
72
+ "rori-command:preview->rori-prefs#preview",
73
+ "rori-command:closed->rori-prefs#revert",
74
+ "rori-command:run->rori-notifications#run",
75
+ "rori:open-shortcuts->rori-prefs#openShortcuts",
76
+ "rori:toggle-terminal->rori-terminal#toggle",
77
+ "rori:open-palette->rori-palette#open"].join(" ") } }
78
+ = render "rori/menubar"
79
+ %main.rori-viewport{ data: { rori_target: "viewport", action: "wheel->rori#wheel:!passive pointermove->rori#hover pointerleave->rori#unhover" } }
80
+ = render "rori/wallpaper"
81
+ = render "rori/wallpaper_credit" if rori_wallpaper
82
+ .rori-stack{ data: { rori_target: "stack" } }
83
+ -# Windows wait here until their page says where they go; modals stay here.
84
+ .rori-floating{ data: { rori_target: "floating" } }
85
+ - if windowed?
86
+ = render "rori/window", frame_id: "win_main", src: (request.fullpath if request.get?), body: safe_join([yield, window_meta_tag])
87
+ - else
88
+ = yield
89
+ -# Tags the window whose field has focus (rori/typing_badge.css, anchor positioning).
90
+ %span.rori-typing-badge.rori-typing-badge--side{ "aria-hidden": "true" }= t("rori.window.typing")
91
+ %span.rori-typing-badge.rori-typing-badge--inside{ "aria-hidden": "true" }= t("rori.window.typing")
92
+ = render "rori/terminal"
93
+ = render "rori/shortcuts"
94
+ = render "rori/palette"
95
+ = render "rori/notifications"
96
+ %template{ data: { rori_target: "template" } }
97
+ = render "rori/window"
@@ -0,0 +1,10 @@
1
+ -# Frame responses: Turbo only reads the frame matching the request, so the
2
+ -# page is wrapped in whatever window frame asked for it.
3
+ !!!
4
+ %html{ lang: I18n.locale }
5
+ %head
6
+ %title= window_title
7
+ %body
8
+ = turbo_frame_tag turbo_frame_request_id do
9
+ = yield
10
+ = window_meta_tag
@@ -0,0 +1,14 @@
1
+ -# The desk's one bar (UI › Menu bar moves it to the bottom edge).
2
+ %header.rori-menubar
3
+ %span.rori-menubar__brand= Rori.app_name
4
+ %nav.rori-workspaces{ "aria-label": t(".workspaces"), data: { rori_target: "workspaces" } }
5
+ %nav.rori-minimap{ "aria-label": t(".columns"), data: { rori_target: "minimap" } }
6
+ %button.rori-menubar__search{ type: "button", data: { action: "rori-palette#open" } }
7
+ = t(".search")
8
+ %kbd ⌘K
9
+
10
+ - if (keys = rori_shortcut(:shortcuts))
11
+ %button.rori-menubar__shortcuts{ type: "button", title: t(".shortcuts"), "aria-label": t(".shortcuts"),
12
+ data: { action: "rori-prefs#openShortcuts" } }
13
+ %kbd= keys
14
+ %time.rori-menubar__clock{ data: { controller: "rori-clock" } }
@@ -0,0 +1,9 @@
1
+ -# locals: (notification:)
2
+ %article{ class: class_names("rori-notification", "rori-notification--#{notification.kind}", "rori-notification--sticky" => notification.sticky?),
3
+ role: notification.sticky? ? "alert" : "status",
4
+ data: { rori_notifications_target: "notification", action: "animationend->rori-notifications#expire" } }
5
+ %strong.rori-notification__title= notification.title
6
+ - if notification.body
7
+ %p.rori-notification__body= notification.body
8
+ %button.rori-notification__close{ type: "button", title: t(".dismiss"), "aria-label": t(".dismiss"),
9
+ data: { action: "rori-notifications#dismiss" } } ×
@@ -0,0 +1,10 @@
1
+ -# Corner notifications (rori-notifications, on <body>): `Rori.notify`
2
+ -# broadcasts reach every open desk through this stream; a server-side
3
+ -# command's result arrives in its run's response. A manual popover, so they
4
+ -# sit in the top layer above modal windows too.
5
+ = turbo_stream_from Rori.notifications_stream
6
+ %section#rori-notifications.rori-notifications{ popover: "manual", "aria-label": t(".label"),
7
+ data: { rori_notifications_target: "stack" } }
8
+ -# Shown when a run gets no answer (network down, server error page).
9
+ %template{ data: { rori_notifications_target: "failure" } }
10
+ = render "rori/notification", notification: Rori::Notification.new(title: t(".failed"), kind: :error)
@@ -0,0 +1,13 @@
1
+ %dialog.rori-palette{ "aria-label": t(".label"), data: { rori_palette_target: "dialog", action: "click->rori-palette#backdrop close->rori-palette#closed" } }
2
+ .rori-palette__field
3
+ %span.rori-palette__crumbs{ hidden: true, data: { rori_palette_target: "crumbs" } }
4
+ %input.rori-palette__input{ type: "search", placeholder: t(".placeholder"), autocomplete: "off", spellcheck: "false",
5
+ data: { "1p-ignore": true, rori_palette_target: "input", action: "input->rori-palette#filter keydown->rori-palette#navigate" } }
6
+ -# No src until the palette opens: it loads the root list (rori-palette's rootValue) on every open.
7
+ = turbo_frame_tag "commands", class: "rori-palette__results",
8
+ data: { rori_palette_target: "frame", action: "turbo:frame-load->rori-palette#loaded" }
9
+ %p.rori-palette__empty{ hidden: true, data: { rori_palette_target: "empty" } }= t(".empty")
10
+ %footer.rori-palette__hint= t(".hint_html")
11
+ -# Prototype for items of browser-provided lists (Rori::Command `source:`).
12
+ %template{ data: { rori_palette_target: "itemTemplate" } }
13
+ = render "rori/commands/command", command: Rori::Command.new(label: "", group: "")
@@ -0,0 +1,47 @@
1
+ -# Keyboard shortcuts, from the same keymap the desk matches (so ⌘ in the
2
+ -# native app). Opened by the `shortcuts` action (Mod+?), the menu bar or ⌘K.
3
+ %dialog.rori-shortcuts{ "aria-labelledby": "rori-shortcuts-title",
4
+ data: { rori_prefs_target: "shortcuts", action: "click->rori-prefs#shortcutsBackdrop" } }
5
+ %header.rori-shortcuts__head
6
+ %h2#rori-shortcuts-title= t(".title")
7
+ %button.rori-shortcuts__close{ type: "button", title: t(".close"), data: { action: "rori-prefs#closeShortcuts" } } ×
8
+ .rori-shortcuts__body
9
+ %section.rori-shortcuts__section
10
+ %h3= t(".sections.general")
11
+ %dl
12
+ %dt= t(".general.palette")
13
+ %dd
14
+ %kbd ⌘K
15
+ %dt= t(".general.terminal")
16
+ %dd
17
+ %kbd `
18
+ %dt= t(".general.leave_field")
19
+ %dd
20
+ %kbd esc
21
+ %dt= t(".general.empty_desk")
22
+ %dd
23
+ %kbd ␣
24
+ %kbd ↵
25
+ - rori_shortcut_sections.each do |section, rows|
26
+ %section.rori-shortcuts__section
27
+ %h3= t(section, scope: "rori.shortcuts.sections")
28
+ %dl
29
+ - rows.each do |action, keys|
30
+ %dt= t(action, scope: "rori.shortcuts.actions")
31
+ %dd
32
+ - keys.each do |key|
33
+ %kbd= key
34
+ - if (hover = rori_hover_shortcuts).any?
35
+ %section.rori-shortcuts__section
36
+ %h3= t(".sections.hover")
37
+ %p.rori-shortcuts__note= t(".hover_note")
38
+ %dl
39
+ - hover.each do |action, keys|
40
+ %dt= t(action, scope: "rori.shortcuts.actions")
41
+ %dd
42
+ - keys.each do |key|
43
+ %kbd= key
44
+ %footer.rori-shortcuts__foot
45
+ %label
46
+ %input{ type: "checkbox", data: { rori_prefs_target: "hintToggle", action: "rori-prefs#toggleHint" } }
47
+ = t(".show_hint")
@@ -0,0 +1,9 @@
1
+ -# Drop-down command line (rori-terminal, on <body>). data-rori-keys=off keeps
2
+ -# desk shortcuts and hover keys out while you type here.
3
+ %section.rori-terminal{ hidden: true, "aria-label": t(".label"), data: { rori_keys: "off", rori_terminal_target: "panel" } }
4
+ .rori-terminal__output{ "aria-live": "polite", data: { rori_terminal_target: "output" } }
5
+ %label.rori-terminal__line
6
+ %span.rori-terminal__prompt= t(".prompt")
7
+ %input.rori-terminal__input{ type: "text", autocomplete: "off", spellcheck: "false", "aria-label": t(".label"),
8
+ data: { "1p-ignore": true, rori_terminal_target: "input", action: "input->rori-terminal#suggest keydown->rori-terminal#navigate" } }
9
+ %ul.rori-terminal__suggestions{ data: { rori_terminal_target: "suggestions" } }
@@ -0,0 +1,7 @@
1
+ -# The empty-desk hint (dismissible; the shortcuts modal brings it back).
2
+ .rori-wallpaper
3
+ %button.rori-wallpaper__dismiss{ type: "button", title: t(".dismiss"), data: { action: "rori-prefs#dismissHint" } } ×
4
+ %p.rori-wallpaper__title= t(".title")
5
+ %p.rori-wallpaper__hint= t(".hint_html")
6
+ - if (shortcuts = rori_shortcut(:shortcuts))
7
+ %p.rori-wallpaper__hint= t(".shortcuts_html", keys: shortcuts)
@@ -0,0 +1,3 @@
1
+ -# The photo's credit, in the wallpaper's bottom-right corner (rori/wallpaper.css).
2
+ = link_to t(".label"), rori_wallpaper.credit_url || "#", class: "rori-wallpaper-credit", target: "_blank", rel: "noopener",
3
+ hidden: rori_wallpaper.credit_url.nil?, data: { rori_wallpaper_target: "credit" }
@@ -0,0 +1,15 @@
1
+ -# locals: (frame_id: nil, src: nil, body: nil)
2
+ -# Rendered once server-side for the page being loaded, and once inside a
3
+ -# <template> the desk clones for every window it opens.
4
+ %dialog.rori-win{ open: body.present?, tabindex: -1, data: { rori_target: "window",
5
+ action: "pointerdown->rori#focusWindow focusin->rori#focusWindow input->rori#markDirty change->rori#markDirty cancel->rori#cancel" } }
6
+ %header.rori-win__bar{ data: { action: "dblclick->rori#toggleFullWidth" } }
7
+ %button.rori-win__btn{ type: "button", title: t(".back"), disabled: true, data: { action: "rori#back", window_part: "back" } } ←
8
+ %span.rori-win__title{ data: { window_part: "title" } }
9
+ %span.rori-win__armed= t(".armed")
10
+ %button.rori-win__btn{ type: "button", title: t(".detach"), data: { action: "rori#detach" } } ↗
11
+ %button.rori-win__btn{ type: "button", title: t(".close"), data: { action: "rori#close" } } ×
12
+ .rori-win__body
13
+ = turbo_frame_tag frame_id, src: src, complete: src.present?, refresh: "morph",
14
+ data: { action: "turbo:frame-load->rori#frameLoaded turbo:frame-missing->rori#frameMissing" } do
15
+ = body
@@ -0,0 +1,17 @@
1
+ -# locals: (command:)
2
+ - shortcut = rori_shortcut(command.action)
3
+ %li.rori-palette__item{ role: "option",
4
+ data: { rori_palette_target: "item", url: command.url, rori_action: command.action, param: command.param,
5
+ children: command.children, source: command.source, run: command.run, confirm: command.confirm || nil, shortcut: shortcut,
6
+ current: command.current || nil, search: "#{command.label} #{command.group}",
7
+ action: "click->rori-palette#pick mousemove->rori-palette#hover" } }
8
+ %span.rori-palette__label
9
+ = command.label
10
+ - if command.children || command.source
11
+ %span.rori-palette__more ›
12
+ - if shortcut
13
+ %kbd.rori-palette__shortcut= shortcut
14
+ %span.rori-palette__group
15
+ - if command.current
16
+ %span.rori-palette__check ✓
17
+ = command.group
@@ -0,0 +1,5 @@
1
+ -# Shared by every palette list (root and nested). The explicit partial path
2
+ -# keeps namespaced controllers (Rori::Commands::…) from prefixing it.
3
+ = turbo_frame_tag "commands" do
4
+ %ul.rori-palette__list{ role: "listbox", data: { rori_palette_target: "list" } }
5
+ = render partial: "rori/commands/command", collection: @commands, as: :command
@@ -0,0 +1 @@
1
+ -# The blank desk: windows are restored or opened client-side (⌘K).
@@ -0,0 +1,3 @@
1
+ # The desk's Stimulus controllers; the host registers them with
2
+ # `import { registerRori } from "rori"`.
3
+ pin_all_from Rori::Engine.root.join("app/javascript/rori"), under: "rori"
@@ -0,0 +1,133 @@
1
+ # The desk's own copy. App-specific palette labels (desk.commands.routes)
2
+ # stay in the app's locale files.
3
+ en:
4
+ rori:
5
+ commands:
6
+ groups:
7
+ open: Open
8
+ rori: Desk
9
+ ui: UI
10
+ run: Run
11
+ actions:
12
+ overview: Toggle overview
13
+ reopen_window: Reopen closed window
14
+ new_workspace: New workspace
15
+ cycle_width: Cycle column width
16
+ full_width: Toggle full-width column
17
+ center_column: Center column
18
+ close_window: Close window
19
+ toggle_terminal: Toggle terminal
20
+ shortcuts: Keyboard shortcuts
21
+ ui:
22
+ label: UI
23
+ theme: Theme
24
+ wallpaper: Wallpaper
25
+ bars: Menu bar
26
+ bars:
27
+ top: Top
28
+ bottom: Bottom
29
+ wallpapers:
30
+ safe: Safe
31
+ cover: Cover menu bar
32
+ "off": "Off"
33
+ next: Next wallpaper
34
+ pin: Pin wallpaper
35
+ workspaces:
36
+ move: Move column to workspace…
37
+ group: Workspace
38
+ new: New workspace
39
+ themes:
40
+ pick: Pick theme…
41
+ default: Default
42
+ auto: Follows system
43
+ dark: Dark
44
+ light: Light
45
+ runs:
46
+ create:
47
+ done: Done
48
+ failed: Something went wrong. Check the server log.
49
+ menubar:
50
+ workspaces: Workspaces
51
+ search: Search or run a command…
52
+ columns: Columns
53
+ shortcuts: Keyboard shortcuts
54
+ wallpaper_credit:
55
+ label: Photo · Unsplash
56
+ wallpaper:
57
+ title: Nothing open
58
+ hint_html: "Press <kbd>⌘K</kbd>, <kbd>space</kbd> or <kbd>↵</kbd> to open a page or run a command."
59
+ # %{keys}: the chord bound to the shortcuts action (⌥? in a browser, ⌘? in the app).
60
+ shortcuts_html: "<kbd>%{keys}</kbd> shows every keyboard shortcut."
61
+ dismiss: Hide this hint (bring it back from the shortcuts)
62
+ shortcuts:
63
+ title: Keyboard shortcuts
64
+ close: Close
65
+ show_hint: Show the hint on an empty desk
66
+ hover_note: On the window under the pointer, or the focused one when no field has focus.
67
+ sections:
68
+ general: General
69
+ focus: Focus
70
+ move: Move column
71
+ workspaces: Workspaces
72
+ columns: Columns
73
+ windows: Windows
74
+ hover: Bare keys
75
+ general:
76
+ palette: Commands (⌘K)
77
+ terminal: Terminal
78
+ leave_field: Leave a text field
79
+ empty_desk: Commands, on an empty desk
80
+ actions:
81
+ focus_left: Left
82
+ focus_right: Right
83
+ focus_up: Up / previous workspace
84
+ focus_down: Down / next workspace
85
+ move_left: Left
86
+ move_right: Right
87
+ move_up: To the workspace above
88
+ move_down: To the workspace below
89
+ switch_to_workspace: Go to workspace
90
+ move_to_workspace: Move column to workspace and follow it
91
+ consume_left: Stack into the left column / unstack
92
+ consume_right: Stack into the right column / unstack
93
+ cycle_width: Cycle width
94
+ full_width: Full width
95
+ center_column: Centre
96
+ overview: Overview
97
+ close_window: Close
98
+ reopen_window: Reopen closed window
99
+ toggle_terminal: Terminal
100
+ shortcuts: This list
101
+ terminal:
102
+ label: Terminal
103
+ prompt: "desk ›"
104
+ # Passed to rori-terminal as one JSON value; %{…} are filled in there.
105
+ messages:
106
+ welcome: "Same commands as ⌘K, matched as whole paths: ui wallpaper cover, or just uthen (UI › Theme › Nord) · Tab completes · ↑↓ history · help · clear · esc or ` closes."
107
+ help: "Top level (anything nested is reachable by typing its path, even abbreviated):"
108
+ list: "%{path} ›"
109
+ empty: " (nothing here)"
110
+ opened: "→ %{path}"
111
+ ran: "✓ %{path}"
112
+ confirm: "Run %{path}? y to confirm, anything else cancels."
113
+ cancelled: Cancelled.
114
+ no_match: "No match for “%{query}”."
115
+ palette:
116
+ label: Command palette
117
+ placeholder: Type a command or search…
118
+ empty: Nothing matches.
119
+ confirm: ↵ again to run
120
+ hint_html: "<kbd>↵</kbd> open · <kbd>⇧↵</kbd> in a new window · <kbd>⌫</kbd> back · <kbd>esc</kbd> close"
121
+ notifications:
122
+ label: Notifications
123
+ failed: The command couldn't be run.
124
+ notification:
125
+ dismiss: Dismiss
126
+ window:
127
+ back: Back
128
+ detach: Open in a new tab
129
+ close: Close
130
+ armed: keys → here
131
+ typing: "<input>"
132
+ discard: This window has unsaved changes. Close it anyway?
133
+ load_error: This page couldn't be shown in a window. Open it in a new tab (↗) to see what went wrong.
@@ -0,0 +1,13 @@
1
+ # The blank desk (Rori::DesktopsController) is mounted by the app, usually as `root`.
2
+ namespace :rori do
3
+ resources :commands, only: :index
4
+ # Nested command lists, shared by ⌘K and the terminal.
5
+ namespace :commands do
6
+ resource :ui, only: :show, controller: "ui"
7
+ resources :themes, only: :index
8
+ resources :wallpapers, only: :index
9
+ resources :bars, only: :index
10
+ # Server-side commands (Rori.command).
11
+ resources :runs, only: :create
12
+ end
13
+ end
@@ -0,0 +1,67 @@
1
+ require "rails/generators"
2
+
3
+ module Rori
4
+ module Generators
5
+ # bin/rails g rori:install — wires a host app up for the desk. Idempotent:
6
+ # steps that find their line already in place skip themselves.
7
+ class InstallGenerator < Rails::Generators::Base
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ desc "Adds the rori initializer, Rori::Windowed, the desk's routes and its Stimulus controllers."
11
+
12
+ def create_initializer
13
+ template "rori.rb", "config/initializers/rori.rb"
14
+ end
15
+
16
+ def include_windowed
17
+ path = "app/controllers/application_controller.rb"
18
+ return say_status(:skip, "#{path} not found", :yellow) unless exist?(path)
19
+ return if read(path).include?("Rori::Windowed")
20
+
21
+ inject_into_class path, "ApplicationController", " include Rori::Windowed\n\n"
22
+ end
23
+
24
+ def draw_routes
25
+ path = "config/routes.rb"
26
+ return say_status(:skip, "#{path} not found", :yellow) unless exist?(path)
27
+
28
+ routes = read(path)
29
+ route "draw :rori" unless routes.include?("draw :rori")
30
+ if routes.match?(/^\s*root\b/)
31
+ say_status :skip, "root route already set; the blank desk is rori/desktops#show", :yellow
32
+ else
33
+ route 'root "rori/desktops#show"'
34
+ end
35
+ end
36
+
37
+ def register_stimulus_controllers
38
+ path = "app/javascript/controllers/index.js"
39
+ return say_status(:skip, "#{path} not found; call registerRori(application) yourself", :yellow) unless exist?(path)
40
+ return if read(path).include?("registerRori")
41
+
42
+ append_to_file path, <<~JS
43
+
44
+ import { registerRori } from "rori"
45
+ registerRori(application)
46
+ JS
47
+ end
48
+
49
+ def next_steps
50
+ say <<~TEXT
51
+
52
+ rori is wired up. Next:
53
+ • Place the desk's CSS among your layers, e.g. in your first stylesheet:
54
+ @layer reset, base, rori, layout, components, utilities;
55
+ • Label pages for ⌘K under rori.commands.routes.<controller>.<action>
56
+ in your locale, and list models in config/initializers/rori.rb.
57
+ • Start the server and press ⌘K (or space on the empty desk).
58
+ TEXT
59
+ end
60
+
61
+ private
62
+ def exist?(path) = File.exist?(File.join(destination_root, path))
63
+
64
+ def read(path) = File.read(File.join(destination_root, path))
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,22 @@
1
+ # The desk (https://github.com/varyform/rori). Every option, with its
2
+ # default, is documented in the gem's lib/rori.rb.
3
+ Rori.configure do |rori|
4
+ rori.app_name = "<%= Rails.application.class.module_parent_name.titleize %>"
5
+
6
+ # Models whose most recently updated records are listed in ⌘K (each needs
7
+ # updated_at, a show route and a readable to_s).
8
+ # rori.records = %w[ User Project ]
9
+
10
+ # Blender-style bare keys on the window under the pointer.
11
+ # rori.hover_keys = true
12
+
13
+ # Background photos from Unsplash (or your own: rori.wallpapers_folder).
14
+ # rori.wallpapers = true
15
+
16
+ # A command that runs on the server, labelled by
17
+ # rori.commands.custom.reindex_search in your locale:
18
+ # rori.command :reindex_search, confirm: true do
19
+ # SearchReindexJob.perform_later
20
+ # "Reindexing…"
21
+ # end
22
+ end
@@ -0,0 +1,19 @@
1
+ module Rori
2
+ # Not isolated: the desk wraps the host app's own pages, so its controllers
3
+ # inherit the app's ApplicationController (which includes Rori::Windowed),
4
+ # its helpers join the app's, and its route file is drawn into the app's
5
+ # routes with `draw :desk` (engines add config/routes to the draw paths).
6
+ # Views, locales, rake tasks and app/assets come along the usual engine way.
7
+ class Engine < ::Rails::Engine
8
+ # Propshaft picks up app/assets; the Stimulus controllers live in
9
+ # app/javascript, which only the host app gets by default.
10
+ initializer "rori.assets" do |app|
11
+ app.config.assets.paths << root.join("app/javascript") if app.config.respond_to?(:assets)
12
+ end
13
+
14
+ initializer "rori.importmap", before: "importmap" do |app|
15
+ app.config.importmap.paths << root.join("config/importmap.rb")
16
+ app.config.importmap.cache_sweepers << root.join("app/javascript")
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ module Rori
2
+ VERSION = "0.1.0"
3
+ end