├── .VSCodeCounter ├── details.md ├── results.csv ├── results.md └── results.txt ├── .browserslistrc ├── .eslintrc.json ├── .github └── workflows │ ├── merge.yml │ └── nightly.yml ├── .gitignore ├── .npmrc ├── .prettierrc ├── .run └── Debug renderer.run.xml ├── CHANGELOG.md ├── Dopamine.full.png ├── Dopamine.png ├── Dopamine.screenshot.png ├── LICENSE ├── README.md ├── _config.yml ├── angular.json ├── angular.webpack.js ├── build ├── Sidebar.bmp ├── flac.icns ├── flac.ico ├── icon.icns ├── icon.ico ├── icon.png ├── icons │ ├── 128x128.png │ ├── 16x16.png │ ├── 24x24.png │ ├── 256x256.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 512x512.png │ ├── 64x64.png │ ├── 96x96.png │ ├── icon.icns │ ├── icon.ico │ └── icon.png ├── m4a.icns ├── m4a.ico ├── mp3.icns ├── mp3.ico ├── ogg.icns ├── ogg.ico ├── opus.icns ├── opus.ico ├── uninstaller.nsh ├── wav.icns └── wav.ico ├── deployment └── AUR │ ├── PKGBUILD │ └── dopamine.desktop ├── electron-builder.config.js ├── get-package-information.js ├── jest.config.js ├── jest.setup.js ├── main.js ├── main.js.map ├── main.ts ├── main ├── api │ └── discord │ │ ├── discord-api-command-type.ts │ │ ├── discord-api-command.ts │ │ ├── discord-api.ts │ │ └── presence-args.ts ├── background-work │ ├── common │ │ ├── api │ │ │ ├── lastfm-album.js │ │ │ └── lastfm.api.js │ │ ├── application │ │ │ ├── application-paths.js │ │ │ ├── application-paths.spec.js │ │ │ ├── constants.js │ │ │ ├── file-formats.js │ │ │ └── sensitive-information.js │ │ ├── date-time.js │ │ ├── guid.factory.js │ │ ├── guid.factory.spec.js │ │ ├── io │ │ │ └── file-access.js │ │ ├── logger.js │ │ ├── scheduling │ │ │ └── timer.js │ │ └── utils │ │ │ ├── math-utils.js │ │ │ ├── math-utils.spec.js │ │ │ ├── string-utils.js │ │ │ └── string-utils.spec.js │ ├── data │ │ ├── database.factory.js │ │ ├── entities │ │ │ ├── album-artwork.js │ │ │ ├── folder-track.js │ │ │ ├── folder.js │ │ │ ├── removed-track.js │ │ │ └── track.js │ │ ├── folder-repository.js │ │ ├── folder-track-repository.js │ │ ├── query-parts.js │ │ ├── removed-track-repository.js │ │ └── track-repository.js │ ├── indexing │ │ ├── album-key-generator.js │ │ ├── collection-checker.js │ │ ├── collection-checker.spec.js │ │ ├── data-delimiter.js │ │ ├── directory-walk-result.js │ │ ├── directory-walk-result.spec.js │ │ ├── directory-walker.js │ │ ├── directory-walker.spec.js │ │ ├── file-metadata.factory.js │ │ ├── indexable-path-fetcher.js │ │ ├── indexable-path-fetcher.spec.js │ │ ├── indexable-path.js │ │ ├── indexable-path.spec.js │ │ ├── indexer.js │ │ ├── messages │ │ │ ├── adding-tracks-message.js │ │ │ ├── dismiss-message.js │ │ │ ├── refreshing-message.js │ │ │ ├── removing-tracks-message.js │ │ │ ├── updating-album-artwork-message.js │ │ │ └── updating-tracks-message.js │ │ ├── metadata-patcher.js │ │ ├── mime-types.js │ │ ├── rating-converter.js │ │ ├── rating-converter.spec.js │ │ ├── tag-lib-file-metadata.js │ │ ├── track-adder.js │ │ ├── track-adder.spec.js │ │ ├── track-field-creator.js │ │ ├── track-field-creator.spec.js │ │ ├── track-filler.js │ │ ├── track-indexer.js │ │ ├── track-indexer.spec.js │ │ ├── track-remover.js │ │ ├── track-remover.spec.js │ │ ├── track-updater.js │ │ ├── track-updater.spec.js │ │ ├── track-verifier.js │ │ └── track-verifier.spec.js │ ├── ioc │ │ └── ioc.js │ ├── mocks │ │ ├── album-artwork-adder-mock.js │ │ ├── album-artwork-cache-mock.js │ │ ├── album-artwork-getter-mock.js │ │ ├── album-artwork-remover-mock.js │ │ ├── album-artwork-repository-mock.js │ │ ├── application-paths-mock.js │ │ ├── directory-walker-mock.js │ │ ├── embedded-album-artwork-getter-mock.js │ │ ├── external-album-artwork-getter-mock.js │ │ ├── external-artwork-path-getter-mock.js │ │ ├── file-access-mock.js │ │ ├── file-metadata-mock.js │ │ ├── file-metadata.factory-mock.js │ │ ├── folder-repository-mock.js │ │ ├── folder-track-repository-mock.js │ │ ├── guid-factory-mock.js │ │ ├── image-processor-mock.js │ │ ├── indexable-path-fetcher-mock.js │ │ ├── lastfm.api-mock.js │ │ ├── logger-mock.js │ │ ├── metadata-patcher-mock.js │ │ ├── online-album-artwork-getter-mock.js │ │ ├── removed-track-repository-mock.js │ │ ├── track-adder-mock.js │ │ ├── track-filler-mock.js │ │ ├── track-remover-mock.js │ │ ├── track-repository-mock.js │ │ ├── track-updater-mock.js │ │ ├── track-verifier-mock.js │ │ └── worker-proxy-mock.js │ ├── worker-proxy.js │ └── workers │ │ └── indexing-worker.js └── common │ └── application │ └── sensitive-information.ts ├── package-lock.json ├── package.json ├── postcss.config.js ├── postinstall-web.js ├── postinstall.js ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── common │ │ ├── api │ │ │ ├── fanart │ │ │ │ └── fanart.api.ts │ │ │ ├── git-hub │ │ │ │ └── git-hub.api.ts │ │ │ ├── lastfm │ │ │ │ ├── lastfm-album.ts │ │ │ │ ├── lastfm-artist.ts │ │ │ │ ├── lastfm-biography.ts │ │ │ │ └── lastfm.api.ts │ │ │ └── lyrics │ │ │ │ ├── a-z-lyrics.api.ts │ │ │ │ ├── chart-lyrics.api.ts │ │ │ │ ├── i-lyrics.api.ts │ │ │ │ ├── lyrics-source-type.ts │ │ │ │ ├── lyrics.spec.ts │ │ │ │ ├── lyrics.ts │ │ │ │ └── web-search-lyrics │ │ │ │ ├── sources │ │ │ │ ├── genius-source.ts │ │ │ │ ├── i-web-search-lyrics-source.ts │ │ │ │ ├── lyrics-source.ts │ │ │ │ └── musixmatch-source.ts │ │ │ │ ├── web-search-lyrics.api.ts │ │ │ │ ├── web-search-result.spec.ts │ │ │ │ ├── web-search-result.ts │ │ │ │ └── web-search.api.ts │ │ ├── application │ │ │ ├── application-paths.spec.ts │ │ │ ├── application-paths.ts │ │ │ ├── constants.ts │ │ │ ├── contact-information.spec.ts │ │ │ ├── contact-information.ts │ │ │ ├── external-component.spec.ts │ │ │ ├── external-component.ts │ │ │ ├── file-formats.ts │ │ │ ├── language.spec.ts │ │ │ ├── language.ts │ │ │ ├── product-information.spec.ts │ │ │ ├── product-information.ts │ │ │ └── sensitive-information.ts │ │ ├── color-converter.spec.ts │ │ ├── color-converter.ts │ │ ├── date-time.spec.ts │ │ ├── date-time.ts │ │ ├── guid.factory.spec.ts │ │ ├── guid.factory.ts │ │ ├── hacks.ts │ │ ├── image-processor.ts │ │ ├── io │ │ │ ├── application.base.ts │ │ │ ├── application.ts │ │ │ ├── date-proxy.ts │ │ │ ├── desktop.base.ts │ │ │ ├── desktop.ts │ │ │ ├── document-proxy.ts │ │ │ ├── file-access.base.ts │ │ │ ├── file-access.ts │ │ │ ├── ipc-proxy.base.ts │ │ │ ├── ipc-proxy.ts │ │ │ ├── log-viewer.spec.ts │ │ │ ├── log-viewer.ts │ │ │ ├── media-session-proxy.ts │ │ │ ├── translate-service-proxy.base.ts │ │ │ ├── translate-service-proxy.ts │ │ │ └── window-size.ts │ │ ├── logger.ts │ │ ├── math-extensions.spec.ts │ │ ├── math-extensions.ts │ │ ├── metadata │ │ │ ├── file-metadata.factory.base.ts │ │ │ ├── file-metadata.factory.ts │ │ │ ├── i-file-metadata.ts │ │ │ ├── metadata-patcher.spec.ts │ │ │ ├── metadata-patcher.ts │ │ │ ├── mime-types.spec.ts │ │ │ ├── mime-types.ts │ │ │ ├── rating-converter.spec.ts │ │ │ ├── rating-converter.ts │ │ │ └── tag-lib-file-metadata.ts │ │ ├── native-element-proxy.spec.ts │ │ ├── native-element-proxy.ts │ │ ├── rgb-color.spec.ts │ │ ├── rgb-color.ts │ │ ├── scheduling │ │ │ ├── scheduler.base.ts │ │ │ ├── scheduler.ts │ │ │ └── timer.ts │ │ ├── semantic-zoom-header-adder.spec.ts │ │ ├── semantic-zoom-header-adder.ts │ │ ├── semantic-zoomable-model.ts │ │ ├── semantic-zoomable.spec.ts │ │ ├── semantic-zoomable.ts │ │ ├── settings │ │ │ ├── settings.base.ts │ │ │ └── settings.ts │ │ ├── shuffler.spec.ts │ │ ├── shuffler.ts │ │ ├── sorting │ │ │ ├── album-sorter.spec.ts │ │ │ ├── album-sorter.ts │ │ │ ├── artist-sorter.spec.ts │ │ │ ├── artist-sorter.ts │ │ │ ├── genre-sorter.spec.ts │ │ │ ├── genre-sorter.ts │ │ │ ├── playlist-sorter.spec.ts │ │ │ ├── playlist-sorter.ts │ │ │ ├── track-sorter.spec.ts │ │ │ └── track-sorter.ts │ │ ├── text-sanitizer.ts │ │ ├── utils │ │ │ ├── collection-utils.spec.ts │ │ │ ├── collections-utils.ts │ │ │ ├── promise-utils.ts │ │ │ ├── string-utils.spec.ts │ │ │ └── string-utils.ts │ │ └── validation │ │ │ ├── file-validator.ts │ │ │ ├── path-validator.spec.ts │ │ │ └── path-validator.ts │ ├── data │ │ ├── album-key-generator.spec.ts │ │ ├── album-key-generator.ts │ │ ├── clause-creator.spec.ts │ │ ├── clause-creator.ts │ │ ├── data-delimiter.spec.ts │ │ ├── data-delimiter.ts │ │ ├── database-factory.ts │ │ ├── database-migrator.base.ts │ │ ├── database-migrator.ts │ │ ├── entities │ │ │ ├── album-artwork.ts │ │ │ ├── album-data.ts │ │ │ ├── artist-data.ts │ │ │ ├── folder-track.ts │ │ │ ├── folder.ts │ │ │ ├── genre-data.ts │ │ │ ├── queued-track.ts │ │ │ ├── removed-track.ts │ │ │ └── track.ts │ │ ├── migration.ts │ │ ├── migrations │ │ │ ├── migration1.ts │ │ │ ├── migration2.ts │ │ │ ├── migration3.ts │ │ │ ├── migration4.ts │ │ │ ├── migration5.ts │ │ │ └── migration6.ts │ │ ├── query-parts.ts │ │ └── repositories │ │ │ ├── album-artwork-repository.base.ts │ │ │ ├── album-artwork-repository.ts │ │ │ ├── folder-repository.base.ts │ │ │ ├── folder-repository.ts │ │ │ ├── queued-track-repository.base.ts │ │ │ ├── queued-track-repository.ts │ │ │ ├── track-repository.base.ts │ │ │ └── track-repository.ts │ ├── globalErrorHandler.ts │ ├── services │ │ ├── album-accent-color │ │ │ └── album-accent-color.service.ts │ │ ├── album-artwork-cache │ │ │ ├── album-artwork-cache-id-factory.ts │ │ │ ├── album-artwork-cache-id.spec.ts │ │ │ ├── album-artwork-cache-id.ts │ │ │ ├── album-artwork-cache.service.base.ts │ │ │ ├── album-artwork-cache.service.spec.ts │ │ │ └── album-artwork-cache.service.ts │ │ ├── album │ │ │ ├── album-model.spec.ts │ │ │ ├── album-model.ts │ │ │ ├── album-service.base.ts │ │ │ ├── album-service.spec.ts │ │ │ └── album-service.ts │ │ ├── appearance │ │ │ ├── appearance.service.base.ts │ │ │ ├── appearance.service.spec.ts │ │ │ ├── appearance.service.ts │ │ │ ├── default-themes-creator.spec.ts │ │ │ ├── default-themes-creator.ts │ │ │ ├── palette.spec.ts │ │ │ ├── palette.ts │ │ │ └── theme │ │ │ │ ├── theme-core-colors.spec.ts │ │ │ │ ├── theme-core-colors.ts │ │ │ │ ├── theme-creator.spec.ts │ │ │ │ ├── theme-creator.ts │ │ │ │ ├── theme-neutral-colors.spec.ts │ │ │ │ ├── theme-neutral-colors.ts │ │ │ │ ├── theme-options.spec.ts │ │ │ │ ├── theme-options.ts │ │ │ │ ├── theme.spec.ts │ │ │ │ └── theme.ts │ │ ├── application │ │ │ ├── application.service.base.ts │ │ │ └── application.service.ts │ │ ├── artist-information │ │ │ ├── artist-information-factory.spec.ts │ │ │ ├── artist-information-factory.ts │ │ │ ├── artist-information.service.base.ts │ │ │ ├── artist-information.service.spec.ts │ │ │ ├── artist-information.service.ts │ │ │ ├── artist-information.spec.ts │ │ │ ├── artist-information.ts │ │ │ ├── online-artist-image-getter.spec.ts │ │ │ └── online-artist-image-getter.ts │ │ ├── artist │ │ │ ├── artist-model.spec.ts │ │ │ ├── artist-model.ts │ │ │ ├── artist-splitter.spec.ts │ │ │ ├── artist-splitter.ts │ │ │ ├── artist-type.ts │ │ │ ├── artist.service.base.ts │ │ │ ├── artist.service.spec.ts │ │ │ └── artist.service.ts │ │ ├── audio-visualizer │ │ │ ├── audio-visualizer.service.base.ts │ │ │ ├── audio-visualizer.service.spec.ts │ │ │ └── audio-visualizer.service.ts │ │ ├── collection-navigation │ │ │ ├── collection-navigation.service.spec.ts │ │ │ └── collection-navigation.service.ts │ │ ├── collection │ │ │ ├── collection.service.base.ts │ │ │ ├── collection.service.spec.ts │ │ │ └── collection.service.ts │ │ ├── dialog │ │ │ ├── confirmation-data.ts │ │ │ ├── dialog.service.base.ts │ │ │ ├── dialog.service.spec.ts │ │ │ ├── dialog.service.ts │ │ │ ├── error-data.ts │ │ │ ├── info-data.ts │ │ │ ├── input-data.ts │ │ │ └── playlist-data.ts │ │ ├── discord │ │ │ ├── discord-api-command-type.ts │ │ │ ├── discord-api-command.ts │ │ │ ├── discord.service.spec.ts │ │ │ ├── discord.service.ts │ │ │ └── presence-args.ts │ │ ├── electron.service.ts │ │ ├── event-listener │ │ │ ├── event-listener.service.base.ts │ │ │ ├── event-listener.service.spec.ts │ │ │ └── event-listener.service.ts │ │ ├── file │ │ │ ├── file.service.base.ts │ │ │ ├── file.service.spec.ts │ │ │ └── file.service.ts │ │ ├── folder │ │ │ ├── folder-model.spec.ts │ │ │ ├── folder-model.ts │ │ │ ├── folder-service-mock.ts │ │ │ ├── folder.service.base.ts │ │ │ ├── folder.service.spec.ts │ │ │ ├── folder.service.ts │ │ │ ├── subfolder-model.spec.ts │ │ │ └── subfolder-model.ts │ │ ├── genre │ │ │ ├── genre-model.spec.ts │ │ │ ├── genre-model.ts │ │ │ ├── genre.service.base.ts │ │ │ ├── genre.service.spec.ts │ │ │ └── genre.service.ts │ │ ├── indexing │ │ │ ├── album-artwork-adder.spec.ts │ │ │ ├── album-artwork-adder.ts │ │ │ ├── album-artwork-getter.spec.ts │ │ │ ├── album-artwork-getter.ts │ │ │ ├── album-artwork-indexer.spec.ts │ │ │ ├── album-artwork-indexer.ts │ │ │ ├── album-artwork-remover.spec.ts │ │ │ ├── album-artwork-remover.ts │ │ │ ├── embedded-album-artwork-getter.spec.ts │ │ │ ├── embedded-album-artwork-getter.ts │ │ │ ├── external-album-artwork-getter.spec.ts │ │ │ ├── external-album-artwork-getter.ts │ │ │ ├── external-artwork-path-getter.spec.ts │ │ │ ├── external-artwork-path-getter.ts │ │ │ ├── indexing.service.spec.ts │ │ │ ├── indexing.service.ts │ │ │ ├── messages │ │ │ │ ├── adding-tracks-message.ts │ │ │ │ └── i-indexing-message.ts │ │ │ ├── online-album-artwork-getter.spec.ts │ │ │ ├── online-album-artwork-getter.ts │ │ │ ├── track-field-creator.spec.ts │ │ │ ├── track-field-creator.ts │ │ │ ├── track-filler.spec.ts │ │ │ └── track-filler.ts │ │ ├── lifetime │ │ │ └── lifetime.service.ts │ │ ├── lyrics │ │ │ ├── embedded-lyrics-getter.spec.ts │ │ │ ├── embedded-lyrics-getter.ts │ │ │ ├── i-lyrics-getter.ts │ │ │ ├── lrc-lyrics-getter.spec.ts │ │ │ ├── lrc-lyrics-getter.ts │ │ │ ├── lyrics-model.spec.ts │ │ │ ├── lyrics-model.ts │ │ │ ├── lyrics.service.base.ts │ │ │ ├── lyrics.service.spec.ts │ │ │ ├── lyrics.service.ts │ │ │ ├── online-lyrics-getter.spec.ts │ │ │ └── online-lyrics-getter.ts │ │ ├── media-session │ │ │ ├── media-session.service.spec.ts │ │ │ └── media-session.service.ts │ │ ├── metadata │ │ │ ├── cached-album-artwork-getter.spec.ts │ │ │ ├── cached-album-artwork-getter.ts │ │ │ ├── image-comparison-status.ts │ │ │ ├── image-render-data.ts │ │ │ ├── metadata.service.spec.ts │ │ │ └── metadata.service.ts │ │ ├── navigation │ │ │ ├── navigation.service.base.ts │ │ │ ├── navigation.service.spec.ts │ │ │ └── navigation.service.ts │ │ ├── notification │ │ │ ├── notification-data.ts │ │ │ ├── notification.service.base.ts │ │ │ ├── notification.service.spec.ts │ │ │ └── notification.service.ts │ │ ├── now-playing-navigation │ │ │ ├── now-playing-navigation.service.base.ts │ │ │ ├── now-playing-navigation.service.spec.ts │ │ │ ├── now-playing-navigation.service.ts │ │ │ └── now-playing-page.ts │ │ ├── playback-indication │ │ │ ├── playback-indication.service.base.ts │ │ │ ├── playback-indication.service.spec.ts │ │ │ └── playback-indication.service.ts │ │ ├── playback-information │ │ │ ├── playback-information.factory.ts │ │ │ ├── playback-information.service.spec.ts │ │ │ ├── playback-information.service.ts │ │ │ ├── playback-information.spec.ts │ │ │ └── playback-information.ts │ │ ├── playback │ │ │ ├── audio-player │ │ │ │ ├── audio-changed-event.ts │ │ │ │ ├── audio-player.factory.ts │ │ │ │ ├── gapless-audio-player.ts │ │ │ │ ├── i-audio-player.ts │ │ │ │ └── legacy-audio-player.ts │ │ │ ├── audio-visualizer.ts │ │ │ ├── loop-mode.ts │ │ │ ├── playback-progress.spec.ts │ │ │ ├── playback-progress.ts │ │ │ ├── playback-started.ts │ │ │ ├── playback.service.spec.ts │ │ │ ├── playback.service.ts │ │ │ ├── queue-persister.ts │ │ │ ├── queue-restore-info.ts │ │ │ ├── queue.spec.ts │ │ │ └── queue.ts │ │ ├── player-switcher │ │ │ └── switch-player.service.ts │ │ ├── playlist-folder │ │ │ ├── playlist-folder-model-factory.spec.ts │ │ │ ├── playlist-folder-model-factory.ts │ │ │ ├── playlist-folder-model.spec.ts │ │ │ ├── playlist-folder-model.ts │ │ │ ├── playlist-folder.service.base.ts │ │ │ ├── playlist-folder.service.spec.ts │ │ │ └── playlist-folder.service.ts │ │ ├── playlist │ │ │ ├── playlist-decoder.spec.ts │ │ │ ├── playlist-decoder.ts │ │ │ ├── playlist-entry.spec.ts │ │ │ ├── playlist-entry.ts │ │ │ ├── playlist-file-manager.spec.ts │ │ │ ├── playlist-file-manager.ts │ │ │ ├── playlist-model-factory.spec.ts │ │ │ ├── playlist-model-factory.ts │ │ │ ├── playlist-model.spec.ts │ │ │ ├── playlist-model.ts │ │ │ ├── playlist-update-info.ts │ │ │ ├── playlist.service.base.ts │ │ │ ├── playlist.service.spec.ts │ │ │ └── playlist.service.ts │ │ ├── scrobbling │ │ │ ├── scrobbling.service.spec.ts │ │ │ ├── scrobbling.service.ts │ │ │ └── sign-in-state.ts │ │ ├── search │ │ │ ├── search.service.base.ts │ │ │ ├── search.service.spec.ts │ │ │ └── search.service.ts │ │ ├── semantic-zoom │ │ │ ├── semantic-zoom.service.base.ts │ │ │ ├── semantic-zoom.service.spec.ts │ │ │ └── semantic-zoom.service.ts │ │ ├── track-columns │ │ │ ├── tracks-columns-order-column.ts │ │ │ ├── tracks-columns-order-direction.ts │ │ │ ├── tracks-columns-order.spec.ts │ │ │ ├── tracks-columns-order.ts │ │ │ ├── tracks-columns-ordering.spec.ts │ │ │ ├── tracks-columns-ordering.ts │ │ │ ├── tracks-columns-visibility.spec.ts │ │ │ ├── tracks-columns-visibility.ts │ │ │ ├── tracks-columns.service.base.ts │ │ │ ├── tracks-columns.service.spec.ts │ │ │ └── tracks-columns.service.ts │ │ ├── track │ │ │ ├── track-model-factory.spec.ts │ │ │ ├── track-model-factory.ts │ │ │ ├── track-model.spec.ts │ │ │ ├── track-model.ts │ │ │ ├── track-models.spec.ts │ │ │ ├── track-models.ts │ │ │ ├── track.service.base.ts │ │ │ ├── track.service.spec.ts │ │ │ └── track.service.ts │ │ ├── translator │ │ │ ├── translator.service.base.ts │ │ │ ├── translator.service.spec.ts │ │ │ └── translator.service.ts │ │ ├── tray │ │ │ ├── tray.service.base.ts │ │ │ ├── tray.service.spec.ts │ │ │ └── tray.service.ts │ │ ├── update │ │ │ ├── update.service.base.ts │ │ │ ├── update.service.spec.ts │ │ │ ├── update.service.ts │ │ │ ├── version-comparer.spec.ts │ │ │ └── version-comparer.ts │ │ └── welcome │ │ │ ├── welcome.service.base.ts │ │ │ ├── welcome.service.spec.ts │ │ │ └── welcome.service.ts │ ├── testing │ │ ├── integration-test-runner.ts │ │ ├── mock-creator.ts │ │ └── settings-mock.ts │ └── ui │ │ ├── animations │ │ └── animations.ts │ │ ├── components │ │ ├── add-folder │ │ │ ├── add-folder.component.html │ │ │ ├── add-folder.component.scss │ │ │ ├── add-folder.component.spec.ts │ │ │ └── add-folder.component.ts │ │ ├── add-to-playlist-menu.spec.ts │ │ ├── add-to-playlist-menu.ts │ │ ├── animated-page.spec.ts │ │ ├── animated-page.ts │ │ ├── back-button │ │ │ ├── back-button.component.html │ │ │ ├── back-button.component.scss │ │ │ ├── back-button.component.spec.ts │ │ │ └── back-button.component.ts │ │ ├── collection │ │ │ ├── album-browser │ │ │ │ ├── album-browser.component.html │ │ │ │ ├── album-browser.component.scss │ │ │ │ ├── album-browser.component.spec.ts │ │ │ │ ├── album-browser.component.ts │ │ │ │ ├── album-row.spec.ts │ │ │ │ ├── album-row.ts │ │ │ │ ├── album-rows-getter.spec.ts │ │ │ │ ├── album-rows-getter.ts │ │ │ │ └── album │ │ │ │ │ ├── album.component.html │ │ │ │ │ ├── album.component.scss │ │ │ │ │ ├── album.component.spec.ts │ │ │ │ │ └── album.component.ts │ │ │ ├── album-order.ts │ │ │ ├── base-albums-persister.ts │ │ │ ├── base-tracks-persister.ts │ │ │ ├── collection-albums │ │ │ │ ├── albums-albums-persister.spec.ts │ │ │ │ ├── albums-albums-persister.ts │ │ │ │ ├── albums-tracks-persister.spec.ts │ │ │ │ ├── albums-tracks-persister.ts │ │ │ │ ├── collection-albums.component.html │ │ │ │ ├── collection-albums.component.scss │ │ │ │ ├── collection-albums.component.spec.ts │ │ │ │ └── collection-albums.component.ts │ │ │ ├── collection-artists │ │ │ │ ├── artist-browser │ │ │ │ │ ├── artist-browser.component.html │ │ │ │ │ ├── artist-browser.component.scss │ │ │ │ │ ├── artist-browser.component.spec.ts │ │ │ │ │ ├── artist-browser.component.ts │ │ │ │ │ └── artist-order.ts │ │ │ │ ├── artist │ │ │ │ │ ├── artist.component.html │ │ │ │ │ ├── artist.component.scss │ │ │ │ │ ├── artist.component.spec.ts │ │ │ │ │ └── artist.component.ts │ │ │ │ ├── artists-albums-persister.spec.ts │ │ │ │ ├── artists-albums-persister.ts │ │ │ │ ├── artists-persister.spec.ts │ │ │ │ ├── artists-persister.ts │ │ │ │ ├── artists-tracks-persister.spec.ts │ │ │ │ ├── artists-tracks-persister.ts │ │ │ │ ├── collection-artists.component.html │ │ │ │ ├── collection-artists.component.scss │ │ │ │ ├── collection-artists.component.spec.ts │ │ │ │ └── collection-artists.component.ts │ │ │ ├── collection-folders │ │ │ │ ├── collection-folders.component.html │ │ │ │ ├── collection-folders.component.scss │ │ │ │ ├── collection-folders.component.spec.ts │ │ │ │ ├── collection-folders.component.ts │ │ │ │ ├── folder-tracks-persister.spec.ts │ │ │ │ ├── folder-tracks-persister.ts │ │ │ │ ├── folders-persister.spec.ts │ │ │ │ └── folders-persister.ts │ │ │ ├── collection-genres │ │ │ │ ├── collection-genres.component.html │ │ │ │ ├── collection-genres.component.scss │ │ │ │ ├── collection-genres.component.spec.ts │ │ │ │ ├── collection-genres.component.ts │ │ │ │ ├── genre-browser │ │ │ │ │ ├── genre-browser.component.html │ │ │ │ │ ├── genre-browser.component.scss │ │ │ │ │ ├── genre-browser.component.spec.ts │ │ │ │ │ ├── genre-browser.component.ts │ │ │ │ │ └── genre-order.ts │ │ │ │ ├── genre │ │ │ │ │ ├── genre.component.html │ │ │ │ │ ├── genre.component.scss │ │ │ │ │ ├── genre.component.spec.ts │ │ │ │ │ └── genre.component.ts │ │ │ │ ├── genres-albums-persister.spec.ts │ │ │ │ ├── genres-albums-persister.ts │ │ │ │ ├── genres-persister.spec.ts │ │ │ │ ├── genres-persister.ts │ │ │ │ ├── genres-tracks-persister.spec.ts │ │ │ │ └── genres-tracks-persister.ts │ │ │ ├── collection-playback-pane │ │ │ │ ├── collection-playback-pane.component.html │ │ │ │ ├── collection-playback-pane.component.scss │ │ │ │ ├── collection-playback-pane.component.spec.ts │ │ │ │ └── collection-playback-pane.component.ts │ │ │ ├── collection-playlists │ │ │ │ ├── collection-playlists.component.html │ │ │ │ ├── collection-playlists.component.scss │ │ │ │ ├── collection-playlists.component.spec.ts │ │ │ │ ├── collection-playlists.component.ts │ │ │ │ ├── playlist-browser │ │ │ │ │ ├── playlist-browser.component.html │ │ │ │ │ ├── playlist-browser.component.scss │ │ │ │ │ ├── playlist-browser.component.spec.ts │ │ │ │ │ ├── playlist-browser.component.ts │ │ │ │ │ ├── playlist-row.ts │ │ │ │ │ └── playlist │ │ │ │ │ │ ├── playlist.component.html │ │ │ │ │ │ ├── playlist.component.scss │ │ │ │ │ │ ├── playlist.component.spec.ts │ │ │ │ │ │ └── playlist.component.ts │ │ │ │ ├── playlist-folder-browser │ │ │ │ │ ├── playlist-folder-browser.component.html │ │ │ │ │ ├── playlist-folder-browser.component.scss │ │ │ │ │ ├── playlist-folder-browser.component.spec.ts │ │ │ │ │ ├── playlist-folder-browser.component.ts │ │ │ │ │ └── playlist-rows-getter.ts │ │ │ │ ├── playlist-folders-persister.spec.ts │ │ │ │ ├── playlist-folders-persister.ts │ │ │ │ ├── playlist-order.ts │ │ │ │ ├── playlist-persister.spec.ts │ │ │ │ ├── playlist-track-browser │ │ │ │ │ ├── playlist-track-browser.component.html │ │ │ │ │ ├── playlist-track-browser.component.scss │ │ │ │ │ ├── playlist-track-browser.component.spec.ts │ │ │ │ │ └── playlist-track-browser.component.ts │ │ │ │ ├── playlists-persister.ts │ │ │ │ └── playlists-tracks-persister.ts │ │ │ ├── collection-tracks │ │ │ │ ├── collection-tracks-table │ │ │ │ │ ├── collection-tracks-table-header │ │ │ │ │ │ ├── collection-tracks-table-header.component.html │ │ │ │ │ │ ├── collection-tracks-table-header.component.scss │ │ │ │ │ │ ├── collection-tracks-table-header.component.spec.ts │ │ │ │ │ │ └── collection-tracks-table-header.component.ts │ │ │ │ │ ├── collection-tracks-table.component.html │ │ │ │ │ ├── collection-tracks-table.component.scss │ │ │ │ │ ├── collection-tracks-table.component.spec.ts │ │ │ │ │ └── collection-tracks-table.component.ts │ │ │ │ ├── collection-tracks.component.html │ │ │ │ ├── collection-tracks.component.scss │ │ │ │ ├── collection-tracks.component.spec.ts │ │ │ │ └── collection-tracks.component.ts │ │ │ ├── collection.component.html │ │ │ ├── collection.component.scss │ │ │ ├── collection.component.spec.ts │ │ │ ├── collection.component.ts │ │ │ ├── item-space-calculator.spec.ts │ │ │ ├── item-space-calculator.ts │ │ │ ├── semantic-zoom │ │ │ │ ├── semantic-zoom-button │ │ │ │ │ ├── semantic-zoom-button.component.html │ │ │ │ │ ├── semantic-zoom-button.component.scss │ │ │ │ │ ├── semantic-zoom-button.component.spec.ts │ │ │ │ │ └── semantic-zoom-button.component.ts │ │ │ │ ├── semantic-zoom.component.html │ │ │ │ ├── semantic-zoom.component.scss │ │ │ │ ├── semantic-zoom.component.spec.ts │ │ │ │ └── semantic-zoom.component.ts │ │ │ ├── totals │ │ │ │ ├── totals.component.html │ │ │ │ ├── totals.component.scss │ │ │ │ ├── totals.component.spec.ts │ │ │ │ └── totals.component.ts │ │ │ ├── track-browser │ │ │ │ ├── track-brower-base.spec.ts │ │ │ │ ├── track-brower-base.ts │ │ │ │ ├── track-browser.component.html │ │ │ │ ├── track-browser.component.scss │ │ │ │ ├── track-browser.component.spec.ts │ │ │ │ └── track-browser.component.ts │ │ │ ├── track-order.ts │ │ │ └── track │ │ │ │ ├── track.component.html │ │ │ │ ├── track.component.scss │ │ │ │ ├── track.component.spec.ts │ │ │ │ └── track.component.ts │ │ ├── context-menu-opener.ts │ │ ├── controls │ │ │ ├── accent-button │ │ │ │ ├── accent-button.component.html │ │ │ │ ├── accent-button.component.scss │ │ │ │ ├── accent-button.component.spec.ts │ │ │ │ └── accent-button.component.ts │ │ │ ├── big-icon-button │ │ │ │ ├── big-icon-button.component.html │ │ │ │ ├── big-icon-button.component.scss │ │ │ │ ├── big-icon-button.component.spec.ts │ │ │ │ └── big-icon-button.component.ts │ │ │ ├── icon-button │ │ │ │ ├── icon-button.component.html │ │ │ │ ├── icon-button.component.scss │ │ │ │ ├── icon-button.component.spec.ts │ │ │ │ └── icon-button.component.ts │ │ │ ├── icon-text-button │ │ │ │ ├── icon-text-button.component.html │ │ │ │ ├── icon-text-button.component.scss │ │ │ │ ├── icon-text-button.component.spec.ts │ │ │ │ └── icon-text-button.component.ts │ │ │ ├── text-icon-secondary-button │ │ │ │ ├── text-icon-secondary-button.component.html │ │ │ │ ├── text-icon-secondary-button.component.scss │ │ │ │ ├── text-icon-secondary-button.component.spec.ts │ │ │ │ └── text-icon-secondary-button.component.ts │ │ │ ├── toggle-switch │ │ │ │ ├── toggle-switch.component.html │ │ │ │ ├── toggle-switch.component.scss │ │ │ │ ├── toggle-switch.component.spec.ts │ │ │ │ └── toggle-switch.component.ts │ │ │ └── transparent-button │ │ │ │ ├── transparent-button.component.html │ │ │ │ ├── transparent-button.component.scss │ │ │ │ ├── transparent-button.component.spec.ts │ │ │ │ └── transparent-button.component.ts │ │ ├── dialogs │ │ │ ├── confirmation-dialog │ │ │ │ ├── confirmation-dialog.component.html │ │ │ │ ├── confirmation-dialog.component.scss │ │ │ │ ├── confirmation-dialog.component.spec.ts │ │ │ │ └── confirmation-dialog.component.ts │ │ │ ├── dialog-header │ │ │ │ ├── dialog-header.component.html │ │ │ │ ├── dialog-header.component.scss │ │ │ │ ├── dialog-header.component.spec.ts │ │ │ │ └── dialog-header.component.ts │ │ │ ├── edit-columns-dialog │ │ │ │ ├── edit-columns-dialog.component.html │ │ │ │ ├── edit-columns-dialog.component.scss │ │ │ │ ├── edit-columns-dialog.component.spec.ts │ │ │ │ └── edit-columns-dialog.component.ts │ │ │ ├── edit-playlist-dialog │ │ │ │ ├── edit-playlist-dialog.component.html │ │ │ │ ├── edit-playlist-dialog.component.scss │ │ │ │ ├── edit-playlist-dialog.component.spec.ts │ │ │ │ └── edit-playlist-dialog.component.ts │ │ │ ├── edit-tracks-dialog │ │ │ │ ├── edit-tracks-dialog.component.html │ │ │ │ ├── edit-tracks-dialog.component.scss │ │ │ │ ├── edit-tracks-dialog.component.spec.ts │ │ │ │ └── edit-tracks-dialog.component.ts │ │ │ ├── error-dialog │ │ │ │ ├── error-dialog.component.html │ │ │ │ ├── error-dialog.component.scss │ │ │ │ ├── error-dialog.component.spec.ts │ │ │ │ └── error-dialog.component.ts │ │ │ ├── info-dialog │ │ │ │ ├── info-dialog.component.html │ │ │ │ ├── info-dialog.component.scss │ │ │ │ ├── info-dialog.component.spec.ts │ │ │ │ └── info-dialog.component.ts │ │ │ ├── input-dialog │ │ │ │ ├── input-dialog.component.html │ │ │ │ ├── input-dialog.component.scss │ │ │ │ ├── input-dialog.component.spec.ts │ │ │ │ └── input-dialog.component.ts │ │ │ └── license-dialog │ │ │ │ ├── license-dialog.component.html │ │ │ │ ├── license-dialog.component.scss │ │ │ │ ├── license-dialog.component.spec.ts │ │ │ │ └── license-dialog.component.ts │ │ ├── information │ │ │ ├── about │ │ │ │ ├── about.component.html │ │ │ │ ├── about.component.scss │ │ │ │ ├── about.component.spec.ts │ │ │ │ └── about.component.ts │ │ │ ├── components │ │ │ │ ├── components.component.html │ │ │ │ ├── components.component.scss │ │ │ │ ├── components.component.spec.ts │ │ │ │ └── components.component.ts │ │ │ ├── information.component.html │ │ │ ├── information.component.scss │ │ │ ├── information.component.spec.ts │ │ │ └── information.component.ts │ │ ├── loading │ │ │ ├── loading.component.html │ │ │ ├── loading.component.scss │ │ │ ├── loading.component.spec.ts │ │ │ └── loading.component.ts │ │ ├── logo-full │ │ │ ├── logo-full.component.html │ │ │ ├── logo-full.component.scss │ │ │ ├── logo-full.component.spec.ts │ │ │ └── logo-full.component.ts │ │ ├── logo-small │ │ │ ├── logo-small.component.html │ │ │ ├── logo-small.component.scss │ │ │ ├── logo-small.component.spec.ts │ │ │ └── logo-small.component.ts │ │ ├── love │ │ │ ├── love.component.html │ │ │ ├── love.component.scss │ │ │ ├── love.component.spec.ts │ │ │ └── love.component.ts │ │ ├── main-menu │ │ │ ├── main-menu.component.html │ │ │ ├── main-menu.component.scss │ │ │ ├── main-menu.component.spec.ts │ │ │ └── main-menu.component.ts │ │ ├── manage-collection │ │ │ ├── manage-albums │ │ │ │ ├── manage-albums.component.html │ │ │ │ ├── manage-albums.component.scss │ │ │ │ ├── manage-albums.component.spec.ts │ │ │ │ └── manage-albums.component.ts │ │ │ ├── manage-collection.component.html │ │ │ ├── manage-collection.component.scss │ │ │ ├── manage-collection.component.spec.ts │ │ │ ├── manage-collection.component.ts │ │ │ ├── manage-music │ │ │ │ ├── manage-music.component.html │ │ │ │ ├── manage-music.component.scss │ │ │ │ ├── manage-music.component.spec.ts │ │ │ │ └── manage-music.component.ts │ │ │ └── manage-refresh │ │ │ │ ├── manage-refresh.component.html │ │ │ │ ├── manage-refresh.component.scss │ │ │ │ ├── manage-refresh.component.spec.ts │ │ │ │ └── manage-refresh.component.ts │ │ ├── metro-page-container │ │ │ ├── metro-page-container.component.html │ │ │ ├── metro-page-container.component.scss │ │ │ ├── metro-page-container.component.spec.ts │ │ │ ├── metro-page-container.component.ts │ │ │ └── metro-page │ │ │ │ ├── metro-page.component.html │ │ │ │ ├── metro-page.component.scss │ │ │ │ ├── metro-page.component.spec.ts │ │ │ │ └── metro-page.component.ts │ │ ├── mini-players │ │ │ └── cover-player │ │ │ │ ├── cover-player-playback-queue │ │ │ │ ├── cover-player-playback-queue.component.html │ │ │ │ ├── cover-player-playback-queue.component.scss │ │ │ │ ├── cover-player-playback-queue.component.spec.ts │ │ │ │ └── cover-player-playback-queue.component.ts │ │ │ │ ├── cover-player-volume-control │ │ │ │ ├── cover-player-volume-control.component.html │ │ │ │ ├── cover-player-volume-control.component.scss │ │ │ │ ├── cover-player-volume-control.component.spec.ts │ │ │ │ └── cover-player-volume-control.component.ts │ │ │ │ ├── cover-player.component.html │ │ │ │ ├── cover-player.component.scss │ │ │ │ ├── cover-player.component.spec.ts │ │ │ │ └── cover-player.component.ts │ │ ├── mouse-selection-watcher.spec.ts │ │ ├── mouse-selection-watcher.ts │ │ ├── notification-bar │ │ │ ├── notification-bar.component.html │ │ │ ├── notification-bar.component.scss │ │ │ ├── notification-bar.component.spec.ts │ │ │ └── notification-bar.component.ts │ │ ├── now-playing │ │ │ ├── now-playing-artist-info │ │ │ │ ├── now-playing-artist-info.component.html │ │ │ │ ├── now-playing-artist-info.component.scss │ │ │ │ ├── now-playing-artist-info.component.spec.ts │ │ │ │ ├── now-playing-artist-info.component.ts │ │ │ │ └── similar-artist │ │ │ │ │ ├── similar-artist.component.html │ │ │ │ │ ├── similar-artist.component.scss │ │ │ │ │ ├── similar-artist.component.spec.ts │ │ │ │ │ └── similar-artist.component.ts │ │ │ ├── now-playing-lyrics │ │ │ │ ├── now-playing-lyrics.component.html │ │ │ │ ├── now-playing-lyrics.component.scss │ │ │ │ ├── now-playing-lyrics.component.spec.ts │ │ │ │ └── now-playing-lyrics.component.ts │ │ │ ├── now-playing-nothing-playing │ │ │ │ ├── now-playing-nothing-playing.component.html │ │ │ │ ├── now-playing-nothing-playing.component.scss │ │ │ │ ├── now-playing-nothing-playing.component.spec.ts │ │ │ │ └── now-playing-nothing-playing.component.ts │ │ │ ├── now-playing-playback-pane │ │ │ │ ├── now-playing-playback-pane.component.html │ │ │ │ ├── now-playing-playback-pane.component.scss │ │ │ │ ├── now-playing-playback-pane.component.spec.ts │ │ │ │ └── now-playing-playback-pane.component.ts │ │ │ ├── now-playing-showcase │ │ │ │ ├── now-playing-showcase.component.html │ │ │ │ ├── now-playing-showcase.component.scss │ │ │ │ ├── now-playing-showcase.component.spec.ts │ │ │ │ └── now-playing-showcase.component.ts │ │ │ ├── now-playing.component.html │ │ │ ├── now-playing.component.scss │ │ │ ├── now-playing.component.spec.ts │ │ │ └── now-playing.component.ts │ │ ├── playback-controls │ │ │ ├── playback-controls.component.html │ │ │ ├── playback-controls.component.scss │ │ │ ├── playback-controls.component.spec.ts │ │ │ └── playback-controls.component.ts │ │ ├── playback-cover-art │ │ │ ├── playback-cover-art.component.html │ │ │ ├── playback-cover-art.component.scss │ │ │ ├── playback-cover-art.component.spec.ts │ │ │ └── playback-cover-art.component.ts │ │ ├── playback-indicator │ │ │ ├── playback-indicator.component.html │ │ │ ├── playback-indicator.component.scss │ │ │ ├── playback-indicator.component.spec.ts │ │ │ └── playback-indicator.component.ts │ │ ├── playback-information │ │ │ ├── playback-information.component.html │ │ │ ├── playback-information.component.scss │ │ │ ├── playback-information.component.spec.ts │ │ │ └── playback-information.component.ts │ │ ├── playback-progress │ │ │ ├── playback-progress.component.html │ │ │ ├── playback-progress.component.scss │ │ │ ├── playback-progress.component.spec.ts │ │ │ └── playback-progress.component.ts │ │ ├── playback-queue │ │ │ ├── playback-queue.component.html │ │ │ ├── playback-queue.component.scss │ │ │ ├── playback-queue.component.spec.ts │ │ │ └── playback-queue.component.ts │ │ ├── playback-time │ │ │ ├── playback-time.component.html │ │ │ ├── playback-time.component.scss │ │ │ ├── playback-time.component.spec.ts │ │ │ └── playback-time.component.ts │ │ ├── rating │ │ │ ├── rating.component.html │ │ │ ├── rating.component.scss │ │ │ ├── rating.component.spec.ts │ │ │ └── rating.component.ts │ │ ├── search-box │ │ │ ├── search-box.component.html │ │ │ ├── search-box.component.scss │ │ │ ├── search-box.component.spec.ts │ │ │ └── search-box.component.ts │ │ ├── settings │ │ │ ├── advanced-settings │ │ │ │ ├── advanced-settings.component.html │ │ │ │ ├── advanced-settings.component.scss │ │ │ │ ├── advanced-settings.component.spec.ts │ │ │ │ └── advanced-settings.component.ts │ │ │ ├── appearance-settings │ │ │ │ ├── appearance-settings.component.html │ │ │ │ ├── appearance-settings.component.scss │ │ │ │ ├── appearance-settings.component.spec.ts │ │ │ │ └── appearance-settings.component.ts │ │ │ ├── behavior-settings │ │ │ │ ├── behavior-settings.component.html │ │ │ │ ├── behavior-settings.component.scss │ │ │ │ ├── behavior-settings.component.spec.ts │ │ │ │ └── behavior-settings.component.ts │ │ │ ├── online-settings │ │ │ │ ├── online-settings.component.html │ │ │ │ ├── online-settings.component.scss │ │ │ │ ├── online-settings.component.spec.ts │ │ │ │ └── online-settings.component.ts │ │ │ ├── settings.component.html │ │ │ ├── settings.component.scss │ │ │ ├── settings.component.spec.ts │ │ │ └── settings.component.ts │ │ ├── slider │ │ │ ├── slider.component.html │ │ │ ├── slider.component.scss │ │ │ ├── slider.component.spec.ts │ │ │ └── slider.component.ts │ │ ├── step-indicator │ │ │ ├── step-indicator.component.html │ │ │ ├── step-indicator.component.scss │ │ │ ├── step-indicator.component.spec.ts │ │ │ └── step-indicator.component.ts │ │ ├── sub-menu │ │ │ ├── sub-menu-item │ │ │ │ ├── sub-menu-item.component.html │ │ │ │ ├── sub-menu-item.component.scss │ │ │ │ ├── sub-menu-item.component.spec.ts │ │ │ │ └── sub-menu-item.component.ts │ │ │ ├── sub-menu.component.html │ │ │ ├── sub-menu.component.scss │ │ │ ├── sub-menu.component.spec.ts │ │ │ └── sub-menu.component.ts │ │ ├── switch-player-button │ │ │ ├── switch-player-button.component.html │ │ │ ├── switch-player-button.component.scss │ │ │ ├── switch-player-button.component.spec.ts │ │ │ └── switch-player-button.component.ts │ │ ├── theme-switcher │ │ │ ├── theme-switcher.component.html │ │ │ ├── theme-switcher.component.scss │ │ │ ├── theme-switcher.component.spec.ts │ │ │ └── theme-switcher.component.ts │ │ ├── volume-control │ │ │ ├── volume-control.component.html │ │ │ ├── volume-control.component.scss │ │ │ ├── volume-control.component.spec.ts │ │ │ └── volume-control.component.ts │ │ ├── volume-icon │ │ │ ├── volume-icon.component.html │ │ │ ├── volume-icon.component.scss │ │ │ ├── volume-icon.component.spec.ts │ │ │ └── volume-icon.component.ts │ │ ├── welcome │ │ │ ├── welcome-appearance │ │ │ │ ├── welcome-appearance.component.html │ │ │ │ ├── welcome-appearance.component.scss │ │ │ │ ├── welcome-appearance.component.spec.ts │ │ │ │ └── welcome-appearance.component.ts │ │ │ ├── welcome-donate │ │ │ │ ├── welcome-donate.component.html │ │ │ │ ├── welcome-donate.component.scss │ │ │ │ ├── welcome-donate.component.spec.ts │ │ │ │ └── welcome-donate.component.ts │ │ │ ├── welcome-done │ │ │ │ ├── welcome-done.component.html │ │ │ │ ├── welcome-done.component.scss │ │ │ │ ├── welcome-done.component.spec.ts │ │ │ │ └── welcome-done.component.ts │ │ │ ├── welcome-greeting │ │ │ │ ├── welcome-greeting.component.html │ │ │ │ ├── welcome-greeting.component.scss │ │ │ │ ├── welcome-greeting.component.spec.ts │ │ │ │ └── welcome-greeting.component.ts │ │ │ ├── welcome-language │ │ │ │ ├── welcome-language.component.html │ │ │ │ ├── welcome-language.component.scss │ │ │ │ ├── welcome-language.component.spec.ts │ │ │ │ └── welcome-language.component.ts │ │ │ ├── welcome-music │ │ │ │ ├── welcome-music.component.html │ │ │ │ ├── welcome-music.component.scss │ │ │ │ ├── welcome-music.component.spec.ts │ │ │ │ └── welcome-music.component.ts │ │ │ ├── welcome-navigation-buttons │ │ │ │ ├── welcome-navigation-buttons.html │ │ │ │ ├── welcome-navigation-buttons.scss │ │ │ │ ├── welcome-navigation-buttons.spec.ts │ │ │ │ └── welcome-navigation-buttons.ts │ │ │ ├── welcome-online │ │ │ │ ├── welcome-online.component.html │ │ │ │ ├── welcome-online.component.scss │ │ │ │ ├── welcome-online.component.spec.ts │ │ │ │ └── welcome-online.component.ts │ │ │ ├── welcome.component.html │ │ │ ├── welcome.component.scss │ │ │ ├── welcome.component.spec.ts │ │ │ └── welcome.component.ts │ │ └── window-controls │ │ │ ├── window-controls.component.html │ │ │ ├── window-controls.component.scss │ │ │ ├── window-controls.component.spec.ts │ │ │ └── window-controls.component.ts │ │ ├── directives │ │ ├── cdk-virtual-scroll-viewport-patch-directive.ts │ │ └── webview.directive.ts │ │ ├── interfaces │ │ └── i-selectable.ts │ │ └── pipes │ │ ├── albums-filter.pipe.spec.ts │ │ ├── albums-filter.pipe.ts │ │ ├── artists-filter.pipe.spec.ts │ │ ├── artists-filter.pipe.ts │ │ ├── folder-name.pipe.spec.ts │ │ ├── folder-name.pipe.ts │ │ ├── format-playback-time.spec.ts │ │ ├── format-playback-time.ts │ │ ├── format-ticks-to-date-time-string.pipe.spec.ts │ │ ├── format-ticks-to-date-time-string.pipe.ts │ │ ├── format-total-duration.pipe.spec.ts │ │ ├── format-total-duration.pipe.ts │ │ ├── format-total-file-size.pipe.spec.ts │ │ ├── format-total-file-size.pipe.ts │ │ ├── format-track-duration.pipe.spec.ts │ │ ├── format-track-duration.pipe.ts │ │ ├── format-track-number.pipe.spec.ts │ │ ├── format-track-number.pipe.ts │ │ ├── genres-filter.pipe.spec.ts │ │ ├── genres-filter.pipe.ts │ │ ├── image-to-file-path.pipe.spec.ts │ │ ├── image-to-file-path.pipe.ts │ │ ├── playlists-filter.spec.ts │ │ ├── playlists-filter.ts │ │ ├── subfolder-name.pipe.spec.ts │ │ ├── subfolder-name.pipe.ts │ │ ├── subfolders-filter.pipe.spec.ts │ │ ├── subfolders-filter.pipe.ts │ │ ├── tracks-filter.pipe.spec.ts │ │ ├── tracks-filter.pipe.ts │ │ ├── zero-to-blank.pipe.spec.ts │ │ └── zero-to-blank.pipe.ts ├── assets │ ├── .gitkeep │ ├── fonts │ │ ├── Monoglyceride.ttf │ │ ├── OpenSans-Bold.ttf │ │ ├── OpenSans-Light.ttf │ │ └── OpenSans-Regular.ttf │ ├── i18n │ │ ├── bg.json │ │ ├── cs.json │ │ ├── de.json │ │ ├── el.json │ │ ├── en.json │ │ ├── es.json │ │ ├── fa.json │ │ ├── fr.json │ │ ├── hr.json │ │ ├── it.json │ │ ├── ja-JP.json │ │ ├── ko.json │ │ ├── ku.json │ │ ├── nl.json │ │ ├── pl.json │ │ ├── pt-BR.json │ │ ├── ru.json │ │ ├── sv.json │ │ ├── tr.json │ │ ├── vi.json │ │ ├── zh-CN.json │ │ └── zh-TW.json │ └── images │ │ └── logo_nobg_256x256.svg ├── css │ ├── angular-material-mods.scss │ ├── angular-material-theming.scss │ ├── animations.scss │ ├── as-split.scss │ ├── cdk-drag-drop.scss │ ├── cdk-virtual-scroll.scss │ ├── custom-classes.scss │ ├── elements.scss │ ├── flex.scss │ ├── font-faces.scss │ ├── scrollbar.scss │ ├── sizing.scss │ ├── spacing.scss │ ├── sub-menu.scss │ ├── theming.scss │ ├── transparent-button.scss │ ├── variables.scss │ ├── window-buttons.scss │ └── window-frame.scss ├── environments │ ├── environment.dev.ts │ ├── environment.prod.ts │ └── environment.ts ├── index.html ├── main.ts ├── polyfills-test.ts ├── polyfills.ts ├── styles.scss ├── test.ts ├── tsconfig.app.json └── typings.d.ts ├── static └── icons │ ├── 128x128.png │ ├── 16x16.png │ ├── 24x24.png │ ├── 256x256.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 512x512.png │ ├── 64x64.png │ ├── 96x96.png │ ├── icon.icns │ ├── icon.ico │ ├── icon.png │ ├── trayTemplate.png │ ├── trayTemplate@2x.png │ ├── tray_black.ico │ ├── tray_black.png │ ├── tray_white.ico │ └── tray_white.png ├── tsconfig-serve.json ├── tsconfig.json └── tsconfig.spec.json /.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.angular/cache 2 | .idea/ 3 | .vscode/ 4 | .VSCodeCounter/ 5 | node_modules/ 6 | dist/ 7 | release/ 8 | coverage/ 9 | .DS_Store -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "singleQuote": true, 4 | "semi": true, 5 | "printWidth": 140 6 | } 7 | -------------------------------------------------------------------------------- /.run/Debug renderer.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Dopamine.full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/Dopamine.full.png -------------------------------------------------------------------------------- /Dopamine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/Dopamine.png -------------------------------------------------------------------------------- /Dopamine.screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/Dopamine.screenshot.png -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-architect -------------------------------------------------------------------------------- /build/Sidebar.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/Sidebar.bmp -------------------------------------------------------------------------------- /build/flac.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/flac.icns -------------------------------------------------------------------------------- /build/flac.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/flac.ico -------------------------------------------------------------------------------- /build/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/icon.icns -------------------------------------------------------------------------------- /build/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/icon.ico -------------------------------------------------------------------------------- /build/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/icon.png -------------------------------------------------------------------------------- /build/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/icons/128x128.png -------------------------------------------------------------------------------- /build/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/icons/16x16.png -------------------------------------------------------------------------------- /build/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/icons/24x24.png -------------------------------------------------------------------------------- /build/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/icons/256x256.png -------------------------------------------------------------------------------- /build/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/icons/32x32.png -------------------------------------------------------------------------------- /build/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/icons/48x48.png -------------------------------------------------------------------------------- /build/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/icons/512x512.png -------------------------------------------------------------------------------- /build/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/icons/64x64.png -------------------------------------------------------------------------------- /build/icons/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/icons/96x96.png -------------------------------------------------------------------------------- /build/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/icons/icon.icns -------------------------------------------------------------------------------- /build/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/icons/icon.ico -------------------------------------------------------------------------------- /build/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/icons/icon.png -------------------------------------------------------------------------------- /build/m4a.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/m4a.icns -------------------------------------------------------------------------------- /build/m4a.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/m4a.ico -------------------------------------------------------------------------------- /build/mp3.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/mp3.icns -------------------------------------------------------------------------------- /build/mp3.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/mp3.ico -------------------------------------------------------------------------------- /build/ogg.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/ogg.icns -------------------------------------------------------------------------------- /build/ogg.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/ogg.ico -------------------------------------------------------------------------------- /build/opus.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/opus.icns -------------------------------------------------------------------------------- /build/opus.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/opus.ico -------------------------------------------------------------------------------- /build/uninstaller.nsh: -------------------------------------------------------------------------------- 1 | !macro customUnInstall 2 | SetShellVarContext current 3 | MessageBox MB_YESNO "Do you also want to delete all configuration data? This will delete your collection, settings and your song ratings. It is recommended to keep them if you plan to install Dopamine again later." \ 4 | /SD IDNO IDNO Skipped IDYES Accepted 5 | 6 | Accepted: 7 | RMDir /r "$APPDATA\${APP_FILENAME}" 8 | !ifdef APP_PRODUCT_FILENAME 9 | RMDir /r "$APPDATA\${APP_PRODUCT_FILENAME}" 10 | !endif 11 | Goto done 12 | Skipped: 13 | Goto done 14 | done: 15 | !macroend -------------------------------------------------------------------------------- /build/wav.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/wav.icns -------------------------------------------------------------------------------- /build/wav.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/build/wav.ico -------------------------------------------------------------------------------- /deployment/AUR/dopamine.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Dopamine 3 | Exec=dopamine 4 | Terminal=false 5 | Type=Application 6 | Icon=dopamine 7 | StartupWMClass=Dopamine 8 | Comment=The audio player that keeps it simple 9 | Categories=Utility; 10 | -------------------------------------------------------------------------------- /get-package-information.js: -------------------------------------------------------------------------------- 1 | function getName(){ 2 | return require('./package.json').name; 3 | } 4 | 5 | function getFullVersion() { 6 | const version = require('./package.json').version; 7 | const versionSuffix = require('./package.json').versionSuffix; 8 | 9 | if(versionSuffix === ""){ 10 | return version; 11 | } 12 | 13 | return version + "-" + versionSuffix; 14 | } 15 | 16 | function getCopyright(){ 17 | return require('./package.json').copyright; 18 | } 19 | 20 | module.exports.getName = getName; 21 | module.exports.getFullVersion = getFullVersion; 22 | module.exports.getCopyright = getCopyright; -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const { pathsToModuleNameMapper } = require('ts-jest'); 2 | const { compilerOptions } = require('./tsconfig'); 3 | 4 | module.exports = { 5 | preset: 'jest-preset-angular', 6 | roots: ['/'], 7 | testMatch: ['/src/**/*(*.)+(spec).+(ts)', '/main/**/*(*.)+(spec).+(js)'], 8 | setupFilesAfterEnv: ['/src/test.ts'], 9 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths || {}, { 10 | prefix: '/', 11 | }), 12 | transform: { 13 | '^.+\\.{ts|tsx}?$': [ 14 | 'ts-jest', 15 | { 16 | babel: true, 17 | tsConfig: 'tsconfig.spec.json', 18 | }, 19 | ], 20 | }, 21 | setupFiles: ['/jest.setup.js'], 22 | }; 23 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | jest.mock('jimp', () => ({ exec: jest.fn() })); 2 | jest.mock('@electron/remote', () => ({ exec: jest.fn() })); 3 | 4 | global.crypto = { 5 | getRandomValues: (arr) => require('crypto').randomBytes(arr.length), 6 | }; 7 | -------------------------------------------------------------------------------- /main/api/discord/discord-api-command-type.ts: -------------------------------------------------------------------------------- 1 | export enum DiscordApiCommandType { 2 | SetPresence, 3 | ClearPresence, 4 | } 5 | -------------------------------------------------------------------------------- /main/api/discord/discord-api-command.ts: -------------------------------------------------------------------------------- 1 | import { PresenceArgs } from './presence-args'; 2 | import { DiscordApiCommandType } from './discord-api-command-type'; 3 | 4 | export class DiscordApiCommand { 5 | public constructor( 6 | public commandType: DiscordApiCommandType, 7 | public args: PresenceArgs | undefined, 8 | ) {} 9 | } 10 | -------------------------------------------------------------------------------- /main/api/discord/presence-args.ts: -------------------------------------------------------------------------------- 1 | export interface PresenceArgs { 2 | title: string; 3 | artists: string; 4 | smallImageKey?: string; 5 | smallImageText?: string; 6 | largeImageKey?: string; 7 | largeImageText?: string; 8 | shouldSendTimestamps?: boolean; 9 | startTime?: number; 10 | } 11 | -------------------------------------------------------------------------------- /main/background-work/common/application/application-paths.js: -------------------------------------------------------------------------------- 1 | class ApplicationPaths { 2 | constructor(fileAccess, workerProxy) { 3 | this.fileAccess = fileAccess; 4 | this.workerProxy = workerProxy; 5 | } 6 | getCoverArtCacheFullPath() { 7 | return this.fileAccess.combinePath([this.workerProxy.applicationDataDirectory(), 'Cache', 'CoverArt']); 8 | } 9 | } 10 | 11 | exports.ApplicationPaths = ApplicationPaths; 12 | -------------------------------------------------------------------------------- /main/background-work/common/application/constants.js: -------------------------------------------------------------------------------- 1 | class Constants { 2 | static cachedCoverArtMaximumSize = 360; 3 | static cachedCoverArtJpegQuality = 80; 4 | 5 | static externalCoverArtPatterns = [ 6 | 'front.png', 7 | 'front.jpg', 8 | 'front.jpeg', 9 | 'cover.png', 10 | 'cover.jpg', 11 | 'cover.jpeg', 12 | 'folder.png', 13 | 'folder.jpg', 14 | 'folder.jpeg', 15 | '%filename%.png', 16 | '%filename%.jpg', 17 | '%filename%.jpeg', 18 | ]; 19 | } 20 | 21 | exports.Constants = Constants; 22 | -------------------------------------------------------------------------------- /main/background-work/common/application/sensitive-information.js: -------------------------------------------------------------------------------- 1 | class SensitiveInformation { 2 | static lastfmApiKey = '1be93666a6e8d3d79b05b1c74682cb62'; 3 | } 4 | 5 | exports.SensitiveInformation = SensitiveInformation; 6 | -------------------------------------------------------------------------------- /main/background-work/common/guid.factory.js: -------------------------------------------------------------------------------- 1 | const uuid = require('uuid'); 2 | 3 | class GuidFactory { 4 | create() { 5 | return uuid.v4(); 6 | } 7 | } 8 | exports.GuidFactory = GuidFactory; 9 | -------------------------------------------------------------------------------- /main/background-work/common/scheduling/timer.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment'); 2 | 3 | class Timer { 4 | #startedMilliseconds; 5 | #stoppedMilliseconds; 6 | 7 | getElapsedMilliseconds() { 8 | return this.#stoppedMilliseconds - this.#startedMilliseconds; 9 | } 10 | 11 | start() { 12 | this.#startedMilliseconds = moment().valueOf(); 13 | } 14 | 15 | stop() { 16 | this.#stoppedMilliseconds = moment().valueOf(); 17 | } 18 | } 19 | 20 | exports.Timer = Timer; 21 | -------------------------------------------------------------------------------- /main/background-work/common/utils/math-utils.js: -------------------------------------------------------------------------------- 1 | class MathUtils { 2 | static calculatePercentage(actualValue, maximumValue) { 3 | return Math.round((actualValue / maximumValue) * 100); 4 | } 5 | } 6 | 7 | exports.MathUtils = MathUtils; 8 | -------------------------------------------------------------------------------- /main/background-work/common/utils/string-utils.js: -------------------------------------------------------------------------------- 1 | class StringUtils { 2 | static isNullOrWhiteSpace(stringToCheck) { 3 | if (stringToCheck === undefined || stringToCheck === null) { 4 | return true; 5 | } 6 | 7 | try { 8 | if (stringToCheck.trim() === '') { 9 | return true; 10 | } 11 | } catch (e) { 12 | return true; 13 | } 14 | 15 | return false; 16 | } 17 | 18 | static replaceAll(sourceString, oldValue, newValue) { 19 | return sourceString.split(oldValue).join(newValue); 20 | } 21 | } 22 | 23 | exports.StringUtils = StringUtils; 24 | -------------------------------------------------------------------------------- /main/background-work/data/database.factory.js: -------------------------------------------------------------------------------- 1 | const Database = require('better-sqlite3'); 2 | 3 | class DatabaseFactory { 4 | constructor(fileAccess, workerProxy) { 5 | this.fileAccess = fileAccess; 6 | this.workerProxy = workerProxy; 7 | } 8 | 9 | create() { 10 | const databaseFile = this.fileAccess.combinePath([this.workerProxy.applicationDataDirectory(), 'Dopamine.db']); 11 | 12 | return new Database(databaseFile); 13 | } 14 | } 15 | 16 | exports.DatabaseFactory = DatabaseFactory; 17 | -------------------------------------------------------------------------------- /main/background-work/data/entities/album-artwork.js: -------------------------------------------------------------------------------- 1 | class AlbumArtwork { 2 | constructor(albumKey, artworkId) { 3 | this.albumArtworkId = ''; 4 | this.albumKey = albumKey; 5 | this.artworkId = artworkId; 6 | } 7 | } 8 | 9 | exports.AlbumArtwork = AlbumArtwork; 10 | -------------------------------------------------------------------------------- /main/background-work/data/entities/folder-track.js: -------------------------------------------------------------------------------- 1 | class FolderTrack { 2 | constructor(folderId, trackId) { 3 | this.folderTrackId = 0; 4 | this.folderId = folderId; 5 | this.trackId = trackId; 6 | } 7 | } 8 | 9 | exports.FolderTrack = FolderTrack; 10 | -------------------------------------------------------------------------------- /main/background-work/data/entities/folder.js: -------------------------------------------------------------------------------- 1 | class Folder { 2 | constructor(path) { 3 | this.path = path; 4 | } 5 | 6 | folderId = 0; 7 | showInCollection = false; 8 | } 9 | 10 | exports.Folder = Folder; 11 | -------------------------------------------------------------------------------- /main/background-work/data/entities/removed-track.js: -------------------------------------------------------------------------------- 1 | class RemovedTrack { 2 | constructor(path) { 3 | this.path = path; 4 | } 5 | 6 | trackId = 0; 7 | dateRemoved = 0; 8 | } 9 | 10 | exports.RemovedTrack = RemovedTrack; 11 | -------------------------------------------------------------------------------- /main/background-work/data/folder-repository.js: -------------------------------------------------------------------------------- 1 | class FolderRepository { 2 | constructor(databaseFactory) { 3 | this.databaseFactory = databaseFactory; 4 | } 5 | 6 | getFolders() { 7 | const database = this.databaseFactory.create(); 8 | const statement = database.prepare(`SELECT FolderID as folderId, Path as path, ShowInCollection as showInCollection FROM Folder;`); 9 | 10 | return statement.all(); 11 | } 12 | } 13 | 14 | exports.FolderRepository = FolderRepository; 15 | -------------------------------------------------------------------------------- /main/background-work/indexing/directory-walk-result.js: -------------------------------------------------------------------------------- 1 | class DirectoryWalkResult { 2 | constructor(filePaths, errors) { 3 | this.filePaths = filePaths; 4 | this.errors = errors; 5 | } 6 | } 7 | 8 | exports.DirectoryWalkResult = DirectoryWalkResult; 9 | -------------------------------------------------------------------------------- /main/background-work/indexing/file-metadata.factory.js: -------------------------------------------------------------------------------- 1 | const { TagLibFileMetadata } = require('./tag-lib-file-metadata'); 2 | 3 | class FileMetadataFactory { 4 | create(path) { 5 | return new TagLibFileMetadata(path); 6 | } 7 | } 8 | 9 | exports.FileMetadataFactory = FileMetadataFactory; 10 | -------------------------------------------------------------------------------- /main/background-work/indexing/indexable-path.js: -------------------------------------------------------------------------------- 1 | class IndexablePath { 2 | constructor(path, dateModifiedTicks, folderId) { 3 | this.path = path; 4 | this.dateModifiedTicks = dateModifiedTicks; 5 | this.folderId = folderId; 6 | } 7 | } 8 | 9 | exports.IndexablePath = IndexablePath; 10 | -------------------------------------------------------------------------------- /main/background-work/indexing/messages/adding-tracks-message.js: -------------------------------------------------------------------------------- 1 | class AddingTracksMessage { 2 | constructor(numberOfAddedTracks, percentageOfAddedTracks) { 3 | this.type = 'addingTracks'; 4 | this.numberOfAddedTracks = numberOfAddedTracks; 5 | this.percentageOfAddedTracks = percentageOfAddedTracks; 6 | } 7 | } 8 | 9 | exports.AddingTracksMessage = AddingTracksMessage; -------------------------------------------------------------------------------- /main/background-work/indexing/messages/dismiss-message.js: -------------------------------------------------------------------------------- 1 | class DismissMessage { 2 | constructor() { 3 | this.type = 'dismiss'; 4 | } 5 | } 6 | 7 | exports.DismissMessage = DismissMessage; 8 | -------------------------------------------------------------------------------- /main/background-work/indexing/messages/refreshing-message.js: -------------------------------------------------------------------------------- 1 | class RefreshingMessage { 2 | constructor() { 3 | this.type = 'refreshing'; 4 | } 5 | } 6 | 7 | exports.RefreshingMessage = RefreshingMessage; 8 | -------------------------------------------------------------------------------- /main/background-work/indexing/messages/removing-tracks-message.js: -------------------------------------------------------------------------------- 1 | class RemovingTracksMessage { 2 | constructor() { 3 | this.type = 'removingTracks'; 4 | } 5 | } 6 | 7 | exports.RemovingTracksMessage = RemovingTracksMessage; 8 | -------------------------------------------------------------------------------- /main/background-work/indexing/messages/updating-album-artwork-message.js: -------------------------------------------------------------------------------- 1 | class UpdatingAlbumArtworkMessage { 2 | constructor() { 3 | this.type = 'updatingAlbumArtwork'; 4 | } 5 | } 6 | 7 | exports.UpdatingAlbumArtworkMessage = UpdatingAlbumArtworkMessage; 8 | -------------------------------------------------------------------------------- /main/background-work/indexing/messages/updating-tracks-message.js: -------------------------------------------------------------------------------- 1 | class UpdatingTracksMessage { 2 | constructor() { 3 | this.type = 'updatingTracks'; 4 | } 5 | } 6 | 7 | exports.UpdatingTracksMessage = UpdatingTracksMessage; 8 | -------------------------------------------------------------------------------- /main/background-work/indexing/track-field-creator.js: -------------------------------------------------------------------------------- 1 | const { DataDelimiter } = require('./data-delimiter'); 2 | 3 | class TrackFieldCreator { 4 | createNumberField(value) { 5 | if (value === undefined || Number.isNaN(value)) { 6 | return 0; 7 | } 8 | 9 | return value; 10 | } 11 | 12 | createTextField(value) { 13 | if (value === undefined) { 14 | return ''; 15 | } 16 | 17 | return value.trim(); 18 | } 19 | 20 | createMultiTextField(valueArray) { 21 | if (valueArray === undefined) { 22 | return ''; 23 | } 24 | 25 | return DataDelimiter.toDelimitedString(valueArray); 26 | } 27 | } 28 | 29 | exports.TrackFieldCreator = TrackFieldCreator; 30 | -------------------------------------------------------------------------------- /main/background-work/mocks/album-artwork-adder-mock.js: -------------------------------------------------------------------------------- 1 | class AlbumArtworkAdderMock { 2 | addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsyncCalls = []; 3 | async addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync() { 4 | this.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsyncCalls.push({}); 5 | } 6 | } 7 | 8 | exports.AlbumArtworkAdderMock = AlbumArtworkAdderMock; 9 | -------------------------------------------------------------------------------- /main/background-work/mocks/album-artwork-cache-mock.js: -------------------------------------------------------------------------------- 1 | class AlbumArtworkCacheMock { 2 | addArtworkDataToCacheAsyncCalls = []; 3 | addArtworkDataToCacheAsyncReturnValues = {}; 4 | 5 | async addArtworkDataToCacheAsync(imageBuffer) { 6 | this.addArtworkDataToCacheAsyncCalls.push(imageBuffer); 7 | 8 | return this.addArtworkDataToCacheAsyncReturnValues[imageBuffer.join(',')]; 9 | } 10 | 11 | coverArtFullPath(artworkId) { 12 | return ''; 13 | } 14 | } 15 | 16 | exports.AlbumArtworkCacheMock = AlbumArtworkCacheMock; 17 | -------------------------------------------------------------------------------- /main/background-work/mocks/album-artwork-getter-mock.js: -------------------------------------------------------------------------------- 1 | class AlbumArtworkGetterMock { 2 | getAlbumArtworkAsyncCalls = []; 3 | getAlbumArtworkAsyncReturnValues = {}; 4 | 5 | async getAlbumArtworkAsync(fileMetadata, getOnlineArtwork) { 6 | this.getAlbumArtworkAsyncCalls.push({}); 7 | 8 | return this.getAlbumArtworkAsyncReturnValues[`${fileMetadata.path};${getOnlineArtwork}`]; 9 | } 10 | } 11 | 12 | exports.AlbumArtworkGetterMock = AlbumArtworkGetterMock; 13 | -------------------------------------------------------------------------------- /main/background-work/mocks/application-paths-mock.js: -------------------------------------------------------------------------------- 1 | class ApplicationPathsMock { 2 | getCoverArtCacheFullPathCalls = 0; 3 | getCoverArtCacheFullPathReturnValue = ''; 4 | 5 | getCoverArtCacheFullPath() { 6 | this.getCoverArtCacheFullPathCalls++; 7 | return this.getCoverArtCacheFullPathReturnValue; 8 | } 9 | } 10 | 11 | exports.ApplicationPathsMock = ApplicationPathsMock; 12 | -------------------------------------------------------------------------------- /main/background-work/mocks/embedded-album-artwork-getter-mock.js: -------------------------------------------------------------------------------- 1 | class EmbeddedAlbumArtworkGetterMock { 2 | getEmbeddedArtworkReturnValues = {}; 3 | 4 | getEmbeddedArtwork(fileMetadata) { 5 | return this.getEmbeddedArtworkReturnValues[fileMetadata.path]; 6 | } 7 | } 8 | 9 | exports.EmbeddedAlbumArtworkGetterMock = EmbeddedAlbumArtworkGetterMock; 10 | -------------------------------------------------------------------------------- /main/background-work/mocks/external-album-artwork-getter-mock.js: -------------------------------------------------------------------------------- 1 | const { StringUtils } = require('../common/utils/string-utils'); 2 | 3 | class ExternalAlbumArtworkGetterMock { 4 | getExternalArtworkAsyncReturnValues = {}; 5 | 6 | async getExternalArtworkAsync(fileMetadata) { 7 | return this.getExternalArtworkAsyncReturnValues[fileMetadata.path]; 8 | } 9 | } 10 | 11 | exports.ExternalAlbumArtworkGetterMock = ExternalAlbumArtworkGetterMock; 12 | -------------------------------------------------------------------------------- /main/background-work/mocks/external-artwork-path-getter-mock.js: -------------------------------------------------------------------------------- 1 | class ExternalArtworkPathGetterMock { 2 | getExternalArtworkPathAsyncReturnValues = {}; 3 | 4 | async getExternalArtworkPathAsync(audioFilePath) { 5 | return this.getExternalArtworkPathAsyncReturnValues[audioFilePath]; 6 | } 7 | } 8 | 9 | exports.ExternalArtworkPathGetterMock = ExternalArtworkPathGetterMock; 10 | -------------------------------------------------------------------------------- /main/background-work/mocks/file-metadata-mock.js: -------------------------------------------------------------------------------- 1 | class FileMetadataMock { 2 | constructor(path) { 3 | this.path = path; 4 | this.bitRate = 0; 5 | this.sampleRate = 0; 6 | this.durationInMilliseconds = 0; 7 | this.title = ''; 8 | this.album = ''; 9 | this.albumArtists = []; 10 | this.artists = []; 11 | this.genres = []; 12 | this.comment = ''; 13 | this.grouping = ''; 14 | this.year = 0; 15 | this.trackNumber = 0; 16 | this.trackCount = 0; 17 | this.discNumber = 0; 18 | this.discCount = 0; 19 | this.lyrics = ''; 20 | this.picture = undefined; 21 | this.rating = 0; 22 | } 23 | } 24 | 25 | exports.FileMetadataMock = FileMetadataMock; 26 | -------------------------------------------------------------------------------- /main/background-work/mocks/file-metadata.factory-mock.js: -------------------------------------------------------------------------------- 1 | class FileMetadataFactoryMock { 2 | createCalls = []; 3 | createReturnValues = {}; 4 | 5 | create(path) { 6 | this.createCalls.push(path); 7 | return this.createReturnValues[path]; 8 | } 9 | } 10 | 11 | exports.FileMetadataFactoryMock = FileMetadataFactoryMock; 12 | -------------------------------------------------------------------------------- /main/background-work/mocks/folder-repository-mock.js: -------------------------------------------------------------------------------- 1 | class FolderRepositoryMock { 2 | getFoldersCalls = 0; 3 | getFoldersReturnValues = []; 4 | 5 | getFolders() { 6 | this.getFoldersCalls++; 7 | return this.getFoldersReturnValues; 8 | } 9 | } 10 | 11 | exports.FolderRepositoryMock = FolderRepositoryMock; 12 | -------------------------------------------------------------------------------- /main/background-work/mocks/guid-factory-mock.js: -------------------------------------------------------------------------------- 1 | class GuidFactoryMock { 2 | createReturnValue = 0; 3 | 4 | create() { 5 | return this.createReturnValue; 6 | } 7 | } 8 | 9 | exports.GuidFactoryMock = GuidFactoryMock; 10 | -------------------------------------------------------------------------------- /main/background-work/mocks/lastfm.api-mock.js: -------------------------------------------------------------------------------- 1 | class LastfmApiMock { 2 | getAlbumInfoAsyncReturnValues = {}; 3 | getAlbumInfoAsyncThrowsError = false; 4 | 5 | async getAlbumInfoAsync(artist, album, autoCorrect, languageCode) { 6 | if (this.getAlbumInfoAsyncThrowsError) { 7 | throw new Error('Error while getting album info'); 8 | } else { 9 | return this.getAlbumInfoAsyncReturnValues[`${artist};${album};${autoCorrect};${languageCode}`]; 10 | } 11 | } 12 | } 13 | 14 | exports.LastfmApiMock = LastfmApiMock; 15 | -------------------------------------------------------------------------------- /main/background-work/mocks/logger-mock.js: -------------------------------------------------------------------------------- 1 | class LoggerMock { 2 | info(message, callerClass, callerMethod) {} 3 | 4 | warn(message, callerClass, callerMethod) {} 5 | 6 | error(error, message, callerClass, callerMethod) {} 7 | } 8 | 9 | exports.LoggerMock = LoggerMock; 10 | -------------------------------------------------------------------------------- /main/background-work/mocks/metadata-patcher-mock.js: -------------------------------------------------------------------------------- 1 | class MetadataPatcherMock { 2 | joinUnsplittableMetadataCalls = 0; 3 | joinUnsplittableMetadataReturnValues = {}; 4 | 5 | joinUnsplittableMetadata(possiblySplittedMetadata) { 6 | this.joinUnsplittableMetadataCalls++; 7 | return this.joinUnsplittableMetadataReturnValues[possiblySplittedMetadata.join(',')]; 8 | } 9 | } 10 | 11 | exports.MetadataPatcherMock = MetadataPatcherMock; 12 | -------------------------------------------------------------------------------- /main/background-work/mocks/online-album-artwork-getter-mock.js: -------------------------------------------------------------------------------- 1 | class OnlineAlbumArtworkGetterMock { 2 | getOnlineArtworkAsyncReturnValues = {}; 3 | 4 | async getOnlineArtworkAsync(fileMetadata) { 5 | return this.getOnlineArtworkAsyncReturnValues[fileMetadata.path]; 6 | } 7 | } 8 | 9 | exports.OnlineAlbumArtworkGetterMock = OnlineAlbumArtworkGetterMock; 10 | -------------------------------------------------------------------------------- /main/background-work/mocks/track-adder-mock.js: -------------------------------------------------------------------------------- 1 | class TrackAdderMock { 2 | addTracksThatAreNotInTheDatabaseAsyncCalls = []; 3 | async addTracksThatAreNotInTheDatabaseAsync() { 4 | this.addTracksThatAreNotInTheDatabaseAsyncCalls.push({}); 5 | } 6 | } 7 | 8 | exports.TrackAdderMock = TrackAdderMock; 9 | -------------------------------------------------------------------------------- /main/background-work/mocks/track-filler-mock.js: -------------------------------------------------------------------------------- 1 | class TrackFillerMock { 2 | addFileMetadataToTrackCalls = []; 3 | addFileMetadataToTrackReturnValues = {}; 4 | 5 | addFileMetadataToTrack(track, fillOnlyEssentialMetadata) { 6 | this.addFileMetadataToTrackCalls.push(`${track.path};${fillOnlyEssentialMetadata}`); 7 | return this.addFileMetadataToTrackReturnValues[`${track.path};${fillOnlyEssentialMetadata}`]; 8 | } 9 | } 10 | 11 | exports.TrackFillerMock = TrackFillerMock; 12 | -------------------------------------------------------------------------------- /main/background-work/mocks/track-remover-mock.js: -------------------------------------------------------------------------------- 1 | class TrackRemoverMock { 2 | removeTracksThatDoNoNotBelongToFoldersAsyncCalls = []; 3 | removeTracksThatAreNotFoundOnDiskAsyncCalls = []; 4 | removeFolderTracksForInexistingTracksAsyncCalls = []; 5 | 6 | async removeTracksThatDoNoNotBelongToFoldersAsync() { 7 | this.removeTracksThatDoNoNotBelongToFoldersAsyncCalls.push({}); 8 | } 9 | 10 | async removeTracksThatAreNotFoundOnDiskAsync() { 11 | this.removeTracksThatAreNotFoundOnDiskAsyncCalls.push({}); 12 | } 13 | 14 | async removeFolderTracksForInexistingTracksAsync() { 15 | this.removeFolderTracksForInexistingTracksAsyncCalls.push({}); 16 | } 17 | } 18 | 19 | exports.TrackRemoverMock = TrackRemoverMock; 20 | -------------------------------------------------------------------------------- /main/background-work/mocks/track-updater-mock.js: -------------------------------------------------------------------------------- 1 | class TrackUpdaterMock { 2 | updateTracksThatAreOutOfDateAsyncCalls = []; 3 | 4 | async updateTracksThatAreOutOfDateAsync() { 5 | this.updateTracksThatAreOutOfDateAsyncCalls.push({}); 6 | } 7 | } 8 | 9 | exports.TrackUpdaterMock = TrackUpdaterMock; 10 | -------------------------------------------------------------------------------- /main/background-work/mocks/track-verifier-mock.js: -------------------------------------------------------------------------------- 1 | class TrackVerifierMock { 2 | isTrackOutOfDateCalls = []; 3 | isTrackOutOfDateReturnValues = {}; 4 | 5 | doesTrackNeedIndexingCalls = []; 6 | doesTrackNeedIndexingReturnValues = {}; 7 | 8 | isTrackOutOfDate(track) { 9 | this.isTrackOutOfDateCalls.push(track.path); 10 | return this.isTrackOutOfDateReturnValues[track.path]; 11 | } 12 | 13 | doesTrackNeedIndexing(track) { 14 | this.doesTrackNeedIndexingCalls.push(track.path); 15 | return this.doesTrackNeedIndexingReturnValues[track.path]; 16 | } 17 | } 18 | 19 | exports.TrackVerifierMock = TrackVerifierMock; 20 | -------------------------------------------------------------------------------- /main/background-work/worker-proxy.js: -------------------------------------------------------------------------------- 1 | const { workerData, parentPort } = require('worker_threads'); 2 | 3 | class WorkerProxy { 4 | task() { 5 | return workerData.arg.task; 6 | } 7 | 8 | postMessage(message) { 9 | parentPort?.postMessage(message); 10 | } 11 | 12 | applicationDataDirectory() { 13 | return workerData.arg.applicationDataDirectory; 14 | } 15 | 16 | skipRemovedFilesDuringRefresh() { 17 | return workerData.arg.skipRemovedFilesDuringRefresh; 18 | } 19 | } 20 | 21 | exports.WorkerProxy = WorkerProxy; 22 | -------------------------------------------------------------------------------- /main/common/application/sensitive-information.ts: -------------------------------------------------------------------------------- 1 | export class SensitiveInformation { 2 | public static readonly discordClientId: string = '826521040275636325'; 3 | } 4 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /postinstall-web.js: -------------------------------------------------------------------------------- 1 | // Allow angular using electron module (native node modules) 2 | const fs = require('fs'); 3 | const f_angular = 'node_modules/@angular-devkit/build-angular/src/angular-cli-files/models/webpack-configs/browser.js'; 4 | 5 | fs.readFile(f_angular, 'utf8', function (err, data) { 6 | if (err) { 7 | return console.log(err); 8 | } 9 | var result = data.replace(/target: "electron-renderer",/g, ''); 10 | var result = result.replace(/target: "web",/g, ''); 11 | var result = result.replace(/return \{/g, 'return {target: "web",'); 12 | 13 | fs.writeFile(f_angular, result, 'utf8', function (err) { 14 | if (err) return console.log(err); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /postinstall.js: -------------------------------------------------------------------------------- 1 | // Allow angular using electron module (native node modules) 2 | const fs = require('fs'); 3 | const f_angular = 'node_modules/@angular-devkit/build-angular/src/angular-cli-files/models/webpack-configs/browser.js'; 4 | 5 | fs.readFile(f_angular, 'utf8', function (err, data) { 6 | if (err) { 7 | return console.log(err); 8 | } 9 | var result = data.replace(/target: "electron-renderer",/g, ''); 10 | var result = result.replace(/target: "web",/g, ''); 11 | var result = result.replace(/return \{/g, 'return {target: "electron-renderer",'); 12 | 13 | fs.writeFile(f_angular, result, 'utf8', function (err) { 14 | if (err) return console.log(err); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | .mat-drawer-container { 2 | width: 100vw; 3 | height: 100vh; 4 | } 5 | 6 | .mat-drawer-container__drawer { 7 | width: 450px; 8 | } 9 | -------------------------------------------------------------------------------- /src/app/common/api/lastfm/lastfm-biography.ts: -------------------------------------------------------------------------------- 1 | export class LastfmBiography { 2 | public published: string; 3 | public summary: string; 4 | public content: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/common/api/lyrics/i-lyrics.api.ts: -------------------------------------------------------------------------------- 1 | import { Lyrics } from './lyrics'; 2 | 3 | export interface ILyricsApi { 4 | readonly sourceName: string; 5 | getLyricsAsync(artist: string, title: string): Promise; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/common/api/lyrics/lyrics-source-type.ts: -------------------------------------------------------------------------------- 1 | export enum LyricsSourceType { 2 | none = 1, 3 | embedded = 2, 4 | lrc = 3, 5 | online = 4, 6 | } 7 | -------------------------------------------------------------------------------- /src/app/common/api/lyrics/lyrics.ts: -------------------------------------------------------------------------------- 1 | export class Lyrics { 2 | public constructor( 3 | public sourceName: string, 4 | public text: string, 5 | ) {} 6 | 7 | public static empty(): Lyrics { 8 | return new Lyrics('', ''); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/app/common/api/lyrics/web-search-lyrics/sources/genius-source.ts: -------------------------------------------------------------------------------- 1 | import { IWebSearchLyricsSource } from './i-web-search-lyrics-source'; 2 | import htmlParser, { HTMLElement } from 'node-html-parser'; 3 | 4 | export class GeniusSource implements IWebSearchLyricsSource { 5 | public get name(): string { 6 | return 'Genius'; 7 | } 8 | 9 | public parse(htmlString: string): string { 10 | const htmlElement: HTMLElement = htmlParser(htmlString); 11 | 12 | return htmlElement 13 | .querySelectorAll('div[data-lyrics-container=true]') 14 | .map((x: HTMLElement) => x.structuredText) 15 | .join('') 16 | .replace(/\[.+\]/g, '') 17 | .trim(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/common/api/lyrics/web-search-lyrics/sources/i-web-search-lyrics-source.ts: -------------------------------------------------------------------------------- 1 | export interface IWebSearchLyricsSource { 2 | readonly name: string; 3 | parse(htmlString: string): string; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/common/api/lyrics/web-search-lyrics/sources/lyrics-source.ts: -------------------------------------------------------------------------------- 1 | import { IWebSearchLyricsSource } from './i-web-search-lyrics-source'; 2 | import htmlParser, { HTMLElement } from 'node-html-parser'; 3 | 4 | export class LyricsSource implements IWebSearchLyricsSource { 5 | public get name(): string { 6 | return 'Lyrics'; 7 | } 8 | 9 | public parse(htmlString: string): string { 10 | const htmlElement: HTMLElement = htmlParser(htmlString); 11 | 12 | return htmlElement.querySelector('pre#lyric-body-text')?.textContent.replace(/(|<\/a>)/g, '') ?? ''; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/common/api/lyrics/web-search-lyrics/sources/musixmatch-source.ts: -------------------------------------------------------------------------------- 1 | import { IWebSearchLyricsSource } from './i-web-search-lyrics-source'; 2 | import htmlParser, { HTMLElement } from 'node-html-parser'; 3 | 4 | export class MusixmatchSource implements IWebSearchLyricsSource { 5 | public get name(): string { 6 | return 'Musixmatch'; 7 | } 8 | 9 | public parse(htmlString: string): string { 10 | const htmlElement: HTMLElement = htmlParser(htmlString); 11 | 12 | return htmlElement 13 | .querySelectorAll('p.mxm-lyrics__content') 14 | .map((x) => x.textContent) 15 | .join(''); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/common/api/lyrics/web-search-lyrics/web-search-result.ts: -------------------------------------------------------------------------------- 1 | export class WebSearchResult { 2 | /** 3 | * Constructs an instance of WebSearchResult 4 | * @param fullUrl The full url to the lyrics in format "https://www.azlyrics.com/lyrics/massiveattack/teardrop.html" 5 | * @param domainUrl The domain url in format "www.azlyrics.com" 6 | */ 7 | public constructor( 8 | public fullUrl: string, 9 | private domainUrl: string, 10 | ) {} 11 | 12 | public get name(): string { 13 | return this.domainUrl?.replace(/(www\.)?(.*)\.\w+$/g, '$2').toLowerCase(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/common/application/contact-information.ts: -------------------------------------------------------------------------------- 1 | export class ContactInformation { 2 | public static readonly donateUrl: string = 'https://digimezzo.github.io/site/donate'; 3 | public static readonly websiteUrl: string = 'https://digimezzo.github.io/site'; 4 | public static readonly mastodonUrl: string = 'https://hachyderm.io/@digimezzo'; 5 | public static readonly blueskyUrl: string = 'https://bsky.app/profile/digimezzo.bsky.social'; 6 | public static readonly githubUrl: string = 'https://github.com/digimezzo'; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/common/application/external-component.ts: -------------------------------------------------------------------------------- 1 | export class ExternalComponent { 2 | public constructor(public name: string, public description: string, public url: string, public licenseUrl: string) {} 3 | } 4 | -------------------------------------------------------------------------------- /src/app/common/application/language.ts: -------------------------------------------------------------------------------- 1 | export class Language { 2 | public constructor( 3 | public code: string, 4 | public englishName: string, 5 | public localizedName: string, 6 | public showEnglishName: boolean, 7 | ) {} 8 | } 9 | -------------------------------------------------------------------------------- /src/app/common/application/product-information.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | /* eslint-disable @typescript-eslint/no-unsafe-call */ 3 | /* eslint-disable @typescript-eslint/no-unsafe-assignment */ 4 | const { getName, getFullVersion, getCopyright } = require('../../../../get-package-information.js'); 5 | 6 | export class ProductInformation { 7 | public static readonly applicationName: string = getName(); 8 | public static readonly applicationVersion: string = getFullVersion(); 9 | public static readonly applicationCopyright: string = getCopyright(); 10 | public static readonly releasesDownloadUrl: string = 'https://github.com/digimezzo/dopamine/releases/'; 11 | } 12 | -------------------------------------------------------------------------------- /src/app/common/application/sensitive-information.ts: -------------------------------------------------------------------------------- 1 | export class SensitiveInformation { 2 | public static readonly lastfmApiKey: string = '1be93666a6e8d3d79b05b1c74682cb62'; 3 | public static readonly lastfmSharedSecret: string = 'c71f3fbae4ee6be95664d5406261094e'; 4 | public static readonly fanartApiKey: string = '80d1c44e9b315f0c1963857e4d671a85'; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/common/guid.factory.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { v4 as uuidv4 } from 'uuid'; 3 | 4 | @Injectable() 5 | export class GuidFactory { 6 | public create(): string { 7 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call 8 | return uuidv4(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/app/common/hacks.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable() 4 | export class Hacks { 5 | /** 6 | * Removes all visible tooltips 7 | */ 8 | public removeTooltips(): void { 9 | while (document.getElementsByTagName('mat-tooltip-component').length > 0) { 10 | document.getElementsByTagName('mat-tooltip-component')[0].remove(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/common/io/application.base.ts: -------------------------------------------------------------------------------- 1 | import { BrowserWindow } from 'electron'; 2 | import { WindowSize } from './window-size'; 3 | import { Observable } from 'rxjs'; 4 | 5 | export abstract class ApplicationBase { 6 | public abstract fullScreenChanged$: Observable; 7 | 8 | public abstract getGlobal(name: string): unknown; 9 | public abstract getCurrentWindow(): BrowserWindow; 10 | public abstract getWindowSize(): WindowSize; 11 | public abstract getParameters(): string[]; 12 | public abstract isFullScreen(): boolean; 13 | } 14 | -------------------------------------------------------------------------------- /src/app/common/io/date-proxy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable() 4 | export class DateProxy { 5 | public now(): number { 6 | return Date.now(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/app/common/io/document-proxy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable() 4 | export class DocumentProxy { 5 | public getDocumentElement(): HTMLElement { 6 | return document.documentElement; 7 | } 8 | 9 | public getBody(): HTMLElement { 10 | return document.body; 11 | } 12 | 13 | public getCanvasById(canvasId: string): HTMLCanvasElement { 14 | return document.getElementById(canvasId) as HTMLCanvasElement; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/app/common/io/ipc-proxy.base.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { Observable } from 'rxjs'; 3 | import { IIndexingMessage } from '../../services/indexing/messages/i-indexing-message'; 4 | 5 | export abstract class IpcProxyBase { 6 | public abstract onIndexingWorkerMessage$: Observable; 7 | public abstract onIndexingWorkerExit$: Observable; 8 | public abstract onApplicationClose$: Observable; 9 | 10 | public abstract sendToMainProcess(channel: string, arg: unknown): void; 11 | } 12 | -------------------------------------------------------------------------------- /src/app/common/io/log-viewer.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Constants } from '../application/constants'; 3 | import { DesktopBase } from './desktop.base'; 4 | import { FileAccessBase } from './file-access.base'; 5 | 6 | @Injectable() 7 | export class LogViewer { 8 | public constructor( 9 | private desktop: DesktopBase, 10 | private fileAccess: FileAccessBase, 11 | ) {} 12 | 13 | public viewLog(): void { 14 | // See: https://stackoverflow.com/questions/30381450/open-external-file-with-electron 15 | this.desktop.showFileInDirectory( 16 | this.fileAccess.combinePath([this.desktop.getApplicationDataDirectory(), 'logs', Constants.logFileName]), 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/common/io/translate-service-proxy.base.ts: -------------------------------------------------------------------------------- 1 | export abstract class TranslateServiceProxyBase { 2 | public abstract setDefaultLang(lang: string): void; 3 | public abstract use(lang: string): Promise; 4 | public abstract get(key: string | Array, interpolateParams?: object): Promise; 5 | public abstract instant(key: string | Array, interpolateParams?: object): string; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/common/io/window-size.ts: -------------------------------------------------------------------------------- 1 | export class WindowSize { 2 | public constructor(public width: number, public height: number) {} 3 | } 4 | -------------------------------------------------------------------------------- /src/app/common/metadata/file-metadata.factory.base.ts: -------------------------------------------------------------------------------- 1 | import { IFileMetadata } from './i-file-metadata'; 2 | 3 | export abstract class FileMetadataFactoryBase { 4 | public abstract createAsync(path: string): Promise; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/common/metadata/file-metadata.factory.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { IFileMetadata } from './i-file-metadata'; 3 | import { TagLibFileMetadata } from './tag-lib-file-metadata'; 4 | import { FileMetadataFactoryBase } from './file-metadata.factory.base'; 5 | 6 | @Injectable() 7 | export class FileMetadataFactory implements FileMetadataFactoryBase { 8 | public async createAsync(path: string): Promise { 9 | const fileMetadata: IFileMetadata = new TagLibFileMetadata(path); 10 | await fileMetadata.loadAsync(); 11 | 12 | return fileMetadata; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/common/metadata/i-file-metadata.ts: -------------------------------------------------------------------------------- 1 | export interface IFileMetadata { 2 | path: string; 3 | bitRate: number; 4 | sampleRate: number; 5 | durationInMilliseconds: number; 6 | title: string; 7 | album: string; 8 | albumArtists: string[]; 9 | artists: string[]; 10 | genres: string[]; 11 | comment: string; 12 | grouping: string; 13 | year: number; 14 | trackNumber: number; 15 | trackCount: number; 16 | discNumber: number; 17 | discCount: number; 18 | lyrics: string; 19 | picture: Buffer | undefined; 20 | rating: number; 21 | 22 | save(): void; 23 | loadAsync(): Promise; 24 | } 25 | -------------------------------------------------------------------------------- /src/app/common/rgb-color.ts: -------------------------------------------------------------------------------- 1 | export class RgbColor { 2 | public constructor( 3 | public red: number, 4 | public green: number, 5 | public blue: number, 6 | ) {} 7 | 8 | public static default(): RgbColor { 9 | return new RgbColor(0, 0, 0); 10 | } 11 | 12 | public toString(): string { 13 | return `${this.red},${this.green},${this.blue}`; 14 | } 15 | 16 | public equals(rgbColor: RgbColor): boolean { 17 | return this.red === rgbColor.red && this.green === rgbColor.green && this.blue === rgbColor.blue; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/common/scheduling/scheduler.base.ts: -------------------------------------------------------------------------------- 1 | export abstract class SchedulerBase { 2 | public abstract sleepAsync(milliseconds: number): Promise; 3 | public abstract sleepUntilConditionIsTrueAsync(milliseconds: number, untilMilliseconds: number, condition: boolean): Promise; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/common/scheduling/timer.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | 3 | export class Timer { 4 | private startedMilliseconds: number; 5 | private stoppedMilliseconds: number; 6 | 7 | public get elapsedMilliseconds(): number { 8 | return this.stoppedMilliseconds - this.startedMilliseconds; 9 | } 10 | 11 | public start(): void { 12 | this.startedMilliseconds = moment().valueOf(); 13 | } 14 | 15 | public stop(): void { 16 | this.stoppedMilliseconds = moment().valueOf(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/app/common/semantic-zoomable-model.ts: -------------------------------------------------------------------------------- 1 | import { SemanticZoomable } from './semantic-zoomable'; 2 | 3 | export class SemanticZoomableModel extends SemanticZoomable { 4 | public constructor(private semanticZoomable: SemanticZoomable) { 5 | super(); 6 | 7 | this.isZoomHeader = true; 8 | } 9 | 10 | public get name(): string { 11 | return this.semanticZoomable.name; 12 | } 13 | 14 | public get displayName(): string { 15 | return ''; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/common/semantic-zoomable.ts: -------------------------------------------------------------------------------- 1 | import { Constants } from './application/constants'; 2 | import { StringUtils } from './utils/string-utils'; 3 | 4 | export abstract class SemanticZoomable { 5 | public abstract name: string; 6 | public abstract displayName: string; 7 | 8 | public isZoomHeader: boolean = false; 9 | 10 | public get sortableName(): string { 11 | return StringUtils.getSortableString(this.name, true); 12 | } 13 | 14 | public get zoomHeader(): string { 15 | const firstCharacter: string = this.sortableName.charAt(0); 16 | 17 | if (Constants.alphabeticalHeaders.includes(firstCharacter)) { 18 | return firstCharacter; 19 | } 20 | 21 | return '#'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/app/common/text-sanitizer.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import sanitize from 'sanitize-filename'; 3 | 4 | @Injectable() 5 | export class TextSanitizer { 6 | public sanitize(textToSanitize: string): string { 7 | return sanitize(textToSanitize); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/app/common/utils/promise-utils.ts: -------------------------------------------------------------------------------- 1 | export class PromiseUtils { 2 | public static noAwait(promise: Promise): void { 3 | promise.catch(() => { 4 | // Do nothing 5 | }); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/app/data/database-migrator.base.ts: -------------------------------------------------------------------------------- 1 | export abstract class DatabaseMigratorBase { 2 | public abstract migrate(): void; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/data/entities/album-artwork.ts: -------------------------------------------------------------------------------- 1 | export class AlbumArtwork { 2 | public constructor(public albumKey: string, public artworkId: string) {} 3 | 4 | public albumArtworkId: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/data/entities/album-data.ts: -------------------------------------------------------------------------------- 1 | export class AlbumData { 2 | public albumTitle: string | undefined; 3 | public albumArtists: string | undefined; 4 | public artists: string | undefined; 5 | public albumKey: string; 6 | public artworkId: string | undefined; 7 | public year: number | undefined; 8 | public genres: string | undefined; 9 | public dateFileCreated: number | undefined; 10 | public dateAdded: number | undefined; 11 | public dateLastPlayed: number | undefined; 12 | } 13 | -------------------------------------------------------------------------------- /src/app/data/entities/artist-data.ts: -------------------------------------------------------------------------------- 1 | export class ArtistData { 2 | public constructor(public artists: string) {} 3 | } 4 | -------------------------------------------------------------------------------- /src/app/data/entities/folder-track.ts: -------------------------------------------------------------------------------- 1 | export class FolderTrack { 2 | public constructor(public folderId: number, public trackId: number) {} 3 | 4 | public folderTrackId: number; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/data/entities/folder.ts: -------------------------------------------------------------------------------- 1 | export class Folder { 2 | public constructor(public path: string) {} 3 | 4 | public folderId: number; 5 | public showInCollection: number | undefined; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/data/entities/genre-data.ts: -------------------------------------------------------------------------------- 1 | export class GenreData { 2 | public constructor(public genres: string) {} 3 | } 4 | -------------------------------------------------------------------------------- /src/app/data/entities/queued-track.ts: -------------------------------------------------------------------------------- 1 | export class QueuedTrack { 2 | public constructor(public path: string) {} 3 | 4 | public queuedTrackId: number; 5 | public isPlaying: number; 6 | public progressSeconds: number; 7 | public orderId: number; 8 | } 9 | -------------------------------------------------------------------------------- /src/app/data/entities/removed-track.ts: -------------------------------------------------------------------------------- 1 | export class RemovedTrack { 2 | public constructor(public path: string) {} 3 | 4 | public trackId: number; 5 | public dateRemoved: number; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/data/migration.ts: -------------------------------------------------------------------------------- 1 | export abstract class Migration { 2 | public id: number; 3 | public name: string; 4 | public statements: string[] = []; 5 | public abstract up(): void; 6 | public abstract down(): void; 7 | 8 | public sql(statement: string): void { 9 | this.statements.push(statement); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/app/data/migrations/migration2.ts: -------------------------------------------------------------------------------- 1 | import { Migration } from '../migration'; 2 | 3 | export class Migration2 extends Migration { 4 | public id: number = 2; 5 | public name: string = 'Migration2'; 6 | 7 | public up(): void { 8 | this.sql('UPDATE Track SET NeedsAlbumArtworkIndexing=1;'); 9 | } 10 | 11 | public down(): void { 12 | // Nothing to do 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/data/migrations/migration3.ts: -------------------------------------------------------------------------------- 1 | import { Migration } from '../migration'; 2 | 3 | export class Migration3 extends Migration { 4 | public id: number = 3; 5 | public name: string = 'Migration3'; 6 | 7 | public up(): void { 8 | this.sql('UPDATE Track SET NeedsAlbumArtworkIndexing=1;'); 9 | } 10 | 11 | public down(): void { 12 | // Nothing to do 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/data/migrations/migration4.ts: -------------------------------------------------------------------------------- 1 | import { Migration } from '../migration'; 2 | 3 | export class Migration4 extends Migration { 4 | public id: number = 4; 5 | public name: string = 'Migration4'; 6 | 7 | public up(): void { 8 | this.sql('ALTER TABLE Track ADD AlbumKey2 TEXT;'); 9 | this.sql('ALTER TABLE Track ADD AlbumKey3 TEXT;'); 10 | } 11 | 12 | public down(): void { 13 | // Nothing to do 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/data/migrations/migration5.ts: -------------------------------------------------------------------------------- 1 | import { Migration } from '../migration'; 2 | 3 | export class Migration5 extends Migration { 4 | public id: number = 5; 5 | public name: string = 'Migration5'; 6 | 7 | public up(): void { 8 | this.sql('UPDATE Track SET NeedsIndexing=1;'); 9 | } 10 | 11 | public down(): void { 12 | // Nothing to do 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/data/migrations/migration6.ts: -------------------------------------------------------------------------------- 1 | import { Migration } from '../migration'; 2 | 3 | export class Migration6 extends Migration { 4 | public id: number = 6; 5 | public name: string = 'Migration6'; 6 | 7 | public up(): void { 8 | this.sql('UPDATE Track SET NeedsIndexing=1;'); 9 | } 10 | 11 | public down(): void { 12 | // Nothing to do 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/data/repositories/album-artwork-repository.base.ts: -------------------------------------------------------------------------------- 1 | import { AlbumArtwork } from '../entities/album-artwork'; 2 | 3 | export abstract class AlbumArtworkRepositoryBase { 4 | public abstract getNumberOfAlbumArtwork(): number; 5 | public abstract getNumberOfAlbumArtworkThatHasNoTrack(albumKeyIndex: string): number; 6 | public abstract deleteAlbumArtworkThatHasNoTrack(albumKeyIndex: string): number; 7 | public abstract addAlbumArtwork(albumArtwork: AlbumArtwork): void; 8 | public abstract getAllAlbumArtwork(): AlbumArtwork[] | undefined; 9 | public abstract getNumberOfAlbumArtworkForTracksThatNeedAlbumArtworkIndexing(albumKeyIndex: string): number; 10 | public abstract deleteAlbumArtworkForTracksThatNeedAlbumArtworkIndexing(albumKeyIndex: string): number; 11 | } 12 | -------------------------------------------------------------------------------- /src/app/data/repositories/folder-repository.base.ts: -------------------------------------------------------------------------------- 1 | import { Folder } from '../entities/folder'; 2 | 3 | export abstract class FolderRepositoryBase { 4 | public abstract addFolder(folder: Folder): void; 5 | public abstract getFolders(): Folder[] | undefined; 6 | public abstract getFolderByPath(folderPath: string): Folder | undefined; 7 | public abstract deleteFolder(folderId: number): void; 8 | public abstract setFolderShowInCollection(folderId: number, showInCollection: number): void; 9 | public abstract setAllFoldersShowInCollection(showInCollection: number): void; 10 | } 11 | -------------------------------------------------------------------------------- /src/app/data/repositories/queued-track-repository.base.ts: -------------------------------------------------------------------------------- 1 | import { QueuedTrack } from '../entities/queued-track'; 2 | 3 | export abstract class QueuedTrackRepositoryBase { 4 | public abstract getSavedQueuedTracks(): QueuedTrack[] | undefined; 5 | public abstract saveQueuedTracks(tracks: QueuedTrack[]): void; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/services/album-artwork-cache/album-artwork-cache-id-factory.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { GuidFactory } from '../../common/guid.factory'; 3 | import { AlbumArtworkCacheId } from './album-artwork-cache-id'; 4 | 5 | @Injectable({ providedIn: 'root' }) 6 | export class AlbumArtworkCacheIdFactory { 7 | public constructor(private guidFactory: GuidFactory) {} 8 | 9 | public create(): AlbumArtworkCacheId { 10 | return new AlbumArtworkCacheId(this.guidFactory); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/app/services/album-artwork-cache/album-artwork-cache-id.ts: -------------------------------------------------------------------------------- 1 | import { GuidFactory } from '../../common/guid.factory'; 2 | 3 | export class AlbumArtworkCacheId { 4 | public constructor(guidFactory: GuidFactory) { 5 | this.id = `album-${guidFactory.create()}`; 6 | } 7 | 8 | public readonly id: string; 9 | } -------------------------------------------------------------------------------- /src/app/services/album-artwork-cache/album-artwork-cache.service.base.ts: -------------------------------------------------------------------------------- 1 | import { AlbumArtworkCacheId } from './album-artwork-cache-id'; 2 | 3 | export abstract class AlbumArtworkCacheServiceBase { 4 | public abstract addArtworkDataToCacheAsync(data: Buffer): Promise; 5 | public abstract removeArtworkDataFromCacheAsync(albumKey: string): Promise; 6 | } -------------------------------------------------------------------------------- /src/app/services/album/album-service.base.ts: -------------------------------------------------------------------------------- 1 | import { ArtistType } from '../artist/artist-type'; 2 | import { AlbumModel } from './album-model'; 3 | import { ArtistModel } from '../artist/artist-model'; 4 | 5 | export abstract class AlbumServiceBase { 6 | public abstract getAllAlbums(): AlbumModel[]; 7 | public abstract getAlbumsForArtists(artists: ArtistModel[], artistType: ArtistType): AlbumModel[]; 8 | public abstract getAlbumsForGenres(genres: string[]): AlbumModel[]; 9 | } 10 | -------------------------------------------------------------------------------- /src/app/services/appearance/theme/theme-core-colors.ts: -------------------------------------------------------------------------------- 1 | export class ThemeCoreColors { 2 | public constructor(public primaryColor: string, public secondaryColor: string, public accentColor: string) {} 3 | } 4 | -------------------------------------------------------------------------------- /src/app/services/appearance/theme/theme-creator.ts: -------------------------------------------------------------------------------- 1 | export class ThemeCreator { 2 | public constructor(public name: string, public email: string) {} 3 | } 4 | -------------------------------------------------------------------------------- /src/app/services/appearance/theme/theme-options.spec.ts: -------------------------------------------------------------------------------- 1 | import { ThemeOptions } from './theme-options'; 2 | 3 | describe('ThemeOptions', () => { 4 | describe('constructor', () => { 5 | it('should create', () => { 6 | // Arrange 7 | 8 | // Act 9 | const creator: ThemeOptions = new ThemeOptions(false); 10 | 11 | // Assert 12 | expect(creator).toBeDefined(); 13 | }); 14 | 15 | it('should set centerAlbumInfoText', () => { 16 | // Arrange 17 | const creator: ThemeOptions = new ThemeOptions(false); 18 | 19 | // Act 20 | 21 | // Assert 22 | expect(creator.centerAlbumInfoText).toBeFalsy(); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/services/appearance/theme/theme-options.ts: -------------------------------------------------------------------------------- 1 | export class ThemeOptions { 2 | public constructor(public centerAlbumInfoText: boolean) {} 3 | } 4 | -------------------------------------------------------------------------------- /src/app/services/appearance/theme/theme.ts: -------------------------------------------------------------------------------- 1 | import { ThemeCoreColors } from './theme-core-colors'; 2 | import { ThemeCreator } from './theme-creator'; 3 | import { ThemeNeutralColors } from './theme-neutral-colors'; 4 | import { ThemeOptions } from './theme-options'; 5 | 6 | export class Theme { 7 | public constructor( 8 | public name: string, 9 | public creator: ThemeCreator, 10 | public coreColors: ThemeCoreColors, 11 | public darkColors: ThemeNeutralColors, 12 | public lightColors: ThemeNeutralColors, 13 | public options: ThemeOptions, 14 | public isBroken: boolean = false 15 | ) {} 16 | } 17 | -------------------------------------------------------------------------------- /src/app/services/application/application.service.base.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | 3 | export abstract class ApplicationServiceBase { 4 | public abstract windowSizeChanged$: Observable; 5 | public abstract mouseButtonReleased$: Observable; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/services/artist-information/artist-information-factory.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ArtistInformation } from './artist-information'; 3 | import { DesktopBase } from '../../common/io/desktop.base'; 4 | 5 | @Injectable() 6 | export class ArtistInformationFactory { 7 | public constructor(private desktop: DesktopBase) {} 8 | 9 | public create(name: string, url: string, imageUrl: string, biography: string): ArtistInformation { 10 | return new ArtistInformation(this.desktop, name, url, imageUrl, biography); 11 | } 12 | 13 | public createEmpty(): ArtistInformation { 14 | return ArtistInformation.empty(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/app/services/artist-information/artist-information.service.base.ts: -------------------------------------------------------------------------------- 1 | import { TrackModel } from '../track/track-model'; 2 | import { ArtistInformation } from './artist-information'; 3 | 4 | export abstract class ArtistInformationServiceBase { 5 | public abstract getQuickArtistInformation(track: TrackModel | undefined): ArtistInformation; 6 | public abstract getArtistInformationAsync(track: TrackModel | undefined): Promise; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/services/artist/artist-type.ts: -------------------------------------------------------------------------------- 1 | export enum ArtistType { 2 | trackArtists = 1, 3 | albumArtists = 2, 4 | allArtists = 3, 5 | } 6 | -------------------------------------------------------------------------------- /src/app/services/artist/artist.service.base.ts: -------------------------------------------------------------------------------- 1 | import { ArtistModel } from './artist-model'; 2 | import { ArtistType } from './artist-type'; 3 | 4 | export abstract class ArtistServiceBase { 5 | public abstract getArtists(artistType: ArtistType): ArtistModel[]; 6 | public abstract getSourceArtists(artists: ArtistModel[]): string[]; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/services/audio-visualizer/audio-visualizer.service.base.ts: -------------------------------------------------------------------------------- 1 | export abstract class AudioVisualizerServiceBase { 2 | public abstract showAudioVisualizer: boolean; 3 | public abstract audioVisualizerStyles: string[]; 4 | public abstract selectedAudioVisualizerStyle: string; 5 | public abstract audioVisualizerFrameRates: number[]; 6 | public abstract selectedAudioVisualizerFrameRate: number; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/services/collection/collection.service.base.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { TrackModel } from '../track/track-model'; 3 | 4 | export abstract class CollectionServiceBase { 5 | public abstract collectionChanged$: Observable; 6 | public abstract deleteTracksAsync(tracks: TrackModel[]): Promise; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/services/dialog/confirmation-data.ts: -------------------------------------------------------------------------------- 1 | export class ConfirmationData { 2 | public constructor(public dialogTitle: string, public dialogText: string) {} 3 | } 4 | -------------------------------------------------------------------------------- /src/app/services/dialog/error-data.ts: -------------------------------------------------------------------------------- 1 | export class ErrorData { 2 | public constructor( 3 | public errorText: string, 4 | public isGlobalError: boolean, 5 | ) {} 6 | } 7 | -------------------------------------------------------------------------------- /src/app/services/dialog/info-data.ts: -------------------------------------------------------------------------------- 1 | export class InfoData { 2 | public constructor(public infoText: string) {} 3 | } 4 | -------------------------------------------------------------------------------- /src/app/services/dialog/input-data.ts: -------------------------------------------------------------------------------- 1 | export class InputData { 2 | public constructor( 3 | public dialogTitle: string, 4 | public inputText: string, 5 | public placeHolderText: string, 6 | public invalidCharacters: string[], 7 | ) {} 8 | } 9 | -------------------------------------------------------------------------------- /src/app/services/dialog/playlist-data.ts: -------------------------------------------------------------------------------- 1 | import { PlaylistModel } from '../playlist/playlist-model'; 2 | 3 | export class PlaylistData { 4 | public constructor(public playlist: PlaylistModel) {} 5 | } 6 | -------------------------------------------------------------------------------- /src/app/services/discord/discord-api-command-type.ts: -------------------------------------------------------------------------------- 1 | export enum DiscordApiCommandType { 2 | SetPresence, 3 | ClearPresence, 4 | } 5 | -------------------------------------------------------------------------------- /src/app/services/discord/discord-api-command.ts: -------------------------------------------------------------------------------- 1 | import { PresenceArgs } from './presence-args'; 2 | import { DiscordApiCommandType } from './discord-api-command-type'; 3 | 4 | export class DiscordApiCommand { 5 | public constructor( 6 | public commandType: DiscordApiCommandType, 7 | public args: PresenceArgs | undefined, 8 | ) {} 9 | } 10 | -------------------------------------------------------------------------------- /src/app/services/discord/presence-args.ts: -------------------------------------------------------------------------------- 1 | export interface PresenceArgs { 2 | title: string; 3 | artists: string; 4 | smallImageKey?: string; 5 | smallImageText?: string; 6 | largeImageKey?: string; 7 | largeImageText?: string; 8 | shouldSendTimestamps?: boolean; 9 | startTime?: number; 10 | } 11 | -------------------------------------------------------------------------------- /src/app/services/event-listener/event-listener.service.base.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | 3 | export abstract class EventListenerServiceBase { 4 | public abstract argumentsReceived$: Observable; 5 | public abstract filesDropped$: Observable; 6 | public abstract listenToEvents(): void; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/services/event-listener/event-listener.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { EventListenerService } from './event-listener.service'; 2 | import { EventListenerServiceBase } from './event-listener.service.base'; 3 | 4 | describe('EventListenerService', () => { 5 | function createSut(): EventListenerServiceBase { 6 | return new EventListenerService(); 7 | } 8 | 9 | describe('constructor', () => { 10 | it('should create', () => { 11 | // Arrange 12 | 13 | // Act 14 | const sut: EventListenerServiceBase = createSut(); 15 | 16 | // Assert 17 | expect(sut).toBeDefined(); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/app/services/file/file.service.base.ts: -------------------------------------------------------------------------------- 1 | export abstract class FileServiceBase { 2 | public abstract hasPlayableFilesAsParameters(): boolean; 3 | public abstract enqueueParameterFilesAsync(): Promise; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/services/folder/folder-model.ts: -------------------------------------------------------------------------------- 1 | import { Folder } from '../../data/entities/folder'; 2 | 3 | export class FolderModel { 4 | public constructor(private folder: Folder) {} 5 | 6 | public get folderId(): number { 7 | return this.folder.folderId; 8 | } 9 | 10 | public get path(): string { 11 | return this.folder.path; 12 | } 13 | 14 | public get showInCollection(): boolean { 15 | return this.folder.showInCollection != undefined && this.folder.showInCollection === 1 ? true : false; 16 | } 17 | public set showInCollection(v: boolean) { 18 | this.folder.showInCollection = v ? 1 : 0; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/services/folder/subfolder-model.ts: -------------------------------------------------------------------------------- 1 | import { ISelectable } from '../../ui/interfaces/i-selectable'; 2 | 3 | export class SubfolderModel implements ISelectable { 4 | public constructor( 5 | public path: string, 6 | public isGoToParent: boolean, 7 | ) {} 8 | 9 | public isSelected: boolean = false; 10 | public isPlaying: boolean = false; 11 | } 12 | -------------------------------------------------------------------------------- /src/app/services/genre/genre.service.base.ts: -------------------------------------------------------------------------------- 1 | import { GenreModel } from './genre-model'; 2 | 3 | export abstract class GenreServiceBase { 4 | public abstract getGenres(): GenreModel[]; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/services/indexing/messages/adding-tracks-message.ts: -------------------------------------------------------------------------------- 1 | import { IIndexingMessage } from './i-indexing-message'; 2 | 3 | export class AddingTracksMessage implements IIndexingMessage { 4 | public type: string = 'addingTracks'; 5 | public numberOfAddedTracks: number; 6 | public percentageOfAddedTracks: number; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/services/indexing/messages/i-indexing-message.ts: -------------------------------------------------------------------------------- 1 | export interface IIndexingMessage { 2 | type: string; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/services/lyrics/i-lyrics-getter.ts: -------------------------------------------------------------------------------- 1 | import { TrackModel } from '../track/track-model'; 2 | import { LyricsModel } from './lyrics-model'; 3 | 4 | export interface ILyricsGetter { 5 | getLyricsAsync(track: TrackModel): Promise; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/services/lyrics/lyrics-model.ts: -------------------------------------------------------------------------------- 1 | import { LyricsSourceType } from '../../common/api/lyrics/lyrics-source-type'; 2 | import { TrackModel } from '../track/track-model'; 3 | 4 | export class LyricsModel { 5 | public constructor( 6 | public track: TrackModel | undefined, 7 | public sourceName: string, 8 | public sourceType: LyricsSourceType, 9 | public text: string, 10 | ) {} 11 | 12 | public static empty(track: TrackModel | undefined): LyricsModel { 13 | return new LyricsModel(track, '', LyricsSourceType.none, ''); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/services/lyrics/lyrics.service.base.ts: -------------------------------------------------------------------------------- 1 | import { TrackModel } from '../track/track-model'; 2 | import { ILyricsGetter } from './i-lyrics-getter'; 3 | import { LyricsModel } from './lyrics-model'; 4 | 5 | export abstract class LyricsServiceBase implements ILyricsGetter { 6 | public abstract getLyricsAsync(track: TrackModel): Promise; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/services/metadata/image-comparison-status.ts: -------------------------------------------------------------------------------- 1 | export enum ImageComparisonStatus { 2 | None = 0, 3 | Identical = 1, 4 | Different = 2, 5 | } 6 | -------------------------------------------------------------------------------- /src/app/services/metadata/image-render-data.ts: -------------------------------------------------------------------------------- 1 | export class ImageRenderData { 2 | public constructor( 3 | public readonly imageUrl: string, 4 | public readonly imageBuffer: Buffer | undefined, 5 | ) {} 6 | } 7 | -------------------------------------------------------------------------------- /src/app/services/notification/notification-data.ts: -------------------------------------------------------------------------------- 1 | export class NotificationData { 2 | public constructor( 3 | public icon: string, 4 | public message: string, 5 | public animateIcon: boolean, 6 | public showCloseButton: boolean, 7 | ) {} 8 | } 9 | -------------------------------------------------------------------------------- /src/app/services/now-playing-navigation/now-playing-navigation.service.base.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { NowPlayingPage } from './now-playing-page'; 3 | 4 | export abstract class NowPlayingNavigationServiceBase { 5 | public abstract navigated$: Observable; 6 | public abstract readonly currentNowPlayingPage: NowPlayingPage; 7 | public abstract navigate(nowPlayingPage: NowPlayingPage); 8 | } 9 | -------------------------------------------------------------------------------- /src/app/services/now-playing-navigation/now-playing-page.ts: -------------------------------------------------------------------------------- 1 | export enum NowPlayingPage { 2 | nothingPlaying = 0, 3 | showcase = 1, 4 | lyrics = 2, 5 | artistInformation = 3, 6 | } 7 | -------------------------------------------------------------------------------- /src/app/services/playback-indication/playback-indication.service.base.ts: -------------------------------------------------------------------------------- 1 | import { SubfolderModel } from '../folder/subfolder-model'; 2 | import { TrackModel } from '../track/track-model'; 3 | 4 | export abstract class PlaybackIndicationServiceBase { 5 | public abstract setPlayingSubfolder(subfolders: SubfolderModel[] | undefined, playingTrack: TrackModel | undefined): void; 6 | public abstract clearPlayingSubfolder(subfolders: SubfolderModel[]): void; 7 | public abstract setPlayingTrack(tracks: TrackModel[] | undefined, playingTrack: TrackModel | undefined): void; 8 | public abstract clearPlayingTrack(tracks: TrackModel[]): void; 9 | } 10 | -------------------------------------------------------------------------------- /src/app/services/playback-information/playback-information.ts: -------------------------------------------------------------------------------- 1 | import { TrackModel } from '../track/track-model'; 2 | 3 | export class PlaybackInformation { 4 | public constructor(private _track: TrackModel | undefined, private _imageUrl: string) {} 5 | 6 | public get track(): TrackModel | undefined { 7 | return this._track; 8 | } 9 | 10 | public get imageUrl(): string { 11 | return this._imageUrl; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/services/playback/audio-player/audio-changed-event.ts: -------------------------------------------------------------------------------- 1 | export class AudioChangedEvent { 2 | public constructor(public audio: HTMLMediaElement) {} 3 | } 4 | -------------------------------------------------------------------------------- /src/app/services/playback/loop-mode.ts: -------------------------------------------------------------------------------- 1 | export enum LoopMode { 2 | None = 1, 3 | All = 2, 4 | One = 3, 5 | } 6 | -------------------------------------------------------------------------------- /src/app/services/playback/playback-progress.ts: -------------------------------------------------------------------------------- 1 | export class PlaybackProgress { 2 | public constructor(public progressSeconds: number, public totalSeconds: number) {} 3 | 4 | public get timeRemainingInMilliSeconds(): number { 5 | if (this.totalSeconds === 0) { 6 | return 0; 7 | } 8 | 9 | return (this.totalSeconds - this.progressSeconds) * 1000; 10 | } 11 | 12 | public get progressPercent(): number { 13 | if (this.totalSeconds === 0) { 14 | return 0; 15 | } 16 | 17 | return (this.progressSeconds / this.totalSeconds) * 100; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/services/playback/playback-started.ts: -------------------------------------------------------------------------------- 1 | import { TrackModel } from '../track/track-model'; 2 | 3 | export class PlaybackStarted { 4 | public constructor(public currentTrack: TrackModel, public isPlayingPreviousTrack: boolean) {} 5 | } 6 | -------------------------------------------------------------------------------- /src/app/services/playback/queue-restore-info.ts: -------------------------------------------------------------------------------- 1 | import { Queue } from './queue'; 2 | import { TrackModel } from '../track/track-model'; 3 | 4 | export class QueueRestoreInfo { 5 | public constructor( 6 | public tracks: TrackModel[], 7 | public playbackOrder: number[], 8 | public playingTrack: TrackModel | undefined, 9 | public progressSeconds: number, 10 | ) {} 11 | } 12 | -------------------------------------------------------------------------------- /src/app/services/playlist-folder/playlist-folder-model.ts: -------------------------------------------------------------------------------- 1 | import { StringUtils } from '../../common/utils/string-utils'; 2 | import { ISelectable } from '../../ui/interfaces/i-selectable'; 3 | 4 | export class PlaylistFolderModel implements ISelectable { 5 | public constructor( 6 | public name: string, 7 | public path: string, 8 | public isModifiable: boolean, 9 | ) {} 10 | 11 | public isSelected: boolean = false; 12 | 13 | public get isDefault(): boolean { 14 | return StringUtils.isNullOrWhiteSpace(this.name); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/app/services/playlist-folder/playlist-folder.service.base.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { PlaylistFolderModel } from './playlist-folder-model'; 3 | 4 | export abstract class PlaylistFolderServiceBase { 5 | public abstract playlistFoldersChanged$: Observable; 6 | public abstract createPlaylistFolder(playlistFolderName: string): void; 7 | public abstract getPlaylistFoldersAsync(): Promise; 8 | public abstract deletePlaylistFolder(playlistFolder: PlaylistFolderModel): void; 9 | public abstract renamePlaylistFolder(playlistFolder: PlaylistFolderModel, newName: string): void; 10 | } 11 | -------------------------------------------------------------------------------- /src/app/services/playlist-folder/playlist-folder.service.spec.ts: -------------------------------------------------------------------------------- 1 | describe('PlaylistFolderService', () => { 2 | test.todo('should write tests'); 3 | }); 4 | -------------------------------------------------------------------------------- /src/app/services/playlist/playlist-decoder.spec.ts: -------------------------------------------------------------------------------- 1 | // import { PlaylistDecoder } from './playlist-decoder'; 2 | 3 | describe('PlaylistDecoder', () => { 4 | test.todo('should write tests'); 5 | }); 6 | 7 | // describe('PlaylistDecoder', () => { 8 | // beforeEach(() => {}); 9 | 10 | // describe('constructor', () => { 11 | // it('should create', () => { 12 | // // Arrange 13 | 14 | // // Act 15 | // const playlistDecoder: PlaylistDecoder = new PlaylistDecoder('Path 1', 'Path 2'); 16 | 17 | // // Assert 18 | // expect(playlistDecoder).toBeDefined(); 19 | // }); 20 | // }); 21 | // }); 22 | -------------------------------------------------------------------------------- /src/app/services/playlist/playlist-entry.ts: -------------------------------------------------------------------------------- 1 | export class PlaylistEntry { 2 | public constructor(private _referencePath: string, private _decodedPath: string) {} 3 | 4 | public get referencePath(): string { 5 | return this._referencePath; 6 | } 7 | 8 | public get decodedPath(): string { 9 | return this._decodedPath; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/app/services/playlist/playlist-file-manager.spec.ts: -------------------------------------------------------------------------------- 1 | describe('FileValidator', () => { 2 | test.todo('should write tests'); 3 | }); 4 | -------------------------------------------------------------------------------- /src/app/services/playlist/playlist-model.ts: -------------------------------------------------------------------------------- 1 | import { StringUtils } from '../../common/utils/string-utils'; 2 | import { ISelectable } from '../../ui/interfaces/i-selectable'; 3 | 4 | export class PlaylistModel implements ISelectable { 5 | public constructor( 6 | public name: string, 7 | public folderName: string, 8 | public path: string, 9 | public imagePath: string, 10 | ) {} 11 | 12 | public isSelected: boolean = false; 13 | 14 | public get isDefault(): boolean { 15 | return StringUtils.isNullOrWhiteSpace(this.name); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/services/playlist/playlist-update-info.ts: -------------------------------------------------------------------------------- 1 | import { TrackModel } from '../track/track-model'; 2 | 3 | export class PlaylistUpdateInfo { 4 | public constructor( 5 | public playlistPath: string, 6 | public tracks: TrackModel[], 7 | ) {} 8 | } 9 | -------------------------------------------------------------------------------- /src/app/services/scrobbling/sign-in-state.ts: -------------------------------------------------------------------------------- 1 | export enum SignInState { 2 | SignedOut = 0, 3 | SignedIn = 1, 4 | Error = 2, 5 | } 6 | -------------------------------------------------------------------------------- /src/app/services/search/search.service.base.ts: -------------------------------------------------------------------------------- 1 | export abstract class SearchServiceBase { 2 | public abstract searchText: string; 3 | public abstract delayedSearchText: string; 4 | public abstract hasSearchText: boolean; 5 | public abstract matchesSearchText(originalText: string, searchText: string): boolean; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/services/semantic-zoom/semantic-zoom.service.base.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | 3 | export abstract class SemanticZoomServiceBase { 4 | public abstract zoomOutRequested$: Observable; 5 | public abstract zoomInRequested$: Observable; 6 | 7 | public abstract requestZoomOut(): void; 8 | public abstract requestZoomIn(text: string): void; 9 | } 10 | -------------------------------------------------------------------------------- /src/app/services/track-columns/tracks-columns-order-column.ts: -------------------------------------------------------------------------------- 1 | export enum TracksColumnsOrderColumn { 2 | none = 0, 3 | trackTitle = 1, 4 | rating = 2, 5 | artists = 3, 6 | album = 4, 7 | genres = 5, 8 | duration = 6, 9 | trackNumber = 7, 10 | year = 8, 11 | playCount = 9, 12 | skipCount = 10, 13 | dateLastPlayed = 11, 14 | dateAdded = 12, 15 | } 16 | -------------------------------------------------------------------------------- /src/app/services/track-columns/tracks-columns-order-direction.ts: -------------------------------------------------------------------------------- 1 | export enum TracksColumnsOrderDirection { 2 | ascending = 1, 3 | descending = 2, 4 | } 5 | -------------------------------------------------------------------------------- /src/app/services/track-columns/tracks-columns-order.ts: -------------------------------------------------------------------------------- 1 | import { TracksColumnsOrderColumn } from './tracks-columns-order-column'; 2 | import { TracksColumnsOrderDirection } from './tracks-columns-order-direction'; 3 | 4 | export class TracksColumnsOrder { 5 | public constructor( 6 | public tracksColumnsOrderColumn: TracksColumnsOrderColumn, 7 | public tracksColumnsOrderDirection: TracksColumnsOrderDirection 8 | ) {} 9 | } 10 | -------------------------------------------------------------------------------- /src/app/services/track-columns/tracks-columns-ordering.spec.ts: -------------------------------------------------------------------------------- 1 | describe('TracksColumnsOrdering', () => { 2 | test.todo('should write tests'); 3 | }); 4 | -------------------------------------------------------------------------------- /src/app/services/track-columns/tracks-columns-visibility.ts: -------------------------------------------------------------------------------- 1 | export class TracksColumnsVisibility { 2 | public showRating: boolean; 3 | public showArtists: boolean; 4 | public showAlbum: boolean; 5 | public showGenres: boolean; 6 | public showDuration: boolean; 7 | public showTrackNumber: boolean; 8 | public showYear: boolean; 9 | public showPlayCount: boolean; 10 | public showSkipCount: boolean; 11 | public showDateLastPlayed: boolean; 12 | public showDateAdded: boolean; 13 | } 14 | -------------------------------------------------------------------------------- /src/app/services/track/track-model-factory.spec.ts: -------------------------------------------------------------------------------- 1 | describe('TrackModelFactory', () => { 2 | test.todo('should write tests'); 3 | }); 4 | -------------------------------------------------------------------------------- /src/app/services/translator/translator.service.base.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { Language } from '../../common/application/language'; 3 | 4 | export abstract class TranslatorServiceBase { 5 | public abstract languageChanged$: Observable; 6 | public abstract languages: Language[]; 7 | public abstract selectedLanguage: Language; 8 | public abstract applyLanguage(): void; 9 | public abstract getAsync(key: string | Array, interpolateParams?: object): Promise; 10 | public abstract get(key: string | Array, interpolateParams?: object): string; 11 | } 12 | -------------------------------------------------------------------------------- /src/app/services/tray/tray.service.base.ts: -------------------------------------------------------------------------------- 1 | export abstract class TrayServiceBase { 2 | public abstract invertNotificationAreaIconColor: boolean; 3 | public abstract get needInvertNotificationAreaIconColor(): boolean; 4 | public abstract updateTrayContextMenu(): void; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/services/update/update.service.base.ts: -------------------------------------------------------------------------------- 1 | export abstract class UpdateServiceBase { 2 | public abstract isUpdateAvailable: boolean; 3 | public abstract latestRelease: string; 4 | public abstract checkForUpdatesAsync(): Promise; 5 | public abstract downloadLatestReleaseAsync(): Promise; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/services/welcome/welcome.service.base.ts: -------------------------------------------------------------------------------- 1 | export abstract class WelcomeServiceBase { 2 | public abstract isLoaded: boolean; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/services/welcome/welcome.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { WelcomeService } from './welcome.service'; 2 | 3 | describe('WelcomeService', () => { 4 | describe('constructor', () => { 5 | it('should create', () => { 6 | // Arrange, Act 7 | const service: WelcomeService = new WelcomeService(); 8 | 9 | // Assert 10 | expect(service).toBeDefined(); 11 | }); 12 | 13 | it('should define isLoaded', () => { 14 | // Arrange, Act 15 | const service: WelcomeService = new WelcomeService(); 16 | 17 | // Assert 18 | expect(service.isLoaded).toBeFalsy(); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/app/services/welcome/welcome.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { WelcomeServiceBase } from './welcome.service.base'; 3 | 4 | @Injectable() 5 | export class WelcomeService implements WelcomeServiceBase { 6 | public isLoaded: boolean; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/ui/components/add-folder/add-folder.component.scss: -------------------------------------------------------------------------------- 1 | .add-folder-component { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | .folder:hover { 7 | background: var(--theme-hovered-item-background); 8 | } 9 | 10 | .folder__icon { 11 | font-size: 18px; 12 | color: var(--theme-accent-color); 13 | transform: rotate(-90deg); 14 | } 15 | 16 | .folder__path { 17 | flex: 1; 18 | } 19 | .folder__action { 20 | font-size: 18px; 21 | opacity: 0.4; 22 | cursor: pointer; 23 | display: none; 24 | } 25 | 26 | .folder:hover .folder-item__action { 27 | background: var(--theme-hovered-item-background); 28 | display: block !important; 29 | } 30 | 31 | .folder__checkbox { 32 | margin-top: -4px !important; 33 | } 34 | -------------------------------------------------------------------------------- /src/app/ui/components/animated-page.ts: -------------------------------------------------------------------------------- 1 | export class AnimatedPage { 2 | public page: number = 0; 3 | public previousPage: number = -1; 4 | 5 | public rightToLeft(page: number): boolean { 6 | return this.page === page && this.previousPage > this.page; 7 | } 8 | 9 | public leftToRight(page: number): boolean { 10 | return this.page === page && this.previousPage <= this.page; 11 | } 12 | 13 | public setPage(page: number): void { 14 | this.previousPage = this.page; 15 | this.page = page; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/ui/components/back-button/back-button.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /src/app/ui/components/back-button/back-button.component.scss: -------------------------------------------------------------------------------- 1 | .back-button-background { 2 | background: linear-gradient(45deg, var(--theme-primary-color) 30%, var(--theme-secondary-color) 100%); 3 | width: 30px; 4 | min-width: 30px; 5 | height: 30px; 6 | border-radius: 15px 0px 15px 15px; 7 | display: flex; 8 | align-items: center; 9 | justify-content: center; 10 | -webkit-app-region: no-drag; 11 | cursor: pointer; 12 | } 13 | 14 | .back-button-background__icon { 15 | font-size: 20px !important; 16 | color: var(--theme-highlight-foreground); 17 | -webkit-transition: font-size 0.15s; 18 | } 19 | 20 | .back-button-background__icon:hover { 21 | font-size: 26px !important; 22 | } 23 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/album-browser/album-browser.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/collection/album-browser/album-browser.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/collection/album-browser/album-row.spec.ts: -------------------------------------------------------------------------------- 1 | import { AlbumRow } from './album-row'; 2 | 3 | describe('AlbumRow', () => { 4 | let albumRow: AlbumRow; 5 | 6 | beforeEach(() => { 7 | albumRow = new AlbumRow(); 8 | }); 9 | 10 | describe('constructor', () => { 11 | it('should create', () => { 12 | // Arrange 13 | 14 | // Act 15 | 16 | // Assert 17 | expect(albumRow).toBeDefined(); 18 | }); 19 | 20 | it('should define an empty list of albums', () => { 21 | // Arrange 22 | 23 | // Act 24 | 25 | // Assert 26 | expect(albumRow.albums).toBeDefined(); 27 | expect(albumRow.albums.length).toEqual(0); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/album-browser/album-row.ts: -------------------------------------------------------------------------------- 1 | import { AlbumModel } from '../../../../services/album/album-model'; 2 | 3 | export class AlbumRow { 4 | public albums: AlbumModel[] = []; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/album-browser/album/album.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { AlbumModel } from '../../../../../services/album/album-model'; 3 | import { AppearanceServiceBase } from '../../../../../services/appearance/appearance.service.base'; 4 | 5 | @Component({ 6 | selector: 'app-album', 7 | host: { style: 'display: block' }, 8 | templateUrl: './album.component.html', 9 | styleUrls: ['./album.component.scss'], 10 | }) 11 | export class AlbumComponent { 12 | public constructor(public appearanceService: AppearanceServiceBase) {} 13 | 14 | @Input() public album: AlbumModel; 15 | @Input() public isSelected: boolean = false; 16 | } 17 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/album-order.ts: -------------------------------------------------------------------------------- 1 | export enum AlbumOrder { 2 | byAlbumTitleAscending = 1, 3 | byAlbumTitleDescending = 2, 4 | byDateAdded = 3, 5 | byDateCreated = 4, 6 | byAlbumArtist = 5, 7 | byYearAscending = 6, 8 | byYearDescending = 7, 9 | byLastPlayed = 8, 10 | random = 9, 11 | } 12 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-albums/collection-albums.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/collection/collection-albums/collection-albums.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-artists/artist-browser/artist-browser.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/collection/collection-artists/artist-browser/artist-browser.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-artists/artist-browser/artist-order.ts: -------------------------------------------------------------------------------- 1 | export enum ArtistOrder { 2 | byArtistAscending = 1, 3 | byArtistDescending = 2, 4 | } 5 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-artists/artist/artist.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ artist.zoomHeader }} 4 |
5 |
15 | {{ artist.displayName }} 16 |
17 |
18 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-artists/collection-artists.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/collection/collection-artists/collection-artists.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-folders/collection-folders.component.scss: -------------------------------------------------------------------------------- 1 | .subfolder { 2 | height: 30px; 3 | } 4 | 5 | .subfolder:hover { 6 | background: var(--theme-hovered-item-background); 7 | } 8 | 9 | .subfolder:active { 10 | background: var(--theme-hovered-item-background); 11 | } 12 | 13 | .folder-browser__list { 14 | height: 100%; 15 | width: 100%; 16 | overflow-x: hidden !important; 17 | overflow-y: auto; 18 | } 19 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-folders/folder-tracks-persister.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Logger } from '../../../../common/logger'; 3 | import { BaseTracksPersister } from '../base-tracks-persister'; 4 | import { SettingsBase } from '../../../../common/settings/settings.base'; 5 | 6 | @Injectable() 7 | export class FolderTracksPersister extends BaseTracksPersister { 8 | public constructor( 9 | public settings: SettingsBase, 10 | public logger: Logger, 11 | ) { 12 | super(settings, logger); 13 | } 14 | 15 | public getSelectedTrackOrderFromSettings(): string { 16 | return 'none'; 17 | } 18 | public saveSelectedTrackOrderToSettings(): void { 19 | // Do nothing 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-genres/collection-genres.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/collection/collection-genres/collection-genres.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-genres/genre-browser/genre-browser.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/collection/collection-genres/genre-browser/genre-browser.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-genres/genre-browser/genre-order.ts: -------------------------------------------------------------------------------- 1 | export enum GenreOrder { 2 | byGenreAscending = 1, 3 | byGenreDescending = 2, 4 | } 5 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-genres/genre/genre.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ genre.zoomHeader }} 4 |
5 |
13 | {{ genre.displayName }} 14 |
15 |
16 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-playlists/collection-playlists.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/collection/collection-playlists/collection-playlists.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-playlists/playlist-browser/playlist-browser.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/collection/collection-playlists/playlist-browser/playlist-browser.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-playlists/playlist-browser/playlist-browser.component.spec.ts: -------------------------------------------------------------------------------- 1 | describe('PlaylistBrowserComponent', () => { 2 | test.todo('should write tests'); 3 | }); 4 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-playlists/playlist-browser/playlist-row.ts: -------------------------------------------------------------------------------- 1 | import { PlaylistModel } from '../../../../../services/playlist/playlist-model'; 2 | 3 | export class PlaylistRow { 4 | public playlists: PlaylistModel[] = []; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-playlists/playlist-browser/playlist/playlist.component.spec.ts: -------------------------------------------------------------------------------- 1 | describe('PlaylistComponent', () => { 2 | test.todo('should write tests'); 3 | }); 4 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-playlists/playlist-browser/playlist/playlist.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { PlaylistModel } from '../../../../../../services/playlist/playlist-model'; 3 | import { AppearanceServiceBase } from '../../../../../../services/appearance/appearance.service.base'; 4 | 5 | @Component({ 6 | selector: 'app-playlist', 7 | host: { style: 'display: block' }, 8 | templateUrl: './playlist.component.html', 9 | styleUrls: ['./playlist.component.scss'], 10 | }) 11 | export class PlaylistComponent { 12 | public constructor(public appearanceService: AppearanceServiceBase) {} 13 | 14 | @Input() public playlist: PlaylistModel; 15 | @Input() public isSelected: boolean = false; 16 | } 17 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-playlists/playlist-order.ts: -------------------------------------------------------------------------------- 1 | export enum PlaylistOrder { 2 | byPlaylistNameAscending = 1, 3 | byPlaylistNameDescending = 2, 4 | } 5 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-playlists/playlist-persister.spec.ts: -------------------------------------------------------------------------------- 1 | describe('PlaylistsPersister', () => { 2 | test.todo('should write tests'); 3 | }); 4 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-playlists/playlist-track-browser/playlist-track-browser.component.scss: -------------------------------------------------------------------------------- 1 | .app-playlist-track-browser { 2 | height: 100%; 3 | width: 100%; 4 | display: flex; 5 | flex-direction: column; 6 | } 7 | 8 | .app-playlist-track-browser__header { 9 | display: flex; 10 | flex-direction: row; 11 | justify-content: space-between; 12 | } 13 | 14 | .app-playlist-track-browser__trackscount { 15 | display: flex; 16 | flex-direction: row; 17 | } 18 | 19 | .app-playlist-track-browser__trackscount__number { 20 | color: var(--theme-accent-color); 21 | } 22 | 23 | .app-playlist-track-browser__tracks { 24 | flex: 1; 25 | } 26 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-playlists/playlist-track-browser/playlist-track-browser.component.spec.ts: -------------------------------------------------------------------------------- 1 | describe('PlaylistTrackBrowserComponent', () => { 2 | test.todo('should write tests'); 3 | }); 4 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-tracks/collection-tracks-table/collection-tracks-table-header/collection-tracks-table-header.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/collection/collection-tracks/collection-tracks-table/collection-tracks-table-header/collection-tracks-table-header.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-tracks/collection-tracks-table/collection-tracks-table-header/collection-tracks-table-header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-collection-tracks-table-header', 5 | host: { style: 'display: block' }, 6 | templateUrl: './collection-tracks-table-header.component.html', 7 | styleUrls: ['./collection-tracks-table-header.component.scss'], 8 | encapsulation: ViewEncapsulation.None, 9 | }) 10 | export class CollectionTracksTableHeaderComponent { 11 | @Input() public text: string; 12 | @Input() public icon: string; 13 | @Input() public isOrderedBy: boolean; 14 | @Input() public isOrderedAscending: boolean; 15 | } 16 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-tracks/collection-tracks-table/collection-tracks-table.component.scss: -------------------------------------------------------------------------------- 1 | .tracks-table { 2 | width: 100%; 3 | table-layout: fixed; 4 | border-collapse: collapse; 5 | overflow-x: hidden; 6 | } 7 | 8 | .tracks-table td { 9 | padding-left: 8px; 10 | padding-right: 8px; 11 | text-align: left; 12 | } 13 | 14 | .tracks-table-row { 15 | height: 28px; 16 | } 17 | 18 | .tracks-table-row:hover { 19 | background: var(--theme-hovered-item-background); 20 | } 21 | 22 | .tracks-table-header { 23 | height: 28px; 24 | } 25 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-tracks/collection-tracks.component.html: -------------------------------------------------------------------------------- 1 |
2 | 6 |
7 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/collection-tracks/collection-tracks.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/collection/collection-tracks/collection-tracks.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/collection/semantic-zoom/semantic-zoom-button/semantic-zoom-button.component.html: -------------------------------------------------------------------------------- 1 |
2 | {{ this.text }} 3 |
4 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/semantic-zoom/semantic-zoom-button/semantic-zoom-button.component.scss: -------------------------------------------------------------------------------- 1 | .app-semantic-zoom-button { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | background: linear-gradient(45deg, var(--theme-primary-color) 30%, var(--theme-secondary-color) 100%); 6 | color: var(--theme-highlight-foreground); 7 | width: 34px; 8 | height: 34px; 9 | border-radius: 16px 0px 16px 16px; 10 | font-size: 16px; 11 | margin: auto; 12 | } 13 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/semantic-zoom/semantic-zoom.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 10 | 11 |
5 | 9 |
12 |
13 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/semantic-zoom/semantic-zoom.component.scss: -------------------------------------------------------------------------------- 1 | .semantic-table { 2 | width: 100%; 3 | height: 100%; 4 | min-width: 200px; 5 | min-height: 300px; 6 | max-width: 300px; 7 | max-height: 600px; 8 | } 9 | 10 | .semantic-row { 11 | width: 100%; 12 | } 13 | 14 | .semantic-column { 15 | width: 25%; 16 | height: 14%; 17 | vertical-align: middle; 18 | } 19 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/totals/totals.component.html: -------------------------------------------------------------------------------- 1 |
2 | {{ totalFileSizeInBytes | formatTotalFileSize }} 3 | {{ totalDurationInMilliseconds | formatTotalDuration }} 4 |
5 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/totals/totals.component.scss: -------------------------------------------------------------------------------- 1 | .app-totals { 2 | display: flex; 3 | flex-direction: row; 4 | justify-content: space-between; 5 | flex-wrap: wrap; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/totals/totals.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-totals', 5 | host: { style: 'display: block;' }, 6 | templateUrl: './totals.component.html', 7 | styleUrls: ['./totals.component.scss'], 8 | }) 9 | export class TotalsComponent { 10 | @Input() public totalFileSizeInBytes: number = 0; 11 | @Input() public totalDurationInMilliseconds: number = 0; 12 | } 13 | -------------------------------------------------------------------------------- /src/app/ui/components/collection/track-browser/track-browser.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/collection/track-browser/track-browser.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/collection/track-order.ts: -------------------------------------------------------------------------------- 1 | export enum TrackOrder { 2 | byTrackTitleAscending = 1, 3 | byTrackTitleDescending = 2, 4 | byAlbum = 3, 5 | byRating = 4, 6 | none = 5, 7 | } 8 | -------------------------------------------------------------------------------- /src/app/ui/components/context-menu-opener.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { MatMenuTrigger } from '@angular/material/menu'; 3 | import { ISelectable } from '../interfaces/i-selectable'; 4 | 5 | @Injectable() 6 | export class ContextMenuOpener { 7 | public positionX: string = '0px'; 8 | public positionY: string = '0px'; 9 | 10 | public open(contextMenu: MatMenuTrigger, event: MouseEvent, selectable: ISelectable): void { 11 | event.preventDefault(); 12 | 13 | this.positionX = `${event.clientX}px`; 14 | this.positionY = `${event.clientY}px`; 15 | 16 | contextMenu.menuData = { data: selectable }; 17 | contextMenu.menu?.focusFirstItem('mouse'); 18 | contextMenu.openMenu(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/ui/components/controls/accent-button/accent-button.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | -------------------------------------------------------------------------------- /src/app/ui/components/controls/accent-button/accent-button.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { AccentButtonComponent } from './accent-button.component'; 2 | 3 | describe('AccentButtonComponent', () => { 4 | let component: AccentButtonComponent; 5 | 6 | beforeEach(() => { 7 | component = new AccentButtonComponent(); 8 | }); 9 | 10 | describe('constructor', () => { 11 | it('should create', () => { 12 | // Arrange 13 | 14 | // Act 15 | 16 | // Assert 17 | expect(component).toBeDefined(); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/app/ui/components/controls/accent-button/accent-button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-accent-button', 5 | templateUrl: './accent-button.component.html', 6 | styleUrls: ['./accent-button.component.scss'], 7 | host: { 8 | style: 'display: inline-block', 9 | }, 10 | }) 11 | export class AccentButtonComponent {} 12 | -------------------------------------------------------------------------------- /src/app/ui/components/controls/big-icon-button/big-icon-button.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | -------------------------------------------------------------------------------- /src/app/ui/components/controls/big-icon-button/big-icon-button.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { BigIconButtonComponent } from './big-icon-button.component'; 4 | 5 | describe('BigIconButtonComponent', () => { 6 | let component: BigIconButtonComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [BigIconButtonComponent] 12 | }); 13 | fixture = TestBed.createComponent(BigIconButtonComponent); 14 | component = fixture.componentInstance; 15 | fixture.detectChanges(); 16 | }); 17 | 18 | it('should create', () => { 19 | expect(component).toBeTruthy(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/app/ui/components/controls/big-icon-button/big-icon-button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-big-icon-button', 5 | templateUrl: './big-icon-button.component.html', 6 | styleUrls: ['./big-icon-button.component.scss'], 7 | }) 8 | export class BigIconButtonComponent { 9 | @Input() public icon: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/app/ui/components/controls/icon-button/icon-button.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | -------------------------------------------------------------------------------- /src/app/ui/components/controls/icon-button/icon-button.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { IconButtonComponent } from './icon-button.component'; 4 | 5 | describe('IconButtonComponent', () => { 6 | let component: IconButtonComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [IconButtonComponent] 12 | }); 13 | fixture = TestBed.createComponent(IconButtonComponent); 14 | component = fixture.componentInstance; 15 | fixture.detectChanges(); 16 | }); 17 | 18 | it('should create', () => { 19 | expect(component).toBeTruthy(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/app/ui/components/controls/icon-button/icon-button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-icon-button', 5 | templateUrl: './icon-button.component.html', 6 | styleUrls: ['./icon-button.component.scss'], 7 | }) 8 | export class IconButtonComponent { 9 | @Input() public icon: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/app/ui/components/controls/icon-text-button/icon-text-button.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 | -------------------------------------------------------------------------------- /src/app/ui/components/controls/icon-text-button/icon-text-button.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { IconTextButtonComponent } from './icon-text-button.component'; 2 | 3 | describe('IconTextButtonComponent', () => { 4 | let component: IconTextButtonComponent; 5 | 6 | beforeEach(() => { 7 | component = new IconTextButtonComponent(); 8 | }); 9 | 10 | describe('constructor', () => { 11 | it('should create', () => { 12 | // Arrange 13 | 14 | // Act 15 | 16 | // Assert 17 | expect(component).toBeDefined(); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/app/ui/components/controls/icon-text-button/icon-text-button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-icon-text-button', 5 | templateUrl: './icon-text-button.component.html', 6 | styleUrls: ['./icon-text-button.component.scss'], 7 | host: { 8 | style: 'display: inline-block', 9 | }, 10 | }) 11 | export class IconTextButtonComponent { 12 | @Input() public icon: string; 13 | } 14 | -------------------------------------------------------------------------------- /src/app/ui/components/controls/text-icon-secondary-button/text-icon-secondary-button.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
7 | -------------------------------------------------------------------------------- /src/app/ui/components/controls/text-icon-secondary-button/text-icon-secondary-button.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TextIconSecondaryButtonComponent } from './text-icon-secondary-button.component'; 2 | 3 | describe('TextIconSecondaryButtonComponent', () => { 4 | let component: TextIconSecondaryButtonComponent; 5 | 6 | beforeEach(() => { 7 | component = new TextIconSecondaryButtonComponent(); 8 | }); 9 | 10 | describe('constructor', () => { 11 | it('should create', () => { 12 | // Arrange 13 | 14 | // Act 15 | 16 | // Assert 17 | expect(component).toBeDefined(); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/app/ui/components/controls/text-icon-secondary-button/text-icon-secondary-button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-text-icon-secondary-button', 5 | templateUrl: './text-icon-secondary-button.component.html', 6 | styleUrls: ['./text-icon-secondary-button.component.scss'], 7 | host: { 8 | style: 'display: inline-block', 9 | }, 10 | }) 11 | export class TextIconSecondaryButtonComponent { 12 | @Input() public icon: string; 13 | } 14 | -------------------------------------------------------------------------------- /src/app/ui/components/controls/toggle-switch/toggle-switch.component.html: -------------------------------------------------------------------------------- 1 |
2 | 7 |
8 | 9 |
10 |
11 | -------------------------------------------------------------------------------- /src/app/ui/components/controls/toggle-switch/toggle-switch.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ToggleSwitchComponent } from './toggle-switch.component'; 4 | 5 | describe('ToggleSwitchComponent', () => { 6 | let component: ToggleSwitchComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ToggleSwitchComponent] 12 | }); 13 | fixture = TestBed.createComponent(ToggleSwitchComponent); 14 | component = fixture.componentInstance; 15 | fixture.detectChanges(); 16 | }); 17 | 18 | it('should create', () => { 19 | expect(component).toBeTruthy(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/app/ui/components/controls/toggle-switch/toggle-switch.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-toggle-switch', 5 | templateUrl: './toggle-switch.component.html', 6 | styleUrls: ['./toggle-switch.component.scss'], 7 | }) 8 | export class ToggleSwitchComponent { 9 | @Input() 10 | public isChecked: boolean = false; 11 | 12 | @Output() 13 | public isCheckedChange: EventEmitter = new EventEmitter(); 14 | 15 | public onCheckedChanged(checked: boolean): void { 16 | this.isChecked = checked; 17 | this.isCheckedChange.emit(this.isChecked); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/ui/components/controls/transparent-button/transparent-button.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | -------------------------------------------------------------------------------- /src/app/ui/components/controls/transparent-button/transparent-button.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TransparentButtonComponent } from './transparent-button.component'; 2 | 3 | describe('TransparentButtonComponent', () => { 4 | let component: TransparentButtonComponent; 5 | 6 | beforeEach(() => { 7 | component = new TransparentButtonComponent(); 8 | }); 9 | 10 | describe('constructor', () => { 11 | it('should create', () => { 12 | // Arrange 13 | 14 | // Act 15 | 16 | // Assert 17 | expect(component).toBeDefined(); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/app/ui/components/controls/transparent-button/transparent-button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-transparent-button', 5 | templateUrl: './transparent-button.component.html', 6 | styleUrls: ['./transparent-button.component.scss'], 7 | host: { 8 | style: 'display: inline-block', 9 | }, 10 | }) 11 | export class TransparentButtonComponent { 12 | @Input() 13 | public fill: boolean = false; 14 | } 15 | -------------------------------------------------------------------------------- /src/app/ui/components/dialogs/confirmation-dialog/confirmation-dialog.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ data?.dialogText }} 4 | 5 | 6 | {{ 'yes' | translate }} 7 | {{ 'no' | translate }} 8 | 9 | -------------------------------------------------------------------------------- /src/app/ui/components/dialogs/confirmation-dialog/confirmation-dialog.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/dialogs/confirmation-dialog/confirmation-dialog.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/dialogs/dialog-header/dialog-header.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
{{ title }}
4 |
5 | -------------------------------------------------------------------------------- /src/app/ui/components/dialogs/dialog-header/dialog-header.component.scss: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | 3 | .dialog-header-component { 4 | display: flex; 5 | flex-direction: row; 6 | align-items: center; 7 | } 8 | 9 | .dialog-header-component__icon { 10 | background: linear-gradient(45deg, var(--theme-primary-color) 30%, var(--theme-secondary-color) 100%); 11 | width: 32px; 12 | min-width: 32px; 13 | height: 32px; 14 | border-radius: 16px 0px 16px 16px; 15 | display: flex; 16 | align-items: center; 17 | justify-content: center; 18 | font-size: 18px; 19 | color: var(--theme-highlight-foreground); 20 | } 21 | -------------------------------------------------------------------------------- /src/app/ui/components/dialogs/dialog-header/dialog-header.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { DialogHeaderComponent } from './dialog-header.component'; 2 | 3 | describe('DialogHeaderComponent', () => { 4 | let component: DialogHeaderComponent; 5 | 6 | beforeEach(() => { 7 | component = new DialogHeaderComponent(); 8 | }); 9 | 10 | describe('constructor', () => { 11 | it('should create', () => { 12 | // Arrange 13 | 14 | // Act 15 | 16 | // Assert 17 | expect(component).toBeDefined(); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/app/ui/components/dialogs/dialog-header/dialog-header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-dialog-header', 5 | templateUrl: './dialog-header.component.html', 6 | styleUrls: ['./dialog-header.component.scss'], 7 | encapsulation: ViewEncapsulation.None, 8 | }) 9 | export class DialogHeaderComponent { 10 | @Input() public icon: string; 11 | @Input() public title: string; 12 | } 13 | -------------------------------------------------------------------------------- /src/app/ui/components/dialogs/edit-columns-dialog/edit-columns-dialog.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/dialogs/edit-columns-dialog/edit-columns-dialog.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/dialogs/error-dialog/error-dialog.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

{{ data?.errorText }}

4 |

{{ 'global-error' | translate }}

5 |
6 | 7 | {{ 'ok' | translate }} 8 | {{ 'view-log' | translate }} 9 | 10 | -------------------------------------------------------------------------------- /src/app/ui/components/dialogs/error-dialog/error-dialog.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/dialogs/error-dialog/error-dialog.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/dialogs/info-dialog/info-dialog.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

{{ data?.infoText }}

4 |
5 | 6 | {{ 'ok' | translate }} 7 | 8 | -------------------------------------------------------------------------------- /src/app/ui/components/dialogs/info-dialog/info-dialog.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/dialogs/info-dialog/info-dialog.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/dialogs/input-dialog/input-dialog.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ 9 | 'ok' | translate 10 | }} 11 | {{ 'cancel' | translate }} 12 | 13 | -------------------------------------------------------------------------------- /src/app/ui/components/dialogs/input-dialog/input-dialog.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/dialogs/input-dialog/input-dialog.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/dialogs/license-dialog/license-dialog.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/dialogs/license-dialog/license-dialog.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/dialogs/license-dialog/license-dialog.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { LicenseDialogComponent } from './license-dialog.component'; 2 | 3 | describe('LicenseDialogComponent', () => { 4 | let component: LicenseDialogComponent; 5 | 6 | beforeEach(() => { 7 | component = new LicenseDialogComponent(); 8 | }); 9 | 10 | describe('constructor', () => { 11 | it('should create', () => { 12 | // Arrange 13 | 14 | // Act 15 | 16 | // Assert 17 | expect(component).toBeDefined(); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/app/ui/components/dialogs/license-dialog/license-dialog.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-license-dialog', 5 | templateUrl: './license-dialog.component.html', 6 | styleUrls: ['./license-dialog.component.scss'], 7 | encapsulation: ViewEncapsulation.None, 8 | }) 9 | export class LicenseDialogComponent {} 10 | -------------------------------------------------------------------------------- /src/app/ui/components/information/about/about.component.scss: -------------------------------------------------------------------------------- 1 | .contact-item { 2 | display: flex; 3 | flex-direction: row; 4 | align-items: center; 5 | } 6 | 7 | .contact-item-icon { 8 | width: 36px; 9 | font-size: 28px; 10 | } 11 | 12 | .contact-item-icon-fa { 13 | padding-left: 3px; 14 | width: 36px; 15 | font-size: 18px; 16 | } 17 | -------------------------------------------------------------------------------- /src/app/ui/components/information/components/components.component.scss: -------------------------------------------------------------------------------- 1 | .component__name { 2 | text-transform: uppercase; 3 | color: var(--theme-secondary-text); 4 | } 5 | -------------------------------------------------------------------------------- /src/app/ui/components/information/information.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/information/information.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/loading/loading.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 |
7 |
8 |
9 | 10 |

{{ 'loading' | translate }}

11 | 12 |
13 |
14 |
15 | -------------------------------------------------------------------------------- /src/app/ui/components/loading/loading.component.scss: -------------------------------------------------------------------------------- 1 | .loading-component { 2 | width: 100%; 3 | height: 100%; 4 | padding: 64px; 5 | display: flex; 6 | flex-direction: column; 7 | justify-content: center; 8 | align-items: center; 9 | } 10 | -------------------------------------------------------------------------------- /src/app/ui/components/logo-full/logo-full.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | {{ applicationName }} 6 | 3 7 |
8 | -------------------------------------------------------------------------------- /src/app/ui/components/logo-full/logo-full.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | import { ProductInformation } from '../../../common/application/product-information'; 3 | 4 | @Component({ 5 | selector: 'app-logo-full', 6 | host: { style: 'display: block' }, 7 | templateUrl: './logo-full.component.html', 8 | styleUrls: ['./logo-full.component.scss'], 9 | encapsulation: ViewEncapsulation.None, 10 | }) 11 | export class LogoFullComponent { 12 | public applicationName: string = ProductInformation.applicationName.toLowerCase(); 13 | } 14 | -------------------------------------------------------------------------------- /src/app/ui/components/logo-small/logo-small.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | -------------------------------------------------------------------------------- /src/app/ui/components/logo-small/logo-small.component.scss: -------------------------------------------------------------------------------- 1 | .logo-small__background { 2 | background: linear-gradient(45deg, var(--theme-primary-color) 30%, var(--theme-secondary-color) 100%); 3 | width: 30px; 4 | min-width: 30px; 5 | height: 30px; 6 | border-radius: 15px 0px 15px 15px; 7 | display: flex; 8 | align-items: center; 9 | justify-content: center; 10 | } 11 | 12 | .logo-small__image { 13 | width: 30px; 14 | height: 30px; 15 | } 16 | -------------------------------------------------------------------------------- /src/app/ui/components/logo-small/logo-small.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { LogoSmallComponent } from './logo-small.component'; 2 | 3 | describe('LogoSmallComponent', () => { 4 | let component: LogoSmallComponent; 5 | 6 | beforeEach(() => { 7 | component = new LogoSmallComponent(); 8 | }); 9 | 10 | describe('constructor', () => { 11 | it('should create', () => { 12 | // Arrange 13 | 14 | // Act 15 | 16 | // Assert 17 | expect(component).toBeDefined(); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/app/ui/components/logo-small/logo-small.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-logo-small', 5 | host: { style: 'display: block' }, 6 | templateUrl: './logo-small.component.html', 7 | styleUrls: ['./logo-small.component.scss'], 8 | encapsulation: ViewEncapsulation.None, 9 | }) 10 | export class LogoSmallComponent {} 11 | -------------------------------------------------------------------------------- /src/app/ui/components/love/love.component.html: -------------------------------------------------------------------------------- 1 |
8 | -------------------------------------------------------------------------------- /src/app/ui/components/love/love.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/love/love.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/main-menu/main-menu.component.scss: -------------------------------------------------------------------------------- 1 | .main-menu__button { 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | 7 | .main-menu__icon { 8 | position: absolute; 9 | left: 50%; 10 | top: 50%; 11 | transform: translate(-50%, -50%); 12 | font-size: 20px !important; 13 | color: var(--theme-window-button-icon) !important; 14 | z-index: 0; 15 | } 16 | 17 | .main-menu__updateindicator { 18 | position: absolute; 19 | top: 12px; 20 | right: 12px; 21 | width: 8px; 22 | height: 8px; 23 | border-radius: 3px; 24 | background-color: var(--theme-accent-color) !important; 25 | z-index: 1; 26 | } 27 | -------------------------------------------------------------------------------- /src/app/ui/components/manage-collection/manage-albums/manage-albums.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/manage-collection/manage-albums/manage-albums.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/manage-collection/manage-collection.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/manage-collection/manage-collection.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/manage-collection/manage-music/manage-music.component.html: -------------------------------------------------------------------------------- 1 |
2 |
{{ 'folders' | translate }}
3 |
{{ 'add-folders-with-your-music' | translate }}
4 | 5 |
6 | -------------------------------------------------------------------------------- /src/app/ui/components/manage-collection/manage-music/manage-music.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/manage-collection/manage-music/manage-music.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/manage-collection/manage-music/manage-music.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ManageMusicComponent } from './manage-music.component'; 2 | 3 | describe('ManageMusicComponent', () => { 4 | let component: ManageMusicComponent; 5 | 6 | beforeEach(() => { 7 | component = new ManageMusicComponent(); 8 | }); 9 | 10 | describe('constructor', () => { 11 | it('should create', () => { 12 | // Arrange 13 | 14 | // Act 15 | 16 | // Assert 17 | expect(component).toBeDefined(); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/app/ui/components/manage-collection/manage-music/manage-music.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-manage-music', 5 | host: { style: 'display: block; width: 100%;' }, 6 | templateUrl: './manage-music.component.html', 7 | styleUrls: ['./manage-music.component.scss'], 8 | encapsulation: ViewEncapsulation.None, 9 | }) 10 | export class ManageMusicComponent {} 11 | -------------------------------------------------------------------------------- /src/app/ui/components/manage-collection/manage-refresh/manage-refresh.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/manage-collection/manage-refresh/manage-refresh.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/metro-page-container/metro-page-container.component.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /src/app/ui/components/metro-page-container/metro-page-container.component.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: row; 4 | overflow-y: auto; 5 | overflow-x: hidden; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/ui/components/metro-page-container/metro-page-container.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { MetroPageContainerComponent } from './metro-page-container.component'; 2 | 3 | describe('MetroPageContainerComponent', () => { 4 | describe('constructor', () => { 5 | it('should create', () => { 6 | // Arrange 7 | 8 | // Act 9 | const component: MetroPageContainerComponent = new MetroPageContainerComponent(); 10 | 11 | // Assert 12 | expect(component).toBeDefined(); 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/app/ui/components/metro-page-container/metro-page-container.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-metro-page-container', 5 | host: { style: 'display: block' }, 6 | templateUrl: './metro-page-container.component.html', 7 | styleUrls: ['./metro-page-container.component.scss'], 8 | }) 9 | export class MetroPageContainerComponent {} 10 | -------------------------------------------------------------------------------- /src/app/ui/components/metro-page-container/metro-page/metro-page.component.html: -------------------------------------------------------------------------------- 1 |
11 | 12 |
13 | -------------------------------------------------------------------------------- /src/app/ui/components/metro-page-container/metro-page/metro-page.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/metro-page-container/metro-page/metro-page.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/mini-players/cover-player/cover-player-playback-queue/cover-player-playback-queue.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/ui/components/mini-players/cover-player/cover-player-playback-queue/cover-player-playback-queue.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/mini-players/cover-player/cover-player-playback-queue/cover-player-playback-queue.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/mini-players/cover-player/cover-player-playback-queue/cover-player-playback-queue.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { CoverPlayerPlaybackQueueComponent } from './cover-player-playback-queue.component'; 2 | 3 | describe('CoverPlayerPlaybackQueueComponent', () => { 4 | let component: CoverPlayerPlaybackQueueComponent; 5 | 6 | beforeEach(() => { 7 | component = new CoverPlayerPlaybackQueueComponent(); 8 | }); 9 | 10 | describe('constructor', () => { 11 | it('should create', () => { 12 | // Assert 13 | expect(component).toBeDefined(); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/app/ui/components/mini-players/cover-player/cover-player-playback-queue/cover-player-playback-queue.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, inject, ViewEncapsulation } from '@angular/core'; 2 | import { MatBottomSheet } from '@angular/material/bottom-sheet'; 3 | 4 | @Component({ 5 | selector: 'app-cover-player-playback-queue', 6 | host: { style: 'display: block' }, 7 | templateUrl: './cover-player-playback-queue.component.html', 8 | styleUrls: ['./cover-player-playback-queue.component.scss'], 9 | encapsulation: ViewEncapsulation.None, 10 | }) 11 | export class CoverPlayerPlaybackQueueComponent {} 12 | -------------------------------------------------------------------------------- /src/app/ui/components/mini-players/cover-player/cover-player-volume-control/cover-player-volume-control.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/ui/components/mini-players/cover-player/cover-player-volume-control/cover-player-volume-control.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/mini-players/cover-player/cover-player-volume-control/cover-player-volume-control.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/mini-players/cover-player/cover-player-volume-control/cover-player-volume-control.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { CoverPlayerVolumeControlComponent } from './cover-player-volume-control.component'; 2 | 3 | describe('CoverPlayerVolumeControlComponent', () => { 4 | let component: CoverPlayerVolumeControlComponent; 5 | 6 | beforeEach(() => { 7 | component = new CoverPlayerVolumeControlComponent(); 8 | }); 9 | 10 | describe('constructor', () => { 11 | it('should create', () => { 12 | // Assert 13 | expect(component).toBeDefined(); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/app/ui/components/mini-players/cover-player/cover-player-volume-control/cover-player-volume-control.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-cover-player-volume-control', 5 | host: { style: 'display: block' }, 6 | templateUrl: './cover-player-volume-control.component.html', 7 | styleUrls: ['./cover-player-volume-control.component.scss'], 8 | encapsulation: ViewEncapsulation.None, 9 | }) 10 | export class CoverPlayerVolumeControlComponent { 11 | public constructor() {} 12 | } 13 | -------------------------------------------------------------------------------- /src/app/ui/components/notification-bar/notification-bar.component.scss: -------------------------------------------------------------------------------- 1 | .notification-bar__background { 2 | background: linear-gradient(45deg, var(--theme-primary-color) 30%, var(--theme-secondary-color) 100%); 3 | } 4 | 5 | .notification-bar__icon { 6 | color: var(--theme-highlight-foreground); 7 | font-size: 18px; 8 | } 9 | 10 | .notification-bar__text { 11 | color: var(--theme-highlight-foreground); 12 | } 13 | 14 | .notification-bar__closebutton { 15 | color: var(--theme-highlight-foreground); 16 | font-size: 18px; 17 | cursor: pointer; 18 | } 19 | -------------------------------------------------------------------------------- /src/app/ui/components/now-playing/now-playing-artist-info/similar-artist/similar-artist.component.html: -------------------------------------------------------------------------------- 1 |
6 | 9 |
10 | 11 |
12 |
13 |

{{ this.similarArtist.name }}

14 |
15 |
16 | -------------------------------------------------------------------------------- /src/app/ui/components/now-playing/now-playing-artist-info/similar-artist/similar-artist.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, ViewEncapsulation } from '@angular/core'; 2 | import { ArtistInformation } from '../../../../../services/artist-information/artist-information'; 3 | 4 | @Component({ 5 | selector: 'similar-artist', 6 | host: { style: 'display: block' }, 7 | templateUrl: './similar-artist.component.html', 8 | styleUrls: ['./similar-artist.component.scss'], 9 | encapsulation: ViewEncapsulation.None, 10 | }) 11 | export class SimilarArtistComponent { 12 | @Input() 13 | public similarArtist: ArtistInformation = ArtistInformation.empty(); 14 | } 15 | -------------------------------------------------------------------------------- /src/app/ui/components/now-playing/now-playing-nothing-playing/now-playing-nothing-playing.component.html: -------------------------------------------------------------------------------- 1 | 
2 |
{{ 'nothing-is-playing' | translate }}
3 |
4 | {{ 'play-all' | translate }} 5 | {{ 'shuffle-all' | translate }} 6 |
7 |
8 | -------------------------------------------------------------------------------- /src/app/ui/components/now-playing/now-playing-nothing-playing/now-playing-nothing-playing.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/now-playing/now-playing-nothing-playing/now-playing-nothing-playing.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/playback-controls/playback-controls.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | import { LoopMode } from '../../../services/playback/loop-mode'; 3 | import { PlaybackService } from '../../../services/playback/playback.service'; 4 | 5 | @Component({ 6 | selector: 'app-playback-controls', 7 | host: { style: 'display: block' }, 8 | templateUrl: './playback-controls.component.html', 9 | styleUrls: ['./playback-controls.component.scss'], 10 | encapsulation: ViewEncapsulation.None, 11 | }) 12 | export class PlaybackControlsComponent { 13 | public constructor(public playbackService: PlaybackService) {} 14 | 15 | // This is required to use enum values in the template 16 | public loopModeEnum: typeof LoopMode = LoopMode; 17 | } 18 | -------------------------------------------------------------------------------- /src/app/ui/components/playback-indicator/playback-indicator.component.html: -------------------------------------------------------------------------------- 1 |
2 |
5 |
8 |
11 |
14 |
17 |
18 | -------------------------------------------------------------------------------- /src/app/ui/components/playback-indicator/playback-indicator.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, ViewEncapsulation } from '@angular/core'; 2 | import { AppearanceServiceBase } from '../../../services/appearance/appearance.service.base'; 3 | 4 | @Component({ 5 | selector: 'app-playback-indicator', 6 | host: { style: 'display: block' }, 7 | templateUrl: './playback-indicator.component.html', 8 | styleUrls: ['./playback-indicator.component.scss'], 9 | encapsulation: ViewEncapsulation.None, 10 | }) 11 | export class PlaybackIndicatorComponent { 12 | public constructor(public appearanceService: AppearanceServiceBase) {} 13 | 14 | @Input() 15 | public isSelected: boolean; 16 | } 17 | -------------------------------------------------------------------------------- /src/app/ui/components/playback-information/playback-information.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/playback-information/playback-information.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/playback-progress/playback-progress.component.html: -------------------------------------------------------------------------------- 1 |
7 |
8 |
9 |
16 |
17 | -------------------------------------------------------------------------------- /src/app/ui/components/playback-queue/playback-queue.component.scss: -------------------------------------------------------------------------------- 1 | .playback-queue__trackscount { 2 | color: var(--theme-accent-color); 3 | } 4 | -------------------------------------------------------------------------------- /src/app/ui/components/playback-time/playback-time.component.html: -------------------------------------------------------------------------------- 1 |
2 |
{{ this.playbackService.progress.progressSeconds | formatPlaybackTime }}
3 |
/
4 |
{{ this.playbackService.progress.totalSeconds | formatPlaybackTime }}
5 |
6 | -------------------------------------------------------------------------------- /src/app/ui/components/playback-time/playback-time.component.scss: -------------------------------------------------------------------------------- 1 | .app-playback-time { 2 | display: flex; 3 | flex-direction: row; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/ui/components/playback-time/playback-time.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | import { PlaybackService } from '../../../services/playback/playback.service'; 3 | 4 | @Component({ 5 | selector: 'app-playback-time', 6 | host: { style: 'display: block' }, 7 | templateUrl: './playback-time.component.html', 8 | styleUrls: ['./playback-time.component.scss'], 9 | encapsulation: ViewEncapsulation.None, 10 | }) 11 | export class PlaybackTimeComponent { 12 | public constructor(public playbackService: PlaybackService) {} 13 | } 14 | -------------------------------------------------------------------------------- /src/app/ui/components/rating/rating.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/rating/rating.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/search-box/search-box.component.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/app/ui/components/settings/advanced-settings/advanced-settings.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/settings/advanced-settings/advanced-settings.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/settings/appearance-settings/appearance-settings.component.scss: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/ui/components/settings/behavior-settings/behavior-settings.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/settings/behavior-settings/behavior-settings.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/settings/online-settings/online-settings.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/settings/online-settings/online-settings.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/settings/settings.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/settings/settings.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/slider/slider.component.html: -------------------------------------------------------------------------------- 1 |
9 |
10 |
11 |
18 |
19 | -------------------------------------------------------------------------------- /src/app/ui/components/step-indicator/step-indicator.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 | -------------------------------------------------------------------------------- /src/app/ui/components/step-indicator/step-indicator.component.scss: -------------------------------------------------------------------------------- 1 | @import 'theming'; 2 | 3 | .step-indicator-component { 4 | height: 8px; 5 | display: flex; 6 | flex-direction: row; 7 | justify-content: center; 8 | } 9 | 10 | .step-indicator-component__activedot { 11 | margin-left: 5px; 12 | margin-right: 5px; 13 | width: 8px; 14 | height: 8px; 15 | border-radius: 4px; 16 | background-color: var(--theme-accent-color); 17 | } 18 | 19 | .step-indicator-component__inactivedot { 20 | opacity: 0.3; 21 | @extend .step-indicator-component__activedot; 22 | } 23 | -------------------------------------------------------------------------------- /src/app/ui/components/sub-menu/sub-menu-item/sub-menu-item.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/ui/components/sub-menu/sub-menu-item/sub-menu-item.component.scss: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | 3 | .sub-menu__item { 4 | display: flex; 5 | height: $titlebar-height; 6 | align-items: center; 7 | padding: 0 24px; 8 | border-bottom: 2px solid transparent; 9 | cursor: pointer; 10 | color: var(--theme-secondary-text); 11 | text-transform: uppercase; 12 | } 13 | 14 | .sub-menu__selected-item { 15 | border-bottom: 2px solid var(--theme-accent-color); 16 | color: var(--theme-primary-text); 17 | } 18 | -------------------------------------------------------------------------------- /src/app/ui/components/sub-menu/sub-menu-item/sub-menu-item.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { SubMenuItemComponent } from './sub-menu-item.component'; 2 | 3 | describe('SubMenuItemComponent', () => { 4 | describe('constructor', () => { 5 | it('should create', () => { 6 | // Arrange 7 | 8 | // Act 9 | const component: SubMenuItemComponent = new SubMenuItemComponent(); 10 | 11 | // Assert 12 | expect(component).toBeDefined(); 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/app/ui/components/sub-menu/sub-menu-item/sub-menu-item.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-sub-menu-item', 5 | host: { style: 'display: block' }, 6 | templateUrl: './sub-menu-item.component.html', 7 | styleUrls: ['./sub-menu-item.component.scss'], 8 | }) 9 | export class SubMenuItemComponent { 10 | @Input() 11 | public page: number; 12 | 13 | @Input() 14 | public selectedPage: number; 15 | } 16 | -------------------------------------------------------------------------------- /src/app/ui/components/sub-menu/sub-menu.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/ui/components/sub-menu/sub-menu.component.scss: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | 3 | .sub-menu { 4 | height: $titlebar-height; 5 | display: flex; 6 | flex-direction: row; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/ui/components/sub-menu/sub-menu.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { SubMenuComponent } from './sub-menu.component'; 2 | 3 | describe('SubMenuComponent', () => { 4 | describe('constructor', () => { 5 | it('should create', () => { 6 | // Arrange 7 | 8 | // Act 9 | const component: SubMenuComponent = new SubMenuComponent(); 10 | 11 | // Assert 12 | expect(component).toBeDefined(); 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/app/ui/components/sub-menu/sub-menu.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-sub-menu', 5 | host: { style: 'display: block' }, 6 | templateUrl: './sub-menu.component.html', 7 | styleUrls: ['./sub-menu.component.scss'], 8 | }) 9 | export class SubMenuComponent {} 10 | -------------------------------------------------------------------------------- /src/app/ui/components/switch-player-button/switch-player-button.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /src/app/ui/components/switch-player-button/switch-player-button.component.scss: -------------------------------------------------------------------------------- 1 | .switch-player-button__icon { 2 | color: var(--theme-window-button-icon); 3 | font-size: 20px; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/ui/components/theme-switcher/theme-switcher.component.scss: -------------------------------------------------------------------------------- 1 | @import 'font-faces'; 2 | 3 | .theme-switcher-component { 4 | display: flex; 5 | flex-wrap: wrap; 6 | } 7 | 8 | .theme-switcher-component-button { 9 | width: 40px; 10 | height: 40px; 11 | cursor: pointer; 12 | display: flex; 13 | align-items: center; 14 | justify-content: center; 15 | position: relative; 16 | } 17 | 18 | .theme-switcher-component-button__selector { 19 | font-size: 22px; 20 | color: white; 21 | z-index: 1; 22 | } 23 | 24 | .theme-switcher-component-button__color { 25 | width: 40px; 26 | height: 40px; 27 | border-radius: 20px; 28 | position: absolute; 29 | z-index: 0; 30 | } 31 | -------------------------------------------------------------------------------- /src/app/ui/components/volume-control/volume-control.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 |
7 | 14 |
15 | -------------------------------------------------------------------------------- /src/app/ui/components/volume-control/volume-control.component.scss: -------------------------------------------------------------------------------- 1 | .app-volume-control { 2 | display: flex; 3 | flex-direction: row; 4 | align-items: center; 5 | } 6 | 7 | .app-volume-control__icon { 8 | display: flex; 9 | flex-direction: row; 10 | font-size: 18px; 11 | width: 20px; 12 | } 13 | 14 | .app-volume-control__icon__alt { 15 | @extend .app-volume-control__icon; 16 | padding-left: 1px; 17 | } 18 | 19 | .app-volume-control__slider { 20 | flex: 1; 21 | } 22 | -------------------------------------------------------------------------------- /src/app/ui/components/volume-icon/volume-icon.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 | -------------------------------------------------------------------------------- /src/app/ui/components/volume-icon/volume-icon.component.scss: -------------------------------------------------------------------------------- 1 | .app-volume-icon { 2 | display: flex; 3 | flex-direction: row; 4 | align-items: center; 5 | } 6 | 7 | .app-volume-icon__icon { 8 | display: flex; 9 | flex-direction: row; 10 | font-size: 18px; 11 | width: 20px; 12 | } 13 | 14 | .app-volume-icon__icon__alt { 15 | @extend .app-volume-icon__icon; 16 | padding-left: 1px; 17 | } 18 | -------------------------------------------------------------------------------- /src/app/ui/components/volume-icon/volume-icon.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { PlaybackService } from '../../../services/playback/playback.service'; 3 | 4 | @Component({ 5 | selector: 'app-volume-icon', 6 | host: { style: 'display: block' }, 7 | templateUrl: './volume-icon.component.html', 8 | styleUrls: ['./volume-icon.component.scss'], 9 | }) 10 | export class VolumeIconComponent { 11 | public constructor(private playbackService: PlaybackService) {} 12 | 13 | public get volume(): number { 14 | return this.playbackService.volume; 15 | } 16 | 17 | public set volume(v: number) { 18 | this.playbackService.volume = v; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome-appearance/welcome-appearance.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ 'appearance' | translate }} 4 |
5 |
6 | {{ 'what-should-dopamine-look-like' | translate }} 7 |
8 | 9 | {{ 'use-light-theme' | translate }} 11 |
12 | -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome-appearance/welcome-appearance.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/welcome/welcome-appearance/welcome-appearance.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome-appearance/welcome-appearance.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { AppearanceServiceBase } from '../../../../services/appearance/appearance.service.base'; 3 | 4 | @Component({ 5 | selector: 'app-welcome-appearance', 6 | host: { style: 'display: block; width: 100%;' }, 7 | templateUrl: './welcome-appearance.component.html', 8 | styleUrls: ['./welcome-appearance.component.scss'], 9 | }) 10 | export class WelcomeAppearanceComponent { 11 | public constructor(public appearanceService: AppearanceServiceBase) {} 12 | } 13 | -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome-donate/welcome-donate.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ 'donate' | translate }} 4 |
5 |
6 | {{ 'please-consider-donating' | translate }} 7 |
8 | 9 | {{ 10 | 'donate-now' | translate 11 | }} 12 |
13 | -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome-donate/welcome-donate.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/welcome/welcome-donate/welcome-donate.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome-donate/welcome-donate.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ContactInformation } from '../../../../common/application/contact-information'; 3 | import { DesktopBase } from '../../../../common/io/desktop.base'; 4 | 5 | @Component({ 6 | selector: 'app-welcome-donate', 7 | host: { style: 'display: block; width: 100%;' }, 8 | templateUrl: './welcome-donate.component.html', 9 | styleUrls: ['./welcome-donate.component.scss'], 10 | }) 11 | export class WelcomeDonateComponent { 12 | public constructor(private desktop: DesktopBase) {} 13 | 14 | public async openDonateUrlAsync(): Promise { 15 | await this.desktop.openLinkAsync(ContactInformation.donateUrl); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome-done/welcome-done.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ 'done' | translate }} 4 |
5 |
6 | {{ 'let-us-start-dopamine' | translate }} 7 |
8 |
9 | -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome-done/welcome-done.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/welcome/welcome-done/welcome-done.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome-done/welcome-done.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { WelcomeDoneComponent } from './welcome-done.component'; 2 | 3 | describe('WelcomeDoneComponent', () => { 4 | describe('constructor', () => { 5 | it('should create', () => { 6 | // Arrange 7 | 8 | // Act 9 | const component: WelcomeDoneComponent = new WelcomeDoneComponent(); 10 | 11 | // Assert 12 | expect(component).toBeDefined(); 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome-done/welcome-done.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-welcome-done', 5 | host: { style: 'display: block; width: 100%;' }, 6 | templateUrl: './welcome-done.component.html', 7 | styleUrls: ['./welcome-done.component.scss'], 8 | }) 9 | export class WelcomeDoneComponent {} 10 | -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome-greeting/welcome-greeting.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ 'hi' | translate }} 4 |
5 |
6 | {{ 'welcome-to-dopamine' | translate }} 7 |
8 |
9 | {{ 'hi' | translate }} 10 |
11 |
12 | {{ 'welcome-to-dopamine' | translate }} 13 |
14 |
15 | -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome-greeting/welcome-greeting.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/welcome/welcome-greeting/welcome-greeting.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome-greeting/welcome-greeting.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { WelcomeServiceBase } from '../../../../services/welcome/welcome.service.base'; 3 | 4 | @Component({ 5 | selector: 'app-welcome-greeting', 6 | host: { style: 'display: block; width: 100%;' }, 7 | templateUrl: './welcome-greeting.component.html', 8 | styleUrls: ['./welcome-greeting.component.scss'], 9 | }) 10 | export class WelcomeGreetingComponent { 11 | public constructor(private welcomeService: WelcomeServiceBase) {} 12 | 13 | public get isLoaded(): boolean { 14 | return this.welcomeService.isLoaded; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome-language/welcome-language.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/welcome/welcome-language/welcome-language.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome-language/welcome-language.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { TranslatorServiceBase } from '../../../../services/translator/translator.service.base'; 3 | 4 | @Component({ 5 | selector: 'app-welcome-language', 6 | host: { style: 'display: block; width: 100%;' }, 7 | templateUrl: './welcome-language.component.html', 8 | styleUrls: ['./welcome-language.component.scss'], 9 | }) 10 | export class WelcomeLanguageComponent { 11 | public constructor(public translatorService: TranslatorServiceBase) {} 12 | } 13 | -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome-music/welcome-music.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ 'music' | translate }} 4 |
5 |
6 | {{ 'let-us-set-up-collection' | translate }} 7 |
8 | 9 |
10 | -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome-music/welcome-music.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/welcome/welcome-music/welcome-music.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome-music/welcome-music.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { WelcomeMusicComponent } from './welcome-music.component'; 2 | 3 | describe('WelcomeMusicComponent', () => { 4 | describe('constructor', () => { 5 | it('should create', () => { 6 | // Arrange 7 | 8 | // Act 9 | const component: WelcomeMusicComponent = new WelcomeMusicComponent(); 10 | 11 | // Assert 12 | expect(component).toBeDefined(); 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome-music/welcome-music.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-welcome-music', 5 | host: { style: 'display: block; width: 100%;' }, 6 | templateUrl: './welcome-music.component.html', 7 | styleUrls: ['./welcome-music.component.scss'], 8 | }) 9 | export class WelcomeMusicComponent {} 10 | -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome-navigation-buttons/welcome-navigation-buttons.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 | -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome-navigation-buttons/welcome-navigation-buttons.scss: -------------------------------------------------------------------------------- 1 | .welcome-navigation-buttons { 2 | display: flex; 3 | flex-direction: row; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome-online/welcome-online.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ 'online' | translate }} 4 |
5 |
6 | {{ 'online-preferences' | translate }} 7 |
8 | 9 | {{ 'download-missing-album-covers' | translate }} 11 | 12 | {{ 'enable-discord-rich-presence' | translate }} 14 |
15 | -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome-online/welcome-online.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/app/ui/components/welcome/welcome-online/welcome-online.component.scss -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome-online/welcome-online.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { SettingsBase } from '../../../../common/settings/settings.base'; 3 | 4 | @Component({ 5 | selector: 'app-welcome-online', 6 | host: { style: 'display: block; width: 100%;' }, 7 | templateUrl: './welcome-online.component.html', 8 | styleUrls: ['./welcome-online.component.scss'], 9 | }) 10 | export class WelcomeOnlineComponent { 11 | public constructor(public settings: SettingsBase) {} 12 | } 13 | -------------------------------------------------------------------------------- /src/app/ui/components/welcome/welcome.component.scss: -------------------------------------------------------------------------------- 1 | @import 'theming'; 2 | @import 'font-faces'; 3 | @import 'variables'; 4 | 5 | .welcome-component { 6 | width: 100vw; 7 | height: calc(100vh - $titlebar-height); 8 | padding: 64px; 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | 15 | .welcome-content { 16 | background: transparent !important; 17 | flex: 1; 18 | width: 100%; 19 | max-width: 400px; 20 | overflow-y: auto; 21 | overflow-x: hidden; 22 | } 23 | -------------------------------------------------------------------------------- /src/app/ui/components/window-controls/window-controls.component.scss: -------------------------------------------------------------------------------- 1 | @import 'theming'; 2 | 3 | .window-controls { 4 | display: flex; 5 | flex-direction: row; 6 | height: 100%; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/ui/directives/webview.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive } from '@angular/core'; 2 | 3 | @Directive({ 4 | selector: 'webview', 5 | }) 6 | export class WebviewDirective {} 7 | -------------------------------------------------------------------------------- /src/app/ui/interfaces/i-selectable.ts: -------------------------------------------------------------------------------- 1 | export interface ISelectable { 2 | isSelected: boolean; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/ui/pipes/format-track-number.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ name: 'formatTrackNumber' }) 4 | export class FormatTrackNumberPipe implements PipeTransform { 5 | public transform(trackNumber: number | undefined): string { 6 | if (trackNumber == undefined) { 7 | return '-'; 8 | } 9 | 10 | if (trackNumber <= 0) { 11 | return '-'; 12 | } 13 | 14 | if (trackNumber >= 1 && trackNumber <= 9) { 15 | return trackNumber.toString(); 16 | } 17 | 18 | return trackNumber.toString(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/ui/pipes/image-to-file-path.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import {Constants} from "../../common/application/constants"; 3 | 4 | @Pipe({ name: 'imageToFilePath' }) 5 | export class ImageToFilePathPipe implements PipeTransform { 6 | public transform(path: string): string { 7 | if (path === Constants.emptyImage) { 8 | return path; 9 | } 10 | 11 | return `file:///${path}`; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/ui/pipes/zero-to-blank.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ name: 'zeroToBlank' }) 4 | export class ZeroToBlankPipe implements PipeTransform { 5 | public transform(number: number | undefined): string { 6 | if (number == undefined) { 7 | return ''; 8 | } 9 | 10 | if (number === 0) { 11 | return ''; 12 | } 13 | 14 | return number.toString(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/fonts/Monoglyceride.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/assets/fonts/Monoglyceride.ttf -------------------------------------------------------------------------------- /src/assets/fonts/OpenSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/assets/fonts/OpenSans-Bold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/OpenSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/assets/fonts/OpenSans-Light.ttf -------------------------------------------------------------------------------- /src/assets/fonts/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/src/assets/fonts/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /src/css/as-split.scss: -------------------------------------------------------------------------------- 1 | .as-split-gutter { 2 | background-color: transparent !important; 3 | } 4 | 5 | .as-split-gutter-icon { 6 | background-image: none !important; 7 | } 8 | 9 | /* Disables native scrolling in split area */ 10 | .as-split-area { 11 | overflow-y: hidden !important; 12 | } 13 | -------------------------------------------------------------------------------- /src/css/cdk-drag-drop.scss: -------------------------------------------------------------------------------- 1 | .cdk-drag-preview { 2 | box-sizing: border-box; 3 | border-radius: 4px; 4 | box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12); 5 | } 6 | 7 | .cdk-drag-placeholder { 8 | opacity: 0; 9 | } 10 | 11 | .cdk-drag-animating { 12 | transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); 13 | } 14 | -------------------------------------------------------------------------------- /src/css/cdk-virtual-scroll.scss: -------------------------------------------------------------------------------- 1 | /* Ensures that cdk-virtual-scroll-content-wrapper sets width of its elements */ 2 | .cdk-virtual-scroll-content-wrapper { 3 | position: unset !important; 4 | top: unset !important; 5 | left: unset !important; 6 | } 7 | 8 | .cdk-overlay-dark-backdrop{ 9 | background: rgba(var(--theme-rgb-base),0.3)!important 10 | } 11 | 12 | -------------------------------------------------------------------------------- /src/css/font-faces.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: monoglyceride; 3 | src: url(../assets/fonts/Monoglyceride.ttf) format('truetype'); 4 | } 5 | 6 | @font-face { 7 | font-family: opensans; 8 | src: url(../assets/fonts/OpenSans-Regular.ttf) format('truetype'); 9 | } 10 | 11 | @font-face { 12 | font-family: opensans; 13 | font-weight: 700; 14 | src: url(../assets/fonts/OpenSans-Bold.ttf) format('truetype'); 15 | } 16 | 17 | @font-face { 18 | font-family: opensans; 19 | font-weight: 100; 20 | src: url(../assets/fonts/OpenSans-Light.ttf) format('truetype'); 21 | } 22 | -------------------------------------------------------------------------------- /src/css/scrollbar.scss: -------------------------------------------------------------------------------- 1 | ::-webkit-scrollbar { 2 | width: 11px; 3 | height: 11px; 4 | } 5 | 6 | ::-webkit-scrollbar-track { 7 | box-shadow: inset 0 0 10px 10px transparent; 8 | border: solid 3px transparent; 9 | } 10 | 11 | ::-webkit-scrollbar-thumb { 12 | box-shadow: inset 0 0 10px 10px var(--theme-scroll-bars); 13 | border: solid 3px transparent; 14 | border-radius: 12px; 15 | } 16 | 17 | ::-webkit-scrollbar-corner { 18 | background: rgba(0, 0, 0, 0); 19 | } 20 | -------------------------------------------------------------------------------- /src/css/sizing.scss: -------------------------------------------------------------------------------- 1 | .h-24px { 2 | height: 24px; 3 | } 4 | 5 | .w-24px { 6 | width: 24px; 7 | } 8 | 9 | .w-48px { 10 | width: 48px; 11 | } 12 | 13 | .w-72px { 14 | width: 72px; 15 | } 16 | 17 | .w-84px { 18 | width: 84px; 19 | } 20 | 21 | .w-4px { 22 | width: 4px; 23 | } 24 | 25 | .radius-12px { 26 | border-radius: 12px; 27 | } 28 | 29 | .font-18px { 30 | font-size: 18px; 31 | } 32 | 33 | .h-100 { 34 | height: 100%; 35 | } 36 | 37 | .w-100 { 38 | width: 100%; 39 | } 40 | 41 | .w-0 { 42 | width: 0; 43 | } 44 | 45 | .vh-100 { 46 | height: 100vh; 47 | } 48 | 49 | .vh-80 { 50 | height: 80vh; 51 | } 52 | 53 | .rotate-90ccw { 54 | transform: rotate(-90deg); 55 | } 56 | -------------------------------------------------------------------------------- /src/css/sub-menu.scss: -------------------------------------------------------------------------------- 1 | .sub-menu { 2 | height: $titlebar-height; 3 | display: flex; 4 | flex-direction: row; 5 | } 6 | 7 | .sub-menu__item { 8 | display: flex; 9 | height: $titlebar-height; 10 | align-items: center; 11 | padding: 0 24px; 12 | border-bottom: 2px solid transparent; 13 | cursor: pointer; 14 | } 15 | 16 | .sub-menu__selected-item { 17 | border-bottom: 2px solid var(--theme-accent-color); 18 | } 19 | -------------------------------------------------------------------------------- /src/css/theming.scss: -------------------------------------------------------------------------------- 1 | .theme-header-background { 2 | background-color: var(--theme-header-background) !important; 3 | border-bottom: 1px solid var(--theme-header-separator); 4 | } 5 | 6 | .theme-footer-background { 7 | background-color: var(--theme-footer-background) !important; 8 | } 9 | -------------------------------------------------------------------------------- /src/css/transparent-button.scss: -------------------------------------------------------------------------------- 1 | .transparent-button { 2 | background-color: Transparent; 3 | background-repeat: no-repeat; 4 | border: none; 5 | cursor: pointer; 6 | overflow: hidden; 7 | outline: none; 8 | transition: all 0.2s ease-in-out; 9 | } 10 | 11 | .transparent-button:hover { 12 | transform: scale(1.2); 13 | } 14 | -------------------------------------------------------------------------------- /src/css/variables.scss: -------------------------------------------------------------------------------- 1 | // Sizes 2 | $titlebar-height: 46px; 3 | 4 | // Colors 5 | :root { 6 | --fontsize: 14px; 7 | --mat-tab-header-margin-right: 0px; 8 | --notification-bar-correction: 0px; 9 | } 10 | -------------------------------------------------------------------------------- /src/environments/environment.dev.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `index.ts`, but if you do 3 | // `ng build --env=prod` then `index.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const AppConfig = { 7 | production: false, 8 | environment: 'DEV', 9 | }; 10 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const AppConfig = { 2 | production: true, 3 | environment: 'PROD', 4 | }; 5 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | export const AppConfig = { 2 | production: false, 3 | environment: 'LOCAL', 4 | }; 5 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Dopamine 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | import { AppModule } from './app/app.module'; 4 | import { AppConfig } from './environments/environment'; 5 | 6 | if (AppConfig.production) { 7 | enableProdMode(); 8 | } 9 | 10 | platformBrowserDynamic() 11 | .bootstrapModule(AppModule, { 12 | preserveWhitespaces: false, 13 | }) 14 | .catch((err) => console.error(err)); 15 | -------------------------------------------------------------------------------- /src/polyfills-test.ts: -------------------------------------------------------------------------------- 1 | import 'core-js/es7/reflect'; 2 | import 'zone.js'; 3 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'theming'; 2 | @import 'variables'; 3 | @import 'font-faces'; 4 | @import 'spacing'; 5 | @import 'sizing'; 6 | @import 'elements'; 7 | @import 'scrollbar'; 8 | @import 'custom-classes'; 9 | @import 'window-frame'; 10 | @import 'window-buttons'; 11 | @import 'angular-material-mods'; 12 | @import 'angular-material-theming'; 13 | @import 'as-split'; 14 | @import 'transparent-button'; 15 | @import 'cdk-virtual-scroll'; 16 | @import 'cdk-drag-drop'; 17 | @import 'animations'; 18 | @import 'flex'; 19 | @import 'sub-menu'; 20 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular/setup-jest'; 2 | 3 | Object.defineProperty(window, 'CSS', { value: undefined }); 4 | Object.defineProperty(window, 'getComputedStyle', { 5 | value: () => { 6 | return { 7 | display: 'none', 8 | appearance: ['-webkit-appearance'], 9 | }; 10 | }, 11 | }); 12 | 13 | Object.defineProperty(document, 'doctype', { 14 | value: '', 15 | }); 16 | Object.defineProperty(document.body.style, 'transform', { 17 | value: () => { 18 | return { 19 | enumerable: true, 20 | configurable: true, 21 | }; 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "esnext", 6 | "baseUrl": "", 7 | "types": [] 8 | }, 9 | "exclude": ["**/*.spec.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare const nodeModule: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | 7 | interface Window { 8 | process: unknown; 9 | require: unknown; 10 | } 11 | -------------------------------------------------------------------------------- /static/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/static/icons/128x128.png -------------------------------------------------------------------------------- /static/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/static/icons/16x16.png -------------------------------------------------------------------------------- /static/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/static/icons/24x24.png -------------------------------------------------------------------------------- /static/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/static/icons/256x256.png -------------------------------------------------------------------------------- /static/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/static/icons/32x32.png -------------------------------------------------------------------------------- /static/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/static/icons/48x48.png -------------------------------------------------------------------------------- /static/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/static/icons/512x512.png -------------------------------------------------------------------------------- /static/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/static/icons/64x64.png -------------------------------------------------------------------------------- /static/icons/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/static/icons/96x96.png -------------------------------------------------------------------------------- /static/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/static/icons/icon.icns -------------------------------------------------------------------------------- /static/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/static/icons/icon.ico -------------------------------------------------------------------------------- /static/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/static/icons/icon.png -------------------------------------------------------------------------------- /static/icons/trayTemplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/static/icons/trayTemplate.png -------------------------------------------------------------------------------- /static/icons/trayTemplate@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/static/icons/trayTemplate@2x.png -------------------------------------------------------------------------------- /static/icons/tray_black.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/static/icons/tray_black.ico -------------------------------------------------------------------------------- /static/icons/tray_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/static/icons/tray_black.png -------------------------------------------------------------------------------- /static/icons/tray_white.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/static/icons/tray_white.ico -------------------------------------------------------------------------------- /static/icons/tray_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digimezzo/dopamine/5d2823e2fb4260cdba1b00f143200b90b6f2a052/static/icons/tray_white.png -------------------------------------------------------------------------------- /tsconfig-serve.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "declaration": false, 5 | "moduleResolution": "node", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "module": "commonjs", 9 | "target": "es2015", 10 | "types": ["node"], 11 | "lib": ["es2017", "es2016", "es2015", "dom"] 12 | }, 13 | "include": ["main.ts"], 14 | "exclude": ["node_modules", "**/*.spec.ts"] 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "module": "es2020", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "allowSyntheticDefaultImports": true, 12 | "strictNullChecks": true, 13 | "target": "ES2022", 14 | "typeRoots": ["node_modules/@types"], 15 | "lib": ["es2017", "es2016", "es2015", "dom", "DOM.Iterable"], 16 | "useDefineForClassFields": false, 17 | "skipLibCheck": true 18 | }, 19 | "include": ["main.ts", "src/**/*", "tsconfig.app.json"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jest", // 1 7 | "node" 8 | ], 9 | "esModuleInterop": true, // 2 10 | "emitDecoratorMetadata": true // 3 11 | }, 12 | "files": ["src/test.ts", "src/polyfills.ts"], 13 | "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] 14 | } 15 | --------------------------------------------------------------------------------