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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +25 -0
- data/LICENSE +21 -0
- data/README.md +279 -0
- data/app/assets/stylesheets/rori/_init.css +79 -0
- data/app/assets/stylesheets/rori/layout.css +162 -0
- data/app/assets/stylesheets/rori/menubar.css +117 -0
- data/app/assets/stylesheets/rori/notifications.css +120 -0
- data/app/assets/stylesheets/rori/palette.css +114 -0
- data/app/assets/stylesheets/rori/shortcuts.css +110 -0
- data/app/assets/stylesheets/rori/terminal.css +98 -0
- data/app/assets/stylesheets/rori/themes.css +156 -0
- data/app/assets/stylesheets/rori/typing_badge.css +56 -0
- data/app/assets/stylesheets/rori/wallpaper.css +52 -0
- data/app/assets/stylesheets/rori/window.css +174 -0
- data/app/controllers/concerns/rori/windowed.rb +15 -0
- data/app/controllers/rori/commands/bars_controller.rb +9 -0
- data/app/controllers/rori/commands/runs_controller.rb +22 -0
- data/app/controllers/rori/commands/themes_controller.rb +9 -0
- data/app/controllers/rori/commands/ui_controller.rb +9 -0
- data/app/controllers/rori/commands/wallpapers_controller.rb +9 -0
- data/app/controllers/rori/commands_controller.rb +7 -0
- data/app/controllers/rori/desktops_controller.rb +7 -0
- data/app/helpers/rori/assets_helper.rb +9 -0
- data/app/helpers/rori/keymap_helper.rb +50 -0
- data/app/helpers/rori/prefs_helper.rb +13 -0
- data/app/helpers/rori/theme_helper.rb +14 -0
- data/app/helpers/rori/wallpaper_helper.rb +19 -0
- data/app/helpers/rori/window_helper.rb +36 -0
- data/app/javascript/rori/clock_controller.js +18 -0
- data/app/javascript/rori/command_index.js +84 -0
- data/app/javascript/rori/fuzzy.js +50 -0
- data/app/javascript/rori/index.js +21 -0
- data/app/javascript/rori/notifications_controller.js +45 -0
- data/app/javascript/rori/palette_controller.js +307 -0
- data/app/javascript/rori/prefs_controller.js +68 -0
- data/app/javascript/rori/rori_controller.js +996 -0
- data/app/javascript/rori/terminal_controller.js +198 -0
- data/app/javascript/rori/theme_controller.js +35 -0
- data/app/javascript/rori/wallpaper_controller.js +67 -0
- data/app/models/rori/command.rb +105 -0
- data/app/models/rori/notification.rb +20 -0
- data/app/models/rori/theme/color.rb +30 -0
- data/app/models/rori/theme.rb +87 -0
- data/app/models/rori/wallpaper.rb +54 -0
- data/app/views/layouts/rori/_head.html.haml +2 -0
- data/app/views/layouts/rori/shell.html.haml +97 -0
- data/app/views/layouts/rori/window.html.haml +10 -0
- data/app/views/rori/_menubar.html.haml +14 -0
- data/app/views/rori/_notification.html.haml +9 -0
- data/app/views/rori/_notifications.html.haml +10 -0
- data/app/views/rori/_palette.html.haml +13 -0
- data/app/views/rori/_shortcuts.html.haml +47 -0
- data/app/views/rori/_terminal.html.haml +9 -0
- data/app/views/rori/_wallpaper.html.haml +7 -0
- data/app/views/rori/_wallpaper_credit.html.haml +3 -0
- data/app/views/rori/_window.html.haml +15 -0
- data/app/views/rori/commands/_command.html.haml +17 -0
- data/app/views/rori/commands/index.html.haml +5 -0
- data/app/views/rori/desktops/show.html.haml +1 -0
- data/config/importmap.rb +3 -0
- data/config/locales/rori.en.yml +133 -0
- data/config/routes/rori.rb +13 -0
- data/lib/generators/rori/install/install_generator.rb +67 -0
- data/lib/generators/rori/install/templates/rori.rb.tt +22 -0
- data/lib/rori/engine.rb +19 -0
- data/lib/rori/version.rb +3 -0
- data/lib/rori.rb +180 -0
- data/lib/tasks/rori.rake +9 -0
- data/vendor/themes/LICENSE +25 -0
- data/vendor/themes/ghostty/Catppuccin Latte +22 -0
- data/vendor/themes/ghostty/Catppuccin Mocha +22 -0
- data/vendor/themes/ghostty/Everforest Dark Med +22 -0
- data/vendor/themes/ghostty/Flexoki Light +22 -0
- data/vendor/themes/ghostty/GitHub Dark Dimmed +22 -0
- data/vendor/themes/ghostty/Gruvbox Material Dark +22 -0
- data/vendor/themes/ghostty/Kanagawa Wave +22 -0
- data/vendor/themes/ghostty/Melange Dark +22 -0
- data/vendor/themes/ghostty/Nord +22 -0
- data/vendor/themes/ghostty/Rose Pine Dawn +22 -0
- data/vendor/themes/ghostty/Rose Pine Moon +22 -0
- data/vendor/themes/ghostty/TokyoNight Storm +22 -0
- data/vendor/themes/ghostty/Tomorrow Night Eighties +22 -0
- data/vendor/themes/ghostty/Zenbones Light +22 -0
- data/vendor/wallpapers/unsplash.yml +23 -0
- 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).
|
data/config/importmap.rb
ADDED
|
@@ -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
|
data/lib/rori/engine.rb
ADDED
|
@@ -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
|
data/lib/rori/version.rb
ADDED