diff options
Diffstat (limited to 'src/containers/settings/RecipesScreen.tsx')
-rw-r--r-- | src/containers/settings/RecipesScreen.tsx | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/src/containers/settings/RecipesScreen.tsx b/src/containers/settings/RecipesScreen.tsx new file mode 100644 index 000000000..c50ff246e --- /dev/null +++ b/src/containers/settings/RecipesScreen.tsx | |||
@@ -0,0 +1,188 @@ | |||
1 | import { readJsonSync } from 'fs-extra'; | ||
2 | import { Component, ReactElement } from 'react'; | ||
3 | import { autorun, IReactionDisposer } from 'mobx'; | ||
4 | import { inject, observer } from 'mobx-react'; | ||
5 | |||
6 | import { StoresProps } from 'src/@types/ferdium-components.types'; | ||
7 | import Recipe from 'src/models/Recipe'; | ||
8 | import RecipesDashboard from '../../components/settings/recipes/RecipesDashboard'; | ||
9 | import ErrorBoundary from '../../components/util/ErrorBoundary'; | ||
10 | import { CUSTOM_WEBSITE_RECIPE_ID, FRANZ_DEV_DOCS } from '../../config'; | ||
11 | import { userDataRecipesPath } from '../../environment-remote'; | ||
12 | import { asarRecipesPath } from '../../helpers/asar-helpers'; | ||
13 | import { communityRecipesStore } from '../../features/communityRecipes'; | ||
14 | import RecipePreview from '../../models/RecipePreview'; | ||
15 | import { openPath } from '../../helpers/url-helpers'; | ||
16 | |||
17 | interface RecipesScreenProps extends StoresProps { | ||
18 | params: { | ||
19 | filter?: string | null; | ||
20 | }; | ||
21 | } | ||
22 | |||
23 | class RecipesScreen extends Component<RecipesScreenProps> { | ||
24 | state: { | ||
25 | needle: string | null; | ||
26 | currentFilter: string; | ||
27 | } = { | ||
28 | needle: null, | ||
29 | currentFilter: 'featured', | ||
30 | }; | ||
31 | |||
32 | autorunDisposer: IReactionDisposer | null = null; | ||
33 | |||
34 | customRecipes: Recipe[] = []; | ||
35 | |||
36 | constructor(props: RecipesScreenProps) { | ||
37 | super(props); | ||
38 | |||
39 | this.props.params.filter = this.props.params.filter || null; | ||
40 | |||
41 | this.customRecipes = readJsonSync(asarRecipesPath('all.json')); | ||
42 | } | ||
43 | |||
44 | componentDidMount(): void { | ||
45 | this.autorunDisposer = autorun(() => { | ||
46 | const { filter } = this.props.params; | ||
47 | const { currentFilter } = this.state; | ||
48 | |||
49 | if (filter === 'all' && currentFilter !== 'all') { | ||
50 | this.setState({ currentFilter: 'all' }); | ||
51 | } else if (filter === 'featured' && currentFilter !== 'featured') { | ||
52 | this.setState({ currentFilter: 'featured' }); | ||
53 | } else if (filter === 'dev' && currentFilter !== 'dev') { | ||
54 | this.setState({ currentFilter: 'dev' }); | ||
55 | } | ||
56 | }); | ||
57 | } | ||
58 | |||
59 | componentWillUnmount(): void { | ||
60 | this.props.stores.services.resetStatus(); | ||
61 | |||
62 | if (typeof this.autorunDisposer === 'function') { | ||
63 | this.autorunDisposer(); | ||
64 | } | ||
65 | } | ||
66 | |||
67 | searchRecipes(needle: string | null): void { | ||
68 | if (needle === '') { | ||
69 | this.resetSearch(); | ||
70 | } else { | ||
71 | const { search } = this.props.actions.recipePreview; | ||
72 | this.setState({ needle }); | ||
73 | search({ needle }); | ||
74 | } | ||
75 | } | ||
76 | |||
77 | _sortByName(recipe1, recipe2): number { | ||
78 | if (recipe1.name.toLowerCase() < recipe2.name.toLowerCase()) { | ||
79 | return -1; | ||
80 | } | ||
81 | if (recipe1.name.toLowerCase() > recipe2.name.toLowerCase()) { | ||
82 | return 1; | ||
83 | } | ||
84 | return 0; | ||
85 | } | ||
86 | |||
87 | prepareRecipes(recipes: RecipePreview[]): RecipePreview[] { | ||
88 | return ( | ||
89 | recipes | ||
90 | // Filter out duplicate recipes | ||
91 | .filter((recipe, index, self) => { | ||
92 | const ids = self.map(rec => rec.id); | ||
93 | return ids.indexOf(recipe.id) === index; | ||
94 | |||
95 | // Sort alphabetically | ||
96 | }) | ||
97 | .sort(this._sortByName) | ||
98 | ); | ||
99 | } | ||
100 | |||
101 | // Create an array of RecipePreviews from an array of recipe objects | ||
102 | createPreviews(recipes: Recipe[]) { | ||
103 | return recipes.map((recipe: any) => new RecipePreview(recipe)); | ||
104 | } | ||
105 | |||
106 | resetSearch(): void { | ||
107 | this.setState({ needle: null }); | ||
108 | } | ||
109 | |||
110 | render(): ReactElement { | ||
111 | const { recipePreviews, recipes, services } = this.props.stores; | ||
112 | |||
113 | const { app: appActions, service: serviceActions } = this.props.actions; | ||
114 | |||
115 | const { filter } = this.props.params; | ||
116 | let recipeFilter; | ||
117 | |||
118 | if (filter === 'all') { | ||
119 | recipeFilter = this.prepareRecipes([ | ||
120 | ...recipePreviews.all, | ||
121 | ...this.createPreviews(this.customRecipes), | ||
122 | ]); | ||
123 | } else if (filter === 'dev') { | ||
124 | recipeFilter = communityRecipesStore.communityRecipes; | ||
125 | } else { | ||
126 | recipeFilter = recipePreviews.featured; | ||
127 | } | ||
128 | recipeFilter = recipeFilter.sort(this._sortByName); | ||
129 | |||
130 | const { needle } = this.state; | ||
131 | const allRecipes = | ||
132 | needle !== null | ||
133 | ? this.prepareRecipes([ | ||
134 | // All search recipes from server | ||
135 | ...recipePreviews.searchResults, | ||
136 | // All search recipes from local recipes | ||
137 | ...this.createPreviews( | ||
138 | this.customRecipes.filter( | ||
139 | (recipe: Recipe) => | ||
140 | recipe.name.toLowerCase().includes(needle.toLowerCase()) || | ||
141 | (recipe.aliases || []).some(alias => | ||
142 | alias.toLowerCase().includes(needle.toLowerCase()), | ||
143 | ), | ||
144 | ), | ||
145 | ), | ||
146 | ]).sort(this._sortByName) | ||
147 | : recipeFilter; | ||
148 | |||
149 | const customWebsiteRecipe = recipePreviews.all.find( | ||
150 | service => service.id === CUSTOM_WEBSITE_RECIPE_ID, | ||
151 | ); | ||
152 | |||
153 | const isLoading = | ||
154 | recipePreviews.featuredRecipePreviewsRequest.isExecuting || | ||
155 | recipePreviews.allRecipePreviewsRequest.isExecuting || | ||
156 | recipes.installRecipeRequest.isExecuting || | ||
157 | recipePreviews.searchRecipePreviewsRequest.isExecuting; | ||
158 | |||
159 | const recipeDirectory = userDataRecipesPath('dev'); | ||
160 | |||
161 | return ( | ||
162 | <ErrorBoundary> | ||
163 | <RecipesDashboard | ||
164 | recipes={allRecipes} | ||
165 | customWebsiteRecipe={customWebsiteRecipe} | ||
166 | isLoading={isLoading} | ||
167 | addedServiceCount={services.all.length} | ||
168 | hasLoadedRecipes={ | ||
169 | recipePreviews.featuredRecipePreviewsRequest.wasExecuted | ||
170 | } | ||
171 | showAddServiceInterface={serviceActions.showAddServiceInterface} | ||
172 | searchRecipes={e => this.searchRecipes(e)} | ||
173 | resetSearch={() => this.resetSearch()} | ||
174 | searchNeedle={this.state.needle} | ||
175 | serviceStatus={services.actionStatus} | ||
176 | recipeFilter={filter} | ||
177 | recipeDirectory={recipeDirectory} | ||
178 | openRecipeDirectory={() => openPath(recipeDirectory)} | ||
179 | openDevDocs={() => | ||
180 | appActions.openExternalUrl({ url: FRANZ_DEV_DOCS }) | ||
181 | } | ||
182 | /> | ||
183 | </ErrorBoundary> | ||
184 | ); | ||
185 | } | ||
186 | } | ||
187 | |||
188 | export default inject('stores', 'actions')(observer(RecipesScreen)); | ||