mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-09 21:07:46 +00:00
[now-static-build][frameworks][examples] Fixes examples and adjust frameworks (#3584)
* [examples] Fix ionic-react example * [examples] Fix vue example * [examples] Fix mithril example * [examples] Fix riot example * Fix readmes * [now-static-build] Add Zola * Add tests * [now-build-utils][frameworks] Adjust detect framework * Move zola back * Undo Hugo detection changes * [examples] Fix Vue logo path * [now-static-build] Use package.json script if defined instead of framework command * [now-static-build] Add buildCommand everywhere * Remove devCommand from frameworks.ts * Fix type * Change output directory * [now-static-build] Remove minNodeRange * Remove devCommand
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Angular Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# React Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Docusaurus Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Eleventy Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Ember Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Gatsby Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Gridsome Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Hexo Example
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# http://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = false
|
||||
67
examples/ionic-react/.gitignore
vendored
67
examples/ionic-react/.gitignore
vendored
@@ -1,67 +1,9 @@
|
||||
# Logs
|
||||
.firebase
|
||||
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
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
@@ -75,9 +17,8 @@ typings/
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.vscode
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
.stencil/
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"appId": "io.ionic.starter",
|
||||
"appName": "ionic-react-conference-app",
|
||||
"bundledWebRuntime": false,
|
||||
"npmClient": "npm",
|
||||
"webDir": "build",
|
||||
"cordova": {}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
{
|
||||
"name": "ionic-react-conference-app",
|
||||
"integrations": {
|
||||
"capacitor": {}
|
||||
},
|
||||
"name": "ionic-react",
|
||||
"integrations": {},
|
||||
"type": "react"
|
||||
}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
{
|
||||
"name": "ionic-react-conference-app",
|
||||
"name": "ionic-react",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@capacitor/core": "1.3.0",
|
||||
"@ionic/react": "^4.11.4",
|
||||
"@ionic/react-router": "^4.11.4",
|
||||
"@types/jest": "24.0.18",
|
||||
"@types/node": "12.7.5",
|
||||
"@types/react": "^16.9.2",
|
||||
"@types/react-dom": "^16.9.0",
|
||||
"@types/react-router": "^5.0.3",
|
||||
"@types/react-router-dom": "^4.3.1",
|
||||
"date-fns": "^2.6.0",
|
||||
"@ionic/react": "^4.11.0",
|
||||
"@ionic/react-router": "^4.11.0",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.4.0",
|
||||
"@testing-library/user-event": "^8.0.3",
|
||||
"@types/jest": "^24.0.25",
|
||||
"@types/node": "^12.12.24",
|
||||
"@types/react": "^16.9.17",
|
||||
"@types/react-dom": "^16.9.4",
|
||||
"@types/react-router": "^5.1.4",
|
||||
"@types/react-router-dom": "^5.1.3",
|
||||
"ionicons": "^4.6.3",
|
||||
"node-sass": "^4.13.0",
|
||||
"react": "^16.9.0",
|
||||
"react-dom": "^16.9.0",
|
||||
"react-router": "^5.0.1",
|
||||
"react-router-dom": "^5.0.1",
|
||||
"react-scripts": "3.2.0",
|
||||
"reselect": "^4.0.0"
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-router": "^5.1.2",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-scripts": "3.3.0",
|
||||
"typescript": "3.7.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
@@ -28,17 +28,20 @@
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"browserslist": [
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not ie <= 11",
|
||||
"not op_mini all"
|
||||
],
|
||||
"description": "An Ionic project",
|
||||
"devDependencies": {
|
||||
"@capacitor/cli": "1.3.0",
|
||||
"@testing-library/react": "^9.3.1",
|
||||
"@types/googlemaps": "^3.38.0",
|
||||
"typescript": "3.6.3"
|
||||
}
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"description": "An Ionic project"
|
||||
}
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { render } from '@testing-library/react';
|
||||
import App from './App';
|
||||
import { render, fireEvent, waitForElement } from '@testing-library/react'
|
||||
|
||||
it('renders without crashing', () => {
|
||||
// const div = document.createElement('div');
|
||||
// ReactDOM.render(<App />, div);
|
||||
// ReactDOM.unmountComponentAtNode(div);
|
||||
const { asFragment, container } = render(<App />);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
test('renders without crashing', () => {
|
||||
const { baseElement } = render(<App />);
|
||||
expect(baseElement).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import { Redirect, Route } from 'react-router-dom';
|
||||
import { IonApp, IonRouterOutlet, IonSplitPane } from '@ionic/react';
|
||||
import { IonApp, IonRouterOutlet } from '@ionic/react';
|
||||
import { IonReactRouter } from '@ionic/react-router';
|
||||
|
||||
import Menu from './components/Menu';
|
||||
import Home from './pages/Home';
|
||||
|
||||
/* Core CSS required for Ionic components to work properly */
|
||||
import '@ionic/react/css/core.css';
|
||||
@@ -23,85 +22,16 @@ import '@ionic/react/css/display.css';
|
||||
|
||||
/* Theme variables */
|
||||
import './theme/variables.css';
|
||||
import MainTabs from './pages/MainTabs';
|
||||
import { connect } from './data/connect';
|
||||
import { AppContextProvider } from './data/AppContext';
|
||||
import { loadConfData } from './data/sessions/sessions.actions';
|
||||
import { setIsLoggedIn, setUsername, loadUserData } from './data/user/user.actions';
|
||||
import Account from './pages/Account';
|
||||
import Login from './pages/Login';
|
||||
import Signup from './pages/Signup';
|
||||
import Support from './pages/Support';
|
||||
import Tutorial from './pages/Tutorial';
|
||||
import HomeOrTutorial from './components/HomeOrTutorial';
|
||||
import { Session } from "./models/Session";
|
||||
|
||||
const App: React.FC = () => {
|
||||
return (
|
||||
<AppContextProvider>
|
||||
<IonicAppConnected />
|
||||
</AppContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
interface StateProps {
|
||||
darkMode: boolean,
|
||||
sessions: Session[],
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
loadConfData: typeof loadConfData;
|
||||
loadUserData: typeof loadUserData;
|
||||
setIsLoggedIn: typeof setIsLoggedIn;
|
||||
setUsername: typeof setUsername;
|
||||
}
|
||||
|
||||
interface IonicAppProps extends StateProps, DispatchProps { }
|
||||
|
||||
const IonicApp: React.FC<IonicAppProps> = ({ darkMode, sessions, setIsLoggedIn, setUsername, loadConfData, loadUserData }) => {
|
||||
|
||||
useEffect(() => {
|
||||
loadUserData();
|
||||
loadConfData();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
return (
|
||||
sessions.length === 0 ? (
|
||||
<div></div>
|
||||
) : (
|
||||
<IonApp className={`${darkMode ? 'dark-theme' : ''}`}>
|
||||
const App: React.FC = () => (
|
||||
<IonApp>
|
||||
<IonReactRouter>
|
||||
<IonSplitPane contentId="main">
|
||||
<Menu />
|
||||
<IonRouterOutlet id="main">
|
||||
<Route path="/tabs" component={MainTabs} />
|
||||
<Route path="/account" component={Account} />
|
||||
<Route path="/login" component={Login} />
|
||||
<Route path="/signup" component={Signup} />
|
||||
<Route path="/support" component={Support} />
|
||||
<Route path="/tutorial" component={Tutorial} />
|
||||
<Route path="/logout" render={() => {
|
||||
setIsLoggedIn(false);
|
||||
setUsername(undefined);
|
||||
return <Redirect to="/tabs" />
|
||||
}} />
|
||||
<Route path="/" component={HomeOrTutorial} exact />
|
||||
<IonRouterOutlet>
|
||||
<Route path="/home" component={Home} exact={true} />
|
||||
<Route exact path="/" render={() => <Redirect to="/home" />} />
|
||||
</IonRouterOutlet>
|
||||
</IonSplitPane>
|
||||
</IonReactRouter>
|
||||
</IonApp>
|
||||
)
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
export default App;
|
||||
|
||||
const IonicAppConnected = connect<{}, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state) => ({
|
||||
darkMode: state.user.darkMode,
|
||||
sessions: state.data.sessions
|
||||
}),
|
||||
mapDispatchToProps: { loadConfData, loadUserData, setIsLoggedIn, setUsername },
|
||||
component: IonicApp
|
||||
});
|
||||
|
||||
@@ -1,280 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders without crashing 1`] = `
|
||||
<DocumentFragment>
|
||||
<ion-app>
|
||||
<ion-split-pane
|
||||
content-id="main"
|
||||
>
|
||||
<ion-menu
|
||||
content-id="main"
|
||||
>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>
|
||||
Menu
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content
|
||||
class="outer-content"
|
||||
>
|
||||
<ion-list>
|
||||
<ion-list-header>
|
||||
Navigate
|
||||
</ion-list-header>
|
||||
<ion-menu-toggle
|
||||
auto-hide="false"
|
||||
>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
/>
|
||||
<ion-label>
|
||||
Schedule
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
<ion-menu-toggle
|
||||
auto-hide="false"
|
||||
>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
/>
|
||||
<ion-label>
|
||||
Speakers
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
<ion-menu-toggle
|
||||
auto-hide="false"
|
||||
>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
/>
|
||||
<ion-label>
|
||||
Map
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
<ion-menu-toggle
|
||||
auto-hide="false"
|
||||
>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
/>
|
||||
<ion-label>
|
||||
About
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
</ion-list>
|
||||
<ion-list>
|
||||
<ion-list-header>
|
||||
Account
|
||||
</ion-list-header>
|
||||
<ion-menu-toggle
|
||||
auto-hide="false"
|
||||
>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
/>
|
||||
<ion-label>
|
||||
Account
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
<ion-menu-toggle
|
||||
auto-hide="false"
|
||||
>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
/>
|
||||
<ion-label>
|
||||
Support
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
<ion-menu-toggle
|
||||
auto-hide="false"
|
||||
>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
/>
|
||||
<ion-label>
|
||||
Logout
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
</ion-list>
|
||||
<ion-list>
|
||||
<ion-list-header>
|
||||
Tutorial
|
||||
</ion-list-header>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
/>
|
||||
Show Tutorial
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
</ion-menu>
|
||||
<ion-router-outlet
|
||||
id="main"
|
||||
>
|
||||
<div
|
||||
style="display: flex; position: absolute; top: 0px; left: 0px; right: 0px; bottom: 0px; flex-direction: column; width: 100%; height: 100%; contain: layout size style;"
|
||||
>
|
||||
<div
|
||||
class="tabs-inner"
|
||||
style="position: relative; flex: 1; contain: layout size style;"
|
||||
>
|
||||
<ion-router-outlet>
|
||||
<div
|
||||
class="ion-page ion-page-invisible"
|
||||
>
|
||||
<ion-header>
|
||||
<ion-toolbar
|
||||
color="primary"
|
||||
>
|
||||
<ion-buttons
|
||||
slot="start"
|
||||
>
|
||||
<ion-menu-button />
|
||||
</ion-buttons>
|
||||
<ion-segment>
|
||||
<ion-segment-button
|
||||
value="all"
|
||||
>
|
||||
All
|
||||
</ion-segment-button>
|
||||
<ion-segment-button
|
||||
value="favorites"
|
||||
>
|
||||
Favorites
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
<ion-buttons
|
||||
slot="end"
|
||||
>
|
||||
<ion-button>
|
||||
<ion-icon
|
||||
slot="icon-only"
|
||||
/>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
<ion-toolbar
|
||||
color="primary"
|
||||
>
|
||||
<ion-searchbar
|
||||
placeholder="Search"
|
||||
/>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher
|
||||
slot="fixed"
|
||||
>
|
||||
<ion-refresher-content />
|
||||
</ion-refresher>
|
||||
<ion-list>
|
||||
<ion-list-header>
|
||||
No Sessions Found
|
||||
</ion-list-header>
|
||||
</ion-list>
|
||||
<ion-list
|
||||
style="display: none;"
|
||||
/>
|
||||
</ion-content>
|
||||
<ion-fab
|
||||
horizontal="end"
|
||||
slot="fixed"
|
||||
vertical="bottom"
|
||||
>
|
||||
<ion-fab-button>
|
||||
<ion-icon />
|
||||
</ion-fab-button>
|
||||
<ion-fab-list
|
||||
side="top"
|
||||
>
|
||||
<ion-fab-button
|
||||
color="vimeo"
|
||||
>
|
||||
<ion-icon />
|
||||
</ion-fab-button>
|
||||
<ion-fab-button
|
||||
color="google"
|
||||
>
|
||||
<ion-icon />
|
||||
</ion-fab-button>
|
||||
<ion-fab-button
|
||||
color="twitter"
|
||||
>
|
||||
<ion-icon />
|
||||
</ion-fab-button>
|
||||
<ion-fab-button
|
||||
color="facebook"
|
||||
>
|
||||
<ion-icon />
|
||||
</ion-fab-button>
|
||||
</ion-fab-list>
|
||||
</ion-fab>
|
||||
</div>
|
||||
</ion-router-outlet>
|
||||
</div>
|
||||
<ion-tab-bar
|
||||
current-path="/tabs/schedule"
|
||||
selected-tab="schedule"
|
||||
slot="bottom"
|
||||
>
|
||||
<ion-tab-button
|
||||
href="/tabs/schedule"
|
||||
tab="schedule"
|
||||
>
|
||||
<ion-icon />
|
||||
<ion-label>
|
||||
Schedule
|
||||
</ion-label>
|
||||
</ion-tab-button>
|
||||
<ion-tab-button
|
||||
href="/tabs/speakers"
|
||||
tab="speakers"
|
||||
>
|
||||
<ion-icon />
|
||||
<ion-label>
|
||||
Speakers
|
||||
</ion-label>
|
||||
</ion-tab-button>
|
||||
<ion-tab-button
|
||||
href="/tabs/map"
|
||||
tab="map"
|
||||
>
|
||||
<ion-icon />
|
||||
<ion-label>
|
||||
Map
|
||||
</ion-label>
|
||||
</ion-tab-button>
|
||||
<ion-tab-button
|
||||
href="/tabs/about"
|
||||
tab="about"
|
||||
>
|
||||
<ion-icon />
|
||||
<ion-label>
|
||||
About
|
||||
</ion-label>
|
||||
</ion-tab-button>
|
||||
</ion-tab-bar>
|
||||
</div>
|
||||
</ion-router-outlet>
|
||||
</ion-split-pane>
|
||||
</ion-app>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
@@ -1,36 +0,0 @@
|
||||
import React from 'react';
|
||||
import { IonList, IonItem, IonLabel } from '@ionic/react';
|
||||
|
||||
interface AboutPopoverProps {
|
||||
dismiss: () => void;
|
||||
};
|
||||
|
||||
const AboutPopover: React.FC<AboutPopoverProps> = ({dismiss}) => {
|
||||
|
||||
const close = (url: string) => {
|
||||
window.open(url, '_blank');
|
||||
dismiss();
|
||||
};
|
||||
|
||||
return (
|
||||
<IonList>
|
||||
<IonItem button onClick={() => close('https://ionicframework.com/getting-started')}>
|
||||
<IonLabel>Learn Ionic</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem button onClick={() => close('https://ionicframework.com/docs/react')}>
|
||||
<IonLabel>Documentation</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem button onClick={() => close('https://showcase.ionicframework.com')}>
|
||||
<IonLabel>Showcase</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem button onClick={() => close('https://github.com/ionic-team/ionic')}>
|
||||
<IonLabel>GitHub Repo</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem button onClick={dismiss}>
|
||||
<IonLabel>Support</IonLabel>
|
||||
</IonItem>
|
||||
</IonList >
|
||||
)
|
||||
}
|
||||
|
||||
export default AboutPopover;
|
||||
@@ -1,18 +0,0 @@
|
||||
import React from 'react';
|
||||
import { connect } from '../data/connect';
|
||||
import { Redirect } from 'react-router';
|
||||
|
||||
interface StateProps {
|
||||
hasSeenTutorial: boolean;
|
||||
}
|
||||
|
||||
const HomeOrTutorial: React.FC<StateProps> = ({ hasSeenTutorial }) => {
|
||||
return hasSeenTutorial ? <Redirect to="/tabs/schedule" /> : <Redirect to="/tutorial" />
|
||||
};
|
||||
|
||||
export default connect<{}, StateProps, {}>({
|
||||
mapStateToProps: (state) => ({
|
||||
hasSeenTutorial: state.user.hasSeenTutorial
|
||||
}),
|
||||
component: HomeOrTutorial
|
||||
});
|
||||
@@ -1,56 +0,0 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import { Location } from '../models/Location';
|
||||
|
||||
interface MapProps {
|
||||
locations: Location[]
|
||||
mapCenter: Location
|
||||
}
|
||||
|
||||
const Map: React.FC<MapProps> = ({ mapCenter, locations }) => {
|
||||
const mapEle = useRef<HTMLDivElement>(null);
|
||||
const map = useRef<google.maps.Map>();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
map.current = new google.maps.Map(mapEle.current, {
|
||||
center: {
|
||||
lat: mapCenter.lat,
|
||||
lng: mapCenter.lng
|
||||
},
|
||||
zoom: 16
|
||||
});
|
||||
|
||||
addMarkers();
|
||||
|
||||
google.maps.event.addListenerOnce(map.current, 'idle', () => {
|
||||
if (mapEle.current) {
|
||||
mapEle.current.classList.add('show-map');
|
||||
}
|
||||
});
|
||||
|
||||
function addMarkers() {
|
||||
locations.forEach((markerData) => {
|
||||
let infoWindow = new google.maps.InfoWindow({
|
||||
content: `<h5>${markerData.name}</h5>`
|
||||
});
|
||||
|
||||
let marker = new google.maps.Marker({
|
||||
position: new google.maps.LatLng(markerData.lat, markerData.lng),
|
||||
map: map.current!,
|
||||
title: markerData.name
|
||||
});
|
||||
|
||||
marker.addListener('click', () => {
|
||||
infoWindow.open(map.current!, marker);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}, [mapCenter, locations]);
|
||||
|
||||
return (
|
||||
<div ref={mapEle} className="map-canvas"></div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Map;
|
||||
@@ -1,119 +0,0 @@
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonList,
|
||||
IonListHeader,
|
||||
IonMenu,
|
||||
IonMenuToggle,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonToggle
|
||||
} from '@ionic/react';
|
||||
import { calendar, contacts, hammer, help, informationCircle, logIn, logOut, map, person, personAdd } from 'ionicons/icons';
|
||||
import React, { useState } from 'react';
|
||||
import { connect } from '../data/connect';
|
||||
import { RouteComponentProps, withRouter } from 'react-router';
|
||||
import { setDarkMode } from '../data/user/user.actions';
|
||||
|
||||
const routes = {
|
||||
appPages: [
|
||||
{ title: 'Schedule', path: '/tabs/schedule', icon: calendar },
|
||||
{ title: 'Speakers', path: '/tabs/speakers', icon: contacts },
|
||||
{ title: 'Map', path: '/tabs/map', icon: map },
|
||||
{ title: 'About', path: '/tabs/about', icon: informationCircle }
|
||||
],
|
||||
loggedInPages: [
|
||||
{ title: 'Account', path: '/account', icon: person },
|
||||
{ title: 'Support', path: '/support', icon: help },
|
||||
{ title: 'Logout', path: '/logout', icon: logOut }
|
||||
],
|
||||
loggedOutPages: [
|
||||
{ title: 'Login', path: '/login', icon: logIn },
|
||||
{ title: 'Support', path: '/support', icon: help },
|
||||
{ title: 'Signup', path: '/signup', icon: personAdd }
|
||||
]
|
||||
};
|
||||
|
||||
interface Pages {
|
||||
title: string,
|
||||
path: string,
|
||||
icon: { ios: string, md: string },
|
||||
routerDirection?: string
|
||||
}
|
||||
interface StateProps {
|
||||
darkMode: boolean;
|
||||
isAuthenticated: boolean;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
setDarkMode: typeof setDarkMode
|
||||
}
|
||||
|
||||
interface MenuProps extends RouteComponentProps, StateProps, DispatchProps { }
|
||||
|
||||
const Menu: React.FC<MenuProps> = ({ darkMode, history, isAuthenticated, setDarkMode }) => {
|
||||
const [disableMenu, setDisableMenu] = useState(false);
|
||||
|
||||
function renderlistItems(list: Pages[]) {
|
||||
return list
|
||||
.filter(route => !!route.path)
|
||||
.map(p => (
|
||||
<IonMenuToggle key={p.title} auto-hide="false">
|
||||
<IonItem button routerLink={p.path} routerDirection="none">
|
||||
<IonIcon slot="start" icon={p.icon} />
|
||||
<IonLabel>{p.title}</IonLabel>
|
||||
</IonItem>
|
||||
</IonMenuToggle>
|
||||
));
|
||||
}
|
||||
|
||||
return (
|
||||
<IonMenu type="overlay" disabled={disableMenu} contentId="main">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Menu</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent class="outer-content">
|
||||
<IonList>
|
||||
<IonListHeader>Navigate</IonListHeader>
|
||||
{renderlistItems(routes.appPages)}
|
||||
</IonList>
|
||||
<IonList>
|
||||
<IonListHeader>Account</IonListHeader>
|
||||
{isAuthenticated ? renderlistItems(routes.loggedInPages) : renderlistItems(routes.loggedOutPages)}
|
||||
</IonList>
|
||||
<IonList>
|
||||
<IonListHeader>Tutorial</IonListHeader>
|
||||
<IonItem onClick={() => {
|
||||
setDisableMenu(true);
|
||||
history.push('/tutorial');
|
||||
}}>
|
||||
<IonIcon slot="start" icon={hammer} />
|
||||
Show Tutorial
|
||||
</IonItem>
|
||||
</IonList>
|
||||
<IonList>
|
||||
<IonItem>
|
||||
<IonLabel>Dark Theme</IonLabel>
|
||||
<IonToggle checked={darkMode} onClick={() => setDarkMode(!darkMode)} />
|
||||
</IonItem>
|
||||
</IonList>
|
||||
</IonContent>
|
||||
</IonMenu>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<{}, StateProps, {}>({
|
||||
mapStateToProps: (state) => ({
|
||||
darkMode: state.user.darkMode,
|
||||
isAuthenticated: state.user.isLoggedin
|
||||
}),
|
||||
mapDispatchToProps: ({
|
||||
setDarkMode
|
||||
}),
|
||||
component: withRouter(Menu)
|
||||
})
|
||||
@@ -1,92 +0,0 @@
|
||||
import { IonItemDivider, IonItemGroup, IonLabel, IonList, IonListHeader, IonAlert, AlertButton } from '@ionic/react';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { Session } from '../models/Session';
|
||||
import SessionListItem from './SessionListItem';
|
||||
import { SessionGroup } from '../models/SessionGroup';
|
||||
import { Time } from '../components/Time';
|
||||
import { connect } from '../data/connect';
|
||||
import { addFavorite, removeFavorite } from '../data/sessions/sessions.actions';
|
||||
|
||||
interface OwnProps {
|
||||
sessionGroups: SessionGroup[]
|
||||
listType: 'all' | 'favorites'
|
||||
hide: boolean;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
favoriteSessions: number[];
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
addFavorite: typeof addFavorite;
|
||||
removeFavorite: typeof removeFavorite;
|
||||
}
|
||||
|
||||
interface SessionListProps extends OwnProps, StateProps, DispatchProps { };
|
||||
|
||||
const SessionList: React.FC<SessionListProps> = ({ addFavorite, removeFavorite, favoriteSessions, hide, sessionGroups, listType }) => {
|
||||
|
||||
const [showAlert, setShowAlert] = useState(false);
|
||||
const [alertHeader, setAlertHeader] = useState('');
|
||||
const [alertButtons, setAlertButtons] = useState<(AlertButton | string)[]>([]);
|
||||
|
||||
const handleShowAlert = useCallback((header: string, buttons: AlertButton[]) => {
|
||||
setAlertHeader(header);
|
||||
setAlertButtons(buttons);
|
||||
setShowAlert(true);
|
||||
}, []);
|
||||
|
||||
if (sessionGroups.length === 0 && !hide) {
|
||||
return (
|
||||
<IonList>
|
||||
<IonListHeader>
|
||||
No Sessions Found
|
||||
</IonListHeader>
|
||||
</IonList>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonList style={hide ? { display: 'none' } : {}}>
|
||||
{sessionGroups.map((group, index: number) => (
|
||||
<IonItemGroup key={`group-${index}`}>
|
||||
<IonItemDivider sticky>
|
||||
<IonLabel>
|
||||
<Time date={group.startTime} />
|
||||
</IonLabel>
|
||||
</IonItemDivider>
|
||||
{group.sessions.map((session: Session, sessionIndex: number) => (
|
||||
<SessionListItem
|
||||
onShowAlert={handleShowAlert}
|
||||
isFavorite={favoriteSessions.indexOf(session.id) > -1}
|
||||
onAddFavorite={addFavorite}
|
||||
onRemoveFavorite={removeFavorite}
|
||||
key={`group-${index}-${sessionIndex}`}
|
||||
session={session}
|
||||
listType={listType}
|
||||
/>
|
||||
))}
|
||||
</IonItemGroup>
|
||||
))}
|
||||
</IonList>
|
||||
<IonAlert
|
||||
isOpen={showAlert}
|
||||
header={alertHeader}
|
||||
buttons={alertButtons}
|
||||
onDidDismiss={() => setShowAlert(false)}
|
||||
></IonAlert>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect({
|
||||
mapStateToProps: (state) => ({
|
||||
favoriteSessions: state.data.favorites
|
||||
}),
|
||||
mapDispatchToProps: ({
|
||||
addFavorite,
|
||||
removeFavorite
|
||||
}),
|
||||
component: SessionList
|
||||
});
|
||||
@@ -1,4 +0,0 @@
|
||||
|
||||
.filter-icon {
|
||||
margin: 7px 16px 7px 0;
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
import React from 'react';
|
||||
import { IonHeader, IonToolbar, IonButtons, IonButton, IonTitle, IonContent, IonList, IonListHeader, IonItem, IonLabel, IonToggle, IonFooter, IonIcon } from '@ionic/react';
|
||||
import { logoAngular, call, document, logoIonic, hammer, restaurant, cog, colorPalette, construct, compass } from 'ionicons/icons';
|
||||
import './SessionListFilter.css'
|
||||
import { connect } from '../data/connect';
|
||||
import { updateFilteredTracks } from '../data/sessions/sessions.actions';
|
||||
|
||||
interface OwnProps {
|
||||
onDismissModal: () => void;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
allTracks: string[],
|
||||
filteredTracks: string[]
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
updateFilteredTracks: typeof updateFilteredTracks;
|
||||
}
|
||||
|
||||
type SessionListFilterProps = OwnProps & StateProps & DispatchProps;
|
||||
|
||||
const SessionListFilter: React.FC<SessionListFilterProps> = ({ allTracks, filteredTracks, onDismissModal, updateFilteredTracks }) => {
|
||||
|
||||
const toggleTrackFilter = (track: string) => {
|
||||
if (filteredTracks.indexOf(track) > -1) {
|
||||
updateFilteredTracks(filteredTracks.filter(x => x !== track));
|
||||
} else {
|
||||
updateFilteredTracks([...filteredTracks, track]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeselectAll = () => {
|
||||
updateFilteredTracks([]);
|
||||
};
|
||||
|
||||
const handleSelectAll = () => {
|
||||
updateFilteredTracks([...allTracks]);
|
||||
};
|
||||
|
||||
const iconMap: { [key: string]: any } = {
|
||||
'Angular': logoAngular,
|
||||
'Documentation': document,
|
||||
'Food': restaurant,
|
||||
'Ionic': logoIonic,
|
||||
'Tooling': hammer,
|
||||
'Design': colorPalette,
|
||||
'Services': cog,
|
||||
'Workshop': construct,
|
||||
'Navigation': compass,
|
||||
'Communication': call
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>
|
||||
Filter Sessions
|
||||
</IonTitle>
|
||||
<IonButtons slot="end">
|
||||
<IonButton onClick={onDismissModal} strong>Done</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent class="outer-content">
|
||||
<IonList>
|
||||
<IonListHeader>Tracks</IonListHeader>
|
||||
{allTracks.map((track, index) => (
|
||||
<IonItem key={track}>
|
||||
<IonIcon className="filter-icon" icon={iconMap[track]} color="medium" />
|
||||
<IonLabel>{track}</IonLabel>
|
||||
<IonToggle
|
||||
onClick={() => toggleTrackFilter(track)}
|
||||
checked={filteredTracks.indexOf(track) !== -1}
|
||||
color="success"
|
||||
value={track}
|
||||
></IonToggle>
|
||||
</IonItem>
|
||||
))}
|
||||
</IonList>
|
||||
</IonContent>
|
||||
|
||||
<IonFooter>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonButton onClick={handleDeselectAll}>Deselect All</IonButton>
|
||||
</IonButtons>
|
||||
<IonButtons slot="end">
|
||||
<IonButton onClick={handleSelectAll}>Select All</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonFooter>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state) => ({
|
||||
allTracks: state.data.allTracks,
|
||||
filteredTracks: state.data.filteredTracks
|
||||
}),
|
||||
mapDispatchToProps: {
|
||||
updateFilteredTracks
|
||||
},
|
||||
component: SessionListFilter
|
||||
})
|
||||
@@ -1,83 +0,0 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { IonItemSliding, IonAlert, IonItem, IonLabel, IonItemOptions, IonItemOption, AlertButton } from '@ionic/react';
|
||||
import { Time } from './Time';
|
||||
import { Session } from '../models/Session';
|
||||
|
||||
interface SessionListItemProps {
|
||||
session: Session;
|
||||
listType: "all" | "favorites";
|
||||
onAddFavorite: (id: number) => void;
|
||||
onRemoveFavorite: (id: number) => void;
|
||||
onShowAlert: (header: string, buttons: AlertButton[]) => void;
|
||||
isFavorite: boolean;
|
||||
}
|
||||
|
||||
const SessionListItem: React.FC<SessionListItemProps> = ({ isFavorite, onAddFavorite, onRemoveFavorite, onShowAlert, session, listType }) => {
|
||||
const ionItemSlidingRef = useRef<HTMLIonItemSlidingElement>(null)
|
||||
|
||||
const dismissAlert = () => {
|
||||
ionItemSlidingRef.current && ionItemSlidingRef.current.close();
|
||||
}
|
||||
|
||||
const removeFavoriteSession = () => {
|
||||
onAddFavorite(session.id);
|
||||
onShowAlert('Favorite already added', [
|
||||
{
|
||||
text: 'Cancel',
|
||||
handler: dismissAlert
|
||||
},
|
||||
{
|
||||
text: 'Remove',
|
||||
handler: () => {
|
||||
onRemoveFavorite(session.id);
|
||||
dismissAlert();
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
const addFavoriteSession = () => {
|
||||
if (isFavorite) {
|
||||
// woops, they already favorited it! What shall we do!?
|
||||
// prompt them to remove it
|
||||
removeFavoriteSession();
|
||||
} else {
|
||||
// remember this session as a user favorite
|
||||
onAddFavorite(session.id);
|
||||
onShowAlert('Favorite Added', [
|
||||
{
|
||||
text: 'OK',
|
||||
handler: dismissAlert
|
||||
}
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IonItemSliding ref={ionItemSlidingRef} class={'track-' + session.tracks[0].toLowerCase()}>
|
||||
<IonItem routerLink={`/tabs/schedule/${session.id}`}>
|
||||
<IonLabel>
|
||||
<h3>{session.name}</h3>
|
||||
<p>
|
||||
<Time date={session.dateTimeStart} /> —
|
||||
<Time date={session.dateTimeEnd} /> —
|
||||
{session.location}
|
||||
</p>
|
||||
</IonLabel>
|
||||
</IonItem>
|
||||
<IonItemOptions>
|
||||
{listType === "favorites" ?
|
||||
<IonItemOption color="danger" onClick={() => removeFavoriteSession()}>
|
||||
Remove
|
||||
</IonItemOption>
|
||||
:
|
||||
<IonItemOption color="favorite" onClick={addFavoriteSession}>
|
||||
Favorite
|
||||
</IonItemOption>
|
||||
}
|
||||
</IonItemOptions>
|
||||
</IonItemSliding>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SessionListItem);
|
||||
@@ -1,46 +0,0 @@
|
||||
import { IonLoading, IonFab, IonFabButton, IonIcon, IonFabList } from "@ionic/react"
|
||||
import { share, logoVimeo, logoGoogleplus, logoTwitter, logoFacebook } from "ionicons/icons"
|
||||
import React, { useState } from "react"
|
||||
|
||||
const ShareSocialFab: React.FC = () => {
|
||||
const [loadingMessage, setLoadingMessage] = useState('')
|
||||
const [showLoading, setShowLoading] = useState(false);
|
||||
|
||||
const openSocial = (network: string) => {
|
||||
setLoadingMessage(`Posting to ${network}`);
|
||||
setShowLoading(true);
|
||||
};
|
||||
|
||||
return(
|
||||
<>
|
||||
<IonLoading
|
||||
isOpen={showLoading}
|
||||
message={loadingMessage}
|
||||
duration={2000}
|
||||
spinner="crescent"
|
||||
onDidDismiss={() => setShowLoading(false)}
|
||||
/>
|
||||
<IonFab slot="fixed" vertical="bottom" horizontal="end">
|
||||
<IonFabButton>
|
||||
<IonIcon icon={share} />
|
||||
</IonFabButton>
|
||||
<IonFabList side="top">
|
||||
<IonFabButton color="vimeo" onClick={() => openSocial('Vimeo')}>
|
||||
<IonIcon icon={logoVimeo} />
|
||||
</IonFabButton>
|
||||
<IonFabButton color="google" onClick={() => openSocial('Google+')}>
|
||||
<IonIcon icon={logoGoogleplus} />
|
||||
</IonFabButton>
|
||||
<IonFabButton color="twitter" onClick={() => openSocial('Twitter')}>
|
||||
<IonIcon icon={logoTwitter} />
|
||||
</IonFabButton>
|
||||
<IonFabButton color="facebook" onClick={() => openSocial('Facebook')}>
|
||||
<IonIcon icon={logoFacebook} />
|
||||
</IonFabButton>
|
||||
</IonFabList>
|
||||
</IonFab>
|
||||
</>
|
||||
)
|
||||
};
|
||||
|
||||
export default ShareSocialFab;
|
||||
@@ -1,125 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Session } from '../models/Session';
|
||||
import { Speaker } from '../models/Speaker';
|
||||
import { IonCard, IonCardHeader, IonItem, IonAvatar, IonCardContent, IonList, IonRow, IonCol, IonButton, IonIcon, IonActionSheet } from '@ionic/react';
|
||||
import { logoTwitter, shareAlt, chatboxes } from 'ionicons/icons';
|
||||
import { ActionSheetButton } from '@ionic/core';
|
||||
|
||||
interface SpeakerItemProps {
|
||||
speaker: Speaker;
|
||||
sessions: Session[];
|
||||
}
|
||||
|
||||
const SpeakerItem: React.FC<SpeakerItemProps> = ({ speaker, sessions }) => {
|
||||
const [showActionSheet, setShowActionSheet] = useState(false);
|
||||
const [actionSheetButtons, setActionSheetButtons] = useState<ActionSheetButton[]>([]);
|
||||
const [actionSheetHeader, setActionSheetHeader] = useState('');
|
||||
|
||||
function openSpeakerShare(speaker: Speaker) {
|
||||
setActionSheetButtons([
|
||||
{
|
||||
text: 'Copy Link',
|
||||
handler: () => {
|
||||
console.log('Copy Link clicked');
|
||||
}
|
||||
},
|
||||
{
|
||||
text: 'Share via ...',
|
||||
handler: () => {
|
||||
console.log('Share via clicked');
|
||||
}
|
||||
},
|
||||
{
|
||||
text: 'Cancel',
|
||||
role: 'cancel',
|
||||
handler: () => {
|
||||
console.log('Cancel clicked');
|
||||
}
|
||||
}
|
||||
]);
|
||||
setActionSheetHeader(`Share ${speaker.name}`);
|
||||
setShowActionSheet(true);
|
||||
}
|
||||
|
||||
function openContact(speaker: Speaker) {
|
||||
setActionSheetButtons([
|
||||
{
|
||||
text: `Email ( ${speaker.email} )`,
|
||||
handler: () => {
|
||||
window.open('mailto:' + speaker.email);
|
||||
}
|
||||
},
|
||||
{
|
||||
text: `Call ( ${speaker.phone} )`,
|
||||
handler: () => {
|
||||
window.open('tel:' + speaker.phone);
|
||||
}
|
||||
}
|
||||
]);
|
||||
setActionSheetHeader(`Share ${speaker.name}`);
|
||||
setShowActionSheet(true);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonCard className="speaker-card">
|
||||
<IonCardHeader>
|
||||
<IonItem button detail={false} routerLink={`/tabs/speakers/${speaker.id}`} lines="none">
|
||||
<IonAvatar slot="start">
|
||||
<img src={process.env.PUBLIC_URL + speaker.profilePic} alt="Speaker profile pic" />
|
||||
</IonAvatar>
|
||||
{speaker.name}
|
||||
</IonItem>
|
||||
</IonCardHeader>
|
||||
|
||||
<IonCardContent class="outer-content">
|
||||
<IonList>
|
||||
{sessions.map(session => (
|
||||
<IonItem routerLink={`/tabs/speakers/sessions/${session.id}`} key={session.name}>
|
||||
<h3>{session.name}</h3>
|
||||
</IonItem>
|
||||
))}
|
||||
<IonItem button routerLink={`/tabs/speakers/${speaker.id}`}>
|
||||
<h3>About {speaker.name}</h3>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
</IonCardContent>
|
||||
|
||||
<IonRow justify-content-center>
|
||||
<IonCol text-left size="4">
|
||||
<IonButton
|
||||
fill="clear"
|
||||
size="small"
|
||||
color="primary"
|
||||
href={`https://www.twitter.com/${speaker.twitter}`}
|
||||
target="_blank"
|
||||
>
|
||||
<IonIcon slot="start" icon={logoTwitter} />
|
||||
Tweet
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
<IonCol text-left size="4">
|
||||
<IonButton fill="clear" size="small" color="primary" onClick={() => openSpeakerShare(speaker)}>
|
||||
<IonIcon slot="start" icon={shareAlt} />
|
||||
Share
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
<IonCol text-left size="4">
|
||||
<IonButton fill="clear" size="small" color="primary" onClick={() => openContact(speaker)}>
|
||||
<IonIcon slot="start" icon={chatboxes} />
|
||||
Contact
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCard>
|
||||
<IonActionSheet
|
||||
isOpen={showActionSheet}
|
||||
header={actionSheetHeader}
|
||||
onDidDismiss={() => setShowActionSheet(false)}
|
||||
buttons={actionSheetButtons}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpeakerItem;
|
||||
@@ -1,8 +0,0 @@
|
||||
import React from 'react';
|
||||
import { format, parseISO as parseDate } from 'date-fns';
|
||||
|
||||
export const Time: React.FC<{ date: string }> = ({ date }) => (
|
||||
<>
|
||||
{format(parseDate(date), "h:mm aaaaa")}m
|
||||
</>
|
||||
)
|
||||
@@ -1,26 +0,0 @@
|
||||
import React, { createContext, useReducer } from 'react';
|
||||
import { initialState, AppState, reducers } from './state'
|
||||
|
||||
export interface AppContextState {
|
||||
state: AppState;
|
||||
dispatch: React.Dispatch<any>;
|
||||
}
|
||||
|
||||
export const AppContext = createContext<AppContextState>({
|
||||
state: initialState,
|
||||
dispatch: () => undefined
|
||||
});
|
||||
|
||||
export const AppContextProvider: React.FC = (props => {
|
||||
|
||||
const [store, dispatch] = useReducer(reducers, initialState);
|
||||
|
||||
return (
|
||||
<AppContext.Provider value={{
|
||||
state: store,
|
||||
dispatch
|
||||
}}>
|
||||
{props.children}
|
||||
</AppContext.Provider>
|
||||
)
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
export function combineReducers<R extends any>(reducers: R) {
|
||||
type keys = keyof typeof reducers;
|
||||
type returnType = { [K in keys]: ReturnType<typeof reducers[K]> };
|
||||
const combinedReducer = (state: any, action: any) => {
|
||||
const newState: returnType = {} as any;
|
||||
const keys = Object.keys(reducers);
|
||||
keys.forEach(key => {
|
||||
const result = reducers[key](state[key], action);
|
||||
newState[key as keys] = result || state[key];
|
||||
});
|
||||
return newState;
|
||||
};
|
||||
return combinedReducer;
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import React, { useContext, useEffect, useState, useMemo } from 'react';
|
||||
import { AppContext } from './AppContext';
|
||||
import { DispatchObject } from '../util/types';
|
||||
import { AppState } from './state';
|
||||
|
||||
interface ConnectParams<TOwnProps, TStateProps, TDispatchProps> {
|
||||
mapStateToProps?: (state: AppState, props: TOwnProps) => TStateProps,
|
||||
mapDispatchToProps?: TDispatchProps,
|
||||
component: React.ComponentType<any>
|
||||
};
|
||||
|
||||
export function connect<TOwnProps = any, TStateProps = any, TDispatchProps = any>({ mapStateToProps = () => ({} as TStateProps), mapDispatchToProps = {} as TDispatchProps, component }: ConnectParams<TOwnProps, TStateProps, TDispatchProps>): React.FunctionComponent<TOwnProps> {
|
||||
|
||||
const Connect = (ownProps: TOwnProps) => {
|
||||
const context = useContext(AppContext);
|
||||
|
||||
const dispatchFuncs = useMemo(() => {
|
||||
const dispatchFuncs: { [key: string]: any } = {};
|
||||
Object.keys(mapDispatchToProps).forEach((key) => {
|
||||
const oldFunc = (mapDispatchToProps as any)[key];
|
||||
const newFunc = (...args: any) => {
|
||||
const dispatchFunc = oldFunc(...args);
|
||||
if (typeof dispatchFunc === 'object') {
|
||||
context.dispatch(dispatchFunc);
|
||||
} else {
|
||||
const result = dispatchFunc(context.dispatch)
|
||||
if (typeof result === 'object' && result.then) {
|
||||
result.then((dispatchObject?: DispatchObject) => {
|
||||
if (dispatchObject && dispatchObject.type) {
|
||||
context.dispatch(dispatchObject);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
dispatchFuncs[key] = newFunc
|
||||
});
|
||||
return dispatchFuncs;
|
||||
}, [mapDispatchToProps])
|
||||
|
||||
|
||||
const props = useMemo(() => {
|
||||
return Object.assign({}, ownProps, mapStateToProps(context.state, ownProps), dispatchFuncs);
|
||||
}, [ownProps, context.state]);
|
||||
|
||||
return React.createElement<TOwnProps>(component, props);
|
||||
}
|
||||
return React.memo(Connect as any);
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import { Plugins } from '@capacitor/core';
|
||||
import { Session } from '../models/Session';
|
||||
import { Speaker } from '../models/Speaker';
|
||||
import { Location } from '../models/Location';
|
||||
|
||||
const { Storage } = Plugins;
|
||||
|
||||
const locationsUrl = '/assets/data/locations.json';
|
||||
const sessionsUrl = '/assets/data/sessions.json';
|
||||
const speakersUrl = '/assets/data/speakers.json';
|
||||
|
||||
const HAS_LOGGED_IN = 'hasLoggedIn';
|
||||
const HAS_SEEN_TUTORIAL = 'hasSeenTutorial';
|
||||
const USERNAME = 'username';
|
||||
|
||||
export const getConfData = async () => {
|
||||
const response = await Promise.all([
|
||||
fetch(sessionsUrl),
|
||||
fetch(locationsUrl),
|
||||
fetch(speakersUrl),
|
||||
]);
|
||||
const sessions = (await response[0].json()) as Session[];
|
||||
const locations = (await response[1].json()) as Location[];
|
||||
const speakers = (await response[2].json()) as Speaker[];
|
||||
const allTracks = sessions
|
||||
.reduce((all, session) => all.concat(session.tracks), [] as string[])
|
||||
.filter((trackName, index, array) => array.indexOf(trackName) === index)
|
||||
.sort();
|
||||
const data = {
|
||||
sessions,
|
||||
locations,
|
||||
speakers,
|
||||
allTracks,
|
||||
filteredTracks: [...allTracks],
|
||||
};
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getUserData = async () => {
|
||||
const response = await Promise.all([
|
||||
Storage.get({ key: HAS_LOGGED_IN }),
|
||||
Storage.get({ key: HAS_SEEN_TUTORIAL }),
|
||||
Storage.get({ key: USERNAME }),
|
||||
]);
|
||||
const isLoggedin = (await response[0].value) === 'true';
|
||||
const hasSeenTutorial = (await response[1].value) === 'true';
|
||||
const username = (await response[2].value) || undefined;
|
||||
const data = {
|
||||
isLoggedin,
|
||||
hasSeenTutorial,
|
||||
username,
|
||||
};
|
||||
return data;
|
||||
};
|
||||
|
||||
export const setIsLoggedInData = async (isLoggedIn: boolean) => {
|
||||
await Storage.set({ key: HAS_LOGGED_IN, value: JSON.stringify(isLoggedIn) });
|
||||
};
|
||||
|
||||
export const setHasSeenTutorialData = async (hasSeenTutorial: boolean) => {
|
||||
await Storage.set({
|
||||
key: HAS_SEEN_TUTORIAL,
|
||||
value: JSON.stringify(hasSeenTutorial),
|
||||
});
|
||||
};
|
||||
|
||||
export const setUsernameData = async (username?: string) => {
|
||||
if (!username) {
|
||||
await Storage.remove({ key: USERNAME });
|
||||
} else {
|
||||
await Storage.set({ key: USERNAME, value: username });
|
||||
}
|
||||
};
|
||||
@@ -1,139 +0,0 @@
|
||||
import { createSelector } from 'reselect';
|
||||
import { parseISO as parseDate } from 'date-fns';
|
||||
import { Session } from '../models/Session';
|
||||
import { SessionGroup } from '../models/SessionGroup';
|
||||
import { AppState } from './state';
|
||||
|
||||
const getSessions = (state: AppState) => state.data.sessions;
|
||||
export const getSpeakers = (state: AppState) => state.data.speakers;
|
||||
const getFilteredTracks = (state: AppState) => state.data.filteredTracks;
|
||||
const getFavoriteIds = (state: AppState) => state.data.favorites;
|
||||
const getSearchText = (state: AppState) => state.data.searchText;
|
||||
|
||||
export const getFilteredSessions = createSelector(
|
||||
getSessions,
|
||||
getFilteredTracks,
|
||||
(sessions, filteredTracks) => {
|
||||
return sessions.filter(session => {
|
||||
let include = false;
|
||||
session.tracks.forEach(track => {
|
||||
if (filteredTracks.indexOf(track) > -1) {
|
||||
include = true;
|
||||
}
|
||||
});
|
||||
return include;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
export const getSearchedSessions = createSelector(
|
||||
getFilteredSessions,
|
||||
getSearchText,
|
||||
(sessions, searchText) => {
|
||||
if (!searchText) {
|
||||
return sessions;
|
||||
}
|
||||
return sessions.filter(
|
||||
session =>
|
||||
session.name.toLowerCase().indexOf(searchText.toLowerCase()) > -1
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const getGroupedSessions = createSelector(
|
||||
getSearchedSessions,
|
||||
sessions => {
|
||||
return groupSessions(sessions);
|
||||
}
|
||||
);
|
||||
|
||||
export const getFavorites = createSelector(
|
||||
getSearchedSessions,
|
||||
getFavoriteIds,
|
||||
(sessions, favoriteIds) =>
|
||||
sessions.filter(x => favoriteIds.indexOf(x.id) > -1)
|
||||
);
|
||||
|
||||
export const getGroupedFavorites = createSelector(
|
||||
getFavorites,
|
||||
sessions => {
|
||||
return groupSessions(sessions);
|
||||
}
|
||||
);
|
||||
|
||||
const getIdParam = (_state: AppState, props: any) => {
|
||||
const stringParam = props.match.params['id'];
|
||||
return parseInt(stringParam, 10);
|
||||
};
|
||||
|
||||
export const getSession = createSelector(
|
||||
getSessions,
|
||||
getIdParam,
|
||||
(sessions, id) => sessions.find(x => x.id === id)
|
||||
);
|
||||
|
||||
function groupSessions(sessions: Session[]) {
|
||||
return sessions
|
||||
.sort(
|
||||
(a, b) =>
|
||||
parseDate(a.dateTimeStart).valueOf() -
|
||||
parseDate(b.dateTimeStart).valueOf()
|
||||
)
|
||||
.reduce(
|
||||
(groups, session) => {
|
||||
let starterHour = parseDate(session.dateTimeStart);
|
||||
starterHour.setMinutes(0);
|
||||
starterHour.setSeconds(0);
|
||||
const starterHourStr = starterHour.toJSON();
|
||||
const foundGroup = groups.find(
|
||||
group => group.startTime === starterHourStr
|
||||
);
|
||||
if (foundGroup) {
|
||||
foundGroup.sessions.push(session);
|
||||
} else {
|
||||
groups.push({
|
||||
startTime: starterHourStr,
|
||||
sessions: [session],
|
||||
});
|
||||
}
|
||||
return groups;
|
||||
},
|
||||
[] as SessionGroup[]
|
||||
);
|
||||
}
|
||||
|
||||
export const getSpeaker = createSelector(
|
||||
getSpeakers,
|
||||
getIdParam,
|
||||
(speakers, id) => speakers.find(x => x.id === id)
|
||||
);
|
||||
|
||||
export const getSpeakerSessions = createSelector(
|
||||
getSessions,
|
||||
sessions => {
|
||||
const speakerSessions: { [key: number]: Session[] } = {};
|
||||
sessions.forEach(session => {
|
||||
session.speakerIds.forEach(speakerId => {
|
||||
if (speakerSessions[speakerId]) {
|
||||
speakerSessions[speakerId].push(session);
|
||||
} else {
|
||||
speakerSessions[speakerId] = [session];
|
||||
}
|
||||
});
|
||||
});
|
||||
return speakerSessions;
|
||||
}
|
||||
);
|
||||
|
||||
export const mapCenter = (state: AppState) => {
|
||||
const item = state.data.locations.find(l => l.id === state.data.mapCenterId);
|
||||
if (item == null) {
|
||||
return {
|
||||
id: 1,
|
||||
name: 'Map Center',
|
||||
lat: 43.071584,
|
||||
lng: -89.38012,
|
||||
};
|
||||
}
|
||||
return item;
|
||||
};
|
||||
@@ -1,54 +0,0 @@
|
||||
import { getConfData } from '../dataApi';
|
||||
import { ActionType } from '../../util/types';
|
||||
import { SessionsState } from './sessions.state';
|
||||
|
||||
export const loadConfData = () => async (dispatch: React.Dispatch<any>) => {
|
||||
dispatch(setLoading(true));
|
||||
const data = await getConfData();
|
||||
dispatch(setData(data));
|
||||
dispatch(setLoading(false));
|
||||
};
|
||||
|
||||
export const setLoading = (isLoading: boolean) =>
|
||||
({
|
||||
type: 'set-conf-loading',
|
||||
isLoading,
|
||||
} as const);
|
||||
|
||||
export const setData = (data: Partial<SessionsState>) =>
|
||||
({
|
||||
type: 'set-conf-data',
|
||||
data,
|
||||
} as const);
|
||||
|
||||
export const addFavorite = (sessionId: number) =>
|
||||
({
|
||||
type: 'add-favorite',
|
||||
sessionId,
|
||||
} as const);
|
||||
|
||||
export const removeFavorite = (sessionId: number) =>
|
||||
({
|
||||
type: 'remove-favorite',
|
||||
sessionId,
|
||||
} as const);
|
||||
|
||||
export const updateFilteredTracks = (filteredTracks: string[]) =>
|
||||
({
|
||||
type: 'update-filtered-tracks',
|
||||
filteredTracks,
|
||||
} as const);
|
||||
|
||||
export const setSearchText = (searchText?: string) =>
|
||||
({
|
||||
type: 'set-search-text',
|
||||
searchText,
|
||||
} as const);
|
||||
|
||||
export type SessionsActions =
|
||||
| ActionType<typeof setLoading>
|
||||
| ActionType<typeof setData>
|
||||
| ActionType<typeof addFavorite>
|
||||
| ActionType<typeof removeFavorite>
|
||||
| ActionType<typeof updateFilteredTracks>
|
||||
| ActionType<typeof setSearchText>;
|
||||
@@ -1,31 +0,0 @@
|
||||
import { SessionsActions } from './sessions.actions';
|
||||
import { SessionsState } from './sessions.state';
|
||||
|
||||
export const sessionsReducer = (
|
||||
state: SessionsState,
|
||||
action: SessionsActions
|
||||
): SessionsState => {
|
||||
switch (action.type) {
|
||||
case 'set-conf-loading': {
|
||||
return { ...state, loading: action.isLoading };
|
||||
}
|
||||
case 'set-conf-data': {
|
||||
return { ...state, ...action.data };
|
||||
}
|
||||
case 'add-favorite': {
|
||||
return { ...state, favorites: [...state.favorites, action.sessionId] };
|
||||
}
|
||||
case 'remove-favorite': {
|
||||
return {
|
||||
...state,
|
||||
favorites: [...state.favorites.filter(x => x !== action.sessionId)],
|
||||
};
|
||||
}
|
||||
case 'update-filtered-tracks': {
|
||||
return { ...state, filteredTracks: action.filteredTracks };
|
||||
}
|
||||
case 'set-search-text': {
|
||||
return { ...state, searchText: action.searchText };
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
import { Location } from '../../models/Location';
|
||||
import { Speaker } from '../../models/Speaker';
|
||||
import { Session } from '../../models/Session';
|
||||
export interface SessionsState {
|
||||
sessions: Session[];
|
||||
speakers: Speaker[];
|
||||
favorites: number[];
|
||||
locations: Location[];
|
||||
filteredTracks: string[];
|
||||
searchText?: string;
|
||||
mapCenterId?: number;
|
||||
loading?: boolean;
|
||||
allTracks: string[];
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { combineReducers } from './combineReducers';
|
||||
import { sessionsReducer } from './sessions/sessions.reducer';
|
||||
import { userReducer } from './user/user.reducer';
|
||||
|
||||
export const initialState: AppState = {
|
||||
data: {
|
||||
sessions: [],
|
||||
speakers: [],
|
||||
favorites: [],
|
||||
locations: [],
|
||||
allTracks: [],
|
||||
filteredTracks: [],
|
||||
mapCenterId: 0,
|
||||
loading: false,
|
||||
},
|
||||
user: {
|
||||
hasSeenTutorial: false,
|
||||
darkMode: false,
|
||||
isLoggedin: false,
|
||||
loading: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const reducers = combineReducers({
|
||||
data: sessionsReducer,
|
||||
user: userReducer,
|
||||
});
|
||||
|
||||
export type AppState = ReturnType<typeof reducers>;
|
||||
@@ -1,76 +0,0 @@
|
||||
import {
|
||||
getUserData,
|
||||
setIsLoggedInData,
|
||||
setUsernameData,
|
||||
setHasSeenTutorialData,
|
||||
} from '../dataApi';
|
||||
import { ActionType } from '../../util/types';
|
||||
import { UserState } from './user.state';
|
||||
|
||||
export const loadUserData = () => async (dispatch: React.Dispatch<any>) => {
|
||||
dispatch(setLoading(true));
|
||||
const data = await getUserData();
|
||||
dispatch(setData(data));
|
||||
dispatch(setLoading(false));
|
||||
};
|
||||
|
||||
export const setLoading = (isLoading: boolean) =>
|
||||
({
|
||||
type: 'set-user-loading',
|
||||
isLoading,
|
||||
} as const);
|
||||
|
||||
export const setData = (data: Partial<UserState>) =>
|
||||
({
|
||||
type: 'set-user-data',
|
||||
data,
|
||||
} as const);
|
||||
|
||||
export const logoutUser = () => async (dispatch: React.Dispatch<any>) => {
|
||||
await setIsLoggedInData(false);
|
||||
dispatch(setUsername());
|
||||
};
|
||||
|
||||
export const setIsLoggedIn = (loggedIn: boolean) => async (
|
||||
dispatch: React.Dispatch<any>
|
||||
) => {
|
||||
await setIsLoggedInData(loggedIn);
|
||||
return {
|
||||
type: 'set-is-loggedin',
|
||||
loggedIn,
|
||||
} as const;
|
||||
};
|
||||
|
||||
export const setUsername = (username?: string) => async (
|
||||
dispatch: React.Dispatch<any>
|
||||
) => {
|
||||
await setUsernameData(username);
|
||||
return {
|
||||
type: 'set-username',
|
||||
username,
|
||||
} as const;
|
||||
};
|
||||
|
||||
export const setHasSeenTutorial = (hasSeenTutorial: boolean) => async (
|
||||
dispatch: React.Dispatch<any>
|
||||
) => {
|
||||
await setHasSeenTutorialData(hasSeenTutorial);
|
||||
return {
|
||||
type: 'set-has-seen-tutorial',
|
||||
hasSeenTutorial,
|
||||
} as const;
|
||||
};
|
||||
|
||||
export const setDarkMode = (darkMode: boolean) =>
|
||||
({
|
||||
type: 'set-dark-mode',
|
||||
darkMode,
|
||||
} as const);
|
||||
|
||||
export type UserActions =
|
||||
| ActionType<typeof setLoading>
|
||||
| ActionType<typeof setData>
|
||||
| ActionType<typeof setIsLoggedIn>
|
||||
| ActionType<typeof setUsername>
|
||||
| ActionType<typeof setHasSeenTutorial>
|
||||
| ActionType<typeof setDarkMode>;
|
||||
@@ -1,19 +0,0 @@
|
||||
import { UserActions } from './user.actions';
|
||||
import { UserState } from './user.state';
|
||||
|
||||
export function userReducer(state: UserState, action: UserActions): UserState {
|
||||
switch (action.type) {
|
||||
case 'set-user-loading':
|
||||
return { ...state, loading: action.isLoading };
|
||||
case 'set-user-data':
|
||||
return { ...state, ...action.data };
|
||||
case 'set-username':
|
||||
return { ...state, username: action.username };
|
||||
case 'set-has-seen-tutorial':
|
||||
return { ...state, hasSeenTutorial: action.hasSeenTutorial };
|
||||
case 'set-dark-mode':
|
||||
return { ...state, darkMode: action.darkMode };
|
||||
case 'set-is-loggedin':
|
||||
return { ...state, isLoggedin: action.loggedIn };
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
export interface UserState {
|
||||
isLoggedin: boolean;
|
||||
username?: string;
|
||||
darkMode: boolean;
|
||||
hasSeenTutorial: boolean;
|
||||
loading: boolean;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
export interface AppPage {
|
||||
url: string;
|
||||
icon: object;
|
||||
title: string;
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
import * as serviceWorker from './serviceWorker';
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
// unregister() to register() below. Note this comes with some pitfalls.
|
||||
// Learn more about service workers: https://bit.ly/CRA-PWA
|
||||
serviceWorker.unregister();
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
export interface Location {
|
||||
id: number;
|
||||
name?: string;
|
||||
lat: number;
|
||||
lng: number;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
export interface Session {
|
||||
id: number;
|
||||
dateTimeStart: string;
|
||||
dateTimeEnd: string;
|
||||
name: string;
|
||||
location: string;
|
||||
description: string;
|
||||
speakerIds: number[];
|
||||
tracks: string[];
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { Session } from './Session';
|
||||
export interface SessionGroup {
|
||||
startTime: string;
|
||||
sessions: Session[];
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
export interface Speaker {
|
||||
id: number;
|
||||
name: string;
|
||||
profilePic: string;
|
||||
twitter: string;
|
||||
about: string;
|
||||
location: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
#about-page {
|
||||
.about-header {
|
||||
background-color: #222;
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
height: 30%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.about-header img {
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.about-info p {
|
||||
color: var(--ion-color-dark);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.about-info ion-icon {
|
||||
margin-inline-end: 32px;
|
||||
}
|
||||
|
||||
.ios .about-info {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { IonHeader, IonToolbar, IonTitle, IonContent, IonPage, IonButtons, IonMenuButton, IonButton, IonIcon, IonDatetime, IonSelectOption, IonList, IonItem, IonLabel, IonSelect, IonPopover } from '@ionic/react';
|
||||
import './About.scss';
|
||||
import { calendar, pin, more } from 'ionicons/icons';
|
||||
import AboutPopover from '../components/AboutPopover';
|
||||
|
||||
interface AboutProps { }
|
||||
|
||||
const About: React.FC<AboutProps> = () => {
|
||||
|
||||
const [showPopover, setShowPopover] = useState(false);
|
||||
const [popoverEvent, setPopoverEvent] = useState();
|
||||
|
||||
const presentPopover = (e: React.MouseEvent) => {
|
||||
setPopoverEvent(e.nativeEvent);
|
||||
setShowPopover(true);
|
||||
};
|
||||
const conferenceDate = '2047-05-17';
|
||||
|
||||
return (
|
||||
<IonPage id="about-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton></IonMenuButton>
|
||||
</IonButtons>
|
||||
<IonTitle>About</IonTitle>
|
||||
<IonButtons slot="end">
|
||||
<IonButton icon-only onClick={presentPopover}>
|
||||
<IonIcon slot="icon-only" icon={more}></IonIcon>
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
|
||||
<div className="about-header">
|
||||
<img src="assets/img/ionic-logo-white.svg" alt="ionic logo" />
|
||||
</div>
|
||||
<div className="about-info">
|
||||
<h4 className="ion-padding-start">Ionic Conference</h4>
|
||||
|
||||
<IonList lines="none">
|
||||
<IonItem>
|
||||
<IonIcon icon={calendar} slot="start"></IonIcon>
|
||||
<IonLabel position="stacked">Date</IonLabel>
|
||||
<IonDatetime displayFormat="MMM DD, YYYY" max="2056" value={conferenceDate}></IonDatetime>
|
||||
</IonItem>
|
||||
|
||||
<IonItem>
|
||||
<IonIcon icon={pin} slot="start"></IonIcon>
|
||||
<IonLabel position="stacked">Location</IonLabel>
|
||||
<IonSelect>
|
||||
<IonSelectOption value="madison" selected>Madison, WI</IonSelectOption>
|
||||
<IonSelectOption value="austin">Austin, TX</IonSelectOption>
|
||||
<IonSelectOption value="chicago">Chicago, IL</IonSelectOption>
|
||||
<IonSelectOption value="seattle">Seattle, WA</IonSelectOption>
|
||||
</IonSelect>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
|
||||
<p className="ion-padding-start ion-padding-end">
|
||||
The Ionic Conference is a one-day conference featuring talks from the Ionic team. It is focused on Ionic applications being
|
||||
built with Ionic 2. This includes migrating apps from Ionic 1 to Ionic 2, Angular concepts, Webpack, Sass, and many
|
||||
other technologies used in Ionic 2. Tickets are completely sold out, and we’re expecting more than 1000 developers
|
||||
– making this the largest Ionic conference ever!
|
||||
</p>
|
||||
</div>
|
||||
</IonContent>
|
||||
<IonPopover
|
||||
isOpen={showPopover}
|
||||
event={popoverEvent}
|
||||
onDidDismiss={() => setShowPopover(false)}
|
||||
>
|
||||
<AboutPopover dismiss={() => setShowPopover(false)} />
|
||||
</IonPopover>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(About);
|
||||
@@ -1,6 +0,0 @@
|
||||
#account-page {
|
||||
img {
|
||||
max-width: 140px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { IonHeader, IonToolbar, IonTitle, IonContent, IonPage, IonButtons, IonMenuButton, IonList, IonItem, IonAlert } from '@ionic/react';
|
||||
import './Account.scss';
|
||||
import { setUsername } from '../data/user/user.actions';
|
||||
import { connect } from '../data/connect';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
|
||||
interface OwnProps extends RouteComponentProps { }
|
||||
|
||||
interface StateProps {
|
||||
username?: string;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
setUsername: typeof setUsername;
|
||||
}
|
||||
|
||||
interface AccountProps extends OwnProps, StateProps, DispatchProps { }
|
||||
|
||||
const Account: React.FC<AccountProps> = ({ setUsername, username }) => {
|
||||
|
||||
const [showAlert, setShowAlert] = useState(false);
|
||||
|
||||
const clicked = (text: string) => {
|
||||
console.log(`Clicked ${text}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage id="account-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton></IonMenuButton>
|
||||
</IonButtons>
|
||||
<IonTitle>Account</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
{username &&
|
||||
(<div className="ion-padding-top ion-text-center">
|
||||
<img src="https://www.gravatar.com/avatar?d=mm&s=140" alt="avatar" />
|
||||
<h2>{ username }</h2>
|
||||
<IonList inset>
|
||||
<IonItem onClick={() => clicked('Update Picture')}>Update Picture</IonItem>
|
||||
<IonItem onClick={() => setShowAlert(true)}>Change Username</IonItem>
|
||||
<IonItem onClick={() => clicked('Change Password')}>Change Password</IonItem>
|
||||
<IonItem routerLink="/support" routerDirection="none">Support</IonItem>
|
||||
<IonItem routerLink="/logout" routerDirection="none">Logout</IonItem>
|
||||
</IonList>
|
||||
</div>)
|
||||
}
|
||||
</IonContent>
|
||||
<IonAlert
|
||||
isOpen={showAlert}
|
||||
header="Change Username"
|
||||
buttons={[
|
||||
'Cancel',
|
||||
{
|
||||
text: 'Ok',
|
||||
handler: (data) => {
|
||||
setUsername(data.username);
|
||||
}
|
||||
}
|
||||
]}
|
||||
inputs={[
|
||||
{
|
||||
type: 'text',
|
||||
name: 'username',
|
||||
value: username,
|
||||
placeholder: 'username'
|
||||
}
|
||||
]}
|
||||
onDidDismiss={() => setShowAlert(false)}
|
||||
/>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state) => ({
|
||||
username: state.user.username
|
||||
}),
|
||||
mapDispatchToProps: {
|
||||
setUsername,
|
||||
},
|
||||
component: Account
|
||||
})
|
||||
26
examples/ionic-react/src/pages/Home.tsx
Normal file
26
examples/ionic-react/src/pages/Home.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react';
|
||||
import React from 'react';
|
||||
|
||||
const Home: React.FC = () => {
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Ionic Blank</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent className="ion-padding">
|
||||
The world is your oyster.
|
||||
<p>
|
||||
If you get lost, the{' '}
|
||||
<a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/">
|
||||
docs
|
||||
</a>{' '}
|
||||
will be your guide.
|
||||
</p>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
@@ -1,16 +0,0 @@
|
||||
#login-page, #signup-page, #support-page {
|
||||
.login-logo {
|
||||
padding: 20px 0;
|
||||
min-height: 200px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-logo img {
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { IonHeader, IonToolbar, IonTitle, IonContent, IonPage, IonButtons, IonMenuButton, IonRow, IonCol, IonButton, IonList, IonItem, IonLabel, IonInput, IonText } from '@ionic/react';
|
||||
import './Login.scss';
|
||||
import { setIsLoggedIn, setUsername } from '../data/user/user.actions';
|
||||
import { connect } from '../data/connect';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
|
||||
interface OwnProps extends RouteComponentProps {}
|
||||
|
||||
interface DispatchProps {
|
||||
setIsLoggedIn: typeof setIsLoggedIn;
|
||||
setUsername: typeof setUsername;
|
||||
}
|
||||
|
||||
interface LoginProps extends OwnProps, DispatchProps { }
|
||||
|
||||
const Login: React.FC<LoginProps> = ({setIsLoggedIn, history, setUsername: setUsernameAction}) => {
|
||||
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [formSubmitted, setFormSubmitted] = useState(false);
|
||||
const [usernameError, setUsernameError] = useState(false);
|
||||
const [passwordError, setPasswordError] = useState(false);
|
||||
|
||||
const login = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setFormSubmitted(true);
|
||||
if(!username) {
|
||||
setUsernameError(true);
|
||||
}
|
||||
if(!password) {
|
||||
setPasswordError(true);
|
||||
}
|
||||
|
||||
if(username && password) {
|
||||
await setIsLoggedIn(true);
|
||||
await setUsernameAction(username);
|
||||
history.push('/tabs/schedule', {direction: 'none'});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage id="login-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton></IonMenuButton>
|
||||
</IonButtons>
|
||||
<IonTitle>Login</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
|
||||
<div className="login-logo">
|
||||
<img src="assets/img/appicon.svg" alt="Ionic logo" />
|
||||
</div>
|
||||
|
||||
<form noValidate onSubmit={login}>
|
||||
<IonList>
|
||||
<IonItem>
|
||||
<IonLabel position="stacked" color="primary">Username</IonLabel>
|
||||
<IonInput name="username" type="text" value={username} spellCheck={false} autocapitalize="off" onIonChange={e => setUsername(e.detail.value!)}
|
||||
required>
|
||||
</IonInput>
|
||||
</IonItem>
|
||||
|
||||
{formSubmitted && usernameError && <IonText color="danger">
|
||||
<p className="ion-padding-start">
|
||||
Username is required
|
||||
</p>
|
||||
</IonText>}
|
||||
|
||||
<IonItem>
|
||||
<IonLabel position="stacked" color="primary">Password</IonLabel>
|
||||
<IonInput name="password" type="password" value={password} onIonChange={e => setPassword(e.detail.value!)}>
|
||||
</IonInput>
|
||||
</IonItem>
|
||||
|
||||
{formSubmitted && passwordError && <IonText color="danger">
|
||||
<p className="ion-padding-start">
|
||||
Password is required
|
||||
</p>
|
||||
</IonText>}
|
||||
</IonList>
|
||||
|
||||
<IonRow>
|
||||
<IonCol>
|
||||
<IonButton type="submit" expand="block">Login</IonButton>
|
||||
</IonCol>
|
||||
<IonCol>
|
||||
<IonButton routerLink="/signup" color="light" expand="block">Signup</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</form>
|
||||
|
||||
</IonContent>
|
||||
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, {}, DispatchProps>({
|
||||
mapDispatchToProps: {
|
||||
setIsLoggedIn,
|
||||
setUsername
|
||||
},
|
||||
component: Login
|
||||
})
|
||||
@@ -1,54 +0,0 @@
|
||||
import React from 'react';
|
||||
import { IonTabs, IonRouterOutlet, IonTabBar, IonTabButton, IonIcon, IonLabel } from '@ionic/react';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
import { calendar, contacts, map, informationCircle } from 'ionicons/icons';
|
||||
import SchedulePage from './SchedulePage';
|
||||
import SpeakerList from './SpeakerList';
|
||||
import SpeakerDetail from './SpeakerDetail';
|
||||
import SessionDetail from './SessionDetail';
|
||||
import MapView from './MapView';
|
||||
import About from './About';
|
||||
|
||||
interface MainTabsProps { }
|
||||
|
||||
const MainTabs: React.FC<MainTabsProps> = () => {
|
||||
|
||||
return (
|
||||
<IonTabs>
|
||||
<IonRouterOutlet>
|
||||
<Redirect exact path="/tabs" to="/tabs/schedule" />
|
||||
{/*
|
||||
Using the render method prop cuts down the number of renders your components will have due to route changes.
|
||||
Use the component prop when your component depends on the RouterComponentProps passed in automatically.
|
||||
*/}
|
||||
<Route path="/tabs/schedule" render={() => <SchedulePage />} exact={true} />
|
||||
<Route path="/tabs/speakers" render={() => <SpeakerList />} exact={true} />
|
||||
<Route path="/tabs/speakers/:id" component={SpeakerDetail} exact={true} />
|
||||
<Route path="/tabs/schedule/:id" component={SessionDetail} />
|
||||
<Route path="/tabs/speakers/sessions/:id" component={SessionDetail} />
|
||||
<Route path="/tabs/map" render={() => <MapView />} exact={true} />
|
||||
<Route path="/tabs/about" render={() => <About />} exact={true} />
|
||||
</IonRouterOutlet>
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="schedule" href="/tabs/schedule">
|
||||
<IonIcon icon={calendar} />
|
||||
<IonLabel>Schedule</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="speakers" href="/tabs/speakers">
|
||||
<IonIcon icon={contacts} />
|
||||
<IonLabel>Speakers</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="map" href="/tabs/map">
|
||||
<IonIcon icon={map} />
|
||||
<IonLabel>Map</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="about" href="/tabs/about">
|
||||
<IonIcon icon={informationCircle} />
|
||||
<IonLabel>About</IonLabel>
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
</IonTabs>
|
||||
);
|
||||
};
|
||||
|
||||
export default MainTabs;
|
||||
@@ -1,18 +0,0 @@
|
||||
#map-view {
|
||||
.map-canvas {
|
||||
position: absolute;
|
||||
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
background-color: transparent;
|
||||
|
||||
opacity: 0;
|
||||
transition: opacity 250ms ease-in;
|
||||
}
|
||||
|
||||
.show-map {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import React from 'react';
|
||||
import Map from '../components/Map';
|
||||
import { IonHeader, IonToolbar, IonButtons, IonMenuButton, IonTitle, IonContent, IonPage } from '@ionic/react';
|
||||
import { Location } from '../models/Location';
|
||||
import { connect } from '../data/connect';
|
||||
import * as selectors from '../data/selectors';
|
||||
import './MapView.scss';
|
||||
|
||||
interface OwnProps { }
|
||||
|
||||
interface StateProps {
|
||||
locations: Location[];
|
||||
mapCenter: Location;
|
||||
}
|
||||
|
||||
interface DispatchProps { }
|
||||
|
||||
interface MapViewProps extends OwnProps, StateProps, DispatchProps { };
|
||||
|
||||
const MapView: React.FC<MapViewProps> = ({ locations, mapCenter }) => {
|
||||
return (
|
||||
<IonPage id="map-view">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton></IonMenuButton>
|
||||
</IonButtons>
|
||||
<IonTitle>Map</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent class="map-page">
|
||||
<Map locations={locations} mapCenter={mapCenter} />
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
)};
|
||||
|
||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state) => ({
|
||||
locations: state.data.locations,
|
||||
mapCenter: selectors.mapCenter(state)
|
||||
}),
|
||||
component: MapView
|
||||
});
|
||||
@@ -1,42 +0,0 @@
|
||||
#schedule-page {
|
||||
ion-item-sliding.track-ionic ion-label {
|
||||
border-left: 2px solid var(--ion-color-primary);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-angular ion-label {
|
||||
border-left: 2px solid var(--ion-color-angular);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-communication ion-label {
|
||||
border-left: 2px solid var(--ion-color-communication);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-tooling ion-label {
|
||||
border-left: 2px solid var(--ion-color-tooling);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-services ion-label {
|
||||
border-left: 2px solid var(--ion-color-services);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-design ion-label {
|
||||
border-left: 2px solid var(--ion-color-design);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-workshop ion-label {
|
||||
border-left: 2px solid var(--ion-color-workshop);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-food ion-label {
|
||||
border-left: 2px solid var(--ion-color-food);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-documentation ion-label {
|
||||
border-left: 2px solid var(--ion-color-documentation);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-navigation ion-label {
|
||||
border-left: 2px solid var(--ion-color-navigation);
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { IonToolbar, IonContent, IonPage, IonButtons, IonMenuButton, IonSegment, IonSegmentButton, IonButton, IonIcon, IonSearchbar, IonRefresher, IonRefresherContent, IonToast, IonModal, IonHeader, getConfig } from '@ionic/react';
|
||||
import { connect } from '../data/connect';
|
||||
import { options } from 'ionicons/icons';
|
||||
import SessionList from '../components/SessionList';
|
||||
import SessionListFilter from '../components/SessionListFilter';
|
||||
import './SchedulePage.scss'
|
||||
import * as selectors from '../data/selectors';
|
||||
import { setSearchText, addFavorite, removeFavorite } from '../data/sessions/sessions.actions';
|
||||
import ShareSocialFab from '../components/ShareSocialFab';
|
||||
import { SessionGroup } from '../models/SessionGroup';
|
||||
|
||||
interface OwnProps { }
|
||||
|
||||
interface StateProps {
|
||||
sessionGroups: SessionGroup[];
|
||||
favoriteGroups: SessionGroup[];
|
||||
mode: 'ios' | 'md'
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
setSearchText: typeof setSearchText;
|
||||
}
|
||||
|
||||
type SchedulePageProps = OwnProps & StateProps & DispatchProps;
|
||||
|
||||
const SchedulePage: React.FC<SchedulePageProps> = ({ favoriteGroups, sessionGroups, setSearchText, mode }) => {
|
||||
const [segment, setSegment] = useState<'all' | 'favorites'>('all');
|
||||
const [showFilterModal, setShowFilterModal] = useState(false);
|
||||
const ionRefresherRef = useRef<HTMLIonRefresherElement>(null);
|
||||
const [showCompleteToast, setShowCompleteToast] = useState(false);
|
||||
|
||||
const doRefresh = () => {
|
||||
setTimeout(() => {
|
||||
ionRefresherRef.current!.complete();
|
||||
setShowCompleteToast(true);
|
||||
}, 2500)
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage id="schedule-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton />
|
||||
</IonButtons>
|
||||
|
||||
<IonSegment onIonChange={(e) => setSegment(e.detail.value as any)}>
|
||||
<IonSegmentButton value="all" checked={segment === 'all'}>
|
||||
All
|
||||
</IonSegmentButton>
|
||||
<IonSegmentButton value="favorites" checked={segment === 'favorites'}>
|
||||
Favorites
|
||||
</IonSegmentButton>
|
||||
</IonSegment>
|
||||
|
||||
<IonButtons slot="end">
|
||||
<IonButton onClick={() => setShowFilterModal(true)}>
|
||||
{mode === 'ios' ? 'Filter' : <IonIcon icon={options} slot="icon-only" />}
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
|
||||
<IonToolbar>
|
||||
<IonSearchbar
|
||||
placeholder="Search"
|
||||
onIonChange={(e: CustomEvent) => setSearchText(e.detail.value)}
|
||||
/>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent>
|
||||
<IonRefresher slot="fixed" ref={ionRefresherRef} onIonRefresh={doRefresh}>
|
||||
<IonRefresherContent />
|
||||
</IonRefresher>
|
||||
<IonToast
|
||||
isOpen={showCompleteToast}
|
||||
message="Refresh complete"
|
||||
duration={2000}
|
||||
onDidDismiss={() => setShowCompleteToast(false)}
|
||||
/>
|
||||
|
||||
<SessionList
|
||||
sessionGroups={sessionGroups}
|
||||
listType={segment}
|
||||
hide={segment === 'favorites'}
|
||||
/>
|
||||
<SessionList
|
||||
sessionGroups={favoriteGroups}
|
||||
listType={segment}
|
||||
hide={segment === 'all'}
|
||||
/>
|
||||
</IonContent>
|
||||
|
||||
<IonModal
|
||||
isOpen={showFilterModal}
|
||||
onDidDismiss={() => setShowFilterModal(false)}
|
||||
>
|
||||
<SessionListFilter
|
||||
onDismissModal={() => setShowFilterModal(false)}
|
||||
/>
|
||||
</IonModal>
|
||||
|
||||
<ShareSocialFab />
|
||||
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state) => ({
|
||||
sessionGroups: selectors.getGroupedSessions(state),
|
||||
favoriteGroups: selectors.getGroupedFavorites(state),
|
||||
mode: getConfig()!.get('mode')
|
||||
}),
|
||||
mapDispatchToProps: {
|
||||
setSearchText
|
||||
},
|
||||
component: React.memo(SchedulePage)
|
||||
});
|
||||
@@ -1,73 +0,0 @@
|
||||
#session-detail-page {
|
||||
.session-track-ionic {
|
||||
color: var(--ion-color-primary);
|
||||
}
|
||||
|
||||
.session-track-angular {
|
||||
color: var(--ion-color-angular);
|
||||
}
|
||||
|
||||
.session-track-communication {
|
||||
color: var(--ion-color-communication);
|
||||
}
|
||||
|
||||
.session-track-tooling {
|
||||
color: var(--ion-color-tooling);
|
||||
}
|
||||
|
||||
.session-track-services {
|
||||
color: var(--ion-color-services);
|
||||
}
|
||||
|
||||
.session-track-design {
|
||||
color: var(--ion-color-design);
|
||||
}
|
||||
|
||||
.session-track-workshop {
|
||||
color: var(--ion-color-workshop);
|
||||
}
|
||||
|
||||
.session-track-food {
|
||||
color: var(--ion-color-food);
|
||||
}
|
||||
|
||||
.session-track-documentation {
|
||||
color: var(--ion-color-documentation);
|
||||
}
|
||||
|
||||
.session-track-navigation {
|
||||
color: var(--ion-color-navigation);
|
||||
}
|
||||
|
||||
.show-favorite {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.icon-heart-empty {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
transform: scale(1);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.icon-heart {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
transform: scale(0);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.show-favorite .icon-heart {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.show-favorite .icon-heart-empty {
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
import React from 'react';
|
||||
import { IonHeader, IonToolbar, IonContent, IonPage, IonButtons, IonBackButton, IonButton, IonIcon, IonText, IonList, IonItem, IonLabel } from '@ionic/react';
|
||||
import { connect } from '../data/connect';
|
||||
import { withRouter, RouteComponentProps } from 'react-router';
|
||||
import * as selectors from '../data/selectors';
|
||||
import { starOutline, star, share, cloudDownload } from 'ionicons/icons';
|
||||
import './SessionDetail.scss';
|
||||
import { Time } from '../components/Time';
|
||||
import { addFavorite, removeFavorite } from '../data/sessions/sessions.actions';
|
||||
import { Session } from '../models/Session';
|
||||
|
||||
interface OwnProps extends RouteComponentProps { };
|
||||
|
||||
interface StateProps {
|
||||
session?: Session;
|
||||
favoriteSessions: number[],
|
||||
};
|
||||
|
||||
interface DispatchProps {
|
||||
addFavorite: typeof addFavorite;
|
||||
removeFavorite: typeof removeFavorite;
|
||||
}
|
||||
|
||||
type SessionDetailProps = OwnProps & StateProps & DispatchProps;
|
||||
|
||||
const SessionDetail: React.FC<SessionDetailProps> = ({ session, addFavorite, removeFavorite, favoriteSessions }) => {
|
||||
|
||||
if (!session) {
|
||||
return <div>Session not found</div>
|
||||
}
|
||||
|
||||
const isFavorite = favoriteSessions.indexOf(session.id) > -1;
|
||||
|
||||
const toggleFavorite = () => {
|
||||
isFavorite ? removeFavorite(session.id) : addFavorite(session.id);
|
||||
};
|
||||
const shareSession = () => { };
|
||||
const sessionClick = (text: string) => {
|
||||
console.log(`Clicked ${text}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage id="session-detail-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton defaultHref="/tabs/schedule"></IonBackButton>
|
||||
</IonButtons>
|
||||
<IonButtons slot="end">
|
||||
<IonButton onClick={() => toggleFavorite()}>
|
||||
{isFavorite ?
|
||||
<IonIcon slot="icon-only" icon={star}></IonIcon> :
|
||||
<IonIcon slot="icon-only" icon={starOutline}></IonIcon>
|
||||
}
|
||||
</IonButton>
|
||||
<IonButton onClick={() => shareSession}>
|
||||
<IonIcon slot="icon-only" icon={share}></IonIcon>
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<div className="ion-padding">
|
||||
<h1>{session.name}</h1>
|
||||
{session.tracks.map(track => (
|
||||
<span key={track} className={`session-track-${track.toLowerCase()}`}>{track}</span>
|
||||
))}
|
||||
<p>{session.description}</p>
|
||||
<IonText color="medium">
|
||||
<Time date={session.dateTimeStart} /> – <Time date={session.dateTimeEnd} />
|
||||
<br />
|
||||
{session.location}
|
||||
</IonText>
|
||||
</div>
|
||||
<IonList>
|
||||
<IonItem onClick={() => sessionClick('watch')} button>
|
||||
<IonLabel color="primary">Watch</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem onClick={() => sessionClick('add to calendar')} button>
|
||||
<IonLabel color="primary">Add to Calendar</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem onClick={() => sessionClick('mark as unwatched')} button>
|
||||
<IonLabel color="primary">Mark as Unwatched</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem onClick={() => sessionClick('download video')} button>
|
||||
<IonLabel color="primary">Download Video</IonLabel>
|
||||
<IonIcon slot="end" color="primary" size="small" icon={cloudDownload}></IonIcon>
|
||||
</IonItem>
|
||||
<IonItem onClick={() => sessionClick('leave feedback')} button>
|
||||
<IonLabel color="primary">Leave Feedback</IonLabel>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state, OwnProps) => ({
|
||||
session: selectors.getSession(state, OwnProps),
|
||||
favoriteSessions: state.data.favorites
|
||||
}),
|
||||
mapDispatchToProps: {
|
||||
addFavorite,
|
||||
removeFavorite
|
||||
},
|
||||
component: withRouter(SessionDetail)
|
||||
})
|
||||
@@ -1,111 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { IonHeader, IonToolbar, IonTitle, IonContent, IonPage, IonButtons, IonMenuButton, IonRow, IonCol, IonButton, IonList, IonItem, IonLabel, IonInput, IonText } from '@ionic/react';
|
||||
import './Login.scss';
|
||||
import { setIsLoggedIn, setUsername } from '../data/user/user.actions';
|
||||
import { connect } from '../data/connect';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
|
||||
interface OwnProps extends RouteComponentProps {}
|
||||
|
||||
interface DispatchProps {
|
||||
setIsLoggedIn: typeof setIsLoggedIn;
|
||||
setUsername: typeof setUsername;
|
||||
}
|
||||
|
||||
interface LoginProps extends OwnProps, DispatchProps { }
|
||||
|
||||
const Login: React.FC<LoginProps> = ({setIsLoggedIn, history, setUsername: setUsernameAction}) => {
|
||||
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [formSubmitted, setFormSubmitted] = useState(false);
|
||||
const [usernameError, setUsernameError] = useState(false);
|
||||
const [passwordError, setPasswordError] = useState(false);
|
||||
|
||||
const login = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setFormSubmitted(true);
|
||||
if(!username) {
|
||||
setUsernameError(true);
|
||||
}
|
||||
if(!password) {
|
||||
setPasswordError(true);
|
||||
}
|
||||
|
||||
if(username && password) {
|
||||
await setIsLoggedIn(true);
|
||||
await setUsernameAction(username);
|
||||
history.push('/tabs/schedule', {direction: 'none'});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage id="signup-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton></IonMenuButton>
|
||||
</IonButtons>
|
||||
<IonTitle>Signup</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
|
||||
<div className="login-logo">
|
||||
<img src="assets/img/appicon.svg" alt="Ionic logo" />
|
||||
</div>
|
||||
|
||||
<form noValidate onSubmit={login}>
|
||||
<IonList>
|
||||
<IonItem>
|
||||
<IonLabel position="stacked" color="primary">Username</IonLabel>
|
||||
<IonInput name="username" type="text" value={username} spellCheck={false} autocapitalize="off" onIonChange={e => {
|
||||
setUsername(e.detail.value!);
|
||||
setUsernameError(false);
|
||||
}}
|
||||
required>
|
||||
</IonInput>
|
||||
</IonItem>
|
||||
|
||||
{formSubmitted && usernameError && <IonText color="danger">
|
||||
<p className="ion-padding-start">
|
||||
Username is required
|
||||
</p>
|
||||
</IonText>}
|
||||
|
||||
<IonItem>
|
||||
<IonLabel position="stacked" color="primary">Password</IonLabel>
|
||||
<IonInput name="password" type="password" value={password} onIonChange={e => {
|
||||
setPassword(e.detail.value!);
|
||||
setPasswordError(false);
|
||||
}}>
|
||||
</IonInput>
|
||||
</IonItem>
|
||||
|
||||
{formSubmitted && passwordError && <IonText color="danger">
|
||||
<p className="ion-padding-start">
|
||||
Password is required
|
||||
</p>
|
||||
</IonText>}
|
||||
</IonList>
|
||||
|
||||
<IonRow>
|
||||
<IonCol>
|
||||
<IonButton type="submit" expand="block">Create</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</form>
|
||||
|
||||
</IonContent>
|
||||
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, {}, DispatchProps>({
|
||||
mapDispatchToProps: {
|
||||
setIsLoggedIn,
|
||||
setUsername
|
||||
},
|
||||
component: Login
|
||||
})
|
||||
@@ -1,11 +0,0 @@
|
||||
#speaker-detail {
|
||||
img {
|
||||
max-width: 140px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
p {
|
||||
color: #60646B;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import React from 'react';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { IonIcon, IonHeader, IonToolbar, IonButtons, IonTitle, IonContent, IonButton, IonBackButton, IonPage } from '@ionic/react'
|
||||
import './SpeakerDetail.scss';
|
||||
import { logoTwitter, logoGithub, logoInstagram } from 'ionicons/icons';
|
||||
import { connect } from '../data/connect';
|
||||
import * as selectors from '../data/selectors';
|
||||
import { Speaker } from '../models/Speaker';
|
||||
|
||||
interface OwnProps extends RouteComponentProps {
|
||||
speaker?: Speaker;
|
||||
};
|
||||
|
||||
interface StateProps {};
|
||||
|
||||
interface DispatchProps {};
|
||||
|
||||
interface SpeakerDetailProps extends OwnProps, StateProps, DispatchProps {};
|
||||
|
||||
const SpeakerDetail: React.FC<SpeakerDetailProps> = ({ speaker }) => {
|
||||
|
||||
if (!speaker) {
|
||||
return <div>Speaker not found</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage id="speaker-detail">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton defaultHref="/tabs/speakers" />
|
||||
</IonButtons>
|
||||
<IonTitle>{speaker.name}</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent className="ion-padding speaker-detail speaker-page-list">
|
||||
<div className="ion-text-center">
|
||||
<img src={speaker.profilePic} alt={speaker.name} />
|
||||
<br />
|
||||
<IonButton fill="clear" size="small" color="twitter">
|
||||
<IonIcon icon={logoTwitter} slot="icon-only"></IonIcon>
|
||||
</IonButton>
|
||||
<IonButton fill="clear" size="small" color="github">
|
||||
<IonIcon icon={logoGithub} slot="icon-only"></IonIcon>
|
||||
</IonButton>
|
||||
<IonButton fill="clear" size="small" color="instagram">
|
||||
<IonIcon icon={logoInstagram} slot="icon-only"></IonIcon>
|
||||
</IonButton>
|
||||
</div>
|
||||
|
||||
<p>{speaker.about}</p>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default connect({
|
||||
mapStateToProps: (state, ownProps) => ({
|
||||
speaker: selectors.getSpeaker(state, ownProps)
|
||||
}),
|
||||
component: SpeakerDetail
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
#speaker-list {
|
||||
.scroll {
|
||||
background: #ededed;
|
||||
}
|
||||
|
||||
.speaker-card {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.speaker-card ion-card-header {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.speaker-card ion-card-header .item {
|
||||
padding: 4px 16px;
|
||||
}
|
||||
|
||||
.speaker-card ion-card-content {
|
||||
flex: 1 1 auto;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import React from 'react';
|
||||
import { IonHeader, IonToolbar, IonTitle, IonContent, IonPage, IonButtons, IonMenuButton, IonList, IonGrid, IonRow, IonCol } from '@ionic/react';
|
||||
import SpeakerItem from '../components/SpeakerItem';
|
||||
import { Speaker } from '../models/Speaker';
|
||||
import { Session } from '../models/Session';
|
||||
import { connect } from '../data/connect';
|
||||
import * as selectors from '../data/selectors';
|
||||
import './SpeakerList.scss';
|
||||
|
||||
interface OwnProps { };
|
||||
|
||||
interface StateProps {
|
||||
speakers: Speaker[];
|
||||
speakerSessions: { [key: number]: Session[] };
|
||||
};
|
||||
|
||||
interface DispatchProps { };
|
||||
|
||||
interface SpeakerListProps extends OwnProps, StateProps, DispatchProps { };
|
||||
|
||||
const SpeakerList: React.FC<SpeakerListProps> = ({ speakers, speakerSessions }) => {
|
||||
|
||||
return (
|
||||
<IonPage id="speaker-list">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton />
|
||||
</IonButtons>
|
||||
<IonTitle>Speakers</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent className={`outer-content`}>
|
||||
<IonList>
|
||||
<IonGrid fixed>
|
||||
<IonRow align-items-stretch>
|
||||
{speakers.map(speaker => (
|
||||
<IonCol size="12" size-md="6" key={speaker.id}>
|
||||
<SpeakerItem
|
||||
key={speaker.id}
|
||||
speaker={speaker}
|
||||
sessions={speakerSessions[speaker.id]}
|
||||
/>
|
||||
</IonCol>
|
||||
))}
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</IonList>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state) => ({
|
||||
speakers: selectors.getSpeakers(state),
|
||||
speakerSessions: selectors.getSpeakerSessions(state)
|
||||
}),
|
||||
component: React.memo(SpeakerList)
|
||||
});
|
||||
@@ -1,83 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { IonHeader, IonToolbar, IonTitle, IonContent, IonPage, IonButtons, IonMenuButton, IonRow, IonCol, IonButton, IonList, IonItem, IonLabel, IonText, IonTextarea, IonToast } from '@ionic/react';
|
||||
import './Login.scss';
|
||||
import { connect } from '../data/connect';
|
||||
|
||||
interface OwnProps { }
|
||||
|
||||
interface DispatchProps { }
|
||||
|
||||
interface SupportProps extends OwnProps, DispatchProps { }
|
||||
|
||||
const Support: React.FC<SupportProps> = () => {
|
||||
|
||||
const [message, setMessage] = useState('');
|
||||
const [formSubmitted, setFormSubmitted] = useState(false);
|
||||
const [messageError, setMessageError] = useState(false);
|
||||
const [showToast, setShowToast] = useState(false);
|
||||
|
||||
const send = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setFormSubmitted(true);
|
||||
if (!message) {
|
||||
setMessageError(true);
|
||||
}
|
||||
if (message) {
|
||||
setMessage('');
|
||||
setShowToast(true);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage id="support-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton></IonMenuButton>
|
||||
</IonButtons>
|
||||
<IonTitle>Support</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
|
||||
<div className="login-logo">
|
||||
<img src="assets/img/appicon.svg" alt="Ionic logo" />
|
||||
</div>
|
||||
|
||||
<form noValidate onSubmit={send}>
|
||||
<IonList>
|
||||
<IonItem>
|
||||
<IonLabel position="stacked" color="primary">Enter your support message below</IonLabel>
|
||||
<IonTextarea name="message" value={message} spellCheck={false} autocapitalize="off" rows={6} onIonChange={e => setMessage(e.detail.value!)}
|
||||
required>
|
||||
</IonTextarea>
|
||||
</IonItem>
|
||||
|
||||
{formSubmitted && messageError && <IonText color="danger">
|
||||
<p className="ion-padding-start">
|
||||
Support message is required
|
||||
</p>
|
||||
</IonText>}
|
||||
</IonList>
|
||||
|
||||
<IonRow>
|
||||
<IonCol>
|
||||
<IonButton type="submit" expand="block">Submit</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</form>
|
||||
|
||||
</IonContent>
|
||||
|
||||
<IonToast
|
||||
isOpen={showToast}
|
||||
duration={3000}
|
||||
message="Your support request has been sent"
|
||||
onDidDismiss={() => setShowToast(false)} />
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, {}, DispatchProps>({
|
||||
component: Support
|
||||
})
|
||||
@@ -1,38 +0,0 @@
|
||||
#tutorial-page {
|
||||
ion-toolbar {
|
||||
// TODO test transparent and fullscreen
|
||||
--background: transparent;
|
||||
--border-color: transparent;
|
||||
}
|
||||
|
||||
.swiper-slide {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.slide-title {
|
||||
margin-top: 2.8rem;
|
||||
}
|
||||
|
||||
.slide-image {
|
||||
max-height: 50%;
|
||||
max-width: 60%;
|
||||
margin: 36px 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
b {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
p {
|
||||
padding: 0 40px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: var(--ion-color-step-600, #60646b);
|
||||
|
||||
b {
|
||||
color: var(--ion-text-color, #000000);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { IonContent, IonPage, IonHeader, IonToolbar, IonButtons, IonButton, IonSlides, IonSlide, IonIcon } from '@ionic/react';
|
||||
import { arrowForward } from 'ionicons/icons';
|
||||
import { setHasSeenTutorial } from '../data/user/user.actions';
|
||||
import './Tutorial.scss';
|
||||
import { connect } from '../data/connect';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
|
||||
interface OwnProps extends RouteComponentProps {};
|
||||
|
||||
interface DispatchProps {
|
||||
setHasSeenTutorial: typeof setHasSeenTutorial
|
||||
}
|
||||
|
||||
interface TutorialProps extends OwnProps, DispatchProps { };
|
||||
|
||||
const Tutorial: React.FC<TutorialProps> = ({ history, setHasSeenTutorial }) => {
|
||||
const [showSkip, setShowSkip] = useState(true);
|
||||
const slideRef = useRef<HTMLIonSlidesElement>(null);
|
||||
|
||||
const startApp = async () => {
|
||||
await setHasSeenTutorial(true);
|
||||
history.push('/tabs/schedule', { direction: 'none' });
|
||||
};
|
||||
|
||||
const handleSlideChangeStart = () => {
|
||||
slideRef.current!.isEnd().then(isEnd => setShowSkip(!isEnd));
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage id="tutorial-page">
|
||||
<IonHeader no-border>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="end">
|
||||
{showSkip && <IonButton color='primary' onClick={startApp}>Skip</IonButton>}
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
|
||||
<IonSlides ref={slideRef} onIonSlideWillChange={handleSlideChangeStart} pager={false}>
|
||||
<IonSlide>
|
||||
<img src="assets/img/ica-slidebox-img-1.png" alt="" className="slide-image" />
|
||||
<h2 className="slide-title">
|
||||
Welcome to <b>ICA</b>
|
||||
</h2>
|
||||
<p>
|
||||
The <b>ionic conference app</b> is a practical preview of the ionic framework in action, and a demonstration of proper code use.
|
||||
</p>
|
||||
</IonSlide>
|
||||
|
||||
<IonSlide>
|
||||
<img src="assets/img/ica-slidebox-img-2.png" alt="" className="slide-image" />
|
||||
<h2 className="slide-title">What is Ionic?</h2>
|
||||
<p>
|
||||
<b>Ionic Framework</b> is an open source SDK that enables developers to build high quality mobile apps with web technologies like HTML, CSS, and JavaScript.
|
||||
</p>
|
||||
</IonSlide>
|
||||
|
||||
<IonSlide>
|
||||
<img src="assets/img/ica-slidebox-img-3.png" alt="" className="slide-image" />
|
||||
<h2 className="slide-title">What is Ionic Appflow?</h2>
|
||||
<p>
|
||||
<b>Ionic Appflow</b> is a powerful set of services and features built on top of Ionic Framework that brings a totally new level of app development agility to mobile dev teams.
|
||||
</p>
|
||||
</IonSlide>
|
||||
|
||||
<IonSlide>
|
||||
<img src="assets/img/ica-slidebox-img-4.png" alt="" className="slide-image" />
|
||||
<h2 className="slide-title">Ready to Play?</h2>
|
||||
<IonButton fill="clear" onClick={startApp}>
|
||||
Continue
|
||||
<IonIcon slot="end" icon={arrowForward} />
|
||||
</IonButton>
|
||||
</IonSlide>
|
||||
</IonSlides>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, {}, DispatchProps>({
|
||||
mapDispatchToProps: ({
|
||||
setHasSeenTutorial
|
||||
}),
|
||||
component: Tutorial
|
||||
});
|
||||
145
examples/ionic-react/src/serviceWorker.ts
Normal file
145
examples/ionic-react/src/serviceWorker.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
// This optional code is used to register a service worker.
|
||||
// register() is not called by default.
|
||||
|
||||
// This lets the app load faster on subsequent visits in production, and gives
|
||||
// it offline capabilities. However, it also means that developers (and users)
|
||||
// will only see deployed updates on subsequent visits to a page, after all the
|
||||
// existing tabs open on the page have been closed, since previously cached
|
||||
// resources are updated in the background.
|
||||
|
||||
// To learn more about the benefits of this model and instructions on how to
|
||||
// opt-in, read https://bit.ly/CRA-PWA
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === 'localhost' ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === '[::1]' ||
|
||||
// 127.0.0.0/8 are considered localhost for IPv4.
|
||||
window.location.hostname.match(
|
||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||
)
|
||||
);
|
||||
|
||||
type Config = {
|
||||
onSuccess?: (registration: ServiceWorkerRegistration) => void;
|
||||
onUpdate?: (registration: ServiceWorkerRegistration) => void;
|
||||
};
|
||||
|
||||
export function register(config?: Config) {
|
||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||
// The URL constructor is available in all browsers that support SW.
|
||||
const publicUrl = new URL(
|
||||
process.env.PUBLIC_URL,
|
||||
window.location.href
|
||||
);
|
||||
if (publicUrl.origin !== window.location.origin) {
|
||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||
// from what our page is served on. This might happen if a CDN is used to
|
||||
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||
|
||||
if (isLocalhost) {
|
||||
// This is running on localhost. Let's check if a service worker still exists or not.
|
||||
checkValidServiceWorker(swUrl, config);
|
||||
|
||||
// Add some additional logging to localhost, pointing developers to the
|
||||
// service worker/PWA documentation.
|
||||
navigator.serviceWorker.ready.then(() => {
|
||||
console.log(
|
||||
'This web app is being served cache-first by a service ' +
|
||||
'worker. To learn more, visit https://bit.ly/CRA-PWA'
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// Is not localhost. Just register service worker
|
||||
registerValidSW(swUrl, config);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function registerValidSW(swUrl: string, config?: Config) {
|
||||
navigator.serviceWorker
|
||||
.register(swUrl)
|
||||
.then(registration => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing;
|
||||
if (installingWorker == null) {
|
||||
return;
|
||||
}
|
||||
installingWorker.onstatechange = () => {
|
||||
if (installingWorker.state === 'installed') {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// At this point, the updated precached content has been fetched,
|
||||
// but the previous service worker will still serve the older
|
||||
// content until all client tabs are closed.
|
||||
console.log(
|
||||
'New content is available and will be used when all ' +
|
||||
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
||||
);
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onUpdate) {
|
||||
config.onUpdate(registration);
|
||||
}
|
||||
} else {
|
||||
// At this point, everything has been precached.
|
||||
// It's the perfect time to display a
|
||||
// "Content is cached for offline use." message.
|
||||
console.log('Content is cached for offline use.');
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onSuccess) {
|
||||
config.onSuccess(registration);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error during service worker registration:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function checkValidServiceWorker(swUrl: string, config?: Config) {
|
||||
// Check if the service worker can be found. If it can't reload the page.
|
||||
fetch(swUrl, {
|
||||
headers: { 'Service-Worker': 'script' }
|
||||
})
|
||||
.then(response => {
|
||||
// Ensure service worker exists, and that we really are getting a JS file.
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (
|
||||
response.status === 404 ||
|
||||
(contentType != null && contentType.indexOf('javascript') === -1)
|
||||
) {
|
||||
// No service worker found. Probably a different app. Reload the page.
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Service worker found. Proceed as normal.
|
||||
registerValidSW(swUrl, config);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.log(
|
||||
'No internet connection found. App is running in offline mode.'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function unregister() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister();
|
||||
});
|
||||
}
|
||||
}
|
||||
5
examples/ionic-react/src/setupTests.ts
Normal file
5
examples/ionic-react/src/setupTests.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
@@ -1,179 +0,0 @@
|
||||
|
||||
/* Ionic Variables and Theming. For more information, please see
|
||||
// https://beta.ionicframework.com/docs/theming/
|
||||
// The app direction is used to include
|
||||
// rtl styles in your app. For more information, please see
|
||||
// https://beta.ionicframework.com/docs/layout/rtl
|
||||
// $app-direction: ltr;
|
||||
// Ionic Colors
|
||||
// --------------------------------------------------
|
||||
// Named colors makes it easy to reuse colors on various components.
|
||||
// It's highly recommended to change the default colors
|
||||
// to match your app's branding. Ionic provides eight layered colors
|
||||
// that can be changed to theme an app. Additional colors can be
|
||||
// added as well (see below). For more information, please see
|
||||
// https://beta.ionicframework.com/docs/theming/advanced
|
||||
// To easily create custom color palettes for your app’s UI,
|
||||
// check out our color generator:
|
||||
// https://beta.ionicframework.com/docs/theming/color-generator
|
||||
*/
|
||||
|
||||
:root {
|
||||
--ion-color-angular: #ac282b;
|
||||
--ion-color-communication: #8e8d93;
|
||||
--ion-color-tooling: #fe4c52;
|
||||
--ion-color-services: #fd8b2d;
|
||||
--ion-color-design: #fed035;
|
||||
--ion-color-workshop: #69bb7b;
|
||||
--ion-color-food: #3bc7c4;
|
||||
--ion-color-documentation: #b16be3;
|
||||
--ion-color-navigation: #6600cc;
|
||||
|
||||
--ion-color-primary: #3880ff;
|
||||
--ion-color-primary-rgb: 56, 128, 255;
|
||||
--ion-color-primary-contrast: #ffffff;
|
||||
--ion-color-primary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-primary-shade: #3171e0;
|
||||
--ion-color-primary-tint: #4c8dff;
|
||||
|
||||
--ion-color-secondary: #0cd1e8;
|
||||
--ion-color-secondary-rgb: 12, 209, 232;
|
||||
--ion-color-secondary-contrast: #ffffff;
|
||||
--ion-color-secondary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-secondary-shade: #0bb8cc;
|
||||
--ion-color-secondary-tint: #24d6ea;
|
||||
|
||||
--ion-color-tertiary: #7044ff;
|
||||
--ion-color-tertiary-rgb: 112, 68, 255;
|
||||
--ion-color-tertiary-contrast: #ffffff;
|
||||
--ion-color-tertiary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-tertiary-shade: #633ce0;
|
||||
--ion-color-tertiary-tint: #7e57ff;
|
||||
|
||||
--ion-color-success: #10dc60;
|
||||
--ion-color-success-rgb: 16, 220, 96;
|
||||
--ion-color-success-contrast: #ffffff;
|
||||
--ion-color-success-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-success-shade: #0ec254;
|
||||
--ion-color-success-tint: #28e070;
|
||||
|
||||
--ion-color-warning: #ffce00;
|
||||
--ion-color-warning-rgb: 255, 206, 0;
|
||||
--ion-color-warning-contrast: #ffffff;
|
||||
--ion-color-warning-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-warning-shade: #e0b500;
|
||||
--ion-color-warning-tint: #ffd31a;
|
||||
|
||||
--ion-color-danger: #f04141;
|
||||
--ion-color-danger-rgb: 245, 61, 61;
|
||||
--ion-color-danger-contrast: #ffffff;
|
||||
--ion-color-danger-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-danger-shade: #d33939;
|
||||
--ion-color-danger-tint: #f25454;
|
||||
|
||||
--ion-color-dark: #222428;
|
||||
--ion-color-dark-rgb: 34, 34, 34;
|
||||
--ion-color-dark-contrast: #ffffff;
|
||||
--ion-color-dark-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-dark-shade: #1e2023;
|
||||
--ion-color-dark-tint: #383a3e;
|
||||
|
||||
--ion-color-medium: #989aa2;
|
||||
--ion-color-medium-rgb: 152, 154, 162;
|
||||
--ion-color-medium-contrast: #ffffff;
|
||||
--ion-color-medium-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-medium-shade: #86888f;
|
||||
--ion-color-medium-tint: #a2a4ab;
|
||||
|
||||
--ion-color-light: #f4f5f8;
|
||||
--ion-color-light-rgb: 244, 244, 244;
|
||||
--ion-color-light-contrast: #000000;
|
||||
--ion-color-light-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-light-shade: #d7d8da;
|
||||
--ion-color-light-tint: #f5f6f9;
|
||||
}
|
||||
|
||||
/* Additional Ionic Colors
|
||||
// --------------------------------------------------
|
||||
// In order to add colors to be used with Ionic components,
|
||||
// the color should be added as a class with the convention `.ion-color-{COLOR}`
|
||||
// where `{COLOR}` is the color to be used on the Ionic component
|
||||
// and each variant is defined for the color. For more information, please see
|
||||
// https://beta.ionicframework.com/docs/theming/advanced
|
||||
*/
|
||||
|
||||
.ion-color-favorite {
|
||||
--ion-color-base: #69bb7b;
|
||||
--ion-color-base-rgb: 105, 187, 123;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #5ca56c;
|
||||
--ion-color-tint: #78c288;
|
||||
}
|
||||
|
||||
.ion-color-twitter {
|
||||
--ion-color-base: #1da1f4;
|
||||
--ion-color-base-rgb: 29, 161, 244;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #1a8ed7;
|
||||
--ion-color-tint: #34aaf5;
|
||||
}
|
||||
|
||||
.ion-color-google {
|
||||
--ion-color-base: #dc4a38;
|
||||
--ion-color-base-rgb: 220, 74, 56;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #c24131;
|
||||
--ion-color-tint: #e05c4c;
|
||||
}
|
||||
|
||||
.ion-color-vimeo {
|
||||
--ion-color-base: #23b6ea;
|
||||
--ion-color-base-rgb: 35, 182, 234;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #1fa0ce;
|
||||
--ion-color-tint: #39bdec;
|
||||
}
|
||||
|
||||
.ion-color-facebook {
|
||||
--ion-color-base: #3b5998;
|
||||
--ion-color-base-rgb: 59, 89, 152;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #344e86;
|
||||
--ion-color-tint: #4f6aa2;
|
||||
}
|
||||
|
||||
/* Shared Variables
|
||||
// --------------------------------------------------
|
||||
// To customize the look and feel of this app, you can override
|
||||
// the CSS variables found in Ionic's source files.
|
||||
// To view all the possible Ionic variables, see:
|
||||
// https://beta.ionicframework.com/docs/theming/css-variables#ionic-variables
|
||||
*/
|
||||
|
||||
:root {
|
||||
--ion-headings-font-weight: 300;
|
||||
|
||||
--ion-color-angular: #ac282b;
|
||||
--ion-color-communication: #8e8d93;
|
||||
--ion-color-tooling: #fe4c52;
|
||||
--ion-color-services: #fd8b2d;
|
||||
--ion-color-design: #fed035;
|
||||
--ion-color-workshop: #69bb7b;
|
||||
--ion-color-food: #3bc7c4;
|
||||
--ion-color-documentation: #b16be3;
|
||||
--ion-color-navigation: #6600cc;
|
||||
}
|
||||
|
||||
.md {
|
||||
--ion-toolbar-background: var(--ion-color-primary);
|
||||
--ion-toolbar-color: #fff;
|
||||
--ion-toolbar-color-activated: #fff;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,34 +1,9 @@
|
||||
/* Ionic Variables and Theming. For more info, please see:
|
||||
http://ionicframework.com/docs/theming/ */
|
||||
|
||||
/* Ionic Variables and Theming. For more information, please see
|
||||
// https://beta.ionicframework.com/docs/theming/
|
||||
// The app direction is used to include
|
||||
// rtl styles in your app. For more information, please see
|
||||
// https://beta.ionicframework.com/docs/layout/rtl
|
||||
// $app-direction: ltr;
|
||||
// Ionic Colors
|
||||
// --------------------------------------------------
|
||||
// Named colors makes it easy to reuse colors on various components.
|
||||
// It's highly recommended to change the default colors
|
||||
// to match your app's branding. Ionic provides eight layered colors
|
||||
// that can be changed to theme an app. Additional colors can be
|
||||
// added as well (see below). For more information, please see
|
||||
// https://beta.ionicframework.com/docs/theming/advanced
|
||||
// To easily create custom color palettes for your app’s UI,
|
||||
// check out our color generator:
|
||||
// https://beta.ionicframework.com/docs/theming/color-generator
|
||||
*/
|
||||
|
||||
/** Ionic CSS Variables **/
|
||||
:root {
|
||||
--ion-color-angular: #ac282b;
|
||||
--ion-color-communication: #8e8d93;
|
||||
--ion-color-tooling: #fe4c52;
|
||||
--ion-color-services: #fd8b2d;
|
||||
--ion-color-design: #fed035;
|
||||
--ion-color-workshop: #69bb7b;
|
||||
--ion-color-food: #3bc7c4;
|
||||
--ion-color-documentation: #b16be3;
|
||||
--ion-color-navigation: #6600cc;
|
||||
|
||||
/** primary **/
|
||||
--ion-color-primary: #3880ff;
|
||||
--ion-color-primary-rgb: 56, 128, 255;
|
||||
--ion-color-primary-contrast: #ffffff;
|
||||
@@ -36,6 +11,7 @@
|
||||
--ion-color-primary-shade: #3171e0;
|
||||
--ion-color-primary-tint: #4c8dff;
|
||||
|
||||
/** secondary **/
|
||||
--ion-color-secondary: #0cd1e8;
|
||||
--ion-color-secondary-rgb: 12, 209, 232;
|
||||
--ion-color-secondary-contrast: #ffffff;
|
||||
@@ -43,6 +19,7 @@
|
||||
--ion-color-secondary-shade: #0bb8cc;
|
||||
--ion-color-secondary-tint: #24d6ea;
|
||||
|
||||
/** tertiary **/
|
||||
--ion-color-tertiary: #7044ff;
|
||||
--ion-color-tertiary-rgb: 112, 68, 255;
|
||||
--ion-color-tertiary-contrast: #ffffff;
|
||||
@@ -50,6 +27,7 @@
|
||||
--ion-color-tertiary-shade: #633ce0;
|
||||
--ion-color-tertiary-tint: #7e57ff;
|
||||
|
||||
/** success **/
|
||||
--ion-color-success: #10dc60;
|
||||
--ion-color-success-rgb: 16, 220, 96;
|
||||
--ion-color-success-contrast: #ffffff;
|
||||
@@ -57,6 +35,7 @@
|
||||
--ion-color-success-shade: #0ec254;
|
||||
--ion-color-success-tint: #28e070;
|
||||
|
||||
/** warning **/
|
||||
--ion-color-warning: #ffce00;
|
||||
--ion-color-warning-rgb: 255, 206, 0;
|
||||
--ion-color-warning-contrast: #ffffff;
|
||||
@@ -64,6 +43,7 @@
|
||||
--ion-color-warning-shade: #e0b500;
|
||||
--ion-color-warning-tint: #ffd31a;
|
||||
|
||||
/** danger **/
|
||||
--ion-color-danger: #f04141;
|
||||
--ion-color-danger-rgb: 245, 61, 61;
|
||||
--ion-color-danger-contrast: #ffffff;
|
||||
@@ -71,6 +51,7 @@
|
||||
--ion-color-danger-shade: #d33939;
|
||||
--ion-color-danger-tint: #f25454;
|
||||
|
||||
/** dark **/
|
||||
--ion-color-dark: #222428;
|
||||
--ion-color-dark-rgb: 34, 34, 34;
|
||||
--ion-color-dark-contrast: #ffffff;
|
||||
@@ -78,6 +59,7 @@
|
||||
--ion-color-dark-shade: #1e2023;
|
||||
--ion-color-dark-tint: #383a3e;
|
||||
|
||||
/** medium **/
|
||||
--ion-color-medium: #989aa2;
|
||||
--ion-color-medium-rgb: 152, 154, 162;
|
||||
--ion-color-medium-contrast: #ffffff;
|
||||
@@ -85,6 +67,7 @@
|
||||
--ion-color-medium-shade: #86888f;
|
||||
--ion-color-medium-tint: #a2a4ab;
|
||||
|
||||
/** light **/
|
||||
--ion-color-light: #f4f5f8;
|
||||
--ion-color-light-rgb: 244, 244, 244;
|
||||
--ion-color-light-contrast: #000000;
|
||||
@@ -92,229 +75,3 @@
|
||||
--ion-color-light-shade: #d7d8da;
|
||||
--ion-color-light-tint: #f5f6f9;
|
||||
}
|
||||
|
||||
/* Additional Ionic Colors
|
||||
// --------------------------------------------------
|
||||
// In order to add colors to be used with Ionic components,
|
||||
// the color should be added as a class with the convention `.ion-color-{COLOR}`
|
||||
// where `{COLOR}` is the color to be used on the Ionic component
|
||||
// and each variant is defined for the color. For more information, please see
|
||||
// https://beta.ionicframework.com/docs/theming/advanced
|
||||
*/
|
||||
|
||||
.ion-color-favorite {
|
||||
--ion-color-base: #69bb7b;
|
||||
--ion-color-base-rgb: 105, 187, 123;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #5ca56c;
|
||||
--ion-color-tint: #78c288;
|
||||
}
|
||||
|
||||
.ion-color-twitter {
|
||||
--ion-color-base: #1da1f4;
|
||||
--ion-color-base-rgb: 29, 161, 244;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #1a8ed7;
|
||||
--ion-color-tint: #34aaf5;
|
||||
}
|
||||
|
||||
.ion-color-google {
|
||||
--ion-color-base: #dc4a38;
|
||||
--ion-color-base-rgb: 220, 74, 56;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #c24131;
|
||||
--ion-color-tint: #e05c4c;
|
||||
}
|
||||
|
||||
.ion-color-vimeo {
|
||||
--ion-color-base: #23b6ea;
|
||||
--ion-color-base-rgb: 35, 182, 234;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #1fa0ce;
|
||||
--ion-color-tint: #39bdec;
|
||||
}
|
||||
|
||||
.ion-color-facebook {
|
||||
--ion-color-base: #3b5998;
|
||||
--ion-color-base-rgb: 59, 89, 152;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #344e86;
|
||||
--ion-color-tint: #4f6aa2;
|
||||
}
|
||||
|
||||
/* Shared Variables
|
||||
// --------------------------------------------------
|
||||
// To customize the look and feel of this app, you can override
|
||||
// the CSS variables found in Ionic's source files.
|
||||
// To view all the possible Ionic variables, see:
|
||||
// https://beta.ionicframework.com/docs/theming/css-variables#ionic-variables
|
||||
*/
|
||||
|
||||
:root {
|
||||
--ion-headings-font-weight: 300;
|
||||
|
||||
--ion-color-angular: #ac282b;
|
||||
--ion-color-communication: #8e8d93;
|
||||
--ion-color-tooling: #fe4c52;
|
||||
--ion-color-services: #fd8b2d;
|
||||
--ion-color-design: #fed035;
|
||||
--ion-color-workshop: #69bb7b;
|
||||
--ion-color-food: #3bc7c4;
|
||||
--ion-color-documentation: #b16be3;
|
||||
--ion-color-navigation: #6600cc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Dark Theme
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
.dark-theme {
|
||||
--ion-color-primary: #428cff;
|
||||
--ion-color-primary-rgb: 66,140,255;
|
||||
--ion-color-primary-contrast: #ffffff;
|
||||
--ion-color-primary-contrast-rgb: 255,255,255;
|
||||
--ion-color-primary-shade: #3a7be0;
|
||||
--ion-color-primary-tint: #5598ff;
|
||||
|
||||
--ion-color-secondary: #50c8ff;
|
||||
--ion-color-secondary-rgb: 80,200,255;
|
||||
--ion-color-secondary-contrast: #ffffff;
|
||||
--ion-color-secondary-contrast-rgb: 255,255,255;
|
||||
--ion-color-secondary-shade: #46b0e0;
|
||||
--ion-color-secondary-tint: #62ceff;
|
||||
|
||||
--ion-color-tertiary: #6a64ff;
|
||||
--ion-color-tertiary-rgb: 106,100,255;
|
||||
--ion-color-tertiary-contrast: #ffffff;
|
||||
--ion-color-tertiary-contrast-rgb: 255,255,255;
|
||||
--ion-color-tertiary-shade: #5d58e0;
|
||||
--ion-color-tertiary-tint: #7974ff;
|
||||
|
||||
--ion-color-success: #2fdf75;
|
||||
--ion-color-success-rgb: 47,223,117;
|
||||
--ion-color-success-contrast: #000000;
|
||||
--ion-color-success-contrast-rgb: 0,0,0;
|
||||
--ion-color-success-shade: #29c467;
|
||||
--ion-color-success-tint: #44e283;
|
||||
|
||||
--ion-color-warning: #ffd534;
|
||||
--ion-color-warning-rgb: 255,213,52;
|
||||
--ion-color-warning-contrast: #000000;
|
||||
--ion-color-warning-contrast-rgb: 0,0,0;
|
||||
--ion-color-warning-shade: #e0bb2e;
|
||||
--ion-color-warning-tint: #ffd948;
|
||||
|
||||
--ion-color-danger: #ff4961;
|
||||
--ion-color-danger-rgb: 255,73,97;
|
||||
--ion-color-danger-contrast: #ffffff;
|
||||
--ion-color-danger-contrast-rgb: 255,255,255;
|
||||
--ion-color-danger-shade: #e04055;
|
||||
--ion-color-danger-tint: #ff5b71;
|
||||
|
||||
--ion-color-dark: #f4f5f8;
|
||||
--ion-color-dark-rgb: 244,245,248;
|
||||
--ion-color-dark-contrast: #000000;
|
||||
--ion-color-dark-contrast-rgb: 0,0,0;
|
||||
--ion-color-dark-shade: #d7d8da;
|
||||
--ion-color-dark-tint: #f5f6f9;
|
||||
|
||||
--ion-color-medium: #989aa2;
|
||||
--ion-color-medium-rgb: 152,154,162;
|
||||
--ion-color-medium-contrast: #000000;
|
||||
--ion-color-medium-contrast-rgb: 0,0,0;
|
||||
--ion-color-medium-shade: #86888f;
|
||||
--ion-color-medium-tint: #a2a4ab;
|
||||
|
||||
--ion-color-light: #222428;
|
||||
--ion-color-light-rgb: 34,36,40;
|
||||
--ion-color-light-contrast: #ffffff;
|
||||
--ion-color-light-contrast-rgb: 255,255,255;
|
||||
--ion-color-light-shade: #1e2023;
|
||||
--ion-color-light-tint: #383a3e;
|
||||
}
|
||||
|
||||
/*
|
||||
* iOS Dark Theme
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
.dark-theme.ios {
|
||||
--ion-background-color: #000000;
|
||||
--ion-background-color-rgb: 0,0,0;
|
||||
|
||||
--ion-text-color: #ffffff;
|
||||
--ion-text-color-rgb: 255,255,255;
|
||||
|
||||
--ion-color-step-50: #0d0d0d;
|
||||
--ion-color-step-100: #1a1a1a;
|
||||
--ion-color-step-150: #262626;
|
||||
--ion-color-step-200: #333333;
|
||||
--ion-color-step-250: #404040;
|
||||
--ion-color-step-300: #4d4d4d;
|
||||
--ion-color-step-350: #595959;
|
||||
--ion-color-step-400: #666666;
|
||||
--ion-color-step-450: #737373;
|
||||
--ion-color-step-500: #808080;
|
||||
--ion-color-step-550: #8c8c8c;
|
||||
--ion-color-step-600: #999999;
|
||||
--ion-color-step-650: #a6a6a6;
|
||||
--ion-color-step-700: #b3b3b3;
|
||||
--ion-color-step-750: #bfbfbf;
|
||||
--ion-color-step-800: #cccccc;
|
||||
--ion-color-step-850: #d9d9d9;
|
||||
--ion-color-step-900: #e6e6e6;
|
||||
--ion-color-step-950: #f2f2f2;
|
||||
|
||||
--ion-toolbar-background: #0d0d0d;
|
||||
|
||||
--ion-item-background: #1c1c1c;
|
||||
--ion-item-background-activated: #313131;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Material Design Dark Theme
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
.dark-theme.md {
|
||||
--ion-background-color: #121212;
|
||||
--ion-background-color-rgb: 18,18,18;
|
||||
|
||||
--ion-text-color: #ffffff;
|
||||
--ion-text-color-rgb: 255,255,255;
|
||||
|
||||
--ion-border-color: #222222;
|
||||
|
||||
--ion-color-step-50: #1e1e1e;
|
||||
--ion-color-step-100: #2a2a2a;
|
||||
--ion-color-step-150: #363636;
|
||||
--ion-color-step-200: #414141;
|
||||
--ion-color-step-250: #4d4d4d;
|
||||
--ion-color-step-300: #595959;
|
||||
--ion-color-step-350: #656565;
|
||||
--ion-color-step-400: #717171;
|
||||
--ion-color-step-450: #7d7d7d;
|
||||
--ion-color-step-500: #898989;
|
||||
--ion-color-step-550: #949494;
|
||||
--ion-color-step-600: #a0a0a0;
|
||||
--ion-color-step-650: #acacac;
|
||||
--ion-color-step-700: #b8b8b8;
|
||||
--ion-color-step-750: #c4c4c4;
|
||||
--ion-color-step-800: #d0d0d0;
|
||||
--ion-color-step-850: #dbdbdb;
|
||||
--ion-color-step-900: #e7e7e7;
|
||||
--ion-color-step-950: #f3f3f3;
|
||||
|
||||
--ion-item-background: #1e1e1e;
|
||||
|
||||
--ion-toolbar-background: #1f1f1f;
|
||||
|
||||
--ion-tab-bar-background: #1f1f1f;
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
export interface DispatchObject {
|
||||
[key: string]: any;
|
||||
type: string;
|
||||
}
|
||||
|
||||
type PromiseResolveValue<T> = T extends Promise<infer R> ? R : T;
|
||||
type EffectType<T extends (...args: any) => any> = ReturnType<ReturnType<T>>;
|
||||
type EffectReturnValue<T extends (...args: any) => any> = PromiseResolveValue<
|
||||
EffectType<T>
|
||||
>;
|
||||
export type ActionType<T extends (...args: any) => any> = ReturnType<
|
||||
T
|
||||
> extends DispatchObject
|
||||
? ReturnType<T>
|
||||
: EffectReturnValue<T>;
|
||||
@@ -13,7 +13,7 @@
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "preserve"
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": ["tslint-react"]
|
||||
}
|
||||
@@ -11,12 +11,12 @@ var Hello = {
|
||||
{
|
||||
onclick: function() {
|
||||
count++;
|
||||
},
|
||||
}
|
||||
},
|
||||
count + ' Clicks'
|
||||
),
|
||||
)
|
||||
]);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
var Splash = {
|
||||
@@ -24,14 +24,14 @@ var Splash = {
|
||||
return m(
|
||||
'a',
|
||||
{
|
||||
href: '#!/hello',
|
||||
href: '#!/hello'
|
||||
},
|
||||
'Enter!'
|
||||
);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
m.route(root, '/splash', {
|
||||
'/splash': Splash,
|
||||
'/hello': Hello,
|
||||
'/hello': Hello
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Next.js Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Polymer Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Preact Example
|
||||
|
||||
|
||||
@@ -3,5 +3,5 @@ import { component } from 'riot';
|
||||
import Random from './random.riot';
|
||||
|
||||
component(Random)(document.getElementById('app'), {
|
||||
title: 'Hi there!',
|
||||
title: 'Hi there!'
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ module.exports = {
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'public'),
|
||||
publicPath: '/public/',
|
||||
filename: 'bundle.js',
|
||||
filename: 'bundle.js'
|
||||
},
|
||||
devtool: 'inline',
|
||||
module: {
|
||||
@@ -18,10 +18,10 @@ module.exports = {
|
||||
{
|
||||
loader: '@riotjs/webpack-loader',
|
||||
options: {
|
||||
hot: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
hot: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
@@ -29,10 +29,10 @@ module.exports = {
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: ['@babel/preset-env'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
presets: ['@babel/preset-env']
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Saber Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# UmiJS Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Vue.js Example
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
module.exports = {
|
||||
presets: ['@vue/app'],
|
||||
};
|
||||
presets: [
|
||||
'@vue/app'
|
||||
]
|
||||
}
|
||||
|
||||
@@ -21,6 +21,20 @@
|
||||
"eslint-plugin-vue": "^5.0.0",
|
||||
"vue-template-compiler": "^2.6.10"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"rules": {},
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
}
|
||||
},
|
||||
"postcss": {
|
||||
"plugins": {
|
||||
"autoprefixer": {}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import Vue from 'vue';
|
||||
import App from './App.vue';
|
||||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
Vue.config.productionTip = false
|
||||
|
||||
new Vue({
|
||||
render: h => h(App),
|
||||
}).$mount('#app');
|
||||
}).$mount('#app')
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"name": "my_site",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "A Zola project, ready for deployment with ZEIT Now.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "zola build",
|
||||
"dev": "zola serve --port $PORT"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
@@ -43,7 +43,7 @@
|
||||
"lint": "eslint . --ext .ts,.js"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts}": [
|
||||
"./{*,{api,packages,test,utils}/**/*}.{js,ts}": [
|
||||
"prettier --write",
|
||||
"eslint",
|
||||
"git add"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"file": "package.json",
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"next\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
@@ -38,7 +38,7 @@
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"file": "package.json",
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"gatsby\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
@@ -65,7 +65,7 @@
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"file": "package.json",
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"hexo\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
@@ -92,7 +92,7 @@
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"file": "package.json",
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@11ty\\/eleventy\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
@@ -119,11 +119,11 @@
|
||||
"detectors": {
|
||||
"some": [
|
||||
{
|
||||
"file": "package.json",
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"docusaurus\":\\s*\".+?\"[^}]*}"
|
||||
},
|
||||
{
|
||||
"file": "package.json",
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@docusaurus\\/core\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
@@ -151,7 +151,7 @@
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"file": "package.json",
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"preact-cli\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
@@ -178,7 +178,7 @@
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"file": "package.json",
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"ember-cli\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
@@ -206,7 +206,7 @@
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"file": "package.json",
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@vue\\/cli-service\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
@@ -232,7 +232,7 @@
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"file": "package.json",
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@scullyio\\/init\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
@@ -260,7 +260,7 @@
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"file": "package.json",
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@angular\\/cli\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
@@ -287,7 +287,7 @@
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"file": "package.json",
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"polymer-cli\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
@@ -315,7 +315,7 @@
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"file": "package.json",
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"sirv-cli\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
@@ -332,6 +332,34 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Ionic React",
|
||||
"slug": "ionic-react",
|
||||
"demo": "https://ionic-react.now-examples.now.sh",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/ionic-react.svg",
|
||||
"tagline": "Ionic React allows you to build mobile PWAs with React and the Ionic Framework.",
|
||||
"description": "An Ionic React site, created with the Ionic CLI.",
|
||||
"website": "https://ionicframework.com",
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@ionic\\/react\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"buildCommand": {
|
||||
"placeholder": "`build` from `package.json`"
|
||||
},
|
||||
"devCommand": {
|
||||
"value": "stencil build --dev --watch --serve --port $PORT"
|
||||
},
|
||||
"outputDirectory": {
|
||||
"value": "public"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Create React App",
|
||||
"slug": "create-react-app",
|
||||
@@ -343,11 +371,11 @@
|
||||
"detectors": {
|
||||
"some": [
|
||||
{
|
||||
"file": "package.json",
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"react-scripts\":\\s*\".+?\"[^}]*}"
|
||||
},
|
||||
{
|
||||
"file": "package.json",
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"react-dev-utils\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
@@ -374,7 +402,7 @@
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"file": "package.json",
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"gridsome\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
@@ -402,7 +430,7 @@
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"file": "package.json",
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"umi\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
@@ -430,7 +458,7 @@
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"file": "package.json",
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"sapper\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
@@ -457,7 +485,7 @@
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"file": "package.json",
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"saber\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
@@ -484,7 +512,7 @@
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"file": "package.json",
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@stencil\\/core\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
@@ -512,7 +540,7 @@
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"file": "package.json",
|
||||
"path": "package.json",
|
||||
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"nuxt\":\\s*\".+?\"[^}]*}"
|
||||
}
|
||||
]
|
||||
@@ -540,13 +568,13 @@
|
||||
"detectors": {
|
||||
"some": [
|
||||
{
|
||||
"file": "config.yaml"
|
||||
"path": "config.yaml"
|
||||
},
|
||||
{
|
||||
"file": "config.toml"
|
||||
"path": "config.toml"
|
||||
},
|
||||
{
|
||||
"file": "config.json"
|
||||
"path": "config.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -572,7 +600,7 @@
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"file": "_config.yml"
|
||||
"path": "_config.yml"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -598,7 +626,7 @@
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"file": "brunch-config.js"
|
||||
"path": "brunch-config.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -624,7 +652,7 @@
|
||||
"detectors": {
|
||||
"every": [
|
||||
{
|
||||
"file": "config.rb"
|
||||
"path": "config.rb"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -773,15 +801,6 @@
|
||||
"tagline": "A static site generator for Grunt.js and Yeoman, Assemble makes it dead simple to build modular sites and blogs.",
|
||||
"description": "An Assemble site, created from the Assemble quickstart."
|
||||
},
|
||||
{
|
||||
"name": "Ionic React",
|
||||
"slug": "ionic-react",
|
||||
"demo": "https://ionic-react.now-examples.now.sh",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/ionic-react.svg",
|
||||
"tagline": "Ionic React allows you to build mobile PWAs with React and the Ionic Framework.",
|
||||
"description": "An Ionic React site, created with the Ionic CLI.",
|
||||
"website": "https://ionicframework.com"
|
||||
},
|
||||
{
|
||||
"name": "Foundation",
|
||||
"slug": "foundation",
|
||||
|
||||
2
packages/frameworks/index.d.ts
vendored
2
packages/frameworks/index.d.ts
vendored
@@ -1,5 +1,5 @@
|
||||
export interface FrameworkDetectionItem {
|
||||
file: string;
|
||||
path: string;
|
||||
matchContent?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,18 +23,22 @@ async function matches(fs: DetectorFilesystem, framework: Framework) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const check = async ({ file, matchContent }: FrameworkDetectionItem) => {
|
||||
if (!file) {
|
||||
const check = async ({ path, matchContent }: FrameworkDetectionItem) => {
|
||||
if (!path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((await fs.exists(file)) === false) {
|
||||
if ((await fs.hasPath(path)) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (matchContent) {
|
||||
if ((await fs.isFile(path)) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const regex = new RegExp(matchContent, 'gm');
|
||||
const content = await fs.readFile(file);
|
||||
const content = await fs.readFile(path);
|
||||
|
||||
if (!regex.test(content.toString())) {
|
||||
return false;
|
||||
|
||||
@@ -24,22 +24,34 @@
|
||||
* methods in this class definition.
|
||||
*/
|
||||
export abstract class DetectorFilesystem {
|
||||
protected abstract _hasPath(name: string): Promise<boolean>;
|
||||
protected abstract _readFile(name: string): Promise<Buffer>;
|
||||
protected abstract _exists(name: string): Promise<boolean>;
|
||||
protected abstract _isFile(name: string): Promise<boolean>;
|
||||
|
||||
private existsCache: Map<string, Promise<boolean>>;
|
||||
private pathCache: Map<string, Promise<boolean>>;
|
||||
private fileCache: Map<string, Promise<boolean>>;
|
||||
private readFileCache: Map<string, Promise<Buffer>>;
|
||||
|
||||
constructor() {
|
||||
this.existsCache = new Map();
|
||||
this.pathCache = new Map();
|
||||
this.fileCache = new Map();
|
||||
this.readFileCache = new Map();
|
||||
}
|
||||
|
||||
public exists = async (name: string): Promise<boolean> => {
|
||||
let p = this.existsCache.get(name);
|
||||
public hasPath = async (path: string): Promise<boolean> => {
|
||||
let p = this.pathCache.get(path);
|
||||
if (!p) {
|
||||
p = this._exists(name);
|
||||
this.existsCache.set(name, p);
|
||||
p = this._hasPath(path);
|
||||
this.pathCache.set(path, p);
|
||||
}
|
||||
return p;
|
||||
};
|
||||
|
||||
public isFile = async (name: string): Promise<boolean> => {
|
||||
let p = this.fileCache.get(name);
|
||||
if (!p) {
|
||||
p = this._isFile(name);
|
||||
this.fileCache.set(name, p);
|
||||
}
|
||||
return p;
|
||||
};
|
||||
|
||||
@@ -21,7 +21,17 @@ class VirtualFilesystem extends DetectorFilesystem {
|
||||
});
|
||||
}
|
||||
|
||||
async _exists(name: string): Promise<boolean> {
|
||||
async _hasPath(path: string): Promise<boolean> {
|
||||
for (const file of this.files.keys()) {
|
||||
if (file.startsWith(path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async _isFile(name: string): Promise<boolean> {
|
||||
return this.files.has(name);
|
||||
}
|
||||
|
||||
@@ -89,6 +99,7 @@ describe('#detectFramework', () => {
|
||||
it('Detect Hugo #1', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'config.yaml': 'config',
|
||||
'content/post.md': '# hello world',
|
||||
});
|
||||
|
||||
expect(await detectFramework({ fs, frameworkList })).toBe('hugo');
|
||||
@@ -97,6 +108,7 @@ describe('#detectFramework', () => {
|
||||
it('Detect Hugo #2', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'config.json': 'config',
|
||||
'content/post.md': '# hello world',
|
||||
});
|
||||
|
||||
expect(await detectFramework({ fs, frameworkList })).toBe('hugo');
|
||||
@@ -105,6 +117,7 @@ describe('#detectFramework', () => {
|
||||
it('Detect Hugo #3', async () => {
|
||||
const fs = new VirtualFilesystem({
|
||||
'config.toml': 'config',
|
||||
'content/post.md': '# hello world',
|
||||
});
|
||||
|
||||
expect(await detectFramework({ fs, frameworkList })).toBe('hugo');
|
||||
|
||||
@@ -3,6 +3,10 @@ import { promisify } from 'util';
|
||||
import { join } from 'path';
|
||||
import { readConfigFile } from '@now/build-utils';
|
||||
import { Route } from '@now/routing-utils';
|
||||
import NowFrameworks, {
|
||||
Framework as NowFramework,
|
||||
SettingValue,
|
||||
} from '@now/frameworks';
|
||||
|
||||
const readirPromise = promisify(readdir);
|
||||
const readFilePromise = promisify(readFile);
|
||||
@@ -11,6 +15,17 @@ const unlinkPromise = promisify(unlink);
|
||||
const isDir = async (file: string): Promise<boolean> =>
|
||||
(await statPromise(file)).isDirectory();
|
||||
|
||||
export interface Framework {
|
||||
name: string;
|
||||
slug: string;
|
||||
dependency?: string;
|
||||
getOutputDirName: (dirPrefix: string) => Promise<string>;
|
||||
defaultRoutes?: Route[] | ((dirPrefix: string) => Promise<Route[]>);
|
||||
cachePattern?: string;
|
||||
buildCommand?: string;
|
||||
devCommand?: string;
|
||||
}
|
||||
|
||||
// Please note that is extremely important
|
||||
// that the `dependency` property needs
|
||||
// to reference a CLI. This is needed because
|
||||
@@ -20,11 +35,12 @@ const isDir = async (file: string): Promise<boolean> =>
|
||||
// Instead, you need to look for `preact-cli`
|
||||
// when optimizing Preact CLI projects.
|
||||
|
||||
export const frameworks: Framework[] = [
|
||||
const frameworkList: Framework[] = [
|
||||
{
|
||||
name: 'Gatsby.js',
|
||||
slug: 'gatsby',
|
||||
dependency: 'gatsby',
|
||||
buildCommand: 'gatsby build',
|
||||
getOutputDirName: async () => 'public',
|
||||
defaultRoutes: async (dirPrefix: string) => {
|
||||
try {
|
||||
@@ -52,18 +68,21 @@ export const frameworks: Framework[] = [
|
||||
name: 'Hexo',
|
||||
slug: 'hexo',
|
||||
dependency: 'hexo',
|
||||
buildCommand: 'hexo generate',
|
||||
getOutputDirName: async () => 'public',
|
||||
},
|
||||
{
|
||||
name: 'Eleventy',
|
||||
slug: 'eleventy',
|
||||
dependency: '@11ty/eleventy',
|
||||
buildCommand: 'npx @11ty/eleventy',
|
||||
getOutputDirName: async () => '_site',
|
||||
},
|
||||
{
|
||||
name: 'Docusaurus 2.0',
|
||||
name: 'Docusaurus',
|
||||
slug: 'docusaurus',
|
||||
dependency: '@docusaurus/core',
|
||||
buildCommand: 'docusaurus-build',
|
||||
getOutputDirName: async (dirPrefix: string) => {
|
||||
const base = 'build';
|
||||
const location = join(dirPrefix, base);
|
||||
@@ -81,6 +100,7 @@ export const frameworks: Framework[] = [
|
||||
name: 'Preact',
|
||||
slug: 'preact',
|
||||
dependency: 'preact-cli',
|
||||
buildCommand: 'preact build',
|
||||
getOutputDirName: async () => 'build',
|
||||
defaultRoutes: [
|
||||
{
|
||||
@@ -96,6 +116,7 @@ export const frameworks: Framework[] = [
|
||||
name: 'Ember',
|
||||
slug: 'ember',
|
||||
dependency: 'ember-cli',
|
||||
buildCommand: 'ember build',
|
||||
getOutputDirName: async () => 'dist',
|
||||
defaultRoutes: [
|
||||
{
|
||||
@@ -111,6 +132,7 @@ export const frameworks: Framework[] = [
|
||||
name: 'Vue.js',
|
||||
slug: 'vue',
|
||||
dependency: '@vue/cli-service',
|
||||
buildCommand: 'vue-cli-service build',
|
||||
getOutputDirName: async () => 'dist',
|
||||
defaultRoutes: [
|
||||
{
|
||||
@@ -136,7 +158,6 @@ export const frameworks: Framework[] = [
|
||||
name: 'Scully',
|
||||
slug: 'scully',
|
||||
dependency: '@scullyio/init',
|
||||
minNodeRange: '10.x',
|
||||
buildCommand: 'ng build && scully',
|
||||
getOutputDirName: async () => 'dist/static',
|
||||
},
|
||||
@@ -144,7 +165,7 @@ export const frameworks: Framework[] = [
|
||||
name: 'Angular',
|
||||
slug: 'angular',
|
||||
dependency: '@angular/cli',
|
||||
minNodeRange: '10.x',
|
||||
buildCommand: 'ng build',
|
||||
getOutputDirName: async (dirPrefix: string) => {
|
||||
const base = 'dist';
|
||||
const location = join(dirPrefix, base);
|
||||
@@ -171,6 +192,7 @@ export const frameworks: Framework[] = [
|
||||
name: 'Polymer',
|
||||
slug: 'polymer',
|
||||
dependency: 'polymer-cli',
|
||||
buildCommand: 'polymer build',
|
||||
getOutputDirName: async (dirPrefix: string) => {
|
||||
const base = 'build';
|
||||
const location = join(dirPrefix, base);
|
||||
@@ -193,6 +215,7 @@ export const frameworks: Framework[] = [
|
||||
name: 'Svelte',
|
||||
slug: 'svelte',
|
||||
dependency: 'sirv-cli',
|
||||
buildCommand: 'rollup -c',
|
||||
getOutputDirName: async () => 'public',
|
||||
defaultRoutes: [
|
||||
{
|
||||
@@ -208,6 +231,7 @@ export const frameworks: Framework[] = [
|
||||
name: 'Create React App',
|
||||
slug: 'create-react-app',
|
||||
dependency: 'react-scripts',
|
||||
buildCommand: 'react-scripts build',
|
||||
getOutputDirName: async () => 'build',
|
||||
defaultRoutes: [
|
||||
{
|
||||
@@ -238,6 +262,7 @@ export const frameworks: Framework[] = [
|
||||
name: 'Create React App (ejected)',
|
||||
slug: 'create-react-app',
|
||||
dependency: 'react-dev-utils',
|
||||
buildCommand: 'react-scripts build',
|
||||
getOutputDirName: async () => 'build',
|
||||
defaultRoutes: [
|
||||
{
|
||||
@@ -268,12 +293,14 @@ export const frameworks: Framework[] = [
|
||||
name: 'Gridsome',
|
||||
slug: 'gridsome',
|
||||
dependency: 'gridsome',
|
||||
buildCommand: 'gridsome build',
|
||||
getOutputDirName: async () => 'dist',
|
||||
},
|
||||
{
|
||||
name: 'UmiJS',
|
||||
slug: 'umijs',
|
||||
dependency: 'umi',
|
||||
buildCommand: 'umi build',
|
||||
getOutputDirName: async () => 'dist',
|
||||
defaultRoutes: [
|
||||
{
|
||||
@@ -289,6 +316,7 @@ export const frameworks: Framework[] = [
|
||||
name: 'Docusaurus 1.0',
|
||||
slug: 'docusaurus',
|
||||
dependency: 'docusaurus',
|
||||
buildCommand: 'docusaurus-build',
|
||||
getOutputDirName: async (dirPrefix: string) => {
|
||||
const base = 'build';
|
||||
const location = join(dirPrefix, base);
|
||||
@@ -306,12 +334,14 @@ export const frameworks: Framework[] = [
|
||||
name: 'Sapper',
|
||||
slug: 'sapper',
|
||||
dependency: 'sapper',
|
||||
buildCommand: 'sapper export',
|
||||
getOutputDirName: async () => '__sapper__/export',
|
||||
},
|
||||
{
|
||||
name: 'Saber',
|
||||
slug: 'saber',
|
||||
dependency: 'saber',
|
||||
buildCommand: 'saber build',
|
||||
getOutputDirName: async () => 'public',
|
||||
defaultRoutes: [
|
||||
{
|
||||
@@ -332,6 +362,7 @@ export const frameworks: Framework[] = [
|
||||
name: 'Stencil',
|
||||
slug: 'stencil',
|
||||
dependency: '@stencil/core',
|
||||
buildCommand: 'stencil build',
|
||||
getOutputDirName: async () => 'www',
|
||||
defaultRoutes: [
|
||||
{
|
||||
@@ -354,7 +385,6 @@ export const frameworks: Framework[] = [
|
||||
name: 'Hugo',
|
||||
slug: 'hugo',
|
||||
buildCommand: 'hugo -D',
|
||||
devCommand: 'hugo server -D -w -p $PORT',
|
||||
getOutputDirName: async (dirPrefix: string): Promise<string> => {
|
||||
const config = await readConfigFile(
|
||||
['config.json', 'config.yaml', 'config.toml'].map(fileName => {
|
||||
@@ -369,7 +399,6 @@ export const frameworks: Framework[] = [
|
||||
name: 'Jekyll',
|
||||
slug: 'jekyll',
|
||||
buildCommand: 'jekyll build',
|
||||
devCommand: 'bundle exec jekyll serve --watch --port $PORT',
|
||||
getOutputDirName: async (dirPrefix: string): Promise<string> => {
|
||||
const config = await readConfigFile(join(dirPrefix, '_config.yml'));
|
||||
return (config && config.destination) || '_site';
|
||||
@@ -378,25 +407,48 @@ export const frameworks: Framework[] = [
|
||||
{
|
||||
name: 'Brunch',
|
||||
slug: 'brunch',
|
||||
buildCommand: 'brunch build --production',
|
||||
getOutputDirName: async () => 'public',
|
||||
},
|
||||
{
|
||||
name: 'Middleman',
|
||||
slug: 'middleman',
|
||||
buildCommand: 'bundle exec middleman build',
|
||||
devCommand: 'bundle exec middleman server -p $PORT',
|
||||
getOutputDirName: async () => 'build',
|
||||
},
|
||||
{
|
||||
name: 'Zola',
|
||||
slug: 'zola',
|
||||
buildCommand: 'zola build',
|
||||
getOutputDirName: async () => 'public',
|
||||
},
|
||||
];
|
||||
|
||||
export interface Framework {
|
||||
name: string;
|
||||
slug: string;
|
||||
dependency?: string;
|
||||
getOutputDirName: (dirPrefix: string) => Promise<string>;
|
||||
defaultRoutes?: Route[] | ((dirPrefix: string) => Promise<Route[]>);
|
||||
minNodeRange?: string;
|
||||
cachePattern?: string;
|
||||
buildCommand?: string;
|
||||
devCommand?: string;
|
||||
function getValue(
|
||||
framework: NowFramework | undefined,
|
||||
name: keyof NowFramework['settings']
|
||||
) {
|
||||
const setting = framework && framework.settings && framework.settings[name];
|
||||
return setting && (setting as SettingValue).value;
|
||||
}
|
||||
|
||||
export const frameworks: Framework[] = frameworkList.map(partialFramework => {
|
||||
const frameworkItem = (NowFrameworks as NowFramework[]).find(
|
||||
f => f.slug === partialFramework.slug
|
||||
);
|
||||
|
||||
const devCommand = getValue(frameworkItem, 'devCommand');
|
||||
const buildCommand = getValue(frameworkItem, 'buildCommand');
|
||||
const outputDirectory = getValue(frameworkItem, 'outputDirectory');
|
||||
|
||||
const getOutputDirName = partialFramework.getOutputDirName
|
||||
? partialFramework.getOutputDirName
|
||||
: async () => outputDirectory || 'public';
|
||||
|
||||
return {
|
||||
devCommand,
|
||||
buildCommand,
|
||||
...partialFramework,
|
||||
getOutputDirName,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -212,21 +212,24 @@ export async function build({
|
||||
|
||||
const pkg = getPkg(entrypoint, workPath);
|
||||
|
||||
const devScript = pkg ? getCommand(pkg, 'dev', config) : null;
|
||||
const buildScript = pkg ? getCommand(pkg, 'build', config) : null;
|
||||
|
||||
const framework = getFramework(config, pkg);
|
||||
const devCommand: string | undefined =
|
||||
config.devCommand || (framework && framework.devCommand);
|
||||
config.devCommand ||
|
||||
(devScript ? undefined : framework && framework.devCommand);
|
||||
const buildCommand: string | undefined =
|
||||
config.buildCommand || (framework && framework.buildCommand);
|
||||
config.buildCommand ||
|
||||
(buildScript ? undefined : framework && framework.buildCommand);
|
||||
|
||||
if (pkg || buildCommand) {
|
||||
const gemfilePath = path.join(workPath, 'Gemfile');
|
||||
const requirementsPath = path.join(workPath, 'requirements.txt');
|
||||
|
||||
let output: Files = {};
|
||||
let minNodeRange: string | undefined = undefined;
|
||||
|
||||
const routes: Route[] = [];
|
||||
const devScript = pkg ? getCommand(pkg, 'dev', config) : null;
|
||||
|
||||
if (config.zeroConfig) {
|
||||
if (existsSync(gemfilePath) && !meta.isDev) {
|
||||
@@ -284,22 +287,11 @@ export async function build({
|
||||
debug(
|
||||
`Detected ${framework.name} framework. Optimizing your deployment...`
|
||||
);
|
||||
|
||||
if (framework.minNodeRange) {
|
||||
minNodeRange = framework.minNodeRange;
|
||||
debug(
|
||||
`${framework.name} requires Node.js ${framework.minNodeRange}. Switching...`
|
||||
);
|
||||
} else {
|
||||
debug(
|
||||
`${framework.name} does not require a specific Node.js version. Continuing ...`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const nodeVersion = await getNodeVersion(
|
||||
entrypointDir,
|
||||
minNodeRange,
|
||||
undefined,
|
||||
config,
|
||||
meta
|
||||
);
|
||||
@@ -394,7 +386,6 @@ export async function build({
|
||||
);
|
||||
}
|
||||
|
||||
const buildScript = pkg ? getCommand(pkg, 'build', config) : null;
|
||||
debug(
|
||||
`Running "${buildCommand || buildScript}" script in "${entrypoint}"`
|
||||
);
|
||||
|
||||
3
packages/now-static-build/test/fixtures/32-zola-with-framework/.gitignore
vendored
Normal file
3
packages/now-static-build/test/fixtures/32-zola-with-framework/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
public
|
||||
.env
|
||||
processed_images/
|
||||
14
packages/now-static-build/test/fixtures/32-zola-with-framework/config.toml
vendored
Normal file
14
packages/now-static-build/test/fixtures/32-zola-with-framework/config.toml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
base_url = "/"
|
||||
compile_sass = false
|
||||
theme = "feather"
|
||||
highlight_code = true
|
||||
build_search_index = false
|
||||
title = "Hello World"
|
||||
description = "A an example zola website with a theme"
|
||||
default_language = "en"
|
||||
generate_categories_pages = true
|
||||
generate_tags_pages = true
|
||||
generate_rss = true
|
||||
|
||||
[extra]
|
||||
#feather_header_image = "/example.svg"
|
||||
17
packages/now-static-build/test/fixtures/32-zola-with-framework/content/1/index.md
vendored
Normal file
17
packages/now-static-build/test/fixtures/32-zola-with-framework/content/1/index.md
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
+++
|
||||
title = "Hello Universe"
|
||||
weight = 1
|
||||
|
||||
[extra]
|
||||
number = "1"
|
||||
+++
|
||||
|
||||
## This is the first.
|
||||
|
||||
Hello to anyone out there. Thanks for coming to my Ted Talk. Goodnight!
|
||||
|
||||
<!-- more -->
|
||||
|
||||
## Keywords
|
||||
|
||||
_default, first, hello_
|
||||
BIN
packages/now-static-build/test/fixtures/32-zola-with-framework/content/1/thumb.jpg
vendored
Normal file
BIN
packages/now-static-build/test/fixtures/32-zola-with-framework/content/1/thumb.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
19
packages/now-static-build/test/fixtures/32-zola-with-framework/content/2/index.md
vendored
Normal file
19
packages/now-static-build/test/fixtures/32-zola-with-framework/content/2/index.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
+++
|
||||
title = "Never Gonna Give You Up"
|
||||
weight = 2
|
||||
|
||||
[extra]
|
||||
number = "2"
|
||||
+++
|
||||
|
||||
{{youtube(id="dQw4w9WgXcQ") }}
|
||||
|
||||
## Let's talk about youtube
|
||||
|
||||
Everyone loves youtube because of the endless videos. I could never give up youtube, could you?
|
||||
|
||||
<!-- more -->
|
||||
|
||||
## Keywords
|
||||
|
||||
_youtube, video, never_
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user