From 466a3e5c7f8f94921a9ee15af42dc1527b7c00fc Mon Sep 17 00:00:00 2001 From: libgenapps <33476799+libgenapps@users.noreply.github.com> Date: Wed, 2 May 2018 04:36:29 +0300 Subject: [PATCH] Bookmarks --- LibgenDesktop.Setup/AppFiles.cs | 1 + LibgenDesktop.Setup/Constants.cs | 4 +- LibgenDesktop/Common/Constants.cs | 6 +- LibgenDesktop/LibgenDesktop.csproj | 7 + .../Localization/LocalizationStorage.cs | 2 +- .../Localizators/MainWindowLocalizator.cs | 4 + .../SearchResultsTabLocalizator.cs | 4 + .../Models/Localization/Translation.cs | 4 + LibgenDesktop/Models/MainModel.cs | 40 + LibgenDesktop/Models/Settings/AppSettings.cs | 55 ++ LibgenDesktop/Resources/Languages/English.lng | 4 + LibgenDesktop/Resources/Languages/Russian.lng | 4 + LibgenDesktop/Resources/Languages/Spanish.lng | 705 ++++++++++++++++++ .../Mirrors/libgen_io_nonfiction.xslt | 5 + .../ViewModels/Bookmarks/BookmarkViewModel.cs | 12 + .../Bookmarks/BookmarkViewModelList.cs | 37 + .../Tabs/SearchResultsTabViewModel.cs | 47 ++ .../ViewModels/Windows/MainWindowViewModel.cs | 87 +++ .../Views/Controls/HorizontalScrollViewer.cs | 31 + LibgenDesktop/Views/Controls/Toolbar.xaml | 21 +- LibgenDesktop/Views/Styles/TabStyles.xaml | 18 + LibgenDesktop/Views/Styles/ToolbarStyles.xaml | 15 +- .../Views/Tabs/FictionSearchResultsTab.xaml | 27 +- .../Tabs/NonFictionSearchResultsTab.xaml | 27 +- .../Views/Tabs/SciMagSearchResultsTab.xaml | 27 +- 25 files changed, 1183 insertions(+), 11 deletions(-) create mode 100644 LibgenDesktop/Resources/Languages/Spanish.lng create mode 100644 LibgenDesktop/ViewModels/Bookmarks/BookmarkViewModel.cs create mode 100644 LibgenDesktop/ViewModels/Bookmarks/BookmarkViewModelList.cs create mode 100644 LibgenDesktop/Views/Controls/HorizontalScrollViewer.cs diff --git a/LibgenDesktop.Setup/AppFiles.cs b/LibgenDesktop.Setup/AppFiles.cs index a7d1a61..f147644 100644 --- a/LibgenDesktop.Setup/AppFiles.cs +++ b/LibgenDesktop.Setup/AppFiles.cs @@ -28,6 +28,7 @@ static AppFiles() AddFile(@"Languages\Russian.lng"); AddFile(@"Languages\Romanian.lng"); AddFile(@"Languages\Ukrainian.lng"); + AddFile(@"Languages\Spanish.lng"); AddFile(@"Mirrors\mirrors.config"); AddFile(@"Mirrors\libgen_io_nonfiction.xslt"); AddFile(@"Mirrors\libgen_io_fiction.xslt"); diff --git a/LibgenDesktop.Setup/Constants.cs b/LibgenDesktop.Setup/Constants.cs index 018a06c..7dd584a 100644 --- a/LibgenDesktop.Setup/Constants.cs +++ b/LibgenDesktop.Setup/Constants.cs @@ -2,8 +2,8 @@ { internal static class Constants { - public const string CURRENT_VERSION = "1.0.3"; - public const string TITLE_VERSION = "1.0.3"; + public const string CURRENT_VERSION = "1.1.0"; + public const string TITLE_VERSION = "1.1.0"; public const string PRODUCT_TITLE_FORMAT = "Libgen Desktop " + TITLE_VERSION + " ({0}-bit)"; public const string SHORTCUT_TITLE_FORMAT = "Libgen Desktop ({0}-bit)"; public const string PRODUCT_COMPANY = "Libgen Apps"; diff --git a/LibgenDesktop/Common/Constants.cs b/LibgenDesktop/Common/Constants.cs index f6534eb..fc2d143 100644 --- a/LibgenDesktop/Common/Constants.cs +++ b/LibgenDesktop/Common/Constants.cs @@ -4,9 +4,9 @@ namespace LibgenDesktop.Common { internal static class Constants { - public const string CURRENT_VERSION = "1.0.3"; - public const string CURRENT_GITHUB_RELEASE_NAME = "1.0.3"; - public static readonly DateTime CURRENT_GITHUB_RELEASE_DATE = new DateTime(2018, 4, 7); + public const string CURRENT_VERSION = "1.1.0"; + public const string CURRENT_GITHUB_RELEASE_NAME = "1.1.0"; + public static readonly DateTime CURRENT_GITHUB_RELEASE_DATE = new DateTime(2018, 5, 2); public const string CURRENT_DATABASE_VERSION = "1.0"; public const string APP_SETTINGS_FILE_NAME = "libgen.config"; diff --git a/LibgenDesktop/LibgenDesktop.csproj b/LibgenDesktop/LibgenDesktop.csproj index 988eb27..5765585 100644 --- a/LibgenDesktop/LibgenDesktop.csproj +++ b/LibgenDesktop/LibgenDesktop.csproj @@ -226,6 +226,8 @@ + + @@ -267,6 +269,7 @@ + ApplicationUpdateWindow.xaml @@ -698,6 +701,10 @@ PreserveNewest Languages\Ukrainian.lng + + PreserveNewest + Languages\Spanish.lng + diff --git a/LibgenDesktop/Models/Localization/LocalizationStorage.cs b/LibgenDesktop/Models/Localization/LocalizationStorage.cs index 106f3c9..07d94b0 100644 --- a/LibgenDesktop/Models/Localization/LocalizationStorage.cs +++ b/LibgenDesktop/Models/Localization/LocalizationStorage.cs @@ -71,7 +71,7 @@ private void LoadLanguages(string languageDirectoryPath, string selectedLanguage } Languages.Add(CreateLanguage(translation, translations, defaultTranslation)); } - Languages = Languages.OrderBy(language => language.LocalizedName).ToList(); + Languages = Languages.OrderBy(language => language.Name).ToList(); Language selectedLanguage = Languages.FirstOrDefault(language => language.Name.CompareOrdinalIgnoreCase(selectedLanguageName)); if (selectedLanguage != null) { diff --git a/LibgenDesktop/Models/Localization/Localizators/MainWindowLocalizator.cs b/LibgenDesktop/Models/Localization/Localizators/MainWindowLocalizator.cs index 0aa581f..2638d0f 100644 --- a/LibgenDesktop/Models/Localization/Localizators/MainWindowLocalizator.cs +++ b/LibgenDesktop/Models/Localization/Localizators/MainWindowLocalizator.cs @@ -10,6 +10,8 @@ public MainWindowLocalizator(List prioritizedTranslationList, Langu { WindowTitle = Format(translation => translation?.WindowTitle); ToolbarDownloadManagerTooltip = Format(translation => translation?.DownloadManagerTooltip); + ToolbarBookmarksTooltip = Format(translation => translation?.BookmarksTooltip); + ToolbarNoBookmarks = Format(translation => translation?.NoBookmarks); ToolbarUpdate = Format(translation => translation?.Update); ToolbarImport = Format(translation => translation?.Import); ToolbarSynchronize = Format(translation => translation?.Synchronize); @@ -20,6 +22,8 @@ public MainWindowLocalizator(List prioritizedTranslationList, Langu public string WindowTitle { get; } public string ToolbarDownloadManagerTooltip { get; } + public string ToolbarBookmarksTooltip { get; } + public string ToolbarNoBookmarks { get; } public string ToolbarUpdate { get; } public string ToolbarImport { get; } public string ToolbarSynchronize { get; } diff --git a/LibgenDesktop/Models/Localization/Localizators/SearchResultsTabLocalizator.cs b/LibgenDesktop/Models/Localization/Localizators/SearchResultsTabLocalizator.cs index 7430376..87b7354 100644 --- a/LibgenDesktop/Models/Localization/Localizators/SearchResultsTabLocalizator.cs +++ b/LibgenDesktop/Models/Localization/Localizators/SearchResultsTabLocalizator.cs @@ -12,6 +12,8 @@ public SearchResultsTabLocalizator(List prioritizedTranslationList, SearchInProgress = Format(translation => translation?.SearchInProgress); Interrupt = Format(translation => translation?.Interrupt); Interrupting = Format(translation => translation?.Interrupting); + AddToBookmarksTooltip = Format(translation => translation?.AddToBookmarksTooltip); + RemoveFromBookmarksTooltip = Format(translation => translation?.RemoveFromBookmarksTooltip); ExportButtonTooltip = Format(translation => translation?.ExportButtonTooltip); } @@ -19,6 +21,8 @@ public SearchResultsTabLocalizator(List prioritizedTranslationList, public string SearchInProgress { get; } public string Interrupt { get; } public string Interrupting { get; } + public string AddToBookmarksTooltip { get; } + public string RemoveFromBookmarksTooltip { get; } public string ExportButtonTooltip { get; } private string Format(Func field, object templateArguments = null) diff --git a/LibgenDesktop/Models/Localization/Translation.cs b/LibgenDesktop/Models/Localization/Translation.cs index 2ade293..8768a3d 100644 --- a/LibgenDesktop/Models/Localization/Translation.cs +++ b/LibgenDesktop/Models/Localization/Translation.cs @@ -31,6 +31,8 @@ internal class FormattingInfo internal class MainMenuTranslation { public string DownloadManagerTooltip { get; set; } + public string BookmarksTooltip { get; set; } + public string NoBookmarks { get; set; } public string Update { get; set; } public string Import { get; set; } public string Synchronize { get; set; } @@ -90,6 +92,8 @@ internal class SearchResultsTabsTranslation public string SearchInProgress { get; set; } public string Interrupt { get; set; } public string Interrupting { get; set; } + public string AddToBookmarksTooltip { get; set; } + public string RemoveFromBookmarksTooltip { get; set; } public string ExportButtonTooltip { get; set; } } diff --git a/LibgenDesktop/Models/MainModel.cs b/LibgenDesktop/Models/MainModel.cs index d30c24d..ec0986e 100644 --- a/LibgenDesktop/Models/MainModel.cs +++ b/LibgenDesktop/Models/MainModel.cs @@ -96,6 +96,7 @@ public MainModel() public Updater.UpdateCheckResult LastApplicationUpdateCheckResult { get; set; } public event EventHandler ApplicationUpdateCheckCompleted; + public event EventHandler BookmarksChanged; public Task> SearchNonFictionAsync(string searchQuery, IProgress progressHandler, CancellationToken cancellationToken) @@ -548,6 +549,45 @@ public Task GetDatabaseStatsAsync() return result; } + public bool HasBookmark(LibgenObjectType libgenObjectType, string searchQuery) + { + if (String.IsNullOrWhiteSpace(searchQuery)) + { + return false; + } + searchQuery = searchQuery.Trim(); + return AppSettings.Bookmarks.Bookmarks.Any(bookmark => bookmark.Type == libgenObjectType && bookmark.Query == searchQuery); + } + + public void AddBookmark(LibgenObjectType libgenObjectType, string name, string searchQuery) + { + AppSettings.Bookmarks.Bookmarks.Add(new AppSettings.BookmarkSettings.Bookmark + { + Name = name, + Query = searchQuery, + Type = libgenObjectType + }); + SaveSettings(); + BookmarksChanged?.Invoke(this, EventArgs.Empty); + } + + public void DeleteBookmark(LibgenObjectType libgenObjectType, string searchQuery) + { + if (String.IsNullOrWhiteSpace(searchQuery)) + { + return; + } + searchQuery = searchQuery.Trim(); + AppSettings.BookmarkSettings.Bookmark bookmark = + AppSettings.Bookmarks.Bookmarks.FirstOrDefault(bookmarkItem => bookmarkItem.Type == libgenObjectType && bookmarkItem.Query == searchQuery); + if (bookmark != null) + { + AppSettings.Bookmarks.Bookmarks.Remove(bookmark); + SaveSettings(); + BookmarksChanged?.Invoke(this, EventArgs.Empty); + } + } + public void SaveSettings() { SettingsStorage.SaveSettings(AppSettings, Environment.AppSettingsFilePath); diff --git a/LibgenDesktop/Models/Settings/AppSettings.cs b/LibgenDesktop/Models/Settings/AppSettings.cs index 0e3c109..c25fa9e 100644 --- a/LibgenDesktop/Models/Settings/AppSettings.cs +++ b/LibgenDesktop/Models/Settings/AppSettings.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using LibgenDesktop.Infrastructure; +using LibgenDesktop.Models.Entities; using static LibgenDesktop.Common.Constants; namespace LibgenDesktop.Models.Settings @@ -296,6 +298,29 @@ public static LastUpdateSettings Default public string IgnoreReleaseName { get; set; } } + internal class BookmarkSettings + { + internal class Bookmark + { + public string Name { get; set; } + public string Query { get; set; } + public LibgenObjectType Type { get; set; } + } + + public static BookmarkSettings Default + { + get + { + return new BookmarkSettings + { + Bookmarks = new List() + }; + } + } + + public List Bookmarks { get; set; } + } + internal class GeneralSettings { internal enum UpdateCheckInterval @@ -474,6 +499,7 @@ public static AppSettings Default SciMag = SciMagSettings.Default, DownloadManagerTab = DownloadManagerTabSettings.Default, LastUpdate = LastUpdateSettings.Default, + Bookmarks = BookmarkSettings.Default, General = GeneralSettings.Default, Network = NetworkSettings.Default, Download = DownloadSettings.Default, @@ -492,6 +518,7 @@ public static AppSettings Default public SciMagSettings SciMag { get; set; } public DownloadManagerTabSettings DownloadManagerTab { get; set; } public LastUpdateSettings LastUpdate { get; set; } + public BookmarkSettings Bookmarks { get; set; } public GeneralSettings General { get; set; } public NetworkSettings Network { get; set; } public DownloadSettings Download { get; set; } @@ -515,6 +542,7 @@ public static AppSettings ValidateAndCorrect(AppSettings appSettings) appSettings.ValidateAndCorrectSciMagSettings(); appSettings.ValidateAndCorrectDownloadManagerTabSettings(); appSettings.ValidateAndCorrectLastUpdateSettings(); + appSettings.ValidateAndCorrectBookmarkSettings(); appSettings.ValidateAndCorrectGeneralSettings(); appSettings.ValidateAndCorrectNetworkSettings(); appSettings.ValidateAndCorrectDownloadSettings(); @@ -804,6 +832,33 @@ private void ValidateAndCorrectLastUpdateSettings() } } + private void ValidateAndCorrectBookmarkSettings() + { + if (Bookmarks == null) + { + Bookmarks = BookmarkSettings.Default; + } + else + { + if (Bookmarks.Bookmarks == null) + { + Bookmarks = BookmarkSettings.Default; + } + else + { + for (int bookmarkIndex = Bookmarks.Bookmarks.Count - 1; bookmarkIndex >= 0; bookmarkIndex--) + { + BookmarkSettings.Bookmark bookmark = Bookmarks.Bookmarks[bookmarkIndex]; + if (String.IsNullOrEmpty(bookmark.Name) || String.IsNullOrEmpty(bookmark.Query) || + !Enum.IsDefined(typeof(LibgenObjectType), bookmark.Type)) + { + Bookmarks.Bookmarks.RemoveAt(bookmarkIndex); + } + } + } + } + } + private void ValidateAndCorrectGeneralSettings() { if (General == null) diff --git a/LibgenDesktop/Resources/Languages/English.lng b/LibgenDesktop/Resources/Languages/English.lng index b3db1e5..e1aece7 100644 --- a/LibgenDesktop/Resources/Languages/English.lng +++ b/LibgenDesktop/Resources/Languages/English.lng @@ -27,6 +27,8 @@ "MainMenu": { "DownloadManagerTooltip": "Download Manager", + "BookmarksTooltip": "Bookmarks", + "NoBookmarks": "No bookmarks", "Update": "Update...", "Import": "Import...", "Synchronize": "Synchronization...", @@ -78,6 +80,8 @@ "SearchInProgress": "Search in progress...", "Interrupt": "INTERRUPT", "Interrupting": "INTERRUPTING...", + "AddToBookmarksTooltip": "Add to bookmarks", + "RemoveFromBookmarksTooltip": "Remove from bookmarks", "ExportButtonTooltip": "Export search results to a file" }, "NonFictionSearchResultsTab": diff --git a/LibgenDesktop/Resources/Languages/Russian.lng b/LibgenDesktop/Resources/Languages/Russian.lng index 4dad14f..cad623d 100644 --- a/LibgenDesktop/Resources/Languages/Russian.lng +++ b/LibgenDesktop/Resources/Languages/Russian.lng @@ -27,6 +27,8 @@ "MainMenu": { "DownloadManagerTooltip": "Менеджер загрузок", + "BookmarksTooltip": "Закладки", + "NoBookmarks": "Нет закладок", "Update": "Обновление...", "Import": "Импорт...", "Synchronize": "Синхронизация...", @@ -78,6 +80,8 @@ "SearchInProgress": "Идет поиск...", "Interrupt": "ПРЕРВАТЬ", "Interrupting": "ПРЕРЫВАЕТСЯ...", + "AddToBookmarksTooltip": "Добавить в закладки", + "RemoveFromBookmarksTooltip": "Удалить из закладок", "ExportButtonTooltip": "Экспорт результатов поиска в файл" }, "NonFictionSearchResultsTab": diff --git a/LibgenDesktop/Resources/Languages/Spanish.lng b/LibgenDesktop/Resources/Languages/Spanish.lng new file mode 100644 index 0000000..b022409 --- /dev/null +++ b/LibgenDesktop/Resources/Languages/Spanish.lng @@ -0,0 +1,705 @@ +{ + "General": + { + "Name": "Spanish", + "LocalizedName": "ES Español", + "CultureCode": "es-ES", + "TranslatorName": "mikasa" + }, + "Formatting": + { + "DecimalSeparator": ".", + "ThousandsSeparator": ",", + "DateFormat": "dd/MM/yyyy", + "TimeFormat": "hh:mm:ss tt", + "FileSizePostfixes": + { + "Byte": "bytes", + "Kilobyte": "KB", + "Megabyte": "MB", + "Gigabyte": "GB", + "Terabyte": "TB" + } + }, + "MainWindow": + { + "WindowTitle": "Libgen Desktop", + "MainMenu": + { + "DownloadManagerTooltip": "Administrador de descargas", + "Update": "Actualizar...", + "Import": "Importar...", + "Synchronize": "Sincronización...", + "Database": "Base de datos...", + "Settings": "Ajustes", + "About": "Acerca de" + } + }, + "CreateDatabaseWindow": + { + "WindowTitle": "Libgen Desktop", + "FirstRunMessage": "Bienvenido a Libgen Desktop.", + "DatabaseNotFound": "Base de datos {database} no encontrada.", + "DatabaseCorrupted": "La base de datos {database} está corrupta.", + "ChooseOption": "Escoja una opción", + "CreateNewDatabase": "Crear una base de datos", + "OpenExistingDatabase": "Abrir una base de datos existente", + "BrowseNewDatabaseDialogTitle": "Localización de la nueva base de datos", + "BrowseExistingDatabaseDialogTitle": "Localización de la base de datos existente", + "Databases": "Bases de datos", + "AllFiles": "Todos los archivos", + "Error": "Error", + "CannotCreateDatabase": "No se puede crear la base de datos.", + "Ok": "OK", + "Cancel": "CANCELAR" + }, + "SearchTab": + { + "TabTitle": "Buscar", + "SearchPlaceHolder": "Buscar", + "NonFictionSelector": "Libros de no ficción", + "FictionSelector": "Libros de ficción", + "SciMagSelector": "Artículos científicos", + "NonFictionSearchBoxTooltip": "Buscar por título, autores, series, editorial e ISBN sin guiones", + "FictionSearchBoxTooltip": "Buscar por título, autores, series, editorial e ISBN con guiones", + "SciMagSearchBoxTooltip": "Buscar por título, autores, nombre de la revista, DOI, ID Pubmed e ISSN (p/e)", + "SearchInProgress": "Realizando la búsqueda...", + "NonFictionSearchProgress": "Libros encontrados: {count}", + "FictionSearchProgress": "Libros encontrados: {count}", + "SciMagSearchProgress": "Artículos encontrados: {count}", + "Interrupt": "INTERRUMPIR", + "Interrupting": "INTERRUMPIENDO...", + "DatabaseIsEmpty": "La base de datos está vacía. Descarga un volcado de la base de datos desde el sitio web de Library Genesis e impórtalo aquí.", + "ImportButton": "Importar" + }, + "SearchResultsTabs": + { + "SearchPlaceHolder": "Buscar", + "SearchInProgress": "Realizando la búsqueda...", + "Interrupt": "INTERRUMPIR", + "Interrupting": "INTERRUMPIENDO...", + "ExportButtonTooltip": "Exportar la búsqueda a un archivo" + }, + "NonFictionSearchResultsTab": + { + "SearchBoxTooltip": "Buscar por título, autores, series, editorial e ISBN sin guiones", + "SearchProgress": "Libros encontrados: {count}", + "StatusBar": "Libros encontrados: {count}", + "Columns": + { + "Title": "Título", + "Authors": "Autores", + "Series": "Series", + "Year": "Año", + "Publisher": "Editorial", + "Format": "Formato", + "FileSize": "Tamaño", + "Ocr": "OCR" + } + }, + "FictionSearchResultsTab": + { + "SearchBoxTooltip": "Buscar por título, autores, series, editorial e ISBN con guiones", + "SearchProgress": "Libros encontrados: {count}", + "StatusBar": "Libros encontrados: {count}", + "Columns": + { + "Title": "Título", + "Authors": "Autores", + "Series": "Series", + "Year": "Año", + "Publisher": "Editorial", + "Format": "Formato", + "FileSize": "Tamaño" + } + }, + "SciMagSearchResultsTab": + { + "SearchBoxTooltip": "Buscar por título, autores, nombre de la revista, DOI, ID Pubmed e ISSN (p/e)", + "SearchProgress": "Artículos encontrados: {count}", + "StatusBar": "Artículos encontrados: {count}", + "Columns": + { + "Title": "Título", + "Authors": "Autores", + "Magazine": "Revista", + "Year": "Año", + "FileSize": "Tamaño", + "Doi": "DOI" + } + }, + "DetailsTabs": + { + "CoverIsLoading": "Cargando la cubierta...", + "NoCover": "No hay disponible una cubierta", + "NoCoverMirror": "No se ha seleccionado un espejo{new-line}para cargar la cubierta", + "NoCoverDueToOfflineMode": "El modo sin conexión está activo.{new-line}La cubierta no se cargará.", + "CoverLoadingError": "No se puede cargar la cubierta", + "Yes": "sí", + "No": "no", + "Unknown": "desconocido", + "Portrait": "retrato", + "Landscape": "paisaje", + "CopyContextMenu": "Copiar \"{text}\"", + "Download": "DESCARGAR", + "DownloadFromMirror": "DESCARGAR DESDE {mirror}", + "Queued": "EN COLA", + "Downloading": "DESCARGANDO", + "Stopped": "DETENIDO", + "Error": "ERROR DE DESCARGA", + "Open": "ABRIR ARCHIVO", + "ErrorMessageTitle": "Error", + "FileNotFoundError": "Archivo {file} no encontrado.", + "NoDownloadMirrorTooltip": "No se ha seleccionado un espejo", + "OfflineModeIsOnTooltip": "El modo sin conexión está activo", + "Close": "CERRAR" + }, + "NonFictionDetailsTab": + { + "Title": "Título", + "Authors": "Autores", + "Series": "Series", + "Publisher": "Editorial", + "Year": "Año", + "Language": "Idioma", + "Format": "Formato", + "Isbn": "ISBN", + "Added": "Añadido", + "LastModified": "Última modificación", + "Library": "Biblioteca", + "FileSize": "Tamaño", + "Topics": "Temas", + "Volume": "Volumen", + "Magazine": "Revista", + "City": "Ciudad", + "Edition": "Edición", + "Pages": "Páginas", + "BodyMatterPages": "asunto del cuerpo", + "TotalPages": "total", + "Tags": "Etiquetas", + "Md5Hash": "MD5 hash", + "Comments": "Comentarios", + "Identifiers": "Identificadores", + "LibgenId": "ID Libgen", + "Issn": "ISSN", + "Udc": "UDC", + "Lbc": "LBC", + "Lcc": "LCC", + "Ddc": "DDC", + "Doi": "DOI", + "OpenLibraryId": "ID Open Library", + "GoogleBookId": "ID Google Books", + "Asin": "ASIN", + "AdditionalAttributes": "Atributos adicionales", + "Dpi": "DPI", + "Ocr": "OCR", + "TableOfContents": "Tabla de contenidos", + "Scanned": "Escaneado", + "Orientation": "Orientación", + "Paginated": "Paginado", + "Colored": "En color", + "Cleaned": "Limpio" + }, + "FictionDetailsTab": + { + "Title": "Título", + "Authors": "Autores", + "RussianAuthor": "Autor en ruso", + "Series": "Series", + "Publisher": "Editorial", + "Edition": "Edición", + "Year": "Año", + "Language": "Idioma", + "Format": "Formato", + "Pages": "Páginas", + "Version": "Versión", + "FileSize": "Tamaño", + "Added": "Añadido", + "LastModified": "Última modificación", + "Md5Hash": "MD5 hash", + "Comments": "Comentarios", + "Identifiers": "Identificadores", + "LibgenId": "ID Libgen", + "Isbn": "ISBN", + "GoogleBookId": "ID Google Books", + "Asin": "ASIN" + }, + "SciMagDetailsTab": + { + "Title": "Título", + "Authors": "Autores", + "Magazine": "Revista", + "Year": "Año", + "Month": "Mes", + "Day": "Día", + "Volume": "Volumen", + "Issue": "Número", + "Pages": "Páginas", + "FileSize": "Tamaño", + "AddedDateTime": "Añadido", + "Md5Hash": "MD5 hash", + "AbstractUrl": "URL abstracta", + "Identifiers": "Identificadores", + "LibgenId": "ID Libgen", + "Doi": "DOI", + "Isbn": "ISBN", + "MagazineId": "ID de Revista", + "Issnp": "ISSN (p)", + "Issne": "ISSN (e)", + "PubmedId": "ID Pubmed", + "Pmc": "PMC", + "Pii": "PII", + "AdditionalAttributes": "Atributos adicionales", + "Attribute1": "Atributo 1", + "Attribute2": "Atributo 2", + "Attribute3": "Atributo 3", + "Attribute4": "Atributo 4", + "Attribute5": "Atributo 5", + "Attribute6": "Atributo 6" + }, + "Import": + { + "WindowTitle": "Importación desde un volcado SQL", + "BrowseImportFileDialogTitle": "Localización del archivo de volcado SQL", + "AllSupportedFiles": "Todos los archivos soportados", + "SqlDumps": "Volcados SQL", + "Archives": "Archivos", + "AllFiles": "Todos los archivos", + "Elapsed": "Tiempo transcurrido: {elapsed}", + "Interrupt": "INTERRUMPIR", + "Interrupting": "INTERRUMPIENDO...", + "Close": "CERRAR", + "StatusMessages": + { + "Step": "Paso {current} de {total}.", + "DataLookup": "Definición de la tabla de búsqueda", + "CreatingIndexes": "Creación de los índices", + "LoadingIds": "Cargando los identificadores", + "ImportingData": "Importación de los datos", + "ImportComplete": "Importación completa", + "ImportCancelled": "La importación se canceló", + "DataNotFound": "Datos no encontrados", + "ImportError": "La importación falló" + }, + "LogMessages": + { + "Step": "Paso {step}", + "DataLookup": "Buscando la tabla de definiciones", + "Scanning": "Escaneando...", + "ScannedProgress": "{percent}% del archivo ha sido escaneado...", + "NonFictionTableFound": "Tabla de libros de no ficción encontrada.", + "FictionTableFound": "Tabla de libros de ficción encontrada.", + "SciMagTableFound": "Tabla de artículos científicos encontrada.", + "CreatingIndexes": "Creando los índices perdidos", + "CreatingIndexForColumn": "Creando un índice para la columna {column}...", + "LoadingIds": "Cargando los identificadores existentes", + "LoadingColumnValues": "Cargando los datos de la columna {column}...", + "ImportingData": "Importación de datos", + "ImportBooksProgressNoUpdate": "Libros añadidos: {added}.", + "ImportBooksProgressWithUpdate": "Libros añadidos: {added}, actualizados: {updated}.", + "ImportArticlesProgressNoUpdate": "Artículos añadidos: {added}.", + "ImportArticlesProgressWithUpdate": "Artículos añadidos: {added}, actualizados: {updated}.", + "ImportSuccessful": "La importación se completó satisfactoriamente.", + "ImportCancelled": "La importación fue cancelada por el usuario.", + "DataNotFound": "No se pudo encontrar ningún dato para importar.", + "ImportError": "Ocuurió un error durante la importación." + } + }, + "ExportPanel": + { + "Header": "Exportar los resultados de la búsqueda a un archivo", + "Format": "Formato del archivo", + "Excel": "Microsoft Excel 2007-2016 (XLSX)", + "Csv": "Valores separados por coma (CSV)", + "Separator": "Separador", + "Comma": "Coma", + "Semicolon": "Punto y coma", + "Tab": "Tabulador", + "SaveAs": "Guarda como", + "Browse": "Examinar...", + "BrowseDialogTitle": "Exportar los resultados de la búsqueda", + "ExcelFiles": "Archivos de Microsoft Excel", + "CsvFiles": "Archivos de CSV", + "TsvFiles": "Archivos de TSV", + "AllFiles": "Todos los archivos", + "ExportRange": "Exportar rango", + "NoLimit": "todo", + "Limit": "solamente los primeros {count} resultados", + "Export": "EXPORTAR", + "Cancel": "CANCELAR", + "SavingFile": "Guardando el archivo.", + "RowCountSingleFile": "Filas exportadas: {rows}.", + "RowCountMultipleFiles": "Filas exportadas: {rows}, archivos creados: {files}.", + "ErrorWarningTitle": "Error", + "InvalidExportPath": "Invalid export file path.", + "DirectoryNotFound": "Directory {directory} doesn't exist.", + "InvalidExportFileName": "Invalid export file name.", + "OverwritePromptTitle": "Overwrite the file?", + "OverwritePromptText": "File {file} already exists. Do you want to overwrite it?", + "RowLimitWarningTitle": "Row limit", + "RowLimitWarningText": "Microsoft Excel maximum row count has been reached. Export cannot be continued. In order to export more rows please enable \"Split into multiple files\" option in the application settings.", + "ExportError": "An error occurred during the export.", + "Interrupt": "INTERRUPT", + "Interrupting": "INTERRUPTING...", + "ExportInterrupted": "Export has been interrupted.", + "Results": "OPEN RESULTS", + "Close": "CLOSE" + }, + "Exporter": + { + "Yes": "yes", + "No": "no", + "Unknown": "unknown", + "Portrait": "portrait", + "Landscape": "landscape", + "NonFictionColumns": + { + "Id": "ID", + "Title": "Título", + "Authors": "Autores", + "Series": "Series", + "Publisher": "Editorial", + "Year": "Año", + "Language": "Idioma", + "Format": "Formato", + "Isbn": "ISBN", + "Added": "Añadido", + "LastModified": "Última modificación", + "Library": "Library", + "FileSize": "Tamaño", + "Topics": "Topics", + "Volume": "Volumen", + "Magazine": "Magazine", + "City": "City", + "Edition": "Edición", + "BodyMatterPages": "Pages (Body Matter)", + "TotalPages": "Pages (Total)", + "Tags": "Tags", + "Md5Hash": "MD5 hash", + "Comments": "Comentarios", + "LibgenId": "Libgen ID", + "Issn": "ISSN", + "Udc": "UDC", + "Lbc": "LBC", + "Lcc": "LCC", + "Ddc": "DDC", + "Doi": "DOI", + "OpenLibraryId": "Open Library ID", + "GoogleBookId": "Google Books ID", + "Asin": "ASIN", + "Dpi": "DPI", + "Ocr": "OCR", + "TableOfContents": "Table Of Contents", + "Scanned": "Scanned", + "Orientation": "Orientation", + "Paginated": "Paginated", + "Colored": "Colored", + "Cleaned": "Cleaned" + }, + "FictionColumns": + { + "Id": "ID", + "Title": "Título", + "Authors": "Autores", + "RussianAuthor": "Author in Russian", + "Series": "Series", + "Publisher": "Editorial", + "Edition": "Edición", + "Year": "Año", + "Language": "Idioma", + "Format": "Formato", + "Pages": "Páginas", + "Version": "Versión", + "FileSize": "Tamaño", + "Added": "Añadido", + "LastModified": "Última modificación", + "Md5Hash": "MD5 hash", + "Comments": "Comentarios", + "LibgenId": "Libgen ID", + "Isbn": "ISBN", + "GoogleBookId": "Google Books ID", + "Asin": "ASIN" + }, + "SciMagColumns": + { + "Id": "ID", + "Title": "Título", + "Authors": "Autores", + "Magazine": "Revista", + "Year": "Año", + "Month": "Mes", + "Day": "Día", + "Volume": "Volumen", + "Issue": "Número", + "Pages": "Páginas", + "FileSize": "Tamaño", + "AddedDateTime": "Añadido", + "Md5Hash": "MD5 hash", + "AbstractUrl": "URL abstracta", + "LibgenId": "ID Libgen", + "Doi1": "DOI 1", + "Doi2": "DOI 2", + "Isbn": "ISBN", + "MagazineId": "ID de Revista", + "Issnp": "ISSN (p)", + "Issne": "ISSN (e)", + "PubmedId": "ID Pubmed", + "Pmc": "PMC", + "Pii": "PII", + "Attribute1": "Atributo 1", + "Attribute2": "Atributo 2", + "Attribute3": "Atributo 3", + "Attribute4": "Atributo 4", + "Attribute5": "Atributo 5", + "Attribute6": "Atributo 6" + } + }, + "Synchronization": + { + "WindowTitle": "Sincronización de la lista de libros de no ficción", + "ErrorMessageTitle": "Error", + "ImportRequired": "Se necesita completar un volcado de la base de datos de libros de no ficción para iniciar la sincronización.", + "NoSynchronizationMirror": "No se ha seleccionado un espejo de sincronización para los libros de no ficción.", + "OfflineModePromptTitle": "Modo sin conexión", + "OfflineModePromptText": "La sincronización no se puede iniciar en el modo sin conexión activo. ¿Desea desactivar dicho modo?", + "Elapsed": "Tiempo transcurrido: {elapsed}", + "Interrupt": "INTERRUMPIR", + "Interrupting": "INTERRUMPIENDO...", + "Close": "CERRAR", + "StatusMessages": + { + "Step": "Paso {current} de {total}.", + "Preparation": "Preparando para la sincronización", + "CreatingIndexes": "Creando los índices", + "LoadingIds": "Caragndo los identificadores", + "SynchronizingData": "Sincronización de los datos", + "SynchronizationComplete": "Sincronización completada", + "SynchronizationCancelled": "La sincronización ha sido cancelada", + "SynchronizationError": "La sincronización falló" + }, + "LogMessages": + { + "Step": "Paso {step}", + "CreatingIndexes": "Creando los índices perdidos", + "CreatingIndexForColumn": "Creando el índice para la columna {column}...", + "LoadingIds": "Cargando los datos de los identificadores existentes", + "LoadingColumnValues": "Cargando los datos para la columna {column}...", + "SynchronizingBookList": "Sincronizando la lista de libros", + "DownloadingNewBooks": "Descargando nuevos medatados de libros", + "SynchronizationProgressNoAddedNoUpdated": "Libros descargados: {downloaded}.", + "SynchronizationProgressAdded": "Libros descargados: {downloaded}, añadidos: {added}.", + "SynchronizationProgressUpdated": "Libros descargados: {downloaded}, actualizados: {updated}.", + "SynchronizationProgressAddedAndUpdated": "Libros descargados: {downloaded}, añadidos: {added}, actualizados: {updated}.", + "SynchronizationSuccessful": "Sincronización completada satisfactoriamente.", + "SynchronizationCancelled": "La sincronización ha sido cancelada por el usuario.", + "SynchronizationError": "Ocurrió un error duante la sincronización." + } + }, + "Database": + { + "WindowTitle": "Información de la base de datos", + "NonFiction": "Libros de no ficción", + "Fiction": "Libros de ficción", + "SciMagArticles": "Artículos científicos", + "TotalBooks": "Total de libros", + "TotalArticles": "Total de artículos", + "LastUpdate": "Última actualización", + "Never": "nunca", + "CreatingIndexes": "Por favor, espere. Creando los índices perdidos de la base de datos...", + "ChangeDatabase": "CAMBIAR LA BASE DE DATOS...", + "BrowseDatabaseDialogTitle": "Localización de la base de datos", + "Databases": "Bases de datos", + "AllFiles": "Todos los archivos", + "Error": "Error", + "CannotOpenDatabase": "Ocurrió un error mientras se abría la base de datos: {file}", + "Close": "CERRAR" + }, + "DownloadManager": + { + "TabTitle": "Descargas", + "Start": "INICIAR", + "Stop": "DETENER", + "Remove": "QUITAR", + "StartAll": "INICIAR TODO", + "StopAll": "DETENER TODO", + "RemoveCompleted": "QUITAR TODO", + "QueuedStatus": "En cola", + "DownloadingStatus": "Descargando", + "StoppedStatus": "Detenido", + "RetryDelayStatus": "Retenido para reintentar", + "ErrorStatus": "Error", + "DownloadProgressKnownFileSize": "Descargados {downloaded} de {total} bytes ({percent}%)", + "DownloadProgressUnknownFileSize": "Descargados {downloaded} bytes del tamaño desconocido", + "Log": "Traza", + "TechnicalDetails": "Detalles técnicos", + "Copy": "Copiar al portapaeles", + "FileNotFoundErrorTitle": "Error", + "FileNotFoundErrorText": "Archivo {file} no encontrado.", + "LogMessages": + { + "Queued": "Agregado a la cola de descarga.", + "Started": "Iniciado.", + "Stopped": "Detenido.", + "RetryDelay": "Reintento en {count} segundos...", + "Completed": "Descarga completa.", + "OfflineModeIsOn": "El modo sin conexión está activo.", + "TransformationError": "La transformación {transformation} falló.", + "TransformationReturnedIncorrectUrl": "La transformación {transformation} devolvió una URL no válida.", + "Attempt": "Reintento {current} de {total}.", + "MaximumDownloadAttempts": "Se alcanzó el número máximo de reintentos.", + "DownloadingPage": "Descargando página: {url}", + "DownloadingFile": "Descargando archivo: {url}", + "StartingFileDownloadKnownFileSize": "Descarga de archivo iniciada, tamaño: {size} bytes.", + "StartingFileDownloadUnknownFileSize": "Descarga de archivo iniciada, tamaño desconocido.", + "ResumingFileDownloadKnownFileSize": "Descarga de archivo retomada, tamaño restante: {remaining} bytes.", + "ResumingFileDownloadUnknownFileSize": "Descarga de archivo retomada, tamaño restante: desconocido.", + "Request": "Solicitud", + "Response": "Respuesta del servidor", + "Redirect": "Redirección a {url}", + "TooManyRedirects": "Se han recibido del servidor muchas respuestas de redirección.", + "NonSuccessfulStatusCode": "El servidor devolvió: {status}.", + "CannotCreateDownloadDirectory": "No se puede crear la carpeta {directory}", + "CannotCreateOrOpenFile": "No se puede crear o abrir el archivo {file}", + "CannotRenamePartFile": "No se puede renombrar el archivo {source} a {destination}.", + "HtmlPageReturned": "El servidor devolvió una página HTML en vez de un archivo.", + "NoPartialDownloadSupport": "El servidor no soporta descargas parciales.", + "NoContentLengthWarning": "Atención: el servidor no devolvió el tamaño del archivo.", + "ServerResponseTimeout": "El servidor respondió con un tiempo de espera agotado.", + "DownloadIncompleteError": "El servidor indica que la descarga está completa, pero no es así. Reintente la descarga", + "FileWriteError": "Error de escritura del archivo.", + "UnexpectedError": "Ocurrió un error inesperado." + } + }, + "ApplicationUpdate": + { + "WindowTitle": "Actualización de la aplicación", + "UpdateAvailable": "Está disponible una nueva versión de Libgen Desktop", + "NewVersion": "Nueva versión: {version} liberada el {date}", + "Download": "DESCARGAR", + "DownloadAndInstall": "DESCARGAR E INSTALAR", + "SkipThisVersion": "SALTAR ESTA VERSION", + "Cancel": "CANCELAR", + "Interrupt": "INTERRUMPIR", + "Interrupting": "INTERRUMPIENDO...", + "InterruptPromptTitle": "¿Cancelar la descargar?", + "InterruptPromptText": "¿Quieres cancelar la descarga de la actualización?", + "Error": "Error", + "IncompleteDownload": "La descarga de la actualización no está completa.", + "Close": "CERRAR" + }, + "Settings": + { + "WindowTitle": "Ajustes", + "Ok": "OK", + "Cancel": "CANCELAR", + "DiscardChangesPromptTitle": "¿Descartar los cambios?", + "DiscardChangesPromptText": "Los ajustes se modificaron. ¿Estás seguro que deseas descartar los cambios?", + "General": + { + "TabHeader": "General", + "Language": "Idioma", + "CheckUpdates": "Buscar actualizaciones de la aplicación", + "UpdateCheckIntervals": + { + "Never": "nunca", + "Daily": "diariamente", + "Weekly": "semanalmente", + "Monthly": "mensualmente" + } + }, + "Network": + { + "TabHeader": "Red", + "OfflineMode": "Modo sin conexión", + "UseHttpProxy": "Usar un proxy HTTP", + "ProxyAddress": "Dirección", + "ProxyAddressRequired": "Campo requerido", + "ProxyPort": "Puerto", + "ProxyPortValidation": "Números desde {min} hasta {max}", + "ProxyUserName": "Nombre de usuario", + "ProxyPassword": "Contraseña", + "ProxyPasswordWarning": "No es seguro almacenar la contraseña aquí." + }, + "Download": + { + "TabHeader": "Descargas", + "DownloadMode": "Modo de descarga", + "OpenInBrowser": "abrir la URL de descarga en el navegador", + "UseDownloadManager": "usar al administrador de descargas interno", + "DownloadDirectory": "Descargar en", + "BrowseDirectoryDialogTitle": "Carpeta de descarga", + "DownloadDirectoryNotFound": "La carpeta no existe", + "Timeout": "Tiempo de espera de la descarga", + "TimeoutValidation": "Números: {min} — {max}", + "Seconds": "segundos", + "DownloadAttempts": "Reintentos de la descarga", + "DownloadAttemptsValidation": "Números: {min} — {max}", + "Times": "veces", + "RetryDelay": "Reintentar la descarga en", + "RetryDelayValidation": "Números: {min} — {max}" + }, + "Mirrors": + { + "TabHeader": "Espejos", + "NonFiction": "Libros de no ficción", + "Fiction": "Libros de ficción", + "SciMagArticles": "Artículos científicos", + "Books": "Libros", + "Articles": "Artículos", + "Covers": "Cubiertas", + "Synchronization": "Sincronización", + "NoMirror": "sin espejo" + }, + "Search": + { + "TabHeader": "Buscar", + "LimitResults": "Limitar los resultados de la búsqueda", + "MaximumResults": "Cantidad máxima de resultados", + "PositiveNumbersOnly": "Sólo números positivos", + "OpenDetails": "Abrir los detalles del libro / artículo:", + "InModalWindow": "en una ventana fija", + "InNonModalWindow": "en una ventana ajustable", + "InNewTab": "en una nueva pestaña" + }, + "Export": + { + "TabHeader": "Exportar", + "OpenResults": "Abrir los resultados luego de la exportación", + "SplitIntoMultipleFiles": "Dividir en varios archivos", + "MaximumRowsPerFile": "Número máximo de filas por archivo", + "MaximumRowsPerFileValidation": "Números desde {min} hasta {max}", + "ExcelLimitNote": "Nota: Microsoft Excel no soporta archivos con más de {count} filas." + }, + "Advanced": + { + "TabHeader": "Avanzados", + "UseLogging": "Escribe la información de diagnóstico en un archivo" + } + }, + "About": + { + "WindowTitle": "Acerca de Libgen Desktop", + "ApplicationName": "Libgen Desktop", + "Version": "Version actual: {version} liberada el {date}", + "CheckForUpdates": "BUSCAR ACTUALIZACIONES", + "CheckingUpdates": "BUSCANDO ACTUALIZACIONES...", + "OfflineModeIsOnTooltip": "El modo sin conexión está activo", + "LatestVersion": "Estás usando la última versión.", + "NewVersionAvailable": "Está disponible una nueva versión: {version} liberada el {date}", + "Update": "ACTUALIZAR", + "Translators": "Traductores:" + }, + "MessageBox": + { + "Ok": "OK", + "Yes": "SI", + "No": "NO" + }, + "ErrorWindow": + { + "WindowTitle": "Error", + "UnexpectedError": "Ocurrió un error inesperado", + "Copy": "COPIAR AL PORTAPAPELES", + "Close": "CERRAR" + } +} diff --git a/LibgenDesktop/Resources/Mirrors/libgen_io_nonfiction.xslt b/LibgenDesktop/Resources/Mirrors/libgen_io_nonfiction.xslt index 487cc91..3d2eb6d 100644 --- a/LibgenDesktop/Resources/Mirrors/libgen_io_nonfiction.xslt +++ b/LibgenDesktop/Resources/Mirrors/libgen_io_nonfiction.xslt @@ -12,6 +12,11 @@ + + + + + diff --git a/LibgenDesktop/ViewModels/Bookmarks/BookmarkViewModel.cs b/LibgenDesktop/ViewModels/Bookmarks/BookmarkViewModel.cs new file mode 100644 index 0000000..43149e3 --- /dev/null +++ b/LibgenDesktop/ViewModels/Bookmarks/BookmarkViewModel.cs @@ -0,0 +1,12 @@ +using LibgenDesktop.Models.Entities; + +namespace LibgenDesktop.ViewModels.Bookmarks +{ + internal class BookmarkViewModel + { + public string Name { get; set; } + public string SearchQuery { get; set; } + public LibgenObjectType LibgenObjectType { get; set; } + public bool IsEnabled { get; set; } + } +} diff --git a/LibgenDesktop/ViewModels/Bookmarks/BookmarkViewModelList.cs b/LibgenDesktop/ViewModels/Bookmarks/BookmarkViewModelList.cs new file mode 100644 index 0000000..ce2d3b2 --- /dev/null +++ b/LibgenDesktop/ViewModels/Bookmarks/BookmarkViewModelList.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using LibgenDesktop.Models; +using static LibgenDesktop.Models.Settings.AppSettings; + +namespace LibgenDesktop.ViewModels.Bookmarks +{ + internal class BookmarkViewModelList : ObservableCollection + { + public BookmarkViewModelList(MainModel mainModel) + { + List bookmarks = mainModel.AppSettings.Bookmarks.Bookmarks; + if (bookmarks.Any()) + { + foreach (BookmarkSettings.Bookmark bookmark in bookmarks) + { + Add(new BookmarkViewModel + { + Name = bookmark.Name, + SearchQuery = bookmark.Query, + LibgenObjectType = bookmark.Type, + IsEnabled = true + }); + } + } + else + { + Add(new BookmarkViewModel + { + Name = mainModel.Localization.CurrentLanguage.MainWindow.ToolbarNoBookmarks, + IsEnabled = false + }); + } + } + } +} diff --git a/LibgenDesktop/ViewModels/Tabs/SearchResultsTabViewModel.cs b/LibgenDesktop/ViewModels/Tabs/SearchResultsTabViewModel.cs index e1beaec..70aacc4 100644 --- a/LibgenDesktop/ViewModels/Tabs/SearchResultsTabViewModel.cs +++ b/LibgenDesktop/ViewModels/Tabs/SearchResultsTabViewModel.cs @@ -13,6 +13,9 @@ internal abstract class SearchResultsTabViewModel : TabViewModel { private CancellationTokenSource searchCancellationTokenSource; private string searchQuery; + private LibgenObjectType libgenObjectType; + private string lastExecutedSearchQuery; + private bool isBookmarkSet; private bool isExportPanelVisible; private bool isSearchProgressPanelVisible; private string interruptButtonText; @@ -21,12 +24,16 @@ internal abstract class SearchResultsTabViewModel : TabViewModel protected SearchResultsTabViewModel(MainModel mainModel, IWindowContext parentWindowContext, LibgenObjectType libgenObjectType, string searchQuery) : base(mainModel, parentWindowContext, searchQuery) { + this.libgenObjectType = libgenObjectType; this.searchQuery = searchQuery; + lastExecutedSearchQuery = searchQuery; + UpdateBookmarkedState(); isExportPanelVisible = false; isSearchProgressPanelVisible = false; ExportPanelViewModel = new ExportPanelViewModel(mainModel, libgenObjectType, parentWindowContext); SearchCommand = new Command(Search); InterruptSearchCommand = new Command(InterruptSearch); + ToggleBookmarkCommand = new Command(ToggleBookmark); } public string SearchQuery @@ -46,6 +53,19 @@ public string SearchQuery } } + public bool IsBookmarkSet + { + get + { + return isBookmarkSet; + } + set + { + isBookmarkSet = value; + NotifyPropertyChanged(); + } + } + public bool IsExportPanelVisible { get @@ -102,6 +122,7 @@ public bool IsInterruptButtonEnabled public Command SearchCommand { get; } public Command InterruptSearchCommand { get; } + public Command ToggleBookmarkCommand { get; } private SearchResultsTabLocalizator Localization { @@ -111,6 +132,12 @@ private SearchResultsTabLocalizator Localization } } + public void Search(string searchQuery) + { + SearchQuery = searchQuery; + Search(); + } + public abstract void ShowExportPanel(); protected abstract SearchResultsTabLocalizator GetLocalization(); protected abstract Task SearchAsync(string searchQuery, CancellationToken cancellationToken); @@ -120,6 +147,8 @@ private async void Search() if (!String.IsNullOrWhiteSpace(SearchQuery) && !IsSearchProgressPanelVisible && !IsExportPanelVisible) { Title = SearchQuery; + lastExecutedSearchQuery = SearchQuery; + UpdateBookmarkedState(); InterruptButtonText = Localization.Interrupt; IsInterruptButtonEnabled = true; IsSearchProgressPanelVisible = true; @@ -136,5 +165,23 @@ private void InterruptSearch() InterruptButtonText = Localization.Interrupting; IsInterruptButtonEnabled = false; } + + private void UpdateBookmarkedState() + { + IsBookmarkSet = MainModel.HasBookmark(libgenObjectType, lastExecutedSearchQuery); + } + + private void ToggleBookmark() + { + if (isBookmarkSet) + { + MainModel.DeleteBookmark(libgenObjectType, lastExecutedSearchQuery); + } + else + { + MainModel.AddBookmark(libgenObjectType, lastExecutedSearchQuery, lastExecutedSearchQuery); + } + UpdateBookmarkedState(); + } } } diff --git a/LibgenDesktop/ViewModels/Windows/MainWindowViewModel.cs b/LibgenDesktop/ViewModels/Windows/MainWindowViewModel.cs index 913ef9e..b475d6a 100644 --- a/LibgenDesktop/ViewModels/Windows/MainWindowViewModel.cs +++ b/LibgenDesktop/ViewModels/Windows/MainWindowViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Text; @@ -10,6 +11,7 @@ using LibgenDesktop.Models.Localization.Localizators; using LibgenDesktop.Models.Settings; using LibgenDesktop.Models.Update; +using LibgenDesktop.ViewModels.Bookmarks; using LibgenDesktop.ViewModels.EventArguments; using LibgenDesktop.ViewModels.Tabs; using static LibgenDesktop.Models.Settings.AppSettings; @@ -25,6 +27,7 @@ internal class MainWindowViewModel : LibgenWindowViewModel private bool isCompletedDownloadCounterVisible; private int completedDownloadCount; private bool isApplicationUpdateAvailable; + private BookmarkViewModelList bookmarks; public MainWindowViewModel(MainModel mainModel) : base(mainModel) @@ -40,12 +43,14 @@ public MainWindowViewModel(MainModel mainModel) ImportCommand = new Command(Import); SynchronizeCommand = new Command(Synchronize); DatabaseCommand = new Command(DatabaseMenuItemClick); + BookmarkCommand = new Command(param => BookmarksMenuItemClick(param as BookmarkViewModel)); SettingsCommand = new Command(SettingsMenuItemClick); AboutCommand = new Command(AboutMenuItemClick); WindowClosedCommand = new Command(WindowClosed); TabViewModels = new ObservableCollection(); Initialize(); mainModel.ApplicationUpdateCheckCompleted += ApplicationUpdateCheckCompleted; + mainModel.BookmarksChanged += BookmarksChanged; mainModel.Localization.LanguageChanged += LocalizationLanguageChanged; mainModel.Downloader.DownloaderEvent += DownloaderEvent; } @@ -173,6 +178,19 @@ public bool IsApplicationUpdateAvailable } } + public BookmarkViewModelList Bookmarks + { + get + { + return bookmarks; + } + set + { + bookmarks = value; + NotifyPropertyChanged(); + } + } + public Command NewTabCommand { get; } public Command CloseTabCommand { get; } public Command CloseCurrentTabCommand { get; } @@ -182,6 +200,7 @@ public bool IsApplicationUpdateAvailable public Command ImportCommand { get; } public Command SynchronizeCommand { get; } public Command DatabaseCommand { get; } + public Command BookmarkCommand { get; } public Command SettingsCommand { get; } public Command AboutCommand { get; } public Command WindowClosedCommand { get; } @@ -213,6 +232,7 @@ private void Initialize() isCompletedDownloadCounterVisible = false; completedDownloadCount = 0; isApplicationUpdateAvailable = false; + bookmarks = new BookmarkViewModelList(MainModel); } private void DefaultSearchTabViewModel_ImportRequested(object sender, EventArgs e) @@ -618,6 +638,68 @@ private void DatabaseMenuItemClick() databaseWindowContext.ShowDialog(); } + private void BookmarksMenuItemClick(BookmarkViewModel bookmarkViewModel) + { + bool useCurrentTab; + if (SelectedTabViewModel != null) + { + switch (bookmarkViewModel.LibgenObjectType) + { + case LibgenObjectType.NON_FICTION_BOOK: + useCurrentTab = SelectedTabViewModel is NonFictionSearchResultsTabViewModel; + break; + case LibgenObjectType.FICTION_BOOK: + useCurrentTab = SelectedTabViewModel is FictionSearchResultsTabViewModel; + break; + case LibgenObjectType.SCIMAG_ARTICLE: + useCurrentTab = SelectedTabViewModel is SciMagSearchResultsTabViewModel; + break; + default: + useCurrentTab = false; + break; + } + } + else + { + useCurrentTab = false; + } + if (useCurrentTab) + { + (SelectedTabViewModel as SearchResultsTabViewModel).Search(bookmarkViewModel.SearchQuery); + } + else + { + SearchResultsTabViewModel newTab; + switch (bookmarkViewModel.LibgenObjectType) + { + case LibgenObjectType.NON_FICTION_BOOK: + newTab = new NonFictionSearchResultsTabViewModel(MainModel, CurrentWindowContext, bookmarkViewModel.SearchQuery, + new List()); + break; + case LibgenObjectType.FICTION_BOOK: + newTab = new FictionSearchResultsTabViewModel(MainModel, CurrentWindowContext, bookmarkViewModel.SearchQuery, + new List()); + break; + case LibgenObjectType.SCIMAG_ARTICLE: + newTab = new SciMagSearchResultsTabViewModel(MainModel, CurrentWindowContext, bookmarkViewModel.SearchQuery, + new List()); + break; + default: + newTab = null; + break; + } + if (newTab != null) + { + newTab.Search(bookmarkViewModel.SearchQuery); + TabViewModels.Add(newTab); + SelectedTabViewModel = newTab; + NotifyPropertyChanged(nameof(IsDefaultSearchTabVisible)); + NotifyPropertyChanged(nameof(AreTabsVisible)); + NotifyPropertyChanged(nameof(IsNewTabButtonVisible)); + } + } + } + private void SettingsMenuItemClick() { SettingsWindowViewModel settingsWindowViewModel = new SettingsWindowViewModel(MainModel); @@ -642,6 +724,11 @@ private void ApplicationUpdateCheckCompleted(object sender, EventArgs e) IsApplicationUpdateAvailable = MainModel.LastApplicationUpdateCheckResult != null; } + private void BookmarksChanged(object sender, EventArgs e) + { + Bookmarks = new BookmarkViewModelList(MainModel); + } + private void LocalizationLanguageChanged(object sender, EventArgs e) { Localization = MainModel.Localization.CurrentLanguage.MainWindow; diff --git a/LibgenDesktop/Views/Controls/HorizontalScrollViewer.cs b/LibgenDesktop/Views/Controls/HorizontalScrollViewer.cs new file mode 100644 index 0000000..b316164 --- /dev/null +++ b/LibgenDesktop/Views/Controls/HorizontalScrollViewer.cs @@ -0,0 +1,31 @@ +using System.Windows.Controls; +using System.Windows.Input; + +namespace LibgenDesktop.Views.Controls +{ + internal class HorizontalScrollViewer : ScrollViewer + { + public HorizontalScrollViewer() + { + HorizontalScrollBarVisibility = ScrollBarVisibility.Auto; + VerticalScrollBarVisibility = ScrollBarVisibility.Disabled; + } + + protected override void OnPreviewMouseWheel(MouseWheelEventArgs e) + { + if (e.Delta > 0) + { + LineLeft(); + LineLeft(); + LineLeft(); + } + else + { + LineRight(); + LineRight(); + LineRight(); + } + e.Handled = true; + } + } +} diff --git a/LibgenDesktop/Views/Controls/Toolbar.xaml b/LibgenDesktop/Views/Controls/Toolbar.xaml index 9806d87..96ead16 100644 --- a/LibgenDesktop/Views/Controls/Toolbar.xaml +++ b/LibgenDesktop/Views/Controls/Toolbar.xaml @@ -34,6 +34,25 @@ + + + + + + + + + + + + + + + +