shoko 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/.bundle/config +4 -0
- data/.bundle/config.bak +3 -0
- data/.rspec_status +42 -0
- data/.rubocop.yml +124 -0
- data/Gemfile +19 -0
- data/LICENSE +21 -0
- data/README.md +82 -0
- data/Rakefile +29 -0
- data/bin/start +15 -0
- data/lib/shoko/adapters/book_sources/document_service.rb +201 -0
- data/lib/shoko/adapters/book_sources/download_service.rb +95 -0
- data/lib/shoko/adapters/book_sources/epub/epub_resource_loader.rb +137 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/html_processor.rb +151 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/metadata_extractor.rb +53 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/entry_reader.rb +77 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/metadata_extractor.rb +67 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_context.rb +86 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_document_index.rb +75 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_document_scanner.rb +47 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_extractor.rb +46 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_label_resolver.rb +83 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_list_item.rb +55 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_result.rb +8 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_selector.rb +100 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_source_locator.rb +93 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_traversal.rb +103 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_walker.rb +56 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/opf_processor.rb +102 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/xhtml_content_parser.rb +661 -0
- data/lib/shoko/adapters/book_sources/epub/parsers/xml_text_normalizer.rb +41 -0
- data/lib/shoko/adapters/book_sources/epub_document.rb +253 -0
- data/lib/shoko/adapters/book_sources/epub_finder/directory_scanner.rb +134 -0
- data/lib/shoko/adapters/book_sources/epub_finder/scanner_context.rb +28 -0
- data/lib/shoko/adapters/book_sources/epub_finder.rb +161 -0
- data/lib/shoko/adapters/book_sources/epub_importer.rb +268 -0
- data/lib/shoko/adapters/book_sources/gutendex_client.rb +150 -0
- data/lib/shoko/adapters/book_sources/library_scanner.rb +93 -0
- data/lib/shoko/adapters/book_sources/source_fingerprint.rb +57 -0
- data/lib/shoko/adapters/input/annotations/mouse_handler.rb +84 -0
- data/lib/shoko/adapters/input/command_bridge.rb +148 -0
- data/lib/shoko/adapters/input/command_factory.rb +255 -0
- data/lib/shoko/adapters/input/commands.rb +60 -0
- data/lib/shoko/adapters/input/dispatcher.rb +69 -0
- data/lib/shoko/adapters/input/input_controller.rb +250 -0
- data/lib/shoko/adapters/input/key_definitions.rb +108 -0
- data/lib/shoko/adapters/input/validators/file_path_validator.rb +81 -0
- data/lib/shoko/adapters/input/validators/terminal_size_validator.rb +76 -0
- data/lib/shoko/adapters/monitoring/logger.rb +150 -0
- data/lib/shoko/adapters/monitoring/perf_tracer.rb +183 -0
- data/lib/shoko/adapters/monitoring/performance_monitor.rb +110 -0
- data/lib/shoko/adapters/output/clipboard/clipboard_service.rb +125 -0
- data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler/image_builder.rb +149 -0
- data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler/text_wrapper.rb +149 -0
- data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler/tokenizer.rb +91 -0
- data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler.rb +144 -0
- data/lib/shoko/adapters/output/formatting/formatting_service/plain_lines_builder.rb +54 -0
- data/lib/shoko/adapters/output/formatting/formatting_service.rb +247 -0
- data/lib/shoko/adapters/output/formatting/wrapping_service.rb +228 -0
- data/lib/shoko/adapters/output/instrumentation_service.rb +52 -0
- data/lib/shoko/adapters/output/kitty/image_transcoder.rb +71 -0
- data/lib/shoko/adapters/output/kitty/kitty_graphics.rb +114 -0
- data/lib/shoko/adapters/output/kitty/kitty_image_renderer.rb +239 -0
- data/lib/shoko/adapters/output/kitty/kitty_unicode_placeholders.rb +139 -0
- data/lib/shoko/adapters/output/kitty/kitty_unicode_placeholders_diacritic_codepoints.txt +26 -0
- data/lib/shoko/adapters/output/notification_service.rb +58 -0
- data/lib/shoko/adapters/output/render_registry.rb +45 -0
- data/lib/shoko/adapters/output/rendering/models/line_geometry.rb +60 -0
- data/lib/shoko/adapters/output/rendering/models/page_rendering_context.rb +22 -0
- data/lib/shoko/adapters/output/rendering/models/render_params.rb +28 -0
- data/lib/shoko/adapters/output/rendering/models/rendering_context.rb +58 -0
- data/lib/shoko/adapters/output/terminal/buffer.rb +275 -0
- data/lib/shoko/adapters/output/terminal/constants/terminal_defaults.rb +11 -0
- data/lib/shoko/adapters/output/terminal/input/decoder.rb +347 -0
- data/lib/shoko/adapters/output/terminal/input.rb +161 -0
- data/lib/shoko/adapters/output/terminal/output.rb +105 -0
- data/lib/shoko/adapters/output/terminal/terminal.rb +167 -0
- data/lib/shoko/adapters/output/terminal/terminal_sanitizer.rb +243 -0
- data/lib/shoko/adapters/output/terminal/terminal_service.rb +138 -0
- data/lib/shoko/adapters/output/terminal/text_metrics.rb +273 -0
- data/lib/shoko/adapters/output/ui/builders/page_setup_builder.rb +47 -0
- data/lib/shoko/adapters/output/ui/components/annotation_editor_overlay/footer_renderer.rb +80 -0
- data/lib/shoko/adapters/output/ui/components/annotation_editor_overlay/geometry.rb +61 -0
- data/lib/shoko/adapters/output/ui/components/annotation_editor_overlay/note_renderer.rb +86 -0
- data/lib/shoko/adapters/output/ui/components/annotation_editor_overlay_component.rb +234 -0
- data/lib/shoko/adapters/output/ui/components/annotations_overlay/list_renderer.rb +142 -0
- data/lib/shoko/adapters/output/ui/components/annotations_overlay_component.rb +185 -0
- data/lib/shoko/adapters/output/ui/components/base_component.rb +110 -0
- data/lib/shoko/adapters/output/ui/components/component_interface.rb +80 -0
- data/lib/shoko/adapters/output/ui/components/content_component.rb +61 -0
- data/lib/shoko/adapters/output/ui/components/enhanced_popup_menu.rb +191 -0
- data/lib/shoko/adapters/output/ui/components/footer_component.rb +120 -0
- data/lib/shoko/adapters/output/ui/components/header_component.rb +46 -0
- data/lib/shoko/adapters/output/ui/components/layouts/horizontal.rb +63 -0
- data/lib/shoko/adapters/output/ui/components/layouts/vertical.rb +73 -0
- data/lib/shoko/adapters/output/ui/components/main_menu_component.rb +103 -0
- data/lib/shoko/adapters/output/ui/components/reading/base_view_renderer.rb +199 -0
- data/lib/shoko/adapters/output/ui/components/reading/config_helpers.rb +42 -0
- data/lib/shoko/adapters/output/ui/components/reading/help_renderer.rb +62 -0
- data/lib/shoko/adapters/output/ui/components/reading/inline_segment_highlighter.rb +144 -0
- data/lib/shoko/adapters/output/ui/components/reading/kitty_image_line_renderer.rb +262 -0
- data/lib/shoko/adapters/output/ui/components/reading/line_content_composer.rb +114 -0
- data/lib/shoko/adapters/output/ui/components/reading/line_drawer.rb +87 -0
- data/lib/shoko/adapters/output/ui/components/reading/line_geometry_builder.rb +41 -0
- data/lib/shoko/adapters/output/ui/components/reading/rendered_lines_recorder.rb +64 -0
- data/lib/shoko/adapters/output/ui/components/reading/single_view_renderer.rb +156 -0
- data/lib/shoko/adapters/output/ui/components/reading/split_view_renderer.rb +221 -0
- data/lib/shoko/adapters/output/ui/components/reading/view_renderer_factory.rb +20 -0
- data/lib/shoko/adapters/output/ui/components/reading/wrapped_lines_fetcher.rb +139 -0
- data/lib/shoko/adapters/output/ui/components/rect.rb +15 -0
- data/lib/shoko/adapters/output/ui/components/render_style.rb +84 -0
- data/lib/shoko/adapters/output/ui/components/screen_component.rb +24 -0
- data/lib/shoko/adapters/output/ui/components/screens/annotation_detail_screen_component.rb +175 -0
- data/lib/shoko/adapters/output/ui/components/screens/annotation_edit_screen_component.rb +221 -0
- data/lib/shoko/adapters/output/ui/components/screens/annotation_editor_screen_component.rb +205 -0
- data/lib/shoko/adapters/output/ui/components/screens/annotation_rendering_helpers.rb +190 -0
- data/lib/shoko/adapters/output/ui/components/screens/annotations_screen_component.rb +266 -0
- data/lib/shoko/adapters/output/ui/components/screens/base_screen_component.rb +49 -0
- data/lib/shoko/adapters/output/ui/components/screens/browse_screen_component.rb +319 -0
- data/lib/shoko/adapters/output/ui/components/screens/download_books_screen_component.rb +340 -0
- data/lib/shoko/adapters/output/ui/components/screens/library_screen_component.rb +205 -0
- data/lib/shoko/adapters/output/ui/components/screens/loading_overlay_component.rb +49 -0
- data/lib/shoko/adapters/output/ui/components/screens/menu_screen_component.rb +107 -0
- data/lib/shoko/adapters/output/ui/components/screens/settings_screen_component.rb +238 -0
- data/lib/shoko/adapters/output/ui/components/sidebar/annotations_tab_renderer.rb +159 -0
- data/lib/shoko/adapters/output/ui/components/sidebar/bookmarks_tab_renderer.rb +139 -0
- data/lib/shoko/adapters/output/ui/components/sidebar/tab_header_component.rb +157 -0
- data/lib/shoko/adapters/output/ui/components/sidebar/toc_tab_renderer.rb +111 -0
- data/lib/shoko/adapters/output/ui/components/sidebar/toc_tab_support.rb +1606 -0
- data/lib/shoko/adapters/output/ui/components/sidebar_panel_component.rb +217 -0
- data/lib/shoko/adapters/output/ui/components/surface.rb +88 -0
- data/lib/shoko/adapters/output/ui/components/tooltip_overlay_component.rb +224 -0
- data/lib/shoko/adapters/output/ui/components/ui/box_drawer.rb +32 -0
- data/lib/shoko/adapters/output/ui/components/ui/list_helpers.rb +33 -0
- data/lib/shoko/adapters/output/ui/components/ui/overlay_layout.rb +79 -0
- data/lib/shoko/adapters/output/ui/components/ui/text_utils.rb +46 -0
- data/lib/shoko/adapters/output/ui/constants/highlighting.rb +21 -0
- data/lib/shoko/adapters/output/ui/constants/messages.rb +12 -0
- data/lib/shoko/adapters/output/ui/constants/themes.rb +79 -0
- data/lib/shoko/adapters/output/ui/constants/ui_constants.rb +85 -0
- data/lib/shoko/adapters/output/ui/rendering/frame_coordinator.rb +42 -0
- data/lib/shoko/adapters/output/ui/rendering/reader_render_coordinator.rb +169 -0
- data/lib/shoko/adapters/output/ui/rendering/render_pipeline.rb +55 -0
- data/lib/shoko/adapters/storage/atomic_file_writer.rb +43 -0
- data/lib/shoko/adapters/storage/background_worker.rb +66 -0
- data/lib/shoko/adapters/storage/book_cache_pipeline.rb +653 -0
- data/lib/shoko/adapters/storage/cache/epub/memory_cache.rb +99 -0
- data/lib/shoko/adapters/storage/cache/epub/persistence.rb +131 -0
- data/lib/shoko/adapters/storage/cache/epub/serializer/deserialize.rb +225 -0
- data/lib/shoko/adapters/storage/cache/epub/serializer/helpers.rb +63 -0
- data/lib/shoko/adapters/storage/cache/epub/serializer/serialize.rb +83 -0
- data/lib/shoko/adapters/storage/cache/epub/serializer.rb +5 -0
- data/lib/shoko/adapters/storage/cache/epub/source_reference.rb +58 -0
- data/lib/shoko/adapters/storage/cache_paths.rb +21 -0
- data/lib/shoko/adapters/storage/cache_pointer_manager.rb +60 -0
- data/lib/shoko/adapters/storage/config_paths.rb +30 -0
- data/lib/shoko/adapters/storage/epub_cache.rb +195 -0
- data/lib/shoko/adapters/storage/file_writer_service.rb +47 -0
- data/lib/shoko/adapters/storage/json_cache_store/chapters.rb +141 -0
- data/lib/shoko/adapters/storage/json_cache_store/layouts.rb +67 -0
- data/lib/shoko/adapters/storage/json_cache_store/manifest.rb +42 -0
- data/lib/shoko/adapters/storage/json_cache_store/payload_helpers.rb +113 -0
- data/lib/shoko/adapters/storage/json_cache_store/resources.rb +84 -0
- data/lib/shoko/adapters/storage/json_cache_store.rb +167 -0
- data/lib/shoko/adapters/storage/lazy_file_string.rb +65 -0
- data/lib/shoko/adapters/storage/pagination_cache.rb +127 -0
- data/lib/shoko/adapters/storage/recent_files.rb +78 -0
- data/lib/shoko/adapters/storage/repositories/annotation_repository.rb +182 -0
- data/lib/shoko/adapters/storage/repositories/base_repository.rb +81 -0
- data/lib/shoko/adapters/storage/repositories/bookmark_repository.rb +132 -0
- data/lib/shoko/adapters/storage/repositories/cached_library_repository.rb +129 -0
- data/lib/shoko/adapters/storage/repositories/config_repository.rb +262 -0
- data/lib/shoko/adapters/storage/repositories/progress_repository.rb +166 -0
- data/lib/shoko/adapters/storage/repositories/storage/annotation_file_store.rb +128 -0
- data/lib/shoko/adapters/storage/repositories/storage/bookmark_file_store.rb +109 -0
- data/lib/shoko/adapters/storage/repositories/storage/file_store_utils.rb +20 -0
- data/lib/shoko/adapters/storage/repositories/storage/progress_file_store.rb +59 -0
- data/lib/shoko/application/annotation_editor_overlay_session.rb +138 -0
- data/lib/shoko/application/cli.rb +134 -0
- data/lib/shoko/application/controllers/menu/input_controller.rb +189 -0
- data/lib/shoko/application/controllers/menu/state_controller.rb +642 -0
- data/lib/shoko/application/controllers/menu_controller.rb +469 -0
- data/lib/shoko/application/controllers/mouseable_reader.rb +377 -0
- data/lib/shoko/application/controllers/reader_controller.rb +449 -0
- data/lib/shoko/application/controllers/state_controller.rb +410 -0
- data/lib/shoko/application/controllers/ui_controller.rb +782 -0
- data/lib/shoko/application/dependency_container.rb +301 -0
- data/lib/shoko/application/infrastructure/event_bus.rb +80 -0
- data/lib/shoko/application/infrastructure/observer_state_store.rb +136 -0
- data/lib/shoko/application/infrastructure/state_store.rb +413 -0
- data/lib/shoko/application/main_menu/menu_progress_presenter.rb +83 -0
- data/lib/shoko/application/pending_jump_handler.rb +122 -0
- data/lib/shoko/application/reader_lifecycle.rb +65 -0
- data/lib/shoko/application/reader_startup_orchestrator.rb +113 -0
- data/lib/shoko/application/selectors/config_selectors.rb +62 -0
- data/lib/shoko/application/selectors/menu_selectors.rb +62 -0
- data/lib/shoko/application/selectors/reader_selectors.rb +186 -0
- data/lib/shoko/application/state/actions/base_action.rb +24 -0
- data/lib/shoko/application/state/actions/quit_to_menu_action.rb +16 -0
- data/lib/shoko/application/state/actions/switch_reader_mode_action.rb +22 -0
- data/lib/shoko/application/state/actions/toggle_view_mode_action.rb +31 -0
- data/lib/shoko/application/state/actions/update_annotation_editor_overlay_action.rb +27 -0
- data/lib/shoko/application/state/actions/update_annotations_action.rb +20 -0
- data/lib/shoko/application/state/actions/update_annotations_overlay_action.rb +27 -0
- data/lib/shoko/application/state/actions/update_bookmarks_action.rb +20 -0
- data/lib/shoko/application/state/actions/update_chapter_action.rb +24 -0
- data/lib/shoko/application/state/actions/update_config_action.rb +22 -0
- data/lib/shoko/application/state/actions/update_field_helpers.rb +26 -0
- data/lib/shoko/application/state/actions/update_menu_action.rb +21 -0
- data/lib/shoko/application/state/actions/update_message_action.rb +35 -0
- data/lib/shoko/application/state/actions/update_page_action.rb +21 -0
- data/lib/shoko/application/state/actions/update_pagination_state_action.rb +21 -0
- data/lib/shoko/application/state/actions/update_popup_menu_action.rb +27 -0
- data/lib/shoko/application/state/actions/update_reader_meta_action.rb +21 -0
- data/lib/shoko/application/state/actions/update_reader_mode_action.rb +20 -0
- data/lib/shoko/application/state/actions/update_rendered_lines_action.rb +40 -0
- data/lib/shoko/application/state/actions/update_selection_action.rb +27 -0
- data/lib/shoko/application/state/actions/update_selections_action.rb +21 -0
- data/lib/shoko/application/state/actions/update_sidebar_action.rb +34 -0
- data/lib/shoko/application/state/actions/update_ui_loading_action.rb +23 -0
- data/lib/shoko/application/ui/reader_view_model_builder.rb +74 -0
- data/lib/shoko/application/ui/view_models/reader_view_model.rb +177 -0
- data/lib/shoko/application/unified_application.rb +48 -0
- data/lib/shoko/application/use_cases/catalog_service.rb +117 -0
- data/lib/shoko/application/use_cases/commands/annotation_editor_commands.rb +105 -0
- data/lib/shoko/application/use_cases/commands/application_commands.rb +208 -0
- data/lib/shoko/application/use_cases/commands/base_command.rb +166 -0
- data/lib/shoko/application/use_cases/commands/bookmark_commands.rb +114 -0
- data/lib/shoko/application/use_cases/commands/conditional_navigation_commands.rb +57 -0
- data/lib/shoko/application/use_cases/commands/menu_commands.rb +170 -0
- data/lib/shoko/application/use_cases/commands/navigation_commands.rb +183 -0
- data/lib/shoko/application/use_cases/commands/reader_commands.rb +46 -0
- data/lib/shoko/application/use_cases/commands/sidebar_commands.rb +55 -0
- data/lib/shoko/application/use_cases/settings_service.rb +123 -0
- data/lib/shoko/core/events/annotation_events.rb +94 -0
- data/lib/shoko/core/events/base_domain_event.rb +169 -0
- data/lib/shoko/core/events/bookmark_events.rb +41 -0
- data/lib/shoko/core/events/domain_event_bus.rb +163 -0
- data/lib/shoko/core/events/progress_events.rb +108 -0
- data/lib/shoko/core/models/bookmark.rb +36 -0
- data/lib/shoko/core/models/bookmark_data.rb +10 -0
- data/lib/shoko/core/models/chapter.rb +25 -0
- data/lib/shoko/core/models/content_block.rb +44 -0
- data/lib/shoko/core/models/reader_settings.rb +20 -0
- data/lib/shoko/core/models/selection_anchor.rb +73 -0
- data/lib/shoko/core/models/toc_entry.rb +14 -0
- data/lib/shoko/core/ports/annotation_repository.rb +0 -0
- data/lib/shoko/core/ports/book_repository.rb +0 -0
- data/lib/shoko/core/ports/book_source.rb +0 -0
- data/lib/shoko/core/ports/bookmark_repository.rb +0 -0
- data/lib/shoko/core/ports/cache.rb +0 -0
- data/lib/shoko/core/ports/input_handler.rb +0 -0
- data/lib/shoko/core/ports/renderer.rb +0 -0
- data/lib/shoko/core/ports/storage.rb +0 -0
- data/lib/shoko/core/services/annotation_service.rb +102 -0
- data/lib/shoko/core/services/base_service.rb +60 -0
- data/lib/shoko/core/services/bookmark_service.rb +267 -0
- data/lib/shoko/core/services/coordinate_service.rb +265 -0
- data/lib/shoko/core/services/layout_service.rb +95 -0
- data/lib/shoko/core/services/navigation/absolute_change_applier.rb +96 -0
- data/lib/shoko/core/services/navigation/absolute_layout.rb +101 -0
- data/lib/shoko/core/services/navigation/absolute_strategy.rb +179 -0
- data/lib/shoko/core/services/navigation/context_builder.rb +52 -0
- data/lib/shoko/core/services/navigation/context_helpers.rb +63 -0
- data/lib/shoko/core/services/navigation/dynamic_change_applier.rb +50 -0
- data/lib/shoko/core/services/navigation/dynamic_strategy.rb +51 -0
- data/lib/shoko/core/services/navigation/image_offset_snapper.rb +150 -0
- data/lib/shoko/core/services/navigation/nav_context.rb +27 -0
- data/lib/shoko/core/services/navigation/state_updater.rb +29 -0
- data/lib/shoko/core/services/navigation/strategy_factory.rb +20 -0
- data/lib/shoko/core/services/navigation_service.rb +150 -0
- data/lib/shoko/core/services/page_calculator_service.rb +242 -0
- data/lib/shoko/core/services/pagination/internal/absolute_page_map_builder.rb +28 -0
- data/lib/shoko/core/services/pagination/internal/chapter_cache.rb +60 -0
- data/lib/shoko/core/services/pagination/internal/dynamic_page_map_builder.rb +157 -0
- data/lib/shoko/core/services/pagination/internal/layout_metrics_calculator.rb +73 -0
- data/lib/shoko/core/services/pagination/internal/page_hydrator.rb +145 -0
- data/lib/shoko/core/services/pagination/internal/pagination_workflow.rb +152 -0
- data/lib/shoko/core/services/pagination/page_info_calculator.rb +247 -0
- data/lib/shoko/core/services/pagination/pagination_cache_preloader.rb +173 -0
- data/lib/shoko/core/services/pagination/pagination_coordinator.rb +202 -0
- data/lib/shoko/core/services/pagination/pagination_orchestrator.rb +291 -0
- data/lib/shoko/core/services/pagination.rb +10 -0
- data/lib/shoko/core/services/progress_helper.rb +22 -0
- data/lib/shoko/core/services/selection_service.rb +126 -0
- data/lib/shoko/core/validator.rb +76 -0
- data/lib/shoko/shared/errors.rb +97 -0
- data/lib/shoko/shared/version.rb +5 -0
- data/lib/shoko/test_support/terminal_double.rb +175 -0
- data/lib/shoko/test_support/test_mode.rb +78 -0
- data/lib/shoko.rb +279 -0
- data/lib/zip.rb +732 -0
- data/zip.rb +5 -0
- metadata +370 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_command'
|
|
4
|
+
require_relative 'navigation_commands'
|
|
5
|
+
require_relative 'sidebar_commands'
|
|
6
|
+
|
|
7
|
+
module Shoko
|
|
8
|
+
module Application
|
|
9
|
+
module Commands
|
|
10
|
+
# Commands that route to different actions based on application state
|
|
11
|
+
class ConditionalNavigationCommand < BaseCommand
|
|
12
|
+
def initialize(primary_action, sidebar_action, name: nil, description: nil)
|
|
13
|
+
@primary_action = primary_action # Action when sidebar not visible
|
|
14
|
+
@sidebar_action = sidebar_action # Action when sidebar is visible
|
|
15
|
+
super(
|
|
16
|
+
name: name || "conditional_#{primary_action}",
|
|
17
|
+
description: description || "Conditional #{primary_action.to_s.tr('_', ' ')} navigation"
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
protected
|
|
22
|
+
|
|
23
|
+
def perform(context, params = {})
|
|
24
|
+
state = context.dependencies.resolve(:global_state)
|
|
25
|
+
sidebar_visible = state.get(%i[reader sidebar_visible])
|
|
26
|
+
|
|
27
|
+
if sidebar_visible
|
|
28
|
+
# Route to sidebar command
|
|
29
|
+
sidebar_command = SidebarCommand.new(@sidebar_action)
|
|
30
|
+
sidebar_command.execute(context, params)
|
|
31
|
+
else
|
|
32
|
+
# Route to navigation command
|
|
33
|
+
nav_command = NavigationCommand.new(@primary_action)
|
|
34
|
+
nav_command.execute(context, params)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
sidebar_visible ? @sidebar_action : @primary_action
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class << self
|
|
41
|
+
# Factory methods for common conditional navigation
|
|
42
|
+
def up_or_sidebar
|
|
43
|
+
new(:scroll_up, :up)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def down_or_sidebar
|
|
47
|
+
new(:scroll_down, :down)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def select_or_sidebar
|
|
51
|
+
new(:next_page, :select) # Enter key: next page normally, select in sidebar
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_command'
|
|
4
|
+
|
|
5
|
+
module Shoko
|
|
6
|
+
module Application
|
|
7
|
+
module Commands
|
|
8
|
+
# Menu commands for top-level and browse screens
|
|
9
|
+
class MenuCommand < BaseCommand
|
|
10
|
+
SETTINGS_ACTIONS = %i[
|
|
11
|
+
back_to_menu
|
|
12
|
+
toggle_view_mode
|
|
13
|
+
cycle_line_spacing
|
|
14
|
+
toggle_page_numbering_mode
|
|
15
|
+
toggle_page_numbers
|
|
16
|
+
toggle_highlight_quotes
|
|
17
|
+
toggle_kitty_images
|
|
18
|
+
wipe_cache
|
|
19
|
+
].freeze
|
|
20
|
+
SETTINGS_MAX_INDEX = SETTINGS_ACTIONS.length - 1
|
|
21
|
+
private_constant :SETTINGS_ACTIONS, :SETTINGS_MAX_INDEX
|
|
22
|
+
|
|
23
|
+
def initialize(action)
|
|
24
|
+
@action = action
|
|
25
|
+
super(name: "menu_#{action}", description: "Menu action #{action}")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def can_execute?(context, _params = {})
|
|
29
|
+
context.respond_to?(:state) && (context.respond_to?(:dependencies) || true)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
protected
|
|
33
|
+
|
|
34
|
+
def perform(context, _params = {})
|
|
35
|
+
state = context.state
|
|
36
|
+
mmc = context.respond_to?(:main_menu_component) ? context.main_menu_component : nil
|
|
37
|
+
can_switch = context.respond_to?(:switch_to_mode)
|
|
38
|
+
case @action
|
|
39
|
+
when :menu_up then update_menu_index(context, :selected, -1, 0, 5)
|
|
40
|
+
when :menu_down then update_menu_index(context, :selected, +1, 0, 5)
|
|
41
|
+
when :menu_select then context.handle_menu_selection if context.respond_to?(:handle_menu_selection)
|
|
42
|
+
when :menu_quit then context.cleanup_and_exit(0, '') if context.respond_to?(:cleanup_and_exit)
|
|
43
|
+
when :back_to_menu then switch_mode(context, :menu, can_switch)
|
|
44
|
+
when :browse_up then browse_nav(context, -1)
|
|
45
|
+
when :browse_down then browse_nav(context, +1)
|
|
46
|
+
when :browse_select then context.open_selected_book if context.respond_to?(:open_selected_book)
|
|
47
|
+
when :library_up then context.library_up if context.respond_to?(:library_up)
|
|
48
|
+
when :library_down then context.library_down if context.respond_to?(:library_down)
|
|
49
|
+
when :library_select then context.library_select if context.respond_to?(:library_select)
|
|
50
|
+
when :settings_up then update_menu_index(context, :settings_selected, -1, 0, SETTINGS_MAX_INDEX)
|
|
51
|
+
when :settings_down then update_menu_index(context, :settings_selected, +1, 0, SETTINGS_MAX_INDEX)
|
|
52
|
+
when :settings_select then perform_settings_select(context, can_switch)
|
|
53
|
+
when :toggle_view_mode then context.toggle_view_mode if context.respond_to?(:toggle_view_mode)
|
|
54
|
+
when :cycle_line_spacing then context.cycle_line_spacing if context.respond_to?(:cycle_line_spacing)
|
|
55
|
+
when :toggle_page_numbers then context.toggle_page_numbers if context.respond_to?(:toggle_page_numbers)
|
|
56
|
+
when :toggle_page_numbering_mode
|
|
57
|
+
context.toggle_page_numbering_mode if context.respond_to?(:toggle_page_numbering_mode)
|
|
58
|
+
when :toggle_highlight_quotes
|
|
59
|
+
context.toggle_highlight_quotes if context.respond_to?(:toggle_highlight_quotes)
|
|
60
|
+
when :toggle_kitty_images
|
|
61
|
+
context.toggle_kitty_images if context.respond_to?(:toggle_kitty_images)
|
|
62
|
+
when :wipe_cache then context.wipe_cache if context.respond_to?(:wipe_cache)
|
|
63
|
+
when :start_search
|
|
64
|
+
if context.respond_to?(:switch_to_search)
|
|
65
|
+
context.switch_to_search
|
|
66
|
+
else
|
|
67
|
+
# Fallback: set mode/search_active via actions
|
|
68
|
+
state.dispatch(Shoko::Application::Actions::UpdateMenuAction.new(mode: :search,
|
|
69
|
+
search_active: true))
|
|
70
|
+
end
|
|
71
|
+
current = (state.get(%i[menu search_query]) || '').to_s
|
|
72
|
+
state.dispatch(Shoko::Application::Actions::UpdateMenuAction.new(search_cursor: current.length))
|
|
73
|
+
when :exit_search
|
|
74
|
+
if context.respond_to?(:switch_to_browse)
|
|
75
|
+
context.switch_to_browse
|
|
76
|
+
else
|
|
77
|
+
state.dispatch(Shoko::Application::Actions::UpdateMenuAction.new(mode: :browse,
|
|
78
|
+
search_active: false))
|
|
79
|
+
end
|
|
80
|
+
# recent_* actions removed
|
|
81
|
+
# Annotations list (menu) actions
|
|
82
|
+
when :annotations_up
|
|
83
|
+
mmc&.annotations_screen&.navigate(:up)
|
|
84
|
+
when :annotations_down
|
|
85
|
+
mmc&.annotations_screen&.navigate(:down)
|
|
86
|
+
when :annotations_select
|
|
87
|
+
if mmc
|
|
88
|
+
screen = mmc.annotations_screen
|
|
89
|
+
ann = screen.current_annotation
|
|
90
|
+
path = screen.current_book_path
|
|
91
|
+
if ann && path
|
|
92
|
+
state.dispatch(Shoko::Application::Actions::UpdateMenuAction.new(
|
|
93
|
+
selected_annotation: ann,
|
|
94
|
+
selected_annotation_book: path
|
|
95
|
+
))
|
|
96
|
+
switch_mode(context, :annotation_detail, can_switch)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
when :annotations_edit, :annotation_detail_edit
|
|
100
|
+
context.open_selected_annotation_for_edit if context.respond_to?(:open_selected_annotation_for_edit)
|
|
101
|
+
when :annotations_delete, :annotation_detail_delete
|
|
102
|
+
delete_selected_annotation_if_available(context, can_switch)
|
|
103
|
+
# Annotation detail actions
|
|
104
|
+
when :annotation_detail_open
|
|
105
|
+
context.open_selected_annotation if context.respond_to?(:open_selected_annotation)
|
|
106
|
+
when :annotation_detail_back
|
|
107
|
+
switch_mode(context, :annotations, can_switch)
|
|
108
|
+
else
|
|
109
|
+
:pass
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
def switch_mode(context, mode, can_switch)
|
|
116
|
+
context.switch_to_mode(mode) if can_switch
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def delete_selected_annotation_if_available(context, can_switch)
|
|
120
|
+
return unless context.respond_to?(:delete_selected_annotation)
|
|
121
|
+
|
|
122
|
+
context.delete_selected_annotation
|
|
123
|
+
switch_mode(context, :annotations, can_switch)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def update_menu_index(context, field, delta, min_idx, max_idx)
|
|
127
|
+
state = context.state
|
|
128
|
+
current = state.get([:menu, field]) || 0
|
|
129
|
+
new_val = (current + delta).clamp(min_idx, max_idx)
|
|
130
|
+
state.dispatch(Shoko::Application::Actions::UpdateMenuAction.new(field => new_val))
|
|
131
|
+
new_val
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def perform_settings_select(context, can_switch)
|
|
135
|
+
state = context.state
|
|
136
|
+
index = state.get(%i[menu settings_selected]) || 0
|
|
137
|
+
action = SETTINGS_ACTIONS[index]
|
|
138
|
+
return unless action
|
|
139
|
+
|
|
140
|
+
if action == :back_to_menu
|
|
141
|
+
switch_mode(context, :menu, can_switch)
|
|
142
|
+
return
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
context.public_send(action) if context.respond_to?(action)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def browse_nav(context, delta)
|
|
149
|
+
state = context.state
|
|
150
|
+
# Prefer component's filtered list length; fall back to public accessor
|
|
151
|
+
mmc = context.respond_to?(:main_menu_component) ? context.main_menu_component : nil
|
|
152
|
+
max_idx = if mmc.respond_to?(:browse_screen)
|
|
153
|
+
cnt = mmc.browse_screen.filtered_count
|
|
154
|
+
[(cnt || 0) - 1, 0].max
|
|
155
|
+
else
|
|
156
|
+
epubs = (context.respond_to?(:filtered_epubs) && context.filtered_epubs) || []
|
|
157
|
+
[epubs.length - 1, 0].max
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
current = state.get(%i[menu browse_selected]) || 0
|
|
161
|
+
new_val = (current + delta).clamp(0, max_idx)
|
|
162
|
+
state.dispatch(Shoko::Application::Actions::UpdateMenuAction.new(browse_selected: new_val))
|
|
163
|
+
new_val
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# recent navigation removed
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_command'
|
|
4
|
+
|
|
5
|
+
module Shoko
|
|
6
|
+
module Application
|
|
7
|
+
module Commands
|
|
8
|
+
# Navigation commands using proper domain services.
|
|
9
|
+
# Eliminates direct controller coupling and state manipulation.
|
|
10
|
+
class NavigationCommand < BaseCommand
|
|
11
|
+
def initialize(action, name: nil, description: nil)
|
|
12
|
+
@action = action
|
|
13
|
+
super(
|
|
14
|
+
name: name || "navigate_#{action}",
|
|
15
|
+
description: description || "Navigate #{humanize_action(action)}"
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def validate_context(context)
|
|
20
|
+
super
|
|
21
|
+
return if context.respond_to?(:dependencies)
|
|
22
|
+
|
|
23
|
+
raise ValidationError.new('Context must provide dependencies', command_name: name)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def can_execute?(context, _params = {})
|
|
27
|
+
deps = context.dependencies
|
|
28
|
+
deps.registered?(:navigation_service) && deps.registered?(:state_store)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
protected
|
|
32
|
+
|
|
33
|
+
def perform(context, _params = {})
|
|
34
|
+
deps = context.dependencies
|
|
35
|
+
navigation_service = deps.resolve(:navigation_service)
|
|
36
|
+
state_store = deps.resolve(:state_store)
|
|
37
|
+
current_chapter = current_chapter_from(state_store)
|
|
38
|
+
|
|
39
|
+
case @action
|
|
40
|
+
when :next_page
|
|
41
|
+
navigation_service.next_page
|
|
42
|
+
when :prev_page
|
|
43
|
+
navigation_service.prev_page
|
|
44
|
+
when :next_chapter
|
|
45
|
+
navigation_service.jump_to_chapter(current_chapter + 1)
|
|
46
|
+
when :prev_chapter
|
|
47
|
+
navigation_service.jump_to_chapter([current_chapter - 1, 0].max)
|
|
48
|
+
when :go_to_start
|
|
49
|
+
navigation_service.go_to_start
|
|
50
|
+
when :go_to_end
|
|
51
|
+
navigation_service.go_to_end
|
|
52
|
+
else
|
|
53
|
+
raise ExecutionError.new("Unknown navigation action: #{@action}", command_name: name)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
@action
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def humanize_action(action)
|
|
62
|
+
action.to_s.tr('_', ' ')
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def current_chapter_from(state_store)
|
|
66
|
+
return 0 unless state_store.respond_to?(:current_state)
|
|
67
|
+
|
|
68
|
+
(state_store.current_state || {}).dig(:reader, :current_chapter) || 0
|
|
69
|
+
rescue StandardError
|
|
70
|
+
0
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Command that scrolls within the current chapter by a number of lines.
|
|
75
|
+
class ScrollCommand < BaseCommand
|
|
76
|
+
def initialize(direction, lines: 1, name: nil, description: nil)
|
|
77
|
+
@direction = direction
|
|
78
|
+
@lines = lines
|
|
79
|
+
super(
|
|
80
|
+
name: name || "scroll_#{direction}",
|
|
81
|
+
description: description || "Scroll #{direction} #{lines} line(s)"
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def validate_parameters(params)
|
|
86
|
+
super
|
|
87
|
+
|
|
88
|
+
valid_directions = %i[up down]
|
|
89
|
+
unless valid_directions.include?(@direction)
|
|
90
|
+
raise ValidationError.new("Direction must be one of #{valid_directions}",
|
|
91
|
+
command_name: name)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
return if @lines.is_a?(Integer) && @lines.positive?
|
|
95
|
+
|
|
96
|
+
raise ValidationError.new('Lines must be a positive integer', command_name: name)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
protected
|
|
100
|
+
|
|
101
|
+
def perform(context, _params = {})
|
|
102
|
+
navigation_service = context.dependencies.resolve(:navigation_service)
|
|
103
|
+
navigation_service.scroll(@direction, @lines)
|
|
104
|
+
|
|
105
|
+
{ direction: @direction, lines: @lines }
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Command that jumps to a specific chapter index.
|
|
110
|
+
class JumpToChapterCommand < BaseCommand
|
|
111
|
+
def initialize(chapter_index = nil, name: nil, description: nil)
|
|
112
|
+
@chapter_index = chapter_index
|
|
113
|
+
super(
|
|
114
|
+
name: name || 'jump_to_chapter',
|
|
115
|
+
description: description || 'Jump to specific chapter'
|
|
116
|
+
)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def validate_parameters(params)
|
|
120
|
+
super
|
|
121
|
+
|
|
122
|
+
# Chapter index can come from params or constructor
|
|
123
|
+
index = params[:chapter_index] || @chapter_index
|
|
124
|
+
|
|
125
|
+
return if index.is_a?(Integer) && index >= 0
|
|
126
|
+
|
|
127
|
+
raise ValidationError.new('Chapter index must be a non-negative integer',
|
|
128
|
+
command_name: name)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
protected
|
|
132
|
+
|
|
133
|
+
def perform(context, params = {})
|
|
134
|
+
navigation_service = context.dependencies.resolve(:navigation_service)
|
|
135
|
+
index = params[:chapter_index] || @chapter_index
|
|
136
|
+
|
|
137
|
+
navigation_service.jump_to_chapter(index)
|
|
138
|
+
|
|
139
|
+
{ chapter_index: index }
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Factory methods for common navigation commands
|
|
144
|
+
module NavigationCommandFactory
|
|
145
|
+
def self.next_page
|
|
146
|
+
NavigationCommand.new(:next_page)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def self.prev_page
|
|
150
|
+
NavigationCommand.new(:prev_page)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def self.next_chapter
|
|
154
|
+
NavigationCommand.new(:next_chapter)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def self.prev_chapter
|
|
158
|
+
NavigationCommand.new(:prev_chapter)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def self.go_to_start
|
|
162
|
+
NavigationCommand.new(:go_to_start)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def self.go_to_end
|
|
166
|
+
NavigationCommand.new(:go_to_end)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def self.scroll_up(lines = 1)
|
|
170
|
+
ScrollCommand.new(:up, lines: lines)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def self.scroll_down(lines = 1)
|
|
174
|
+
ScrollCommand.new(:down, lines: lines)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def self.jump_to_chapter(index)
|
|
178
|
+
JumpToChapterCommand.new(index)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_command'
|
|
4
|
+
|
|
5
|
+
module Shoko
|
|
6
|
+
module Application
|
|
7
|
+
module Commands
|
|
8
|
+
# Handles reader mode transitions (help) via domain command flow.
|
|
9
|
+
class ReaderModeCommand < BaseCommand
|
|
10
|
+
def initialize(action)
|
|
11
|
+
@action = action
|
|
12
|
+
super(name: "reader_mode_#{action}", description: "Reader mode action #{action}")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
protected
|
|
16
|
+
|
|
17
|
+
def perform(context, _params = {})
|
|
18
|
+
ui_controller = resolve_ui_controller(context)
|
|
19
|
+
return :pass unless ui_controller
|
|
20
|
+
|
|
21
|
+
case @action
|
|
22
|
+
when :exit_help
|
|
23
|
+
ui_controller.switch_mode(:read)
|
|
24
|
+
else
|
|
25
|
+
raise ExecutionError.new("Unknown reader mode action: #{@action}", command_name: name)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def resolve_ui_controller(context)
|
|
32
|
+
if context.respond_to?(:dependencies)
|
|
33
|
+
begin
|
|
34
|
+
deps = context.dependencies
|
|
35
|
+
return deps.resolve(:ui_controller) if deps.respond_to?(:resolve)
|
|
36
|
+
rescue StandardError
|
|
37
|
+
# fall through
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
context.respond_to?(:ui_controller) ? context.ui_controller : nil
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_command'
|
|
4
|
+
|
|
5
|
+
module Shoko
|
|
6
|
+
module Application
|
|
7
|
+
module Commands
|
|
8
|
+
# Commands for sidebar navigation and interaction
|
|
9
|
+
class SidebarCommand < BaseCommand
|
|
10
|
+
def initialize(action, name: nil, description: nil)
|
|
11
|
+
@action = action
|
|
12
|
+
super(
|
|
13
|
+
name: name || "sidebar_#{action}",
|
|
14
|
+
description: description || "Sidebar #{action.to_s.tr('_', ' ')}"
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
protected
|
|
19
|
+
|
|
20
|
+
def perform(context, _params = {})
|
|
21
|
+
# Get UI controller through dependency injection
|
|
22
|
+
ui_controller = context.dependencies.resolve(:ui_controller)
|
|
23
|
+
|
|
24
|
+
case @action
|
|
25
|
+
when :up
|
|
26
|
+
ui_controller.sidebar_up
|
|
27
|
+
when :down
|
|
28
|
+
ui_controller.sidebar_down
|
|
29
|
+
when :select
|
|
30
|
+
ui_controller.sidebar_select
|
|
31
|
+
else
|
|
32
|
+
raise ExecutionError.new("Unknown sidebar action: #{@action}", command_name: name)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
@action
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class << self
|
|
39
|
+
# Factory methods for common sidebar commands
|
|
40
|
+
def up
|
|
41
|
+
new(:up)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def down
|
|
45
|
+
new(:down)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def select
|
|
49
|
+
new(:select)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
5
|
+
require_relative '../../core/services/base_service.rb'
|
|
6
|
+
require_relative '../../adapters/storage/cache_paths'
|
|
7
|
+
require_relative '../../adapters/storage/recent_files'
|
|
8
|
+
require_relative '../state/actions/update_config_action.rb'
|
|
9
|
+
require_relative '../selectors/config_selectors'
|
|
10
|
+
|
|
11
|
+
module Shoko
|
|
12
|
+
module Application::UseCases
|
|
13
|
+
# Centralises configuration toggles and cache maintenance for menu settings flows.
|
|
14
|
+
class SettingsService < BaseService
|
|
15
|
+
WIPE_CACHE_MESSAGE = "All caches wiped. Use 'Find Book' to rescan"
|
|
16
|
+
|
|
17
|
+
def initialize(dependencies)
|
|
18
|
+
super
|
|
19
|
+
@state_store = resolve(:state_store)
|
|
20
|
+
@terminal_service = resolve(:terminal_service)
|
|
21
|
+
@wrapping_service = resolve(:wrapping_service) if registered?(:wrapping_service)
|
|
22
|
+
@recent_repository = Adapters::Storage::RecentFiles
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Toggle split/single view mode and persist the change.
|
|
26
|
+
def toggle_view_mode
|
|
27
|
+
current = Shoko::Application::Selectors::ConfigSelectors.view_mode(@state_store) || :split
|
|
28
|
+
new_mode = current == :split ? :single : :split
|
|
29
|
+
dispatch_config(view_mode: new_mode)
|
|
30
|
+
new_mode
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Toggle whether page numbers are displayed.
|
|
34
|
+
def toggle_page_numbers
|
|
35
|
+
current = Shoko::Application::Selectors::ConfigSelectors.show_page_numbers(@state_store)
|
|
36
|
+
dispatch_config(show_page_numbers: !current)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Cycle through line spacing options (compact → normal → relaxed → ...).
|
|
40
|
+
def cycle_line_spacing
|
|
41
|
+
modes = Shoko::Core::Models::ReaderSettings::LINE_SPACING_VALUES
|
|
42
|
+
current = Shoko::Application::Selectors::ConfigSelectors.line_spacing(@state_store) || Shoko::Core::Models::ReaderSettings::DEFAULT_LINE_SPACING
|
|
43
|
+
next_mode = modes[(modes.index(current) || 1) + 1] || modes.first
|
|
44
|
+
dispatch_config(line_spacing: next_mode)
|
|
45
|
+
next_mode
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Toggle quote highlighting preference.
|
|
49
|
+
def toggle_highlight_quotes
|
|
50
|
+
current = Shoko::Application::Selectors::ConfigSelectors.highlight_quotes(@state_store)
|
|
51
|
+
dispatch_config(highlight_quotes: !current)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def toggle_kitty_images
|
|
55
|
+
current = Shoko::Application::Selectors::ConfigSelectors.kitty_images(@state_store)
|
|
56
|
+
dispatch_config(kitty_images: !current)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Toggle dynamic/absolute page numbering mode.
|
|
60
|
+
def toggle_page_numbering_mode
|
|
61
|
+
current = Shoko::Application::Selectors::ConfigSelectors.page_numbering_mode(@state_store) || :dynamic
|
|
62
|
+
next_mode = current == :absolute ? :dynamic : :absolute
|
|
63
|
+
dispatch_config(page_numbering_mode: next_mode)
|
|
64
|
+
next_mode
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Wipe cached EPUB data, recent file history, and wrapping caches.
|
|
68
|
+
# Returns the status message applied to the catalog.
|
|
69
|
+
def wipe_cache(catalog: nil)
|
|
70
|
+
Shoko::Adapters::BookSources::EPUBFinder.clear_cache
|
|
71
|
+
remove_epub_cache_on_disk
|
|
72
|
+
@recent_repository.clear
|
|
73
|
+
@wrapping_service&.clear_cache
|
|
74
|
+
|
|
75
|
+
target_catalog = catalog || resolve(:catalog_service)
|
|
76
|
+
if target_catalog
|
|
77
|
+
target_catalog.update_entries([])
|
|
78
|
+
target_catalog.scan_status = :idle
|
|
79
|
+
target_catalog.scan_message = WIPE_CACHE_MESSAGE
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
WIPE_CACHE_MESSAGE
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def required_dependencies
|
|
88
|
+
%i[state_store terminal_service]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def dispatch_config(payload)
|
|
92
|
+
@state_store.dispatch(Shoko::Application::Actions::UpdateConfigAction.new(payload))
|
|
93
|
+
@state_store.save_config if @state_store.respond_to?(:save_config)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def remove_epub_cache_on_disk
|
|
97
|
+
cache_root = Adapters::Storage::CachePaths.cache_root
|
|
98
|
+
return unless cache_root && File.directory?(cache_root)
|
|
99
|
+
|
|
100
|
+
cache_real = safe_realpath(cache_root)
|
|
101
|
+
return unless cache_real
|
|
102
|
+
|
|
103
|
+
FileUtils.rm_rf(cache_real)
|
|
104
|
+
rescue StandardError
|
|
105
|
+
nil
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def safe_realpath(path)
|
|
109
|
+
root_real = File.realpath(File.dirname(path))
|
|
110
|
+
cache_real = File.realpath(path)
|
|
111
|
+
return cache_real if cache_real.start_with?(root_real) && allowed_cache_dir?(File.basename(cache_real))
|
|
112
|
+
|
|
113
|
+
nil
|
|
114
|
+
rescue StandardError
|
|
115
|
+
allowed_cache_dir?(File.basename(path)) ? path : nil
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def allowed_cache_dir?(name)
|
|
119
|
+
%w[shoko reader].include?(name)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|