mirror of
https://github.com/dkhamsing/open-source-ios-apps.git
synced 2024-11-21 16:23:11 +03:00
Add Gatsby to build site (#887) [ci skip]
This commit is contained in:
parent
42ec2aa9c2
commit
43958025e7
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/.gtm/
|
10
gatsby/.editorconfig
Normal file
10
gatsby/.editorconfig
Normal file
@ -0,0 +1,10 @@
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
5
gatsby/.eslintignore
Normal file
5
gatsby/.eslintignore
Normal file
@ -0,0 +1,5 @@
|
||||
.cache
|
||||
package.json
|
||||
package-lock.json
|
||||
public
|
||||
node_modules
|
40
gatsby/.eslintrc
Normal file
40
gatsby/.eslintrc
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier/@typescript-eslint",
|
||||
"prettier"
|
||||
],
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"react",
|
||||
"react-hooks",
|
||||
"prettier"
|
||||
],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"react/prop-types": 0,
|
||||
"import/prefer-default-export": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off"
|
||||
}
|
||||
}
|
69
gatsby/.gitignore
vendored
Normal file
69
gatsby/.gitignore
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# dotenv environment variable files
|
||||
.env*
|
||||
|
||||
# gatsby files
|
||||
.cache/
|
||||
public
|
||||
|
||||
# Mac files
|
||||
.DS_Store
|
||||
|
||||
# Yarn
|
||||
yarn-error.log
|
||||
.pnp/
|
||||
.pnp.js
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
5
gatsby/.prettierignore
Normal file
5
gatsby/.prettierignore
Normal file
@ -0,0 +1,5 @@
|
||||
.cache
|
||||
package.json
|
||||
package-lock.json
|
||||
public
|
||||
node_modules
|
8
gatsby/.prettierrc
Normal file
8
gatsby/.prettierrc
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"arrowParens": "avoid",
|
||||
"semi": false,
|
||||
"printWidth": 80,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "all"
|
||||
}
|
53
gatsby/.vscode/settings.json
vendored
Normal file
53
gatsby/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"files.encoding": "utf8",
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"files.insertFinalNewline": true,
|
||||
"search.exclude": {
|
||||
"public/**": true,
|
||||
"node_modules/**": true
|
||||
},
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact"
|
||||
],
|
||||
"files.exclude": {
|
||||
"**/logs": true,
|
||||
"**/*.log": true,
|
||||
"**/npm-debug.log*": true,
|
||||
"**/yarn-debug.log*": true,
|
||||
"**/yarn-error.log*": true,
|
||||
"**/pids": true,
|
||||
"**/*.pid": true,
|
||||
"**/*.seed": true,
|
||||
"**/*.pid.lock": true,
|
||||
"**/lib-cov": true,
|
||||
"**/coverage": true,
|
||||
"**/.nyc_output": true,
|
||||
"**/.grunt": true,
|
||||
"**/bower_components": true,
|
||||
"**/.lock-wscript": true,
|
||||
"build/Release": true,
|
||||
"**/node_modules/": true,
|
||||
"**/jspm_packages/": true,
|
||||
"**/typings/": true,
|
||||
"**/.npm": true,
|
||||
"**/.eslintcache": true,
|
||||
"**/.node_repl_history": true,
|
||||
"**/*.tgz": true,
|
||||
"**/.env*": true,
|
||||
"**/.cache/": true,
|
||||
"**/public": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/yarn-error.log": true,
|
||||
"**/.pnp/": true,
|
||||
"**/.pnp.js": true,
|
||||
"**/.yarn-integrity": true
|
||||
},
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true,
|
||||
"source.fixAll": true
|
||||
}
|
||||
}
|
22
gatsby/LICENSE
Normal file
22
gatsby/LICENSE
Normal file
@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2020 junscuzzy
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
117
gatsby/README.md
Normal file
117
gatsby/README.md
Normal file
@ -0,0 +1,117 @@
|
||||
# Gatsby's Typescript + Material-ui starter
|
||||
|
||||
[![Netlify Status](https://api.netlify.com/api/v1/badges/1b625068-4ac6-42d5-87fb-902d9077bbef/deploy-status)](https://app.netlify.com/sites/gatsby-material-typescript-starter/deploys)
|
||||
|
||||
Kick off your project with this [Material-ui](https://material-ui.com/) boilerplate. This starter ships with the main Gatsby configuration files you might need to get up and running blazing fast with the blazing fast app generator for React.
|
||||
It includes support for Typescript in front-side and node-side and uses Eslint & Prettier.
|
||||
|
||||
This starter don't have any source or style supports, it's your choice.
|
||||
|
||||
_Have another more specific idea? You may want to check out our vibrant collection of [official and community-created starters](https://www.gatsbyjs.org/docs/gatsby-starters/)._
|
||||
|
||||
## 🚀 Quick start
|
||||
|
||||
1. **Create a Gatsby site.**
|
||||
|
||||
Use the Gatsby CLI to create a new site, specifying the default starter.
|
||||
|
||||
```shell
|
||||
# create a new Gatsby site using the starter
|
||||
gatsby new gatsby-material-typescript-starter https://github.com/Junscuzzy/gatsby-material-typescript-starter
|
||||
```
|
||||
|
||||
1. **Start developing.**
|
||||
|
||||
Navigate into your new site’s directory and start it up.
|
||||
|
||||
```shell
|
||||
cd gatsby-material-typescript-starter/
|
||||
yarn develop
|
||||
```
|
||||
|
||||
1. **Open the source code and start editing!**
|
||||
|
||||
Your site is now running at `http://localhost:8000`!
|
||||
|
||||
_Note: You'll also see a second link: _`http://localhost:8000/___graphql`_. This is a tool you can use to experiment with querying your data. Learn more about using this tool in the [Gatsby tutorial](https://www.gatsbyjs.org/tutorial/part-five/#introducing-graphiql)._
|
||||
|
||||
Open the `gatsby-material-typescript-starter` directory in your code editor of choice and edit `src/pages/index.tsx`. Save your changes and the browser will update in real time!
|
||||
|
||||
1. **Bonus**: Check all linters using
|
||||
|
||||
```shell
|
||||
yarn lint
|
||||
```
|
||||
|
||||
Will execute Prettier, Eslint and Typescript checking
|
||||
|
||||
All the commands are in your `package.json > scripts`.
|
||||
|
||||
## 🧐 What's inside?
|
||||
|
||||
A quick look at the top-level files and directories you'll see in a Gatsby project.
|
||||
|
||||
.
|
||||
├── .vscode/
|
||||
├── node_modules/
|
||||
├── src/
|
||||
├── static/
|
||||
├── .editorconfig
|
||||
├── .eslintrc
|
||||
├── .gitignore
|
||||
├── .prettierrc
|
||||
├── gatsby-browser.js
|
||||
├── gatsby-config.js
|
||||
├── gatsby-node.js
|
||||
├── gatsby-ssr.js
|
||||
├── LICENSE
|
||||
├── package.json
|
||||
├── README.md
|
||||
├── tsconfig.json
|
||||
└── yarn.lock
|
||||
|
||||
1. **`/.vscode`**: VSCode projects settings.
|
||||
|
||||
1. **`/node_modules`**: This directory contains all of the modules of code that your project depends on (npm packages) are automatically installed.
|
||||
|
||||
1. **`/src`**: This directory will contain all of the code related to what you will see on the front-end of your site (what you see in the browser) such as your site header or a page template. `src` is a convention for “source code”.
|
||||
|
||||
1. **`/static`**: Static files like `robots.txt` or `favicon.ico`.
|
||||
|
||||
1. **`.editorconfig`**: EditorConfig helps maintain consistent coding styles for multiple developers working on the same project across various editors and IDEs.
|
||||
|
||||
1. **`.eslintrc`**: This is a configuration file for [Eslint](https://eslint.org/). Find and fix problems in your JavaScript code
|
||||
|
||||
1. **`.gitignore`**: This file tells git which files it should not track / not maintain a version history for.
|
||||
|
||||
1. **`.prettierrc`**: This is a configuration file for [Prettier](https://prettier.io/). Prettier is a tool to help keep the formatting of your code consistent.
|
||||
|
||||
1. **`gatsby-browser.js`**: This file is where Gatsby expects to find any usage of the [Gatsby browser APIs](https://www.gatsbyjs.org/docs/browser-apis/) (if any). These allow customization/extension of default Gatsby settings affecting the browser.
|
||||
|
||||
1. **`gatsby-config.js`**: This is the main configuration file for a Gatsby site. This is where you can specify information about your site (metadata) like the site title and description, which Gatsby plugins you’d like to include, etc. (Check out the [config docs](https://www.gatsbyjs.org/docs/gatsby-config/) for more detail).
|
||||
|
||||
1. **`gatsby-node.js`**: This file is where Gatsby expects to find any usage of the [Gatsby Node APIs](https://www.gatsbyjs.org/docs/node-apis/) (if any). These allow customization/extension of default Gatsby settings affecting pieces of the site build process.
|
||||
|
||||
1. **`gatsby-ssr.js`**: This file is where Gatsby expects to find any usage of the [Gatsby server-side rendering APIs](https://www.gatsbyjs.org/docs/ssr-apis/) (if any). These allow customization of default Gatsby settings affecting server-side rendering.
|
||||
|
||||
1. **`LICENSE`**: Gatsby is licensed under the MIT license.
|
||||
|
||||
1. **`package.json`**: A manifest file for Node.js projects, which includes things like metadata (the project’s name, author, etc). This manifest is how npm knows which packages to install for your project.
|
||||
|
||||
1. **`README.md`**: A text file containing useful reference information about your project.
|
||||
|
||||
1. **`tsconfig.json`**: This is a configuration file for [Typescript](https://www.typescriptlang.org/). TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.
|
||||
|
||||
1. **`yarn.lock`** (See `package.json` below, first). This is an automatically generated file based on the exact versions of your npm dependencies that were installed for your project. **(You won’t change this file directly).**
|
||||
|
||||
## 🎓 Learning Gatsby
|
||||
|
||||
Looking for more guidance? Full documentation for Gatsby lives [on the website](https://www.gatsbyjs.org/). Here are some places to start:
|
||||
|
||||
- **For most developers, we recommend starting with our [in-depth tutorial for creating a site with Gatsby](https://www.gatsbyjs.org/tutorial/).** It starts with zero assumptions about your level of ability and walks through every step of the process.
|
||||
|
||||
- **To dive straight into code samples, head [to our documentation](https://www.gatsbyjs.org/docs/).** In particular, check out the _Guides_, _API Reference_, and _Advanced Tutorials_ sections in the sidebar.
|
||||
|
||||
## 💫 Deploy
|
||||
|
||||
As a static generated website, you can deploy it on [Netlify](https://www.netlify.com), [Github Page](https://pages.github.com/) or [ZEIT Now](https://zeit.co/)
|
BIN
gatsby/content/images/gatsby.jpeg
Normal file
BIN
gatsby/content/images/gatsby.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
8
gatsby/gatsby-browser.js
Normal file
8
gatsby/gatsby-browser.js
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Implement Gatsby's Browser APIs in this file.
|
||||
*
|
||||
* See: https://www.gatsbyjs.org/docs/browser-apis/
|
||||
*/
|
||||
|
||||
export { default as wrapRootElement } from './src/libs/wrapRootElement'
|
||||
export { default as wrapPageElement } from './src/libs/wrapPageElement'
|
56
gatsby/gatsby-config.js
Normal file
56
gatsby/gatsby-config.js
Normal file
@ -0,0 +1,56 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const join = require('path').join
|
||||
|
||||
module.exports = {
|
||||
pathPrefix: '/open-source-ios/',
|
||||
siteMetadata: {
|
||||
title: `Open Source iOS Apps`,
|
||||
description: `A collaborative list of open-source iOS, watchOS and tvOS apps.`,
|
||||
},
|
||||
plugins: [
|
||||
`gatsby-plugin-typescript`,
|
||||
`gatsby-plugin-react-helmet`,
|
||||
{
|
||||
resolve: 'gatsby-plugin-material-ui',
|
||||
// If you want to use styled components you should change the injection order.
|
||||
options: {
|
||||
// stylesProvider: {
|
||||
// injectFirst: true,
|
||||
// },
|
||||
},
|
||||
},
|
||||
{
|
||||
resolve: `gatsby-plugin-manifest`,
|
||||
options: {
|
||||
name: `open-source-ios-apps`,
|
||||
short_name: `os-ios-apps`,
|
||||
start_url: `/`,
|
||||
background_color: `#663399`,
|
||||
theme_color: `#3E3F3A`,
|
||||
display: `minimal-ui`,
|
||||
},
|
||||
},
|
||||
`gatsby-transformer-json`,
|
||||
{
|
||||
resolve: `gatsby-source-filesystem`,
|
||||
options: {
|
||||
name: `apps`,
|
||||
path: join(__dirname, `../`),
|
||||
// This aims to ignore everything EXCEPT the `contents.json` file at the
|
||||
// root of the repo
|
||||
ignore: [
|
||||
`**/.*`,
|
||||
`**/.*/**`,
|
||||
`**/gatsby/**`,
|
||||
`**/*.md`,
|
||||
`**/*.toml`,
|
||||
`**/LICENSE`,
|
||||
`**/Dangerfile`,
|
||||
],
|
||||
},
|
||||
},
|
||||
// this (optional) plugin enables Progressive Web App + Offline functionality
|
||||
// To learn more, visit: https://gatsby.dev/offline
|
||||
// `gatsby-plugin-offline`,
|
||||
],
|
||||
}
|
142
gatsby/gatsby-node.js
Normal file
142
gatsby/gatsby-node.js
Normal file
@ -0,0 +1,142 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const path = require('path')
|
||||
const crypto = require('crypto')
|
||||
const Bluebird = require('bluebird')
|
||||
|
||||
// Set this to true to enable more logging in this file
|
||||
const DEBUG = false
|
||||
const isDev = process.env.NODE_ENV === 'development'
|
||||
|
||||
/**
|
||||
* Implement Gatsby's Node APIs in this file.
|
||||
*
|
||||
* See: https://www.gatsbyjs.org/docs/node-apis/
|
||||
*/
|
||||
|
||||
// You can delete this file if you're not using it
|
||||
|
||||
exports.onCreateNode = async ({ node, actions }) => {
|
||||
const { createNode } = actions
|
||||
if (node.internal.type === 'OpenSourceIosAppsJson') {
|
||||
const { categories, projects } = node
|
||||
|
||||
categories.forEach(category => {
|
||||
if (typeof category.id !== 'string' || category.id.length < 1) {
|
||||
console.error('Invalid category #veJYyW', category)
|
||||
return
|
||||
}
|
||||
|
||||
// Count how many projects are in each category. We can't easily do this
|
||||
// in Gatsby's GraphQL API.
|
||||
const projectCount = projects.filter(project =>
|
||||
project['category-ids'].includes(category.id),
|
||||
).length
|
||||
|
||||
createNode({
|
||||
...category,
|
||||
slug: category.id,
|
||||
parentSlug: category.parent,
|
||||
projectCount,
|
||||
id: `Category__${category.id}`,
|
||||
parent: node.id,
|
||||
internal: {
|
||||
type: `AppCategory`,
|
||||
contentDigest: crypto
|
||||
.createHash(`md5`)
|
||||
.update(JSON.stringify(category))
|
||||
.digest(`hex`),
|
||||
content: JSON.stringify(category),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
await Bluebird.each(projects, async project => {
|
||||
const contentDigest = crypto
|
||||
.createHash(`md5`)
|
||||
.update(JSON.stringify(project))
|
||||
.digest(`hex`)
|
||||
const id = `Project__${contentDigest}`
|
||||
|
||||
const projectNode = {
|
||||
...project,
|
||||
parent: node.id,
|
||||
id,
|
||||
internal: {
|
||||
type: `AppProject`,
|
||||
contentDigest,
|
||||
content: JSON.stringify(project),
|
||||
},
|
||||
}
|
||||
await createNode(projectNode)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
exports.createPages = async ({ actions, graphql }) => {
|
||||
const { createPage } = actions
|
||||
|
||||
const categoryTemplate = path.resolve('src/templates/category.tsx')
|
||||
const tagTemplate = path.resolve('src/templates/tag.tsx')
|
||||
|
||||
const categoryResult = await graphql(`
|
||||
query Categories {
|
||||
allAppCategory {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
title
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
const categories = categoryResult.data.allAppCategory.edges
|
||||
|
||||
categories.forEach(({ node: category }) => {
|
||||
createPage({
|
||||
path: `/category/${category.slug}/`,
|
||||
component: categoryTemplate,
|
||||
context: {
|
||||
id: category.id,
|
||||
slug: category.slug,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
const projectResult = await graphql(`
|
||||
query Projects {
|
||||
allAppProject {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
tags
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
const projects = projectResult.data.allAppProject.edges
|
||||
const tags = new Set()
|
||||
|
||||
projects.forEach(({ node: project }) => {
|
||||
if (project.tags && project.tags.length > 0) {
|
||||
project.tags.forEach(tag => {
|
||||
tags.add(tag)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
tags.forEach(tag => {
|
||||
createPage({
|
||||
path: `/tag/${tag}/`,
|
||||
component: tagTemplate,
|
||||
context: {
|
||||
tag,
|
||||
slug: tag,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
8
gatsby/gatsby-ssr.js
Normal file
8
gatsby/gatsby-ssr.js
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Implement Gatsby's SSR (Server Side Rendering) APIs in this file.
|
||||
*
|
||||
* See: https://www.gatsbyjs.org/docs/ssr-apis/
|
||||
*/
|
||||
|
||||
export { default as wrapRootElement } from './src/libs/wrapRootElement'
|
||||
export { default as wrapPageElement } from './src/libs/wrapPageElement'
|
9
gatsby/netlify.toml
Normal file
9
gatsby/netlify.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[[plugins]]
|
||||
package = "netlify-plugin-gatsby-cache"
|
||||
|
||||
[build.environment]
|
||||
SHARP_IGNORE_GLOBAL_LIBVIPS="true"
|
||||
|
||||
[build]
|
||||
publish = "public/"
|
||||
command = "yarn run build"
|
76
gatsby/package.json
Normal file
76
gatsby/package.json
Normal file
@ -0,0 +1,76 @@
|
||||
{
|
||||
"name": "gatsby-material-typescript-starter",
|
||||
"private": true,
|
||||
"description": "A simple starter using Typescript & Material-ui",
|
||||
"version": "0.1.0",
|
||||
"author": "Julien CARON <juliencaron@protonmail.com>",
|
||||
"keywords": [
|
||||
"gatsby",
|
||||
"typescript",
|
||||
"eslint",
|
||||
"prettier",
|
||||
"material-ui"
|
||||
],
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "NODE_ENV=production gatsby build",
|
||||
"develop": "gatsby develop",
|
||||
"deploy-gh": "yarn build --prefix-paths && yarn gh-pages -d public",
|
||||
"start": "yarn develop",
|
||||
"serve": "gatsby serve",
|
||||
"clean": "gatsby clean",
|
||||
"lint": "run-p lint:**",
|
||||
"lint:prettier": "prettier --write \"**/*.{js,jsx,json,md}\"",
|
||||
"lint:eslint": "eslint 'src/**/*.{ts,tsx}'",
|
||||
"lint:typescript": "tsc",
|
||||
"test": "echo \"Write tests! -> https://gatsby.dev/unit-testing\" && exit 1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/styles": "^4.10.0",
|
||||
"@reduxjs/toolkit": "^1.4.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"gatsby": "^2.24.56",
|
||||
"gatsby-plugin-manifest": "^2.4.28",
|
||||
"gatsby-plugin-material-ui": "^2.1.10",
|
||||
"gatsby-plugin-offline": "^3.2.27",
|
||||
"gatsby-plugin-react-helmet": "^3.3.10",
|
||||
"gatsby-plugin-typescript": "^2.4.19",
|
||||
"gatsby-source-filesystem": "^2.3.29",
|
||||
"gatsby-transformer-json": "^2.4.11",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-image-lightbox": "^5.1.1",
|
||||
"react-redux": "^7.2.1",
|
||||
"redux": "^4.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.6.4",
|
||||
"@types/react": "^16.9.49",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react-helmet": "^6.1.0",
|
||||
"@types/react-redux": "^7.1.9",
|
||||
"@types/redux": "^3.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.1.0",
|
||||
"@typescript-eslint/parser": "^4.1.0",
|
||||
"bluebird": "^3.7.2",
|
||||
"eslint": "^7.8.1",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-plugin-prettier": "^3.1.4",
|
||||
"eslint-plugin-react": "^7.20.6",
|
||||
"eslint-plugin-react-hooks": "^4.1.0",
|
||||
"gh-pages": "^3.1.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.1.1",
|
||||
"typescript": "^4.0.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Junscuzzy/gatsby-material-typescript-starter"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/Junscuzzy/gatsby-material-typescript-starter/issues"
|
||||
}
|
||||
}
|
209
gatsby/src/components/ProjectCard.tsx
Normal file
209
gatsby/src/components/ProjectCard.tsx
Normal file
@ -0,0 +1,209 @@
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
createStyles,
|
||||
Divider,
|
||||
GridList,
|
||||
GridListTile,
|
||||
makeStyles,
|
||||
Theme,
|
||||
Typography,
|
||||
} from '@material-ui/core'
|
||||
import Star from '@material-ui/icons/Star'
|
||||
import { graphql, Link } from 'gatsby'
|
||||
import React, { useState } from 'react'
|
||||
import Lightbox from 'react-image-lightbox'
|
||||
import 'react-image-lightbox/style.css'
|
||||
import { Project } from '../types'
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
description: {
|
||||
marginTop: theme.spacing(1),
|
||||
marginBottom: theme.spacing(1),
|
||||
fontSize: '1.2em',
|
||||
},
|
||||
tag: {
|
||||
marginRight: theme.spacing(1),
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
const urlToReaddable = (url: string) => {
|
||||
if (url.indexOf('https://github.com/') === 0) {
|
||||
return `@${url.substr('https://github.com/'.length)}`
|
||||
} else if (url.indexOf('http://github.com/') === 0) {
|
||||
return `@${url.substr('http://github.com/'.length)}`
|
||||
} else return url
|
||||
}
|
||||
|
||||
const starCountToIconCount = (githubStars: number): number => {
|
||||
if (githubStars > 2000) {
|
||||
return 5
|
||||
} else if (githubStars > 1000) {
|
||||
return 4
|
||||
} else if (githubStars > 500) {
|
||||
return 3
|
||||
} else if (githubStars > 200) {
|
||||
return 2
|
||||
} else if (githubStars > 100) {
|
||||
return 1
|
||||
} else return 0
|
||||
}
|
||||
|
||||
const RepeatingStars = ({ count }: { count: number }) => {
|
||||
return (
|
||||
<span>
|
||||
{Array.from({ length: count }).map((_, i) => (
|
||||
<Star key={i} />
|
||||
))}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const ProjectCard = ({ project }: { project: Project }) => {
|
||||
const classes = useStyles()
|
||||
const [isLightboxOpen, setIsLightboxOpen] = useState(false)
|
||||
const [lightboxSlide, setLightboxSlide] = useState(0)
|
||||
|
||||
const showStarCount = starCountToIconCount(project.stars)
|
||||
const screenshotSources = project.screenshots || []
|
||||
const tags = project.tags || []
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h4" component="h2">
|
||||
{project.title}
|
||||
</Typography>
|
||||
{showStarCount > 0 ? <RepeatingStars count={showStarCount} /> : null}
|
||||
<Typography className={classes.description}>
|
||||
{project.description}
|
||||
</Typography>
|
||||
<Divider />
|
||||
<Typography>Lang: {project.lang || 'en'}</Typography>
|
||||
<Typography>Added: {project.date_added}</Typography>
|
||||
<Typography>GitHub Stars: {project.stars}</Typography>
|
||||
<Typography>
|
||||
Code:{' '}
|
||||
{project.source ? (
|
||||
<a href={project.source} target="_blank" rel="noreferrer">
|
||||
{urlToReaddable(project.source)}
|
||||
</a>
|
||||
) : (
|
||||
'n/a'
|
||||
)}
|
||||
</Typography>
|
||||
<Typography>
|
||||
License:{' '}
|
||||
{project.license ? (
|
||||
<a
|
||||
href={`https://choosealicense.com/licenses/${project.license}/`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{project.license}
|
||||
</a>
|
||||
) : (
|
||||
'n/a'
|
||||
)}
|
||||
</Typography>
|
||||
<Typography>
|
||||
iTunes:{' '}
|
||||
{project.itunes ? (
|
||||
<a href={project.itunes} target="_blank" rel="noreferrer">
|
||||
{project.itunes}
|
||||
</a>
|
||||
) : (
|
||||
'n/a'
|
||||
)}
|
||||
</Typography>
|
||||
<Typography>
|
||||
Homepage:{' '}
|
||||
{project.homepage ? (
|
||||
<a href={project.homepage} target="_blank" rel="noreferrer">
|
||||
{project.homepage}
|
||||
</a>
|
||||
) : (
|
||||
'n/a'
|
||||
)}
|
||||
</Typography>
|
||||
<Typography>
|
||||
Tags:{' '}
|
||||
{tags.map(tag => {
|
||||
return (
|
||||
<Link key={tag} to={`/tag/${tag}/`} className={classes.tag}>
|
||||
#{tag}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</Typography>
|
||||
{screenshotSources.length === 0 ? null : (
|
||||
<>
|
||||
<GridList cellHeight={160} cols={4}>
|
||||
{screenshotSources.map((url, i) => {
|
||||
return (
|
||||
<GridListTile key={i} cols={1}>
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
onClick={event => {
|
||||
event.preventDefault()
|
||||
setLightboxSlide(i)
|
||||
setIsLightboxOpen(true)
|
||||
}}
|
||||
>
|
||||
<img src={url} width="120" height="160" />
|
||||
</a>
|
||||
</GridListTile>
|
||||
)
|
||||
})}
|
||||
</GridList>
|
||||
{!isLightboxOpen ? null : (
|
||||
<Lightbox
|
||||
mainSrc={screenshotSources[lightboxSlide]}
|
||||
nextSrc={screenshotSources[lightboxSlide + 1]}
|
||||
prevSrc={screenshotSources[lightboxSlide - 1]}
|
||||
onCloseRequest={() => setIsLightboxOpen(false)}
|
||||
onMoveNextRequest={() => {
|
||||
setLightboxSlide(
|
||||
(lightboxSlide + screenshotSources.length + 1) %
|
||||
screenshotSources.length,
|
||||
)
|
||||
}}
|
||||
onMovePrevRequest={() => {
|
||||
setLightboxSlide(
|
||||
(lightboxSlide + screenshotSources.length - 1) %
|
||||
screenshotSources.length,
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProjectCard
|
||||
|
||||
export const projectCardFragment = graphql`
|
||||
fragment ProjectCardFields on AppProject {
|
||||
category_ids
|
||||
date_added
|
||||
description
|
||||
homepage
|
||||
id
|
||||
itunes
|
||||
lang
|
||||
license
|
||||
screenshots
|
||||
source
|
||||
stars
|
||||
suggested_by
|
||||
tags
|
||||
title
|
||||
}
|
||||
`
|
40
gatsby/src/components/hero.tsx
Normal file
40
gatsby/src/components/hero.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import React, { FC } from 'react'
|
||||
import { makeStyles } from '@material-ui/styles'
|
||||
import { Typography, Container, Theme } from '@material-ui/core'
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
heroContent: {
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
padding: theme.spacing(8, 0, 6),
|
||||
},
|
||||
}))
|
||||
|
||||
export interface HeroProps {
|
||||
title: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
const Hero: FC<HeroProps> = ({ title, description = '', children }) => {
|
||||
const classes = useStyles()
|
||||
return (
|
||||
<div className={classes.heroContent}>
|
||||
<Container maxWidth="md">
|
||||
<Typography
|
||||
component="h1"
|
||||
variant="h2"
|
||||
align="center"
|
||||
color="textPrimary"
|
||||
gutterBottom
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
<Typography variant="h5" align="center" color="textSecondary" paragraph>
|
||||
{description}
|
||||
</Typography>
|
||||
{children}
|
||||
</Container>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Hero
|
93
gatsby/src/components/seo.tsx
Normal file
93
gatsby/src/components/seo.tsx
Normal file
@ -0,0 +1,93 @@
|
||||
/**
|
||||
* SEO component that queries for data with
|
||||
* Gatsby's useStaticQuery React hook
|
||||
*
|
||||
* See: https://www.gatsbyjs.org/docs/use-static-query/
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { useStaticQuery, graphql } from 'gatsby'
|
||||
|
||||
interface MetaProperty {
|
||||
property: string
|
||||
content: string
|
||||
}
|
||||
|
||||
interface MetaName {
|
||||
name: string
|
||||
content: string
|
||||
}
|
||||
|
||||
type Meta = MetaName | MetaProperty
|
||||
|
||||
export interface SEOProps {
|
||||
title: string
|
||||
description?: string
|
||||
lang?: string
|
||||
meta?: Meta[]
|
||||
}
|
||||
|
||||
const SEO: FC<SEOProps> = ({
|
||||
description = '',
|
||||
lang = 'en',
|
||||
meta = [],
|
||||
title,
|
||||
}) => {
|
||||
const { site } = useStaticQuery(
|
||||
graphql`
|
||||
query {
|
||||
site {
|
||||
siteMetadata {
|
||||
title
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
)
|
||||
|
||||
const metaDescription = description || site.siteMetadata.description
|
||||
|
||||
return (
|
||||
<Helmet
|
||||
htmlAttributes={{
|
||||
lang,
|
||||
}}
|
||||
title={title}
|
||||
titleTemplate={`%s | ${site.siteMetadata.title}`}
|
||||
meta={[
|
||||
{
|
||||
name: `description`,
|
||||
content: metaDescription,
|
||||
},
|
||||
{
|
||||
property: `og:title`,
|
||||
content: title,
|
||||
},
|
||||
{
|
||||
property: `og:description`,
|
||||
content: metaDescription,
|
||||
},
|
||||
{
|
||||
property: `og:type`,
|
||||
content: `website`,
|
||||
},
|
||||
{
|
||||
name: `twitter:card`,
|
||||
content: `summary`,
|
||||
},
|
||||
{
|
||||
name: `twitter:title`,
|
||||
content: title,
|
||||
},
|
||||
{
|
||||
name: `twitter:description`,
|
||||
content: metaDescription,
|
||||
},
|
||||
].concat(meta)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default SEO
|
21
gatsby/src/hooks/useSiteMetadata.ts
Normal file
21
gatsby/src/hooks/useSiteMetadata.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { graphql, useStaticQuery } from 'gatsby'
|
||||
|
||||
export interface SiteMetadata {
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export default (): SiteMetadata => {
|
||||
const data = useStaticQuery(graphql`
|
||||
{
|
||||
site {
|
||||
siteMetadata {
|
||||
title
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
return data.site.siteMetadata
|
||||
}
|
BIN
gatsby/src/images/gatsby-icon.png
Normal file
BIN
gatsby/src/images/gatsby-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
34
gatsby/src/layout/footer.tsx
Normal file
34
gatsby/src/layout/footer.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import React, { FC } from 'react'
|
||||
|
||||
import { makeStyles } from '@material-ui/styles'
|
||||
import { Container, Typography, Theme, Link } from '@material-ui/core'
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
footer: {
|
||||
padding: theme.spacing(3, 2),
|
||||
marginTop: theme.spacing(4),
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
},
|
||||
}))
|
||||
|
||||
const Footer: FC = () => {
|
||||
const classes = useStyles()
|
||||
return (
|
||||
<footer className={classes.footer}>
|
||||
<Container maxWidth="md">
|
||||
<Typography variant="body1" color="textSecondary">
|
||||
Built with the data from{' '}
|
||||
<Link
|
||||
href="https://github.com/dkhamsing/open-source-ios-apps"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
@dkhamsing/open-source-ios-apps
|
||||
</Link>
|
||||
</Typography>
|
||||
</Container>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
export default Footer
|
50
gatsby/src/layout/header.tsx
Normal file
50
gatsby/src/layout/header.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import { AppBar, Button, Link, Toolbar, Typography } from '@material-ui/core'
|
||||
import { makeStyles } from '@material-ui/styles'
|
||||
import { Link as GatsbyLink } from 'gatsby'
|
||||
import React, { FC } from 'react'
|
||||
|
||||
const useStyles = makeStyles({
|
||||
toolbar: {},
|
||||
title: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
link: {
|
||||
textDecoration: 'none',
|
||||
},
|
||||
})
|
||||
|
||||
export interface HeaderProps {
|
||||
siteTitle?: string
|
||||
}
|
||||
|
||||
const Header: FC<HeaderProps> = ({ siteTitle = '' }) => {
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
<AppBar component="header" position="static">
|
||||
<Toolbar className={classes.toolbar}>
|
||||
<Typography variant="h6" className={classes.title}>
|
||||
<Link
|
||||
to="/"
|
||||
component={GatsbyLink}
|
||||
color="inherit"
|
||||
className={classes.link}
|
||||
>
|
||||
{siteTitle}
|
||||
</Link>
|
||||
</Typography>
|
||||
<Button
|
||||
color="inherit"
|
||||
component="a"
|
||||
href="https://github.com/dkhamsing/open-source-ios-apps"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
GitHub
|
||||
</Button>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
)
|
||||
}
|
||||
|
||||
export default Header
|
46
gatsby/src/layout/index.tsx
Normal file
46
gatsby/src/layout/index.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import React, { FC } from 'react'
|
||||
import { makeStyles, ThemeProvider } from '@material-ui/styles'
|
||||
import { Theme } from '@material-ui/core'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
import Header from './header'
|
||||
import Footer from './footer'
|
||||
import themes from '../theme'
|
||||
import useSiteMetadata from '../hooks/useSiteMetadata'
|
||||
import { RootState } from '../redux/store'
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
root: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
minHeight: '100vh',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
main: {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
}))
|
||||
|
||||
const LayoutComponent: FC = ({ children }) => {
|
||||
const classes = useStyles()
|
||||
const { title } = useSiteMetadata()
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Header siteTitle={title} />
|
||||
<main className={classes.main}>{children}</main>
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Layout: FC = ({ children }) => {
|
||||
const { theme } = useSelector((state: RootState) => state.app)
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={themes[theme]}>
|
||||
<LayoutComponent>{children}</LayoutComponent>
|
||||
</ThemeProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default Layout
|
17
gatsby/src/libs/wrapPageElement.tsx
Normal file
17
gatsby/src/libs/wrapPageElement.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import React, { ReactNode } from 'react'
|
||||
import { ThemeProvider } from '@material-ui/styles'
|
||||
import { CssBaseline } from '@material-ui/core'
|
||||
|
||||
import themes from '../theme'
|
||||
import Layout from '../layout'
|
||||
|
||||
const wrapPageElement = ({ element }: { element: ReactNode }) => {
|
||||
return (
|
||||
<ThemeProvider theme={themes['light']}>
|
||||
<CssBaseline />
|
||||
<Layout>{element}</Layout>
|
||||
</ThemeProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default wrapPageElement
|
10
gatsby/src/libs/wrapRootElement.tsx
Normal file
10
gatsby/src/libs/wrapRootElement.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import React, { ReactNode } from 'react'
|
||||
import { Provider } from 'react-redux'
|
||||
|
||||
import store from '../redux/store'
|
||||
|
||||
const wrapRootElement = ({ element }: { element: ReactNode }) => {
|
||||
return <Provider store={store}>{element}</Provider>
|
||||
}
|
||||
|
||||
export default wrapRootElement
|
30
gatsby/src/pages/404.tsx
Normal file
30
gatsby/src/pages/404.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import React, { FC } from 'react'
|
||||
import { makeStyles } from '@material-ui/styles'
|
||||
import { Theme } from '@material-ui/core'
|
||||
|
||||
import SEO from '../components/seo'
|
||||
import { Typography, Container } from '@material-ui/core'
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
root: {
|
||||
marginTop: theme.spacing(8),
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
}))
|
||||
|
||||
const NotFoundPage: FC = () => {
|
||||
const classes = useStyles()
|
||||
return (
|
||||
<Container maxWidth="md" className={classes.root}>
|
||||
<SEO title="404: Not found" />
|
||||
<Typography variant="h2" gutterBottom component="h1">
|
||||
NOT FOUND
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
You just hit a route that doesn't exist... the sadness.
|
||||
</Typography>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default NotFoundPage
|
158
gatsby/src/pages/index.tsx
Normal file
158
gatsby/src/pages/index.tsx
Normal file
@ -0,0 +1,158 @@
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
Grid,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
Theme,
|
||||
Typography,
|
||||
} from '@material-ui/core'
|
||||
import { makeStyles } from '@material-ui/styles'
|
||||
import { graphql, Link } from 'gatsby'
|
||||
import React, { FC } from 'react'
|
||||
import Hero from '../components/hero'
|
||||
import SEO from '../components/seo'
|
||||
import { Category } from '../types'
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
wrapper: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(2),
|
||||
},
|
||||
button: {
|
||||
marginTop: theme.spacing(2),
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
}))
|
||||
|
||||
const CategoryItem = ({
|
||||
category,
|
||||
categories,
|
||||
}: {
|
||||
category: Category
|
||||
categories: Category[]
|
||||
}) => {
|
||||
const classes = useStyles()
|
||||
|
||||
const childCategories = categories.filter(
|
||||
cat => cat.parentSlug === category.slug,
|
||||
)
|
||||
|
||||
return (
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h3">{category.title}</Typography>
|
||||
<Typography>{category.description}</Typography>
|
||||
<Typography>{category.projectCount} projects</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
component={Link}
|
||||
fullWidth
|
||||
to={`/category/${category.slug}/`}
|
||||
className={classes.button}
|
||||
>
|
||||
Browse {category.title}
|
||||
</Button>
|
||||
|
||||
{childCategories.length === 0 ? null : (
|
||||
<>
|
||||
<Typography variant="h5" component="h4">
|
||||
Child categories:
|
||||
</Typography>
|
||||
<List>
|
||||
{childCategories.map(childCategory => {
|
||||
return (
|
||||
<ListItem
|
||||
button
|
||||
key={childCategory.id}
|
||||
component={Link}
|
||||
to={`/category/${childCategory.slug}/`}
|
||||
>
|
||||
<ListItemText
|
||||
primary={`${childCategory.title} (${childCategory.projectCount} projects)`}
|
||||
/>
|
||||
</ListItem>
|
||||
)
|
||||
})}
|
||||
</List>
|
||||
</>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
type IndexPageProps = {
|
||||
data: {
|
||||
allAppCategory: {
|
||||
edges: {
|
||||
node: Category
|
||||
}[]
|
||||
}
|
||||
allAppProject: {
|
||||
totalCount: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const IndexPage: FC<IndexPageProps> = props => {
|
||||
const classes = useStyles()
|
||||
|
||||
const { edges: categoryEdges } = props.data.allAppCategory
|
||||
const projectCount = props.data.allAppProject.totalCount
|
||||
|
||||
const categories = categoryEdges.map(e => e.node)
|
||||
|
||||
const topLevelCategories = categories.filter(category => {
|
||||
return category.parentSlug === null
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<SEO title="Home" />
|
||||
<Hero
|
||||
title="Open Source iOS Apps"
|
||||
description={`A community curated set of ${projectCount} open source iOS apps.`}
|
||||
/>
|
||||
<div className={classes.wrapper}>
|
||||
<Grid container spacing={2}>
|
||||
{topLevelCategories.map(cat => {
|
||||
return (
|
||||
<CategoryItem
|
||||
key={cat.id}
|
||||
category={cat}
|
||||
categories={categories}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Grid>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const pageQuery = graphql`
|
||||
query IndexPageQuery {
|
||||
allAppCategory {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
slug
|
||||
parentSlug
|
||||
projectCount
|
||||
description
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
allAppProject {
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default IndexPage
|
24
gatsby/src/redux/appModule.ts
Normal file
24
gatsby/src/redux/appModule.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
export interface AppState {
|
||||
theme: 'light' | 'dark'
|
||||
}
|
||||
|
||||
const initialState: AppState = {
|
||||
theme: 'light',
|
||||
}
|
||||
|
||||
const app = createSlice({
|
||||
name: 'app',
|
||||
initialState,
|
||||
reducers: {
|
||||
toggleTheme(state) {
|
||||
const newTheme = state.theme === 'light' ? 'dark' : 'light'
|
||||
state.theme = newTheme
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const { toggleTheme } = app.actions
|
||||
|
||||
export default app.reducer
|
23
gatsby/src/redux/persistStore.ts
Normal file
23
gatsby/src/redux/persistStore.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { RootState } from './store'
|
||||
|
||||
export const loadState: () => RootState = () => {
|
||||
try {
|
||||
const serializedState = localStorage.getItem('state')
|
||||
if (serializedState === null || serializedState === 'undefined') {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return JSON.parse(serializedState)
|
||||
} catch (err) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export const saveState = (state: Partial<RootState>) => {
|
||||
try {
|
||||
const serializedState = JSON.stringify(state)
|
||||
localStorage.setItem('state', serializedState)
|
||||
} catch (err) {
|
||||
// Ignore write errors
|
||||
}
|
||||
}
|
26
gatsby/src/redux/store.ts
Normal file
26
gatsby/src/redux/store.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { configureStore } from '@reduxjs/toolkit'
|
||||
import { combineReducers } from 'redux'
|
||||
|
||||
import app from '../redux/appModule'
|
||||
import { loadState, saveState } from './persistStore'
|
||||
|
||||
const preloadedState = loadState()
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
app,
|
||||
})
|
||||
|
||||
const store = configureStore({
|
||||
reducer: rootReducer,
|
||||
preloadedState,
|
||||
})
|
||||
|
||||
store.subscribe(() => {
|
||||
const { app } = store.getState()
|
||||
saveState({ app })
|
||||
})
|
||||
|
||||
export type RootState = ReturnType<typeof rootReducer>
|
||||
export type RootDispatch = typeof store.dispatch
|
||||
|
||||
export default store
|
93
gatsby/src/templates/category.tsx
Normal file
93
gatsby/src/templates/category.tsx
Normal file
@ -0,0 +1,93 @@
|
||||
import {
|
||||
Button,
|
||||
createStyles,
|
||||
Grid,
|
||||
makeStyles,
|
||||
Theme,
|
||||
Typography,
|
||||
} from '@material-ui/core'
|
||||
import { graphql, Link } from 'gatsby'
|
||||
import React from 'react'
|
||||
import Hero from '../components/hero'
|
||||
import ProjectCard from '../components/ProjectCard'
|
||||
import SEO from '../components/seo'
|
||||
import { Category, Project } from '../types'
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
wrapper: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(2),
|
||||
},
|
||||
backButton: {
|
||||
textAlign: 'center',
|
||||
margin: theme.spacing(4),
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
type Props = {
|
||||
data: {
|
||||
appCategory: Category
|
||||
allAppProject: {
|
||||
edges: {
|
||||
node: Project
|
||||
}[]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const CategoryTemplate: React.FC<Props> = props => {
|
||||
const classes = useStyles()
|
||||
|
||||
const category = props.data.appCategory
|
||||
const projectEdges = props.data.allAppProject.edges
|
||||
const projects: Project[] = projectEdges.map(n => n.node)
|
||||
|
||||
return (
|
||||
<>
|
||||
<SEO title="Home" />
|
||||
<Hero
|
||||
title={`Category: ${category.title}`}
|
||||
description={category.description || ''}
|
||||
></Hero>
|
||||
<Typography className={classes.backButton}>
|
||||
<Button component={Link} to="/" variant="contained">
|
||||
Back to category list
|
||||
</Button>
|
||||
</Typography>
|
||||
<div className={classes.wrapper}>
|
||||
<Grid container spacing={2}>
|
||||
{projects.map(project => {
|
||||
return (
|
||||
<Grid item xs={12} sm={6} key={project.id}>
|
||||
<ProjectCard project={project} />
|
||||
</Grid>
|
||||
)
|
||||
})}
|
||||
</Grid>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default CategoryTemplate
|
||||
|
||||
export const pageQuery = graphql`
|
||||
query CategoryPageQuery($slug: String!) {
|
||||
appCategory(slug: { eq: $slug }) {
|
||||
id
|
||||
description
|
||||
slug
|
||||
title
|
||||
}
|
||||
|
||||
allAppProject(filter: { category_ids: { eq: $slug } }) {
|
||||
edges {
|
||||
node {
|
||||
...ProjectCardFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
74
gatsby/src/templates/tag.tsx
Normal file
74
gatsby/src/templates/tag.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import { createStyles, Grid, makeStyles, Theme } from '@material-ui/core'
|
||||
import { graphql } from 'gatsby'
|
||||
import React from 'react'
|
||||
import Hero from '../components/hero'
|
||||
import ProjectCard from '../components/ProjectCard'
|
||||
import SEO from '../components/seo'
|
||||
import { Category, Project } from '../types'
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
wrapper: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(2),
|
||||
},
|
||||
backButton: {
|
||||
textAlign: 'center',
|
||||
margin: theme.spacing(4),
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
type Props = {
|
||||
data: {
|
||||
appCategory: Category
|
||||
allAppProject: {
|
||||
edges: {
|
||||
node: Project
|
||||
}[]
|
||||
}
|
||||
}
|
||||
pageContext: {
|
||||
tag: string
|
||||
slug: string
|
||||
}
|
||||
}
|
||||
|
||||
const TagTemplate: React.FC<Props> = props => {
|
||||
const classes = useStyles()
|
||||
|
||||
const projectEdges = props.data.allAppProject.edges
|
||||
const projects: Project[] = projectEdges.map(n => n.node)
|
||||
|
||||
return (
|
||||
<>
|
||||
<SEO title="Home" />
|
||||
<Hero title={`Tag: #${props.pageContext.tag}`} description=""></Hero>
|
||||
<div className={classes.wrapper}>
|
||||
<Grid container spacing={2}>
|
||||
{projects.map(project => {
|
||||
return (
|
||||
<Grid item xs={12} sm={6} key={project.id}>
|
||||
<ProjectCard project={project} />
|
||||
</Grid>
|
||||
)
|
||||
})}
|
||||
</Grid>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagTemplate
|
||||
|
||||
export const pageQuery = graphql`
|
||||
query TagPageQuery($slug: String!) {
|
||||
allAppProject(filter: { tags: { eq: $slug } }) {
|
||||
edges {
|
||||
node {
|
||||
...ProjectCardFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
46
gatsby/src/theme.ts
Normal file
46
gatsby/src/theme.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import deepMerge from 'deepmerge'
|
||||
import { red } from '@material-ui/core/colors'
|
||||
import {
|
||||
createMuiTheme,
|
||||
responsiveFontSizes,
|
||||
ThemeOptions,
|
||||
Theme,
|
||||
} from '@material-ui/core/styles'
|
||||
|
||||
const makeTheme = (variant: ThemeOptions): Theme => {
|
||||
const common = {
|
||||
palette: {
|
||||
primary: {
|
||||
main: '#3E3F3A',
|
||||
},
|
||||
secondary: {
|
||||
main: '#19857b',
|
||||
},
|
||||
error: {
|
||||
main: red.A400,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const theme = createMuiTheme(deepMerge(common, variant))
|
||||
return responsiveFontSizes(theme)
|
||||
}
|
||||
|
||||
const light: ThemeOptions = {
|
||||
palette: {
|
||||
type: 'light',
|
||||
},
|
||||
}
|
||||
|
||||
const dark: ThemeOptions = {
|
||||
palette: {
|
||||
type: 'dark',
|
||||
},
|
||||
}
|
||||
|
||||
const themes = {
|
||||
light: makeTheme(light),
|
||||
dark: makeTheme(dark),
|
||||
}
|
||||
|
||||
export default themes
|
25
gatsby/src/types.ts
Normal file
25
gatsby/src/types.ts
Normal file
@ -0,0 +1,25 @@
|
||||
export type Category = {
|
||||
id: string
|
||||
slug: string
|
||||
parentSlug: string | null
|
||||
projectCount: number
|
||||
description: string | null
|
||||
title: string | null
|
||||
}
|
||||
|
||||
export type Project = {
|
||||
category_ids: string[]
|
||||
date_added: string
|
||||
description: string | null
|
||||
homepage: string | null
|
||||
id: string
|
||||
itunes: string | null
|
||||
lang: string | null
|
||||
license: string
|
||||
screenshots: string[]
|
||||
source: string | null
|
||||
stars: number
|
||||
suggested_by: string
|
||||
tags: string[]
|
||||
title: string
|
||||
}
|
BIN
gatsby/static/favicon.ico
Normal file
BIN
gatsby/static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 198 B |
2
gatsby/static/robots.txt
Normal file
2
gatsby/static/robots.txt
Normal file
@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow:
|
19
gatsby/tsconfig.json
Normal file
19
gatsby/tsconfig.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"lib": ["dom", "esnext"],
|
||||
"noImplicitAny": true,
|
||||
"jsx": "preserve",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"noEmit": true,
|
||||
"isolatedModules": true,
|
||||
"strict": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "public", ".cache"]
|
||||
}
|
13578
gatsby/yarn.lock
Normal file
13578
gatsby/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
2
netlify.toml
Normal file
2
netlify.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[build]
|
||||
base = "gatsby"
|
Loading…
Reference in New Issue
Block a user