yt-grabber 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/.eslintrc.json +29 -0
  2. package/.prettierrc +19 -0
  3. package/.vscode/extensions.json +7 -0
  4. package/.vscode/settings.json +23 -0
  5. package/.yarnrc.yml +13 -0
  6. package/LICENSE +21 -0
  7. package/README.md +11 -0
  8. package/package.json +115 -0
  9. package/public/index.html +20 -0
  10. package/public/screenshots/cutting.png +0 -0
  11. package/public/screenshots/downloading.png +0 -0
  12. package/public/screenshots/editing.png +0 -0
  13. package/public/screenshots/errors.png +0 -0
  14. package/public/screenshots/settings.png +0 -0
  15. package/public/screenshots/tracklist.png +0 -0
  16. package/src/@types/global.d.ts +7 -0
  17. package/src/@types/i18next-scanner-webpack.d.ts +1 -0
  18. package/src/@types/stylus.d.ts +4 -0
  19. package/src/@types/svg.d.ts +1 -0
  20. package/src/App.styl +24 -0
  21. package/src/App.tsx +31 -0
  22. package/src/bootstrap.tsx +30 -0
  23. package/src/common/CancellablePromise.ts +22 -0
  24. package/src/common/ComponentDisplayMode.ts +8 -0
  25. package/src/common/Delay.ts +3 -0
  26. package/src/common/FileSystem.ts +171 -0
  27. package/src/common/Helpers.ts +270 -0
  28. package/src/common/Mappings.ts +14 -0
  29. package/src/common/PuppeteerOptions.ts +45 -0
  30. package/src/common/Selectors.ts +21 -0
  31. package/src/common/Store.ts +108 -0
  32. package/src/common/Theme.ts +4 -0
  33. package/src/common/Youtube.ts +80 -0
  34. package/src/components/appBar/AppBar.styl +22 -0
  35. package/src/components/appBar/AppBar.tsx +73 -0
  36. package/src/components/directoryPicker/DirectoryPicker.tsx +44 -0
  37. package/src/components/fileField/FileField.styl +3 -0
  38. package/src/components/fileField/FileField.tsx +152 -0
  39. package/src/components/languagePicker/LanguagePicker.styl +38 -0
  40. package/src/components/languagePicker/LanguagePicker.tsx +145 -0
  41. package/src/components/logo/Logo.tsx +15 -0
  42. package/src/components/modals/DetailsModal.styl +9 -0
  43. package/src/components/modals/DetailsModal.tsx +85 -0
  44. package/src/components/numberField/NumberField.styl +13 -0
  45. package/src/components/numberField/NumberField.tsx +154 -0
  46. package/src/components/progress/Progress.styl +15 -0
  47. package/src/components/progress/Progress.tsx +18 -0
  48. package/src/components/splitButton/SplitButton.styl +0 -0
  49. package/src/components/splitButton/SplitButton.tsx +125 -0
  50. package/src/components/themePicker/ThemePicker.styl +19 -0
  51. package/src/components/themePicker/ThemePicker.tsx +65 -0
  52. package/src/components/themeSwitcher/ThemeSwitcher.styl +10 -0
  53. package/src/components/themeSwitcher/ThemeSwitcher.tsx +43 -0
  54. package/src/components/youtube/formatSelector/FormatSelector.styl +3 -0
  55. package/src/components/youtube/formatSelector/FormatSelector.tsx +202 -0
  56. package/src/components/youtube/inputPanel/InputPanel.styl +7 -0
  57. package/src/components/youtube/inputPanel/InputPanel.tsx +189 -0
  58. package/src/components/youtube/mediaInfoPanel/MediaInfoPanel.styl +80 -0
  59. package/src/components/youtube/mediaInfoPanel/MediaInfoPanel.tsx +113 -0
  60. package/src/components/youtube/trackList/TrackList.styl +64 -0
  61. package/src/components/youtube/trackList/TrackList.tsx +258 -0
  62. package/src/enums/DataResponse.ts +5 -0
  63. package/src/enums/Media.ts +16 -0
  64. package/src/enums/MediaFormat.ts +10 -0
  65. package/src/enums/MimeTypes.ts +14 -0
  66. package/src/hooks/useCancellablePromises.ts +25 -0
  67. package/src/hooks/useClickCounter.ts +24 -0
  68. package/src/hooks/useData.ts +61 -0
  69. package/src/hooks/useMultiClickHandler.ts +41 -0
  70. package/src/i18next.ts +33 -0
  71. package/src/index.ts +65 -0
  72. package/src/react/actions/Action.ts +3 -0
  73. package/src/react/actions/AppActions.ts +41 -0
  74. package/src/react/contexts/AppContext.tsx +51 -0
  75. package/src/react/contexts/AppThemeContext.tsx +38 -0
  76. package/src/react/contexts/DataContext copy.tsx +76 -0
  77. package/src/react/contexts/DataContext.tsx +41 -0
  78. package/src/react/hooks/useAppTheme.ts +14 -0
  79. package/src/react/reducers/AppReducer.tsx +45 -0
  80. package/src/react/reducers/Reducer.ts +7 -0
  81. package/src/react/states/AppState.ts +29 -0
  82. package/src/react/states/State.ts +29 -0
  83. package/src/renderer.tsx +13 -0
  84. package/src/resources/bin/yt-dlp.exe +0 -0
  85. package/src/resources/fonts/Baloo-Regular.ttf +0 -0
  86. package/src/resources/fonts/Lato-Black.ttf +0 -0
  87. package/src/resources/fonts/Lato-BlackItalic.ttf +0 -0
  88. package/src/resources/fonts/Lato-Bold.ttf +0 -0
  89. package/src/resources/fonts/Lato-BoldItalic.ttf +0 -0
  90. package/src/resources/fonts/Lato-Italic.ttf +0 -0
  91. package/src/resources/fonts/Lato-Light.ttf +0 -0
  92. package/src/resources/fonts/Lato-LightItalic.ttf +0 -0
  93. package/src/resources/fonts/Lato-Regular.ttf +0 -0
  94. package/src/resources/fonts/Lato-Thin.ttf +0 -0
  95. package/src/resources/fonts/Lato-ThinItalic.ttf +0 -0
  96. package/src/resources/fonts/Material-Icons.woff2 +0 -0
  97. package/src/resources/icons/favicon-16x16.png +0 -0
  98. package/src/resources/icons/favicon-32x32.png +0 -0
  99. package/src/resources/icons/favicon.ico +0 -0
  100. package/src/resources/icons/logo-shape.png +0 -0
  101. package/src/resources/icons/logo-shape.svg +59 -0
  102. package/src/resources/images/loading.svg +28 -0
  103. package/src/resources/images/logo.png +0 -0
  104. package/src/resources/locales/de-DE/flag.svg +1 -0
  105. package/src/resources/locales/de-DE/translation.json +44 -0
  106. package/src/resources/locales/en-GB/flag.svg +43 -0
  107. package/src/resources/locales/en-GB/translation.json +44 -0
  108. package/src/resources/locales/pl-PL/flag.svg +36 -0
  109. package/src/resources/locales/pl-PL/translation.json +44 -0
  110. package/src/styles/MaterialThemes.ts +331 -0
  111. package/src/styles/fonts.styl +71 -0
  112. package/src/styles/mixins.styl +22 -0
  113. package/src/tests/CompleteTracksMock.ts +17384 -0
  114. package/src/tests/MissingDetailsTracksMock.ts +7737 -0
  115. package/src/theme/ColorThemes.ts +190 -0
  116. package/src/theme/Colors.ts +92 -0
  117. package/src/theme/Shadows.ts +9 -0
  118. package/src/theme/Shape.ts +7 -0
  119. package/src/theme/Theme.ts +24 -0
  120. package/src/theme/Typography.ts +56 -0
  121. package/src/views/development/DevelopmentView.styl +22 -0
  122. package/src/views/development/DevelopmentView.tsx +57 -0
  123. package/src/views/home/HomeView.styl +60 -0
  124. package/src/views/home/HomeView.tsx +505 -0
  125. package/src/views/settings/SettingsView.styl +27 -0
  126. package/src/views/settings/SettingsView.tsx +255 -0
  127. package/tsconfig.json +20 -0
  128. package/webpack.config.ts +226 -0
@@ -0,0 +1,41 @@
1
+ import cancellablePromise from "../common/CancellablePromise";
2
+ import delay from "../common/Delay";
3
+ import useCancellablePromises from "./useCancellablePromises";
4
+
5
+ export interface IUseMultiClickHandlerProps {
6
+ timeout?: number;
7
+ onClick?: (data?: any) => void;
8
+ onDoubleClick?: (data?: any) => void;
9
+ }
10
+
11
+ const useMultiClickHandler = (props: IUseMultiClickHandlerProps) => {
12
+ const { timeout = 300, onClick, onDoubleClick } = props;
13
+ const api = useCancellablePromises();
14
+
15
+ const handleClick = (data?: any) => {
16
+ api.clearPendingPromises();
17
+ const waitForClick = cancellablePromise(delay(timeout));
18
+ api.appendPendingPromise(waitForClick);
19
+
20
+ return waitForClick.promise
21
+ .then(() => {
22
+ api.removePendingPromise(waitForClick);
23
+ onClick(data);
24
+ })
25
+ .catch((errorInfo: any) => {
26
+ api.removePendingPromise(waitForClick);
27
+ if (!errorInfo.isCanceled) {
28
+ throw errorInfo.error;
29
+ }
30
+ });
31
+ };
32
+
33
+ const handleDoubleClick = (data?: any) => {
34
+ api.clearPendingPromises();
35
+ onDoubleClick(data);
36
+ };
37
+
38
+ return [handleClick, handleDoubleClick];
39
+ };
40
+
41
+ export default useMultiClickHandler;
package/src/i18next.ts ADDED
@@ -0,0 +1,33 @@
1
+ import i18next from "i18next";
2
+ import i18nextBackend from "i18next-node-fs-backend";
3
+ import $_ from "lodash";
4
+ import path from "path";
5
+ import {initReactI18next} from "react-i18next";
6
+
7
+ const isMac = process.platform === "darwin";
8
+ const isDev = process.env.NODE_ENV === "development";
9
+ const prependPath = isMac && !isDev ? path.join(process.resourcesPath, "..") : ".";
10
+ const localePath = $_.replace(!isDev ? path.join(process.resourcesPath, "locales") : path.join(__dirname, "resources", "locales"), /\\/g, "/");
11
+
12
+ i18next.use(i18nextBackend).use(initReactI18next).init({
13
+ backend: {
14
+ loadPath: localePath + "/{{lng}}/{{ns}}.json",
15
+ addPath: prependPath + "/src/resources/locales/{{lng}}/{{ns}}.missing.json",
16
+ },
17
+ lng: "en-GB",
18
+ preload: ["en-GB", "de-DE", "pl-PL"],
19
+ debug: false, // process.env.NODE_ENV === "development",
20
+ saveMissing: false,
21
+ saveMissingTo: "all",
22
+ load: "currentOnly",
23
+ returnEmptyString: false,
24
+ fallbackLng: false,
25
+ defaultNS: "translation",
26
+ ns: "translation",
27
+ interpolation: {
28
+ escapeValue: false,
29
+ },
30
+ supportedLngs: ["en-GB", "de-DE", "pl-PL"],
31
+ });
32
+
33
+ export default i18next;
package/src/index.ts ADDED
@@ -0,0 +1,65 @@
1
+ import {app, BrowserWindow} from "electron";
2
+ import installExtension, {REACT_DEVELOPER_TOOLS} from "electron-devtools-installer";
3
+ import Store from "electron-store";
4
+ import path from "path";
5
+
6
+ import i18n from "./i18next";
7
+
8
+ let mainWindow: BrowserWindow | null;
9
+
10
+ process.traceProcessWarnings = true;
11
+ Store.initRenderer();
12
+
13
+ const isDev = () => !app.isPackaged;
14
+
15
+ const createWindow = async () => {
16
+ mainWindow = new BrowserWindow({
17
+ width: 1000,
18
+ height: 900,
19
+ frame: true,
20
+ roundedCorners: true,
21
+ title: "YT Grabber",
22
+ webPreferences: {
23
+ nodeIntegration: true,
24
+ contextIsolation: false,
25
+ },
26
+ });
27
+ mainWindow.loadFile(path.join(__dirname, "index.html"));
28
+
29
+ if (isDev()) {
30
+ mainWindow.webContents.openDevTools({mode: "detach"});
31
+ } else {
32
+ mainWindow.removeMenu();
33
+ mainWindow.setMenu(null);
34
+ }
35
+
36
+ mainWindow.on("closed", () => {
37
+ mainWindow = null;
38
+ });
39
+ };
40
+
41
+ app.on("ready", createWindow);
42
+
43
+ app.on("window-all-closed", () => {
44
+ if (process.platform !== "darwin") {
45
+ app.quit();
46
+ }
47
+ });
48
+
49
+ app.on("activate", () => {
50
+ if (mainWindow === null) {
51
+ createWindow();
52
+ }
53
+ });
54
+
55
+ app.on("before-quit", () => {
56
+ if (mainWindow !== null) {
57
+ mainWindow.removeAllListeners("closed");
58
+ }
59
+ });
60
+
61
+ app.whenReady().then(() => {
62
+ installExtension(REACT_DEVELOPER_TOOLS)
63
+ .then((name) => console.log(`Added Extension: ${name}`))
64
+ .catch((err) => console.log("An error occurred: ", err));
65
+ });
@@ -0,0 +1,3 @@
1
+ export interface IAction<T> {
2
+ type: T;
3
+ }
@@ -0,0 +1,41 @@
1
+ import {Dispatch} from "react";
2
+
3
+ import {ColorMode} from "../../common/Theme";
4
+ import {MediaFormat} from "../../enums/Media";
5
+ import {IAction} from "./Action";
6
+
7
+ export enum AppActions {
8
+ SetLocation = "SET_LOCATION",
9
+ SetTheme = "SET_THEME",
10
+ SetMode = "SET_MODE",
11
+ SetUrls = "SET_URLS",
12
+ SetSelectedAction = "SET_SELECTED_ACTION",
13
+ SetFormat = "SET_FORMAT",
14
+ SetLoading = "SET_LOADING",
15
+ SetQueue = "SET_QUEUE",
16
+ SetControllers = "SET_CONTROLLERS",
17
+ }
18
+
19
+ export interface IAppAction extends IAction<AppActions> {
20
+ location?: string;
21
+ theme?: string;
22
+ mode?: ColorMode;
23
+ urls?: string[];
24
+ selectedAction?: string;
25
+ format?: MediaFormat;
26
+ loading?: boolean;
27
+ queue?: string[];
28
+ controllers?: AbortController[];
29
+ }
30
+
31
+ export const actions = (dispatch: Dispatch<IAppAction>) => ({
32
+ setLocation: (location: string) => dispatch({ type: AppActions.SetLocation, location }),
33
+ setTheme: (theme: string) => dispatch({ type: AppActions.SetTheme, theme }),
34
+ setMode: (mode: ColorMode) => dispatch({ type: AppActions.SetMode, mode }),
35
+ setUrls: (urls: string[]) => dispatch({ type: AppActions.SetUrls, urls }),
36
+ setSelectedAction: (selectedAction: string) => dispatch({ type: AppActions.SetSelectedAction, selectedAction }),
37
+ setFormat: (format: MediaFormat) => dispatch({ type: AppActions.SetFormat, format }),
38
+ setLoading: (loading: boolean) => dispatch({ type: AppActions.SetLoading, loading }),
39
+ setQueue: (queue: string[]) => dispatch({ type: AppActions.SetQueue, queue }),
40
+ setControllers: (controllers: AbortController[]) => dispatch({ type: AppActions.SetControllers, controllers }),
41
+ });
@@ -0,0 +1,51 @@
1
+ import React, {createContext, FC, ReactNode, useReducer} from "react";
2
+
3
+ import {ColorMode} from "../../common/Theme";
4
+ import {MediaFormat} from "../../enums/Media";
5
+ import {actions} from "../actions/AppActions";
6
+ import reducer from "../reducers/AppReducer";
7
+ import {createDefaultState, IAppState} from "../states/AppState";
8
+
9
+ interface IAppContext {
10
+ state: IAppState;
11
+ actions: {
12
+ setLocation: (location: string) => void;
13
+ setTheme: (theme: string) => void;
14
+ setMode: (mode: ColorMode) => void;
15
+ setUrls: (urls: string[]) => void;
16
+ setSelectedAction: (selectedAction: string) => void;
17
+ setFormat: (format: MediaFormat) => void;
18
+ setLoading: (loading: boolean) => void;
19
+ setQueue: (queue: string[]) => void;
20
+ setControllers: (controllers: AbortController[]) => void;
21
+ };
22
+ }
23
+
24
+ interface IAppContextProviderProps {
25
+ children: ReactNode;
26
+ }
27
+
28
+ const AppContext = createContext<IAppContext | undefined>(undefined);
29
+
30
+ export const AppContextProvider: FC<IAppContextProviderProps> = (props: any) => {
31
+ const [reducerState, dispatch] = useReducer(reducer, createDefaultState());
32
+ const reducerActions = actions(dispatch);
33
+ const context: IAppContext = {
34
+ state: { ...reducerState },
35
+ actions: { ...reducerActions },
36
+ };
37
+
38
+ return <AppContext.Provider value={context}>{props.children}</AppContext.Provider>;
39
+ };
40
+
41
+ export const useAppContext = (): IAppContext => {
42
+ const context = React.useContext(AppContext);
43
+
44
+ if (!context) {
45
+ throw new Error("useAppContext must be used within AppContextProvider");
46
+ }
47
+
48
+ return context;
49
+ };
50
+
51
+ export default AppContext;
@@ -0,0 +1,38 @@
1
+ import _merge from "lodash/merge";
2
+ import React, {createContext, FC, PropsWithChildren, useContext, useState} from "react";
3
+
4
+ import {createTheme, ThemeProvider} from "@mui/material/styles";
5
+
6
+ import {getThemeDefinition} from "../../theme/Theme";
7
+
8
+ export interface IAppThemeProps {
9
+ mode?: "light" | "dark";
10
+ }
11
+
12
+ export interface IAppThemeProviderProps extends IAppThemeProps, PropsWithChildren {}
13
+
14
+ const defaultState = {};
15
+
16
+ export const AppThemeContext = createContext<IAppThemeProps>(defaultState);
17
+
18
+ export const AppThemeProvider: FC<IAppThemeProviderProps> = (props: IAppThemeProviderProps) => {
19
+ const {children, ...rest} = props;
20
+ const mergedState = _merge(defaultState, rest);
21
+ const [mode] = useState(mergedState.mode);
22
+
23
+ const theme = React.useMemo(() => {
24
+ return createTheme({
25
+ cssVariables: {
26
+ colorSchemeSelector: "data-theme-mode",
27
+ cssVarPrefix: "theme",
28
+ },
29
+ ...getThemeDefinition("Sky"),
30
+ });
31
+ }, [mode]);
32
+
33
+ return <ThemeProvider theme={theme} defaultMode={mode} disableTransitionOnChange>{children}</ThemeProvider>;
34
+ };
35
+
36
+ export const useAppThemeContext = () => useContext(AppThemeContext);
37
+
38
+ export default AppThemeContext;
@@ -0,0 +1,76 @@
1
+ import _includes from "lodash/includes";
2
+ import _isNil from "lodash/isNil";
3
+ import _merge from "lodash/merge";
4
+ import _union from "lodash/union";
5
+ import React, {createContext, useContext, useEffect, useRef, useState} from "react";
6
+
7
+ import {AlbumInfo, TrackInfo, TrackStatusInfo} from "../../common/Youtube";
8
+
9
+ export type DataState = {[key: string]: DataStateItem}
10
+
11
+ export type DataStateItem = {
12
+ album?: AlbumInfo;
13
+ tracks: TrackInfo[];
14
+ trackStatus: TrackStatusInfo[];
15
+ // setAlbum: React.Dispatch<React.SetStateAction<AlbumInfo>>;
16
+ // setTracks: React.Dispatch<React.SetStateAction<TrackInfo[]>>;
17
+ // setTrackStatus: React.Dispatch<React.SetStateAction<TrackStatusInfo[]>>;
18
+ // clear: () => void;
19
+ }
20
+
21
+ export type DataContextState = {
22
+ data: DataState;
23
+ setData: React.Dispatch<React.SetStateAction<DataState>>
24
+ getData: (id: string) => DataStateItem;
25
+ }
26
+
27
+ const DataContext = createContext<DataContextState | null>(null);
28
+
29
+ export function DataProvider(props: any) {
30
+ const [data, setData] = useState<DataState>({});
31
+ const dataRef = useRef<DataState>({});
32
+
33
+ const getData = (id: string): DataStateItem => {
34
+ if (id && !_isNil(data[id])) {
35
+ return data[id];
36
+ } else if (id && _isNil(data[id])) {
37
+ _merge(data,
38
+ {
39
+ [id]: {
40
+ album: undefined,
41
+ tracks: [],
42
+ trackStatus: [],
43
+ }
44
+ }
45
+ );
46
+ // setData((prev) => {
47
+ // return {
48
+ // ...prev,
49
+ // [id]: {
50
+ // album: undefined,
51
+ // tracks: [],
52
+ // trackStatus: [],
53
+ // }
54
+ // };
55
+ // })
56
+
57
+ return data[id]; //dataRef.current[id];
58
+ }
59
+ }
60
+
61
+
62
+ useEffect(() => {
63
+ dataRef.current = data;
64
+ }, [data])
65
+
66
+ return (
67
+ // <DataContext.Provider value={{album, tracks, trackStatus, setAlbum, setTracks, setTrackStatus, clear}}>
68
+ <DataContext.Provider value={{data, setData, getData}}>
69
+ {props.children}
70
+ </DataContext.Provider>
71
+ );
72
+ }
73
+
74
+ export function useDataState() {
75
+ return useContext(DataContext);
76
+ }
@@ -0,0 +1,41 @@
1
+ import React, {createContext, useContext, useState} from "react";
2
+
3
+ import {AlbumInfo, TrackInfo, TrackStatusInfo} from "../../common/Youtube";
4
+
5
+ export type DataState = {
6
+ album?: AlbumInfo;
7
+ tracks: TrackInfo[];
8
+ trackStatus: TrackStatusInfo[];
9
+ trackCuts: {[key: string]: number[]};
10
+ setAlbum: React.Dispatch<React.SetStateAction<AlbumInfo>>;
11
+ setTracks: React.Dispatch<React.SetStateAction<TrackInfo[]>>;
12
+ setTrackStatus: React.Dispatch<React.SetStateAction<TrackStatusInfo[]>>;
13
+ setTrackCuts: React.Dispatch<React.SetStateAction<{[key: string]: number[]}>>;
14
+ clear: () => void;
15
+ }
16
+
17
+ const DataContext = createContext<DataState | null>(null);
18
+
19
+ export function DataProvider(props: any) {
20
+ const [album, setAlbum] = useState<AlbumInfo | undefined>();
21
+ const [tracks, setTracks] = useState<TrackInfo[]>([]);
22
+ const [trackStatus, setTrackStatus] = useState<TrackStatusInfo[]>([]);
23
+ const [trackCuts, setTrackCuts] = useState<{[key: string]: number[]}>({});
24
+
25
+ const clear = () => {
26
+ setAlbum(undefined);
27
+ setTracks([]);
28
+ setTrackStatus([]);
29
+ setTrackCuts({});
30
+ };
31
+
32
+ return (
33
+ <DataContext.Provider value={{album, tracks, trackStatus, trackCuts, setAlbum, setTracks, setTrackStatus, setTrackCuts, clear}}>
34
+ {props.children}
35
+ </DataContext.Provider>
36
+ );
37
+ }
38
+
39
+ export function useDataState() {
40
+ return useContext(DataContext);
41
+ }
@@ -0,0 +1,14 @@
1
+ import {createTheme} from "@mui/material/styles";
2
+
3
+ import {getThemeDefinition} from "../../styles/MaterialThemes";
4
+ import {useAppContext} from "../contexts/AppContext";
5
+
6
+ const useAppTheme = () => {
7
+ const { state } = useAppContext();
8
+ const themeDefinition = getThemeDefinition(state.theme, state.mode);
9
+ const theme = createTheme(themeDefinition, {});
10
+
11
+ return { theme };
12
+ };
13
+
14
+ export default useAppTheme;
@@ -0,0 +1,45 @@
1
+ import {AppActions, IAppAction} from "../actions/AppActions";
2
+ import {IAppState} from "../states/AppState";
3
+ import {reduce} from "./Reducer";
4
+
5
+ const reducer = (state: IAppState, action: IAppAction): IAppState => {
6
+ if (action.type === AppActions.SetLocation) {
7
+ return reduce(state, { location: action.location });
8
+ }
9
+
10
+ if (action.type === AppActions.SetTheme) {
11
+ return reduce(state, { theme: action.theme });
12
+ }
13
+
14
+ if (action.type === AppActions.SetMode) {
15
+ return reduce(state, { mode: action.mode });
16
+ }
17
+
18
+ if (action.type === AppActions.SetUrls) {
19
+ return reduce(state, { urls: action.urls });
20
+ }
21
+
22
+ if (action.type === AppActions.SetSelectedAction) {
23
+ return reduce(state, { selectedAction: action.selectedAction });
24
+ }
25
+
26
+ if (action.type === AppActions.SetFormat) {
27
+ return reduce(state, { format: action.format });
28
+ }
29
+
30
+ if (action.type === AppActions.SetLoading) {
31
+ return reduce(state, { loading: action.loading });
32
+ }
33
+
34
+ if (action.type === AppActions.SetQueue) {
35
+ return reduce(state, { queue: action.queue });
36
+ }
37
+
38
+ if (action.type === AppActions.SetControllers) {
39
+ return reduce(state, { controllers: action.controllers });
40
+ }
41
+
42
+ throw new Error();
43
+ };
44
+
45
+ export default reducer;
@@ -0,0 +1,7 @@
1
+ import $_ from "lodash";
2
+
3
+ import {StateCreator} from "../states/State";
4
+
5
+ export const reduce = <TState extends object>(state = StateCreator.create<TState>(), stateChange: Partial<TState>) => {
6
+ return $_.assign({}, state, stateChange);
7
+ };
@@ -0,0 +1,29 @@
1
+ import {ColorMode} from "../../common/Theme";
2
+ import {MediaFormat} from "../../enums/Media";
3
+ import {StateCreator} from "./State";
4
+
5
+ export interface IAppState {
6
+ location?: string;
7
+ theme?: string;
8
+ mode?: ColorMode;
9
+ urls: string[];
10
+ selectedAction?: string;
11
+ format?: MediaFormat;
12
+ loading?: boolean;
13
+ queue?: string[];
14
+ controllers?: AbortController[];
15
+ }
16
+
17
+ export const createDefaultState = () => {
18
+ return StateCreator.create<IAppState>({
19
+ location: "/",
20
+ theme: "purple-rain",
21
+ mode: ColorMode.Light,
22
+ urls: [],
23
+ selectedAction: undefined,
24
+ format: MediaFormat.Audio,
25
+ loading: false,
26
+ queue: [],
27
+ controllers: [],
28
+ });
29
+ };
@@ -0,0 +1,29 @@
1
+ import $_ from "lodash";
2
+
3
+ export type TPartial<T> = {
4
+ [P in keyof T]?: TPartial<T[P]>;
5
+ };
6
+
7
+ export const createNextState = <T>(obj: T, ...assignments: Array<TPartial<T> | T>): T => {
8
+ return Object.assign({}, obj, ...assignments);
9
+ };
10
+
11
+ export const createNextNamespacedState = <T>(
12
+ obj: T,
13
+ assignments: TPartial<T> | T,
14
+ namespace: string,
15
+ ): { [key: string]: T } => {
16
+ return Object.assign({}, { [namespace]: $_.assign({}, obj, assignments) });
17
+ };
18
+
19
+ export const removeFromNextState = <T extends object>(obj: T, path: string | string[]): T => {
20
+ return $_.omit(obj, path) as T;
21
+ };
22
+
23
+ export class StateCreator {
24
+ public static create<TState extends object>(state?: TState): TState {
25
+ const initialState: TState = new Object() as TState;
26
+
27
+ return Object.freeze($_.merge<TState, TState>(initialState, state));
28
+ }
29
+ }
@@ -0,0 +1,13 @@
1
+ import Store from "electron-store";
2
+ import * as React from "react";
3
+ import * as ReactDOM from "react-dom/client";
4
+
5
+ import {Bootstrap} from "./bootstrap";
6
+ import schema from "./common/Store";
7
+
8
+ global.store = new Store({ schema, clearInvalidConfig: true });
9
+
10
+ const container = document.getElementById("root");
11
+ const root = ReactDOM.createRoot(container!);
12
+
13
+ root.render(React.createElement(Bootstrap));
Binary file
Binary file
@@ -0,0 +1,59 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
4
+ xmlns:cc="http://creativecommons.org/ns#"
5
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6
+ xmlns:svg="http://www.w3.org/2000/svg"
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
9
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
10
+ version="1.1"
11
+ id="aa_1"
12
+ x="0px"
13
+ y="0px"
14
+ width="200px"
15
+ height="222.977px"
16
+ viewBox="0 0 200 222.977"
17
+ enable-background="new 0 0 200 222.977"
18
+ xml:space="preserve"
19
+ sodipodi:docname="logo-shape.svg"
20
+ inkscape:version="0.92.4 (5da689c313, 2019-01-14)"><metadata
21
+ id="metadata13"><rdf:RDF><cc:Work
22
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
23
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><sodipodi:namedview
24
+ pagecolor="#ffffff"
25
+ bordercolor="#666666"
26
+ borderopacity="1"
27
+ objecttolerance="10"
28
+ gridtolerance="10"
29
+ guidetolerance="10"
30
+ inkscape:pageopacity="0"
31
+ inkscape:pageshadow="2"
32
+ inkscape:window-width="1920"
33
+ inkscape:window-height="1027"
34
+ id="namedview11"
35
+ showgrid="false"
36
+ inkscape:zoom="2.6545005"
37
+ inkscape:cx="-64.472767"
38
+ inkscape:cy="82.298866"
39
+ inkscape:window-x="-8"
40
+ inkscape:window-y="-8"
41
+ inkscape:window-maximized="1"
42
+ inkscape:current-layer="aa_1" /><defs
43
+ class="defs"
44
+ id="defs2" /><path
45
+ d="M 98.881799,10.790587 177.01505,55.901835 98.881799,101.0121 20.748549,55.901835 98.881799,10.790587 m 0,-8.5892888 c -1.48295,0 -2.965901,0.3842861 -4.294151,1.1508876 L 16.454398,48.463436 c -2.657486,1.534188 -4.295136,4.370022 -4.295136,7.438399 0,3.068377 1.63765,5.905197 4.295136,7.439385 l 78.13325,45.11026 c 1.329236,0.7666 2.812186,1.14991 4.294151,1.14991 1.483931,0 2.965901,-0.38429 4.295141,-1.14991 l 78.13324,-45.11026 c 2.65749,-1.534188 4.29416,-4.371008 4.29416,-7.439385 0,-3.068377 -1.63667,-5.904211 -4.29416,-7.438399 L 103.17792,3.3521858 C 101.8477,2.5855843 100.36573,2.2012982 98.881799,2.2012982 Z"
46
+ class="color c1"
47
+ id="path4"
48
+ inkscape:connector-curvature="0"
49
+ style="fill:#dd5f7a;stroke-width:0.985349" /><path
50
+ d="m 10.056527,77.990404 78.13325,45.111246 v 90.22053 L 10.056527,168.21093 V 77.990404 m 0,-8.589287 c -1.4829508,0 -2.965901,0.3833 -4.2951368,1.149902 -2.6565009,1.534188 -4.294151,4.371008 -4.294151,7.439385 v 90.220526 c 0,3.06739 1.6376501,5.90421 4.294151,7.4384 l 78.1332508,45.11026 c 1.329235,0.76759 2.8112,1.15089 4.29415,1.15089 1.482951,0 2.965901,-0.3833 4.295137,-1.15089 2.657486,-1.53419 4.294151,-4.37002 4.294151,-7.4384 v -90.22052 c 0,-3.06838 -1.636665,-5.9052 -4.294151,-7.43939 L 14.351664,70.551019 c -1.329236,-0.766602 -2.811201,-1.149902 -4.295137,-1.149902 z"
51
+ class="color c2"
52
+ id="path6"
53
+ inkscape:connector-curvature="0"
54
+ style="fill:#a32f48;stroke-width:0.985349" /><path
55
+ d="M 189.94775,76.732113 V 166.95264 L 111.8145,212.0629 v -90.21954 l 78.13325,-45.111247 m 0,-8.589287 c -1.48393,0 -2.9659,0.383301 -4.29513,1.149902 l -78.13325,45.110262 c -2.65749,1.53517 -4.29415,4.37101 -4.29415,7.44037 v 90.21954 c 0,3.06936 1.63666,5.90421 4.29415,7.43939 1.32923,0.7666 2.81218,1.15088 4.29513,1.15088 1.48295,0 2.96591,-0.38428 4.29317,-1.15088 l 78.13424,-45.11125 c 2.6565,-1.53419 4.29415,-4.37002 4.29415,-7.4384 V 76.732113 c 0,-3.069362 -1.63765,-5.905196 -4.29415,-7.439385 -1.32826,-0.766601 -2.81219,-1.149902 -4.29416,-1.149902 z"
56
+ class="color c3"
57
+ id="path8"
58
+ inkscape:connector-curvature="0"
59
+ style="fill:#e23a59;stroke-width:0.985349" /></svg>