├── .prettierignore
├── example
├── .prettierignore
├── src
│ ├── fonts
│ │ └── pricedown.ttf
│ ├── globalStyles.js
│ ├── reducers.js
│ ├── containers
│ │ ├── App
│ │ │ ├── reducer.js
│ │ │ └── index.js
│ │ ├── CharacterSelect
│ │ │ ├── reducer.js
│ │ │ └── index.js
│ │ └── WindowListener
│ │ │ └── index.js
│ ├── index.html
│ ├── util
│ │ └── Nui.js
│ ├── configureStore.js
│ ├── components
│ │ └── character
│ │ │ └── index.js
│ └── app.js
├── .prettierrc
├── __resource.lua
├── .editorconfig
├── .babelrc
├── webpack
│ ├── webpack.dev.js
│ ├── webpack.prod.js
│ └── webpack.common.js
├── LICENSE.md
├── gitignore
├── .eslintrc.js
├── package.json
└── client.lua
├── src
├── fonts
│ └── pricedown.ttf
├── reducers.js
├── globalStyles.js
├── containers
│ ├── App
│ │ ├── reducer.js
│ │ └── index.js
│ └── WindowListener
│ │ └── index.js
├── index.html
├── util
│ └── Nui.js
├── configureStore.js
└── app.js
├── .prettierrc
├── .editorconfig
├── .babelrc
├── README.md
├── webpack
├── webpack.dev.js
├── webpack.prod.js
└── webpack.common.js
├── LICENSE.md
├── .gitignore
├── .eslintrc.js
└── package.json
/.prettierignore:
--------------------------------------------------------------------------------
1 | build/
2 | node_modules/
3 | package-lock.json
4 |
--------------------------------------------------------------------------------
/example/.prettierignore:
--------------------------------------------------------------------------------
1 | build/
2 | node_modules/
3 | package-lock.json
4 |
--------------------------------------------------------------------------------
/src/fonts/pricedown.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/calumari/fivem-react-boilerplate/HEAD/src/fonts/pricedown.ttf
--------------------------------------------------------------------------------
/example/src/fonts/pricedown.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/calumari/fivem-react-boilerplate/HEAD/example/src/fonts/pricedown.ttf
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": true,
7 | "trailingComma": "all"
8 | }
9 |
--------------------------------------------------------------------------------
/example/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": true,
7 | "trailingComma": "all"
8 | }
9 |
--------------------------------------------------------------------------------
/src/reducers.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import appReducer from 'containers/App/reducer';
4 |
5 | export default () =>
6 | combineReducers({
7 | app: appReducer,
8 | });
9 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | end_of_line = lf
8 | insert_final_newline = true
9 | indent_style = space
10 | indent_size = 2
11 | trim_trailing_whitespace = true
12 |
13 | [*.md]
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/example/__resource.lua:
--------------------------------------------------------------------------------
1 | resource_manifest_version '44febabe-d386-4d18-afbe-5e627f4af937'
2 |
3 | client_script "client.lua"
4 |
5 | ui_page "html/index.html"
6 |
7 | files {
8 | "html/96c342ee6d5d50fc5b3fa76a6f6932b9.tff",
9 | "html/index.html",
10 | "html/main.js"
11 | }
12 |
--------------------------------------------------------------------------------
/example/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | end_of_line = lf
8 | insert_final_newline = true
9 | indent_style = space
10 | indent_size = 2
11 | trim_trailing_whitespace = true
12 |
13 | [*.md]
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/src/globalStyles.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 | import Pricedown from 'fonts/pricedown.ttf';
3 |
4 | const GlobalStyle = createGlobalStyle`
5 | @font-face {
6 | font-family: 'Pricedown';
7 | src: url(${Pricedown});
8 | }
9 | `;
10 |
11 | export default GlobalStyle;
12 |
--------------------------------------------------------------------------------
/example/src/globalStyles.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 | import Pricedown from 'fonts/pricedown.ttf';
3 |
4 | const GlobalStyle = createGlobalStyle`
5 | @font-face {
6 | font-family: 'Pricedown';
7 | src: url(${Pricedown});
8 | }
9 | `;
10 |
11 | export default GlobalStyle;
12 |
--------------------------------------------------------------------------------
/example/src/reducers.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import appReducer from 'containers/App/reducer';
4 | import characterReducer from 'containers/CharacterSelect/reducer';
5 |
6 | export default () =>
7 | combineReducers({
8 | app: appReducer,
9 | characters: characterReducer,
10 | });
11 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "modules": false
7 | }
8 | ],
9 | "@babel/preset-react"
10 | ],
11 | "plugins": [
12 | "styled-components",
13 | "@babel/plugin-proposal-class-properties",
14 | "@babel/plugin-syntax-dynamic-import"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/example/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "modules": false
7 | }
8 | ],
9 | "@babel/preset-react"
10 | ],
11 | "plugins": [
12 | "styled-components",
13 | "@babel/plugin-proposal-class-properties",
14 | "@babel/plugin-syntax-dynamic-import"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/src/containers/App/reducer.js:
--------------------------------------------------------------------------------
1 | export const initialState = {
2 | hidden: true,
3 | };
4 |
5 | const appReducer = (state = initialState, action) => {
6 | switch (action.type) {
7 | case 'APP_SHOW':
8 | return Object.assign({}, state, { hidden: false });
9 | case 'APP_HIDE':
10 | return Object.assign({}, state, { hidden: true });
11 | default:
12 | return state;
13 | }
14 | };
15 |
16 | export default appReducer;
17 |
--------------------------------------------------------------------------------
/example/src/containers/App/reducer.js:
--------------------------------------------------------------------------------
1 | export const initialState = {
2 | hidden: true,
3 | };
4 |
5 | const appReducer = (state = initialState, action) => {
6 | switch (action.type) {
7 | case 'APP_SHOW':
8 | return Object.assign({}, state, { hidden: false });
9 | case 'APP_HIDE':
10 | return Object.assign({}, state, { hidden: true });
11 | default:
12 | return state;
13 | }
14 | };
15 |
16 | export default appReducer;
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # fivem-react-boilerplate
2 | ## Notice
3 | I am no longer involved in FiveM and apart from the occasional dependency bump this project is unmaintained. PRs highly encouraged!
4 |
5 | # Quick start
6 | ### 1. Clone repo
7 | ```
8 | git clone https://github.com/calumari/fivem-react-boilerplate.git html
9 | ```
10 |
11 | ### 2. Install dependencies
12 | ```
13 | cd html
14 | npm install
15 | ```
16 |
17 | ### 3. Build
18 | ```
19 | npm run build
20 | ```
21 |
22 | ### 4. Copy the contents of the generated manifest to your resource manifest!
23 |
24 | # Commands
25 | ### Run locally for development
26 | ```
27 | npm run start
28 | ```
29 |
--------------------------------------------------------------------------------
/example/src/containers/CharacterSelect/reducer.js:
--------------------------------------------------------------------------------
1 | export const initialState = {
2 | hidden: true,
3 | characters: [],
4 | };
5 |
6 | const appReducer = (state = initialState, action) => {
7 | switch (action.type) {
8 | case 'RECIEVE_CHARACTERS':
9 | return Object.assign({}, state, {
10 | characters: action.payload.characters,
11 | });
12 | case 'CHARACTERS_SHOW':
13 | return Object.assign({}, state, { hidden: false });
14 | case 'CHARACTERS_HIDE':
15 | return Object.assign({}, state, { hidden: true });
16 | default:
17 | return state;
18 | }
19 | };
20 |
21 | export default appReducer;
22 |
--------------------------------------------------------------------------------
/src/containers/App/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import PropTypes from 'prop-types';
4 |
5 | import styled from 'styled-components';
6 |
7 | import GlobalStyle from '../../globalStyles';
8 |
9 | const H1 = styled('h1')`
10 | font-family: Pricedown;
11 | visibility: ${props => props.hidden};
12 | `;
13 |
14 | const App = ({ hidden }) => (
15 |
16 |
Hello world
17 |
18 |
19 | );
20 |
21 | App.propTypes = {
22 | hidden: PropTypes.bool.isRequired,
23 | };
24 |
25 | const mapStateToProps = state => ({ hidden: state.app.hidden });
26 |
27 | export default connect(mapStateToProps)(App);
28 |
--------------------------------------------------------------------------------
/example/src/containers/App/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import PropTypes from 'prop-types';
4 |
5 | import styled from 'styled-components';
6 |
7 | import GlobalStyle from '../../globalStyles';
8 |
9 | const H1 = styled('h1')`
10 | font-family: Pricedown;
11 | visibility: ${props => props.hidden};
12 | `;
13 |
14 | const App = ({ hidden }) => (
15 |
16 |
Hello world
17 |
18 |
19 | );
20 |
21 | App.propTypes = {
22 | hidden: PropTypes.bool.isRequired,
23 | };
24 |
25 | const mapStateToProps = state => ({ hidden: state.app.hidden });
26 |
27 | export default connect(mapStateToProps)(App);
28 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | FiveM Boilerplate
8 |
9 |
10 |
11 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/util/Nui.js:
--------------------------------------------------------------------------------
1 | export default {
2 | async send(event, data = {}) {
3 | /// #if DEBUG
4 | return new Promise(resolve => setTimeout(resolve, 100));
5 | /// #endif
6 |
7 | /* eslint-disable no-unreachable */
8 | return fetch(`http:///${event}`, {
9 | method: 'post',
10 | headers: {
11 | 'Content-type': 'application/json; charset=UTF-8',
12 | },
13 | body: JSON.stringify(data),
14 | });
15 | /* eslint-enable no-unreachable */
16 | },
17 | emulate(type, data = null) {
18 | window.dispatchEvent(
19 | new MessageEvent('message', {
20 | data: {
21 | type,
22 | data,
23 | },
24 | }),
25 | );
26 | },
27 | };
28 |
--------------------------------------------------------------------------------
/example/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | FiveM Boilerplate
8 |
9 |
10 |
11 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/example/src/util/Nui.js:
--------------------------------------------------------------------------------
1 | export default {
2 | async send(event, data = {}) {
3 | // /// #if DEBUG
4 | // return new Promise(resolve => setTimeout(resolve, 100));
5 | // /// #endif
6 |
7 | /* eslint-disable no-unreachable */
8 | return fetch(`http://example/${event}`, {
9 | method: 'post',
10 | headers: {
11 | 'Content-type': 'application/json; charset=UTF-8',
12 | },
13 | body: JSON.stringify(data),
14 | });
15 | /* eslint-enable no-unreachable */
16 | },
17 | emulate(type, data = null) {
18 | window.dispatchEvent(
19 | new MessageEvent('message', {
20 | data: {
21 | type,
22 | data,
23 | },
24 | }),
25 | );
26 | },
27 | };
28 |
--------------------------------------------------------------------------------
/src/configureStore.js:
--------------------------------------------------------------------------------
1 | import { createStore, compose } from 'redux';
2 | import createReducer from './reducers';
3 |
4 | export default function configureStore(initialState) {
5 | let composeEnhancers = compose;
6 |
7 | if (process.env.NODE_ENV !== 'production' && typeof window === 'object') {
8 | if (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) {
9 | composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({});
10 | }
11 | }
12 |
13 | const enhancers = [];
14 |
15 | const store = createStore(
16 | createReducer(),
17 | initialState,
18 | composeEnhancers(...enhancers),
19 | );
20 |
21 | if (module.hot) {
22 | module.hot.accept('./reducers', () => {
23 | store.replaceReducer(createReducer(store.injectedReducers));
24 | });
25 | }
26 |
27 | return store;
28 | }
29 |
--------------------------------------------------------------------------------
/example/src/configureStore.js:
--------------------------------------------------------------------------------
1 | import { createStore, compose } from 'redux';
2 | import createReducer from './reducers';
3 |
4 | export default function configureStore(initialState) {
5 | let composeEnhancers = compose;
6 |
7 | if (process.env.NODE_ENV !== 'production' && typeof window === 'object') {
8 | if (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) {
9 | composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({});
10 | }
11 | }
12 |
13 | const enhancers = [];
14 |
15 | const store = createStore(
16 | createReducer(),
17 | initialState,
18 | composeEnhancers(...enhancers),
19 | );
20 |
21 | if (module.hot) {
22 | module.hot.accept('./reducers', () => {
23 | store.replaceReducer(createReducer(store.injectedReducers));
24 | });
25 | }
26 |
27 | return store;
28 | }
29 |
--------------------------------------------------------------------------------
/src/app.js:
--------------------------------------------------------------------------------
1 | import '@babel/polyfill';
2 |
3 | import React from 'react';
4 | import ReactDOM from 'react-dom';
5 | import { Provider } from 'react-redux';
6 |
7 | import App from 'containers/App';
8 |
9 | import WindowListener from 'containers/WindowListener';
10 |
11 | import configureStore from './configureStore';
12 |
13 | const initialState = {};
14 | const store = configureStore(initialState);
15 | const MOUNT_NODE = document.getElementById('app');
16 |
17 | const render = () => {
18 | ReactDOM.render(
19 |
20 |
21 |
22 |
23 | ,
24 | MOUNT_NODE,
25 | );
26 | };
27 |
28 | if (module.hot) {
29 | module.hot.accept(['containers/App'], () => {
30 | ReactDOM.unmountComponentAtNode(MOUNT_NODE);
31 | render();
32 | });
33 | }
34 |
35 | render();
36 |
--------------------------------------------------------------------------------
/src/containers/WindowListener/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import PropTypes from 'prop-types';
4 |
5 | class WindowListener extends React.Component {
6 | componentWillMount() {
7 | window.addEventListener('message', this.handleEvent);
8 | }
9 |
10 | componentWillUnmount() {
11 | window.removeEventListener('message', this.handleEvent);
12 | }
13 |
14 | handleEvent = event => {
15 | const { dispatch } = this.props;
16 | const { type, data } = event.data;
17 | dispatch({ type, payload: { ...data } });
18 | };
19 |
20 | render() {
21 | return React.Children.only(this.props.children);
22 | }
23 | }
24 |
25 | WindowListener.propTypes = {
26 | dispatch: PropTypes.func.isRequired,
27 | children: PropTypes.element.isRequired,
28 | };
29 |
30 | export default connect(
31 | null,
32 | null,
33 | )(WindowListener);
34 |
--------------------------------------------------------------------------------
/example/src/containers/WindowListener/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import PropTypes from 'prop-types';
4 |
5 | class WindowListener extends React.Component {
6 | componentWillMount() {
7 | window.addEventListener('message', this.handleEvent);
8 | }
9 |
10 | componentWillUnmount() {
11 | window.removeEventListener('message', this.handleEvent);
12 | }
13 |
14 | handleEvent = event => {
15 | const { dispatch } = this.props;
16 | const { type, data } = event.data;
17 | dispatch({ type, payload: { ...data } });
18 | };
19 |
20 | render() {
21 | return React.Children.only(this.props.children);
22 | }
23 | }
24 |
25 | WindowListener.propTypes = {
26 | dispatch: PropTypes.func.isRequired,
27 | children: PropTypes.element.isRequired,
28 | };
29 |
30 | export default connect(
31 | null,
32 | null,
33 | )(WindowListener);
34 |
--------------------------------------------------------------------------------
/webpack/webpack.dev.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 |
5 | module.exports = require('./webpack.common')({
6 | mode: 'development',
7 |
8 | // Add hot reloading in development
9 | entry: [
10 | path.join(process.cwd(), 'src/app.js'), // Start with js/app.js
11 | ],
12 |
13 | // Add development plugins
14 | plugins: [
15 | new webpack.HotModuleReplacementPlugin(), // Tell webpack we want hot reloading
16 | new HtmlWebpackPlugin({
17 | inject: true, // Inject all files that are generated by webpack, e.g. bundle.js
18 | template: 'src/index.html',
19 | }),
20 | ],
21 |
22 | // Emit a source map for easier debugging
23 | // See https://webpack.js.org/configuration/devtool/#devtool
24 | // devtool: 'eval-source-map',
25 |
26 | performance: {
27 | hints: false,
28 | },
29 | });
30 |
--------------------------------------------------------------------------------
/example/webpack/webpack.dev.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 |
5 | module.exports = require('./webpack.common')({
6 | mode: 'development',
7 |
8 | // Add hot reloading in development
9 | entry: [
10 | path.join(process.cwd(), 'src/app.js'), // Start with js/app.js
11 | ],
12 |
13 | // Add development plugins
14 | plugins: [
15 | new webpack.HotModuleReplacementPlugin(), // Tell webpack we want hot reloading
16 | new HtmlWebpackPlugin({
17 | inject: true, // Inject all files that are generated by webpack, e.g. bundle.js
18 | template: 'src/index.html',
19 | }),
20 | ],
21 |
22 | // Emit a source map for easier debugging
23 | // See https://webpack.js.org/configuration/devtool/#devtool
24 | // devtool: 'eval-source-map',
25 |
26 | performance: {
27 | hints: false,
28 | },
29 | });
30 |
--------------------------------------------------------------------------------
/example/src/components/character/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { Box } from '@rebass/grid';
5 | import styled from 'styled-components';
6 |
7 | const Background = styled(Box)`
8 | background-color: white;
9 | border: 1px solid black;
10 | margin: 5px;
11 | padding: 5px;
12 | `;
13 |
14 | const Character = ({ forename, surname, description, balance, onSelect }) => (
15 |
16 |
17 | {forename} {surname}
18 |
19 | {description}
20 | ${balance}
21 |
22 |
23 | );
24 |
25 | Character.propTypes = {
26 | forename: PropTypes.string.isRequired,
27 | surname: PropTypes.string.isRequired,
28 | description: PropTypes.string,
29 | balance: PropTypes.number.isRequired,
30 | onSelect: PropTypes.func.isRequired,
31 | };
32 |
33 | export default Character;
34 |
--------------------------------------------------------------------------------
/example/src/app.js:
--------------------------------------------------------------------------------
1 | import '@babel/polyfill';
2 |
3 | import React from 'react';
4 | import ReactDOM from 'react-dom';
5 | import { Provider } from 'react-redux';
6 |
7 | import App from 'containers/App';
8 |
9 | import WindowListener from 'containers/WindowListener';
10 |
11 | import configureStore from './configureStore';
12 | import CharacterSelect from './containers/CharacterSelect';
13 |
14 | const initialState = {};
15 | const store = configureStore(initialState);
16 | const MOUNT_NODE = document.getElementById('app');
17 |
18 | const render = () => {
19 | ReactDOM.render(
20 |
21 |
22 | <>
23 |
24 |
25 | >
26 |
27 | ,
28 | MOUNT_NODE,
29 | );
30 | };
31 |
32 | if (module.hot) {
33 | module.hot.accept(['containers/App'], () => {
34 | ReactDOM.unmountComponentAtNode(MOUNT_NODE);
35 | render();
36 | });
37 | }
38 |
39 | render();
40 |
--------------------------------------------------------------------------------
/webpack/webpack.prod.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 | const ResourceManifestPlugin = require('webpack-fivem-manifest');
4 |
5 | module.exports = require('./webpack.common')({
6 | mode: 'production',
7 |
8 | // In production, we skip all hot-reloading stuff
9 | entry: [path.join(process.cwd(), 'src/app.js')],
10 |
11 | plugins: [
12 | new HtmlWebpackPlugin({
13 | template: 'src/index.html',
14 | minify: {
15 | removeComments: true,
16 | collapseWhitespace: true,
17 | removeRedundantAttributes: true,
18 | useShortDoctype: true,
19 | removeEmptyAttributes: true,
20 | removeStyleLinkTypeAttributes: true,
21 | keepClosingSlash: true,
22 | minifyJS: true,
23 | minifyCSS: true,
24 | minifyURLs: true,
25 | },
26 | inject: true,
27 | }),
28 | new ResourceManifestPlugin(),
29 | ],
30 |
31 | performance: {
32 | assetFilter: assetFilename =>
33 | !/(\.map$)|(^(main\.|favicon\.))/.test(assetFilename),
34 | },
35 | });
36 |
--------------------------------------------------------------------------------
/example/webpack/webpack.prod.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 | const ResourceManifestPlugin = require('webpack-fivem-manifest');
4 |
5 | module.exports = require('./webpack.common')({
6 | mode: 'production',
7 |
8 | // In production, we skip all hot-reloading stuff
9 | entry: [path.join(process.cwd(), 'src/app.js')],
10 |
11 | plugins: [
12 | new HtmlWebpackPlugin({
13 | template: 'src/index.html',
14 | minify: {
15 | removeComments: true,
16 | collapseWhitespace: true,
17 | removeRedundantAttributes: true,
18 | useShortDoctype: true,
19 | removeEmptyAttributes: true,
20 | removeStyleLinkTypeAttributes: true,
21 | keepClosingSlash: true,
22 | minifyJS: true,
23 | minifyCSS: true,
24 | minifyURLs: true,
25 | },
26 | inject: true,
27 | }),
28 | // new ResourceManifestPlugin(),
29 | ],
30 |
31 | performance: {
32 | assetFilter: assetFilename =>
33 | !/(\.map$)|(^(main\.|favicon\.))/.test(assetFilename),
34 | },
35 | });
36 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Callum Night
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/example/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Callum Night
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/example/src/containers/CharacterSelect/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import PropTypes from 'prop-types';
4 |
5 | import { Flex } from '@rebass/grid';
6 |
7 | import Character from 'components/character';
8 | import Nui from '../../util/Nui';
9 |
10 | class CharacterSelect extends React.Component {
11 | selectCharacter = id => () => {
12 | Nui.send('SELECT_CHARACTER', { id });
13 | };
14 |
15 | render() {
16 | const { hidden, characters } = this.props;
17 | if (hidden) {
18 | return null;
19 | }
20 | return (
21 |
22 | {characters.map(c => (
23 |
24 | ))}
25 |
26 | );
27 | }
28 | }
29 |
30 | CharacterSelect.propTypes = {
31 | characters: PropTypes.arrayOf(PropTypes.object),
32 | };
33 |
34 | const mapStateToProps = state => ({
35 | hidden: state.characters.hidden,
36 | characters: state.characters.characters,
37 | });
38 |
39 | export default connect(mapStateToProps)(CharacterSelect);
40 |
41 | /// #if DEBUG
42 |
43 | // Emulate a client event during local testing.
44 |
45 | setTimeout(() => {
46 | Nui.emulate('RECIEVE_CHARACTERS', {
47 | characters: [
48 | {
49 | id: 1,
50 | forename: 'Joe',
51 | surname: 'Bloggs',
52 | description: 'Recently signed a Mixer contract.',
53 | balance: 1000,
54 | },
55 | {
56 | id: 2,
57 | forename: 'John',
58 | surname: 'Everyman',
59 | description: 'Author of Roleplaying 101.',
60 | balance: 20320732,
61 | },
62 | ],
63 | });
64 | }, 100);
65 |
66 | /// #endif
67 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | html
3 |
4 | # Logs
5 | logs
6 | *.log
7 | npm-debug.log*
8 | yarn-debug.log*
9 | yarn-error.log*
10 | lerna-debug.log*
11 |
12 | # Diagnostic reports (https://nodejs.org/api/report.html)
13 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
14 |
15 | # Runtime data
16 | pids
17 | *.pid
18 | *.seed
19 | *.pid.lock
20 |
21 | # Directory for instrumented libs generated by jscoverage/JSCover
22 | lib-cov
23 |
24 | # Coverage directory used by tools like istanbul
25 | coverage
26 | *.lcov
27 |
28 | # nyc test coverage
29 | .nyc_output
30 |
31 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
32 | .grunt
33 |
34 | # Bower dependency directory (https://bower.io/)
35 | bower_components
36 |
37 | # node-waf configuration
38 | .lock-wscript
39 |
40 | # Compiled binary addons (https://nodejs.org/api/addons.html)
41 | build/Release
42 |
43 | # Dependency directories
44 | node_modules/
45 | jspm_packages/
46 |
47 | # TypeScript v1 declaration files
48 | typings/
49 |
50 | # TypeScript cache
51 | *.tsbuildinfo
52 |
53 | # Optional npm cache directory
54 | .npm
55 |
56 | # Optional eslint cache
57 | .eslintcache
58 |
59 | # Optional REPL history
60 | .node_repl_history
61 |
62 | # Output of 'npm pack'
63 | *.tgz
64 |
65 | # Yarn Integrity file
66 | .yarn-integrity
67 |
68 | # dotenv environment variables file
69 | .env
70 | .env.test
71 |
72 | # parcel-bundler cache (https://parceljs.org/)
73 | .cache
74 |
75 | # next.js build output
76 | .next
77 |
78 | # nuxt.js build output
79 | .nuxt
80 |
81 | # vuepress build output
82 | .vuepress/dist
83 |
84 | # Serverless directories
85 | .serverless/
86 |
87 | # FuseBox cache
88 | .fusebox/
89 |
90 | # DynamoDB Local files
91 | .dynamodb/
92 |
93 | # Cruft
94 | .DS_Store
95 | .idea
96 |
--------------------------------------------------------------------------------
/example/gitignore:
--------------------------------------------------------------------------------
1 | build
2 | html
3 |
4 | # Logs
5 | logs
6 | *.log
7 | npm-debug.log*
8 | yarn-debug.log*
9 | yarn-error.log*
10 | lerna-debug.log*
11 |
12 | # Diagnostic reports (https://nodejs.org/api/report.html)
13 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
14 |
15 | # Runtime data
16 | pids
17 | *.pid
18 | *.seed
19 | *.pid.lock
20 |
21 | # Directory for instrumented libs generated by jscoverage/JSCover
22 | lib-cov
23 |
24 | # Coverage directory used by tools like istanbul
25 | coverage
26 | *.lcov
27 |
28 | # nyc test coverage
29 | .nyc_output
30 |
31 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
32 | .grunt
33 |
34 | # Bower dependency directory (https://bower.io/)
35 | bower_components
36 |
37 | # node-waf configuration
38 | .lock-wscript
39 |
40 | # Compiled binary addons (https://nodejs.org/api/addons.html)
41 | build/Release
42 |
43 | # Dependency directories
44 | node_modules/
45 | jspm_packages/
46 |
47 | # TypeScript v1 declaration files
48 | typings/
49 |
50 | # TypeScript cache
51 | *.tsbuildinfo
52 |
53 | # Optional npm cache directory
54 | .npm
55 |
56 | # Optional eslint cache
57 | .eslintcache
58 |
59 | # Optional REPL history
60 | .node_repl_history
61 |
62 | # Output of 'npm pack'
63 | *.tgz
64 |
65 | # Yarn Integrity file
66 | .yarn-integrity
67 |
68 | # dotenv environment variables file
69 | .env
70 | .env.test
71 |
72 | # parcel-bundler cache (https://parceljs.org/)
73 | .cache
74 |
75 | # next.js build output
76 | .next
77 |
78 | # nuxt.js build output
79 | .nuxt
80 |
81 | # vuepress build output
82 | .vuepress/dist
83 |
84 | # Serverless directories
85 | .serverless/
86 |
87 | # FuseBox cache
88 | .fusebox/
89 |
90 | # DynamoDB Local files
91 | .dynamodb/
92 |
93 | # Cruft
94 | .DS_Store
95 | .idea
96 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const { resolve } = require('path');
3 |
4 | const prettierOptions = JSON.parse(
5 | fs.readFileSync(resolve(__dirname, '.prettierrc'), 'utf8'),
6 | );
7 |
8 | module.exports = {
9 | parser: 'babel-eslint',
10 | extends: ['airbnb', 'prettier', 'prettier/react'],
11 | plugins: ['prettier', 'react', 'react-hooks', 'jsx-a11y'],
12 | parserOptions: {
13 | ecmaVersion: 6,
14 | sourceType: 'module',
15 | ecmaFeatures: {
16 | jsx: true,
17 | },
18 | },
19 | env: {
20 | jest: true,
21 | browser: true,
22 | node: true,
23 | es6: true,
24 | },
25 | rules: {
26 | 'prettier/prettier': ['error', prettierOptions],
27 | 'arrow-body-style': [2, 'as-needed'],
28 | 'class-methods-use-this': 0,
29 | 'import/imports-first': 0,
30 | 'import/newline-after-import': 0,
31 | 'import/no-dynamic-require': 0,
32 | 'import/no-extraneous-dependencies': 0,
33 | 'import/no-named-as-default': 0,
34 | 'import/no-unresolved': 2,
35 | 'import/no-webpack-loader-syntax': 0,
36 | 'import/prefer-default-export': 0,
37 | indent: [
38 | 2,
39 | 2,
40 | {
41 | SwitchCase: 1,
42 | },
43 | ],
44 | 'jsx-a11y/aria-props': 2,
45 | 'jsx-a11y/heading-has-content': 0,
46 | 'jsx-a11y/label-has-associated-control': [
47 | 2,
48 | {
49 | // NOTE: If this error triggers, either disable it or add
50 | // your custom components, labels and attributes via these options
51 | // See https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/label-has-associated-control.md
52 | controlComponents: ['Input'],
53 | },
54 | ],
55 | 'jsx-a11y/label-has-for': 0,
56 | 'jsx-a11y/mouse-events-have-key-events': 2,
57 | 'jsx-a11y/role-has-required-aria-props': 2,
58 | 'jsx-a11y/role-supports-aria-props': 2,
59 | 'max-len': 0,
60 | 'newline-per-chained-call': 0,
61 | 'no-confusing-arrow': 0,
62 | 'no-console': 1,
63 | 'no-unused-vars': 2,
64 | 'no-use-before-define': 0,
65 | 'prefer-template': 2,
66 | 'react/destructuring-assignment': 0,
67 | 'react-hooks/rules-of-hooks': 'error',
68 | 'react/jsx-closing-tag-location': 0,
69 | 'react/forbid-prop-types': 0,
70 | 'react/jsx-first-prop-new-line': [2, 'multiline'],
71 | 'react/jsx-filename-extension': 0,
72 | 'react/jsx-no-target-blank': 0,
73 | 'react/jsx-uses-vars': 2,
74 | 'react/require-default-props': 0,
75 | 'react/require-extension': 0,
76 | 'react/self-closing-comp': 0,
77 | 'react/sort-comp': 0,
78 | 'require-yield': 0,
79 | 'no-underscore-dangle': [
80 | 'error',
81 | { allow: ['__REDUX_DEVTOOLS_EXTENSION_COMPOSE__'] },
82 | ],
83 | },
84 | settings: {
85 | 'import/resolver': {
86 | webpack: {
87 | config: 'webpack/webpack.prod.js',
88 | },
89 | },
90 | },
91 | };
92 |
--------------------------------------------------------------------------------
/example/.eslintrc.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const { resolve } = require('path');
3 |
4 | const prettierOptions = JSON.parse(
5 | fs.readFileSync(resolve(__dirname, '.prettierrc'), 'utf8'),
6 | );
7 |
8 | module.exports = {
9 | parser: 'babel-eslint',
10 | extends: ['airbnb', 'prettier', 'prettier/react'],
11 | plugins: ['prettier', 'react', 'react-hooks', 'jsx-a11y'],
12 | parserOptions: {
13 | ecmaVersion: 6,
14 | sourceType: 'module',
15 | ecmaFeatures: {
16 | jsx: true,
17 | },
18 | },
19 | env: {
20 | jest: true,
21 | browser: true,
22 | node: true,
23 | es6: true,
24 | },
25 | rules: {
26 | 'prettier/prettier': ['error', prettierOptions],
27 | 'arrow-body-style': [2, 'as-needed'],
28 | 'class-methods-use-this': 0,
29 | 'import/imports-first': 0,
30 | 'import/newline-after-import': 0,
31 | 'import/no-dynamic-require': 0,
32 | 'import/no-extraneous-dependencies': 0,
33 | 'import/no-named-as-default': 0,
34 | 'import/no-unresolved': 2,
35 | 'import/no-webpack-loader-syntax': 0,
36 | 'import/prefer-default-export': 0,
37 | indent: [
38 | 2,
39 | 2,
40 | {
41 | SwitchCase: 1,
42 | },
43 | ],
44 | 'jsx-a11y/aria-props': 2,
45 | 'jsx-a11y/heading-has-content': 0,
46 | 'jsx-a11y/label-has-associated-control': [
47 | 2,
48 | {
49 | // NOTE: If this error triggers, either disable it or add
50 | // your custom components, labels and attributes via these options
51 | // See https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/label-has-associated-control.md
52 | controlComponents: ['Input'],
53 | },
54 | ],
55 | 'jsx-a11y/label-has-for': 0,
56 | 'jsx-a11y/mouse-events-have-key-events': 2,
57 | 'jsx-a11y/role-has-required-aria-props': 2,
58 | 'jsx-a11y/role-supports-aria-props': 2,
59 | 'max-len': 0,
60 | 'newline-per-chained-call': 0,
61 | 'no-confusing-arrow': 0,
62 | 'no-console': 1,
63 | 'no-unused-vars': 2,
64 | 'no-use-before-define': 0,
65 | 'prefer-template': 2,
66 | 'react/destructuring-assignment': 0,
67 | 'react-hooks/rules-of-hooks': 'error',
68 | 'react/jsx-closing-tag-location': 0,
69 | 'react/forbid-prop-types': 0,
70 | 'react/jsx-first-prop-new-line': [2, 'multiline'],
71 | 'react/jsx-filename-extension': 0,
72 | 'react/jsx-no-target-blank': 0,
73 | 'react/jsx-uses-vars': 2,
74 | 'react/require-default-props': 0,
75 | 'react/require-extension': 0,
76 | 'react/self-closing-comp': 0,
77 | 'react/sort-comp': 0,
78 | 'require-yield': 0,
79 | 'no-underscore-dangle': [
80 | 'error',
81 | { allow: ['__REDUX_DEVTOOLS_EXTENSION_COMPOSE__'] },
82 | ],
83 | },
84 | settings: {
85 | 'import/resolver': {
86 | webpack: {
87 | config: 'webpack/webpack.prod.js',
88 | },
89 | },
90 | },
91 | };
92 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-fivem",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "cross-env NODE_ENV=production webpack --config webpack/webpack.prod.js --color -p --progress --hide-modules --display-optimization-bailout",
8 | "build:clean": "rimraf ./build",
9 | "start": "cross-env NODE_ENV=development webpack-dev-server --config webpack/webpack.dev.js --open",
10 | "lint": "eslint src/**/**.js",
11 | "lint:fix": "eslint src/**/**.js --fix",
12 | "lint:staged": "lint-staged",
13 | "test": "echo \"Error: no test specified\" && exit 1",
14 | "prettify": "prettier --write **/*.js"
15 | },
16 | "lint-staged": {
17 | "*.js": [
18 | "npm run lint:fix",
19 | "git add --force"
20 | ],
21 | "*.json": [
22 | "prettier --write",
23 | "git add --force"
24 | ]
25 | },
26 | "pre-commit": "lint:staged",
27 | "repository": {
28 | "type": "git",
29 | "url": "git+https://github.com/2277/react-fivem.git"
30 | },
31 | "keywords": [
32 | "fivem",
33 | "react"
34 | ],
35 | "author": "2277",
36 | "license": "MIT",
37 | "bugs": {
38 | "url": "https://github.com/2277/react-fivem/issues"
39 | },
40 | "homepage": "https://github.com/2277/react-fivem#readme",
41 | "dependencies": {
42 | "@babel/polyfill": "^7.4.4",
43 | "cross-env": "^5.2.0",
44 | "prop-types": "^15.7.2",
45 | "react": "^16.8.6",
46 | "react-dom": "^16.8.6",
47 | "react-redux": "^7.0.3",
48 | "redux": "^4.0.1",
49 | "styled-components": "^4.2.0"
50 | },
51 | "devDependencies": {
52 | "@babel/cli": "^7.4.4",
53 | "@babel/core": "^7.4.5",
54 | "@babel/plugin-proposal-class-properties": "^7.4.4",
55 | "@babel/plugin-syntax-dynamic-import": "^7.2.0",
56 | "@babel/preset-env": "^7.4.5",
57 | "@babel/preset-react": "^7.0.0",
58 | "babel-eslint": "^10.0.1",
59 | "babel-loader": "^8.0.6",
60 | "babel-plugin-styled-components": "^1.10.0",
61 | "babel-plugin-transform-react-remove-prop-types": "^0.4.24",
62 | "css-loader": "^2.1.1",
63 | "eslint": "^5.16.0",
64 | "eslint-config-airbnb": "^17.1.0",
65 | "eslint-config-prettier": "^4.3.0",
66 | "eslint-import-resolver-webpack": "^0.11.1",
67 | "eslint-loader": "^2.1.2",
68 | "eslint-plugin-babel": "^5.3.0",
69 | "eslint-plugin-import": "^2.17.3",
70 | "eslint-plugin-jsx-a11y": "^6.2.1",
71 | "eslint-plugin-prettier": "^3.1.0",
72 | "eslint-plugin-react": "^7.13.0",
73 | "eslint-plugin-react-hooks": "^1.6.0",
74 | "file-loader": "^3.0.1",
75 | "html-loader": "^0.5.5",
76 | "html-webpack-plugin": "^3.2.0",
77 | "ifdef-loader": "^2.1.4",
78 | "image-webpack-loader": "^4.6.0",
79 | "lint-staged": "^8.1.7",
80 | "prettier": "^1.17.1",
81 | "rimraf": "^2.6.3",
82 | "style-loader": "^0.23.1",
83 | "svg-url-loader": "^2.3.2",
84 | "terser-webpack-plugin": "^1.3.0",
85 | "url-loader": "^1.1.2",
86 | "webpack": "^4.32.2",
87 | "webpack-cli": "^3.3.11",
88 | "webpack-dev-server": "^3.11.0",
89 | "webpack-fivem-manifest": "git+https://github.com/2277/webpack-fivem-manifest.git"
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-fivem",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "cross-env NODE_ENV=production webpack --config webpack/webpack.prod.js --color -p --progress --hide-modules --display-optimization-bailout",
8 | "build:clean": "rimraf ./build",
9 | "start": "cross-env NODE_ENV=development webpack-dev-server --config webpack/webpack.dev.js --open",
10 | "lint": "eslint src/**/**.js",
11 | "lint:fix": "eslint src/**/**.js --fix",
12 | "lint:staged": "lint-staged",
13 | "test": "echo \"Error: no test specified\" && exit 1",
14 | "prettify": "prettier --write **/*.js"
15 | },
16 | "lint-staged": {
17 | "*.js": [
18 | "npm run lint:fix",
19 | "git add --force"
20 | ],
21 | "*.json": [
22 | "prettier --write",
23 | "git add --force"
24 | ]
25 | },
26 | "pre-commit": "lint:staged",
27 | "repository": {
28 | "type": "git",
29 | "url": "git+https://github.com/2277/react-fivem.git"
30 | },
31 | "keywords": [
32 | "fivem",
33 | "react"
34 | ],
35 | "author": "2277",
36 | "license": "MIT",
37 | "bugs": {
38 | "url": "https://github.com/2277/react-fivem/issues"
39 | },
40 | "homepage": "https://github.com/2277/react-fivem#readme",
41 | "dependencies": {
42 | "@babel/polyfill": "^7.4.4",
43 | "@rebass/grid": "^6.1.0",
44 | "cross-env": "^5.2.0",
45 | "prop-types": "^15.7.2",
46 | "react": "^16.8.6",
47 | "react-dom": "^16.8.6",
48 | "react-redux": "^7.0.3",
49 | "redux": "^4.0.1",
50 | "styled-components": "^4.2.0"
51 | },
52 | "devDependencies": {
53 | "@babel/cli": "^7.4.4",
54 | "@babel/core": "^7.4.5",
55 | "@babel/plugin-proposal-class-properties": "^7.4.4",
56 | "@babel/plugin-syntax-dynamic-import": "^7.2.0",
57 | "@babel/preset-env": "^7.4.5",
58 | "@babel/preset-react": "^7.0.0",
59 | "babel-eslint": "^10.0.1",
60 | "babel-loader": "^8.0.6",
61 | "babel-plugin-styled-components": "^1.10.0",
62 | "babel-plugin-transform-react-remove-prop-types": "^0.4.24",
63 | "css-loader": "^2.1.1",
64 | "eslint": "^5.16.0",
65 | "eslint-config-airbnb": "^17.1.0",
66 | "eslint-config-prettier": "^4.3.0",
67 | "eslint-import-resolver-webpack": "^0.11.1",
68 | "eslint-loader": "^2.1.2",
69 | "eslint-plugin-babel": "^5.3.0",
70 | "eslint-plugin-import": "^2.17.3",
71 | "eslint-plugin-jsx-a11y": "^6.2.1",
72 | "eslint-plugin-prettier": "^3.1.0",
73 | "eslint-plugin-react": "^7.13.0",
74 | "eslint-plugin-react-hooks": "^1.6.0",
75 | "file-loader": "^3.0.1",
76 | "html-loader": "^0.5.5",
77 | "html-webpack-plugin": "^3.2.0",
78 | "ifdef-loader": "^2.1.4",
79 | "image-webpack-loader": "^4.6.0",
80 | "lint-staged": "^8.1.7",
81 | "prettier": "^1.17.1",
82 | "rimraf": "^2.6.3",
83 | "style-loader": "^0.23.1",
84 | "svg-url-loader": "^2.3.2",
85 | "terser-webpack-plugin": "^1.3.0",
86 | "url-loader": "^1.1.2",
87 | "webpack": "^4.32.2",
88 | "webpack-cli": "^3.3.11",
89 | "webpack-dev-server": "^3.11.0",
90 | "webpack-fivem-manifest": "git+https://github.com/2277/webpack-fivem-manifest.git"
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/webpack/webpack.common.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 |
4 | module.exports = options => ({
5 | mode: options.mode,
6 | entry: options.entry,
7 | output: {
8 | path: path.resolve(process.cwd(), 'html'),
9 | filename: '[name].js',
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.jsx?$/, // Transform all .js and .jsx files required somewhere with Babel
15 | exclude: /node_modules/,
16 | use: {
17 | loader: 'babel-loader',
18 | },
19 | },
20 | {
21 | // Preprocess our own .css files
22 | // This is the place to add your own loaders (e.g. sass/less etc.)
23 | // for a list of loaders, see https://webpack.js.org/loaders/#styling
24 | test: /\.css$/,
25 | exclude: /node_modules/,
26 | use: ['style-loader', 'css-loader'],
27 | },
28 | {
29 | // Preprocess 3rd party .css files located in node_modules
30 | test: /\.css$/,
31 | include: /node_modules/,
32 | use: ['style-loader', 'css-loader'],
33 | },
34 | {
35 | test: /\.(eot|otf|ttf|woff|woff2)$/,
36 | use: 'file-loader',
37 | },
38 | {
39 | test: /\.svg$/,
40 | use: [
41 | {
42 | loader: 'svg-url-loader',
43 | options: {
44 | // Inline files smaller than 10 kB
45 | limit: 10 * 1024,
46 | noquotes: true,
47 | },
48 | },
49 | ],
50 | },
51 | {
52 | test: /\.(jpg|png|gif)$/,
53 | use: [
54 | {
55 | loader: 'url-loader',
56 | options: {
57 | // Inline files smaller than 10 kB
58 | limit: 10 * 1024,
59 | },
60 | },
61 | {
62 | loader: 'image-webpack-loader',
63 | options: {
64 | mozjpeg: {
65 | enabled: false,
66 | // NOTE: mozjpeg is disabled as it causes errors in some Linux environments
67 | // Try enabling it in your environment by switching the config to:
68 | // enabled: true,
69 | // progressive: true,
70 | },
71 | gifsicle: {
72 | interlaced: false,
73 | },
74 | optipng: {
75 | optimizationLevel: 7,
76 | },
77 | pngquant: {
78 | quality: '65-90',
79 | speed: 4,
80 | },
81 | },
82 | },
83 | ],
84 | },
85 | {
86 | test: /\.html$/,
87 | use: 'html-loader',
88 | },
89 | {
90 | test: /\.(mp4|webm)$/,
91 | use: {
92 | loader: 'url-loader',
93 | options: {
94 | limit: 10000,
95 | },
96 | },
97 | },
98 | {
99 | test: /\.js$/,
100 | exclude: /node_modules/,
101 | use: [
102 | {
103 | loader: `ifdef-loader`,
104 | options: {
105 | DEBUG: options.mode !== 'production',
106 | version: 3,
107 | 'ifdef-verbose': true, // add this for verbose output
108 | 'ifdef-triple-slash': true, // add this to use double slash comment instead of default triple slash
109 | },
110 | },
111 | ],
112 | },
113 | ],
114 | },
115 | plugins: options.plugins.concat([
116 | // Always expose NODE_ENV to webpack, in order to use `process.env.NODE_ENV`
117 | // inside your code for any environment checks; Terser will automatically
118 | // drop any unreachable code.
119 | new webpack.EnvironmentPlugin({
120 | NODE_ENV: 'development',
121 | }),
122 | ]),
123 | resolve: {
124 | modules: ['src', 'node_modules'],
125 | extensions: ['.js', '.jsx', '.react.js'],
126 | },
127 | });
128 |
--------------------------------------------------------------------------------
/example/webpack/webpack.common.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 |
4 | module.exports = options => ({
5 | mode: options.mode,
6 | entry: options.entry,
7 | output: {
8 | path: path.resolve(process.cwd(), 'html'),
9 | filename: '[name].js',
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.jsx?$/, // Transform all .js and .jsx files required somewhere with Babel
15 | exclude: /node_modules/,
16 | use: {
17 | loader: 'babel-loader',
18 | },
19 | },
20 | {
21 | // Preprocess our own .css files
22 | // This is the place to add your own loaders (e.g. sass/less etc.)
23 | // for a list of loaders, see https://webpack.js.org/loaders/#styling
24 | test: /\.css$/,
25 | exclude: /node_modules/,
26 | use: ['style-loader', 'css-loader'],
27 | },
28 | {
29 | // Preprocess 3rd party .css files located in node_modules
30 | test: /\.css$/,
31 | include: /node_modules/,
32 | use: ['style-loader', 'css-loader'],
33 | },
34 | {
35 | test: /\.(eot|otf|ttf|woff|woff2)$/,
36 | use: 'file-loader',
37 | },
38 | {
39 | test: /\.svg$/,
40 | use: [
41 | {
42 | loader: 'svg-url-loader',
43 | options: {
44 | // Inline files smaller than 10 kB
45 | limit: 10 * 1024,
46 | noquotes: true,
47 | },
48 | },
49 | ],
50 | },
51 | {
52 | test: /\.(jpg|png|gif)$/,
53 | use: [
54 | {
55 | loader: 'url-loader',
56 | options: {
57 | // Inline files smaller than 10 kB
58 | limit: 10 * 1024,
59 | },
60 | },
61 | {
62 | loader: 'image-webpack-loader',
63 | options: {
64 | mozjpeg: {
65 | enabled: false,
66 | // NOTE: mozjpeg is disabled as it causes errors in some Linux environments
67 | // Try enabling it in your environment by switching the config to:
68 | // enabled: true,
69 | // progressive: true,
70 | },
71 | gifsicle: {
72 | interlaced: false,
73 | },
74 | optipng: {
75 | optimizationLevel: 7,
76 | },
77 | pngquant: {
78 | quality: '65-90',
79 | speed: 4,
80 | },
81 | },
82 | },
83 | ],
84 | },
85 | {
86 | test: /\.html$/,
87 | use: 'html-loader',
88 | },
89 | {
90 | test: /\.(mp4|webm)$/,
91 | use: {
92 | loader: 'url-loader',
93 | options: {
94 | limit: 10000,
95 | },
96 | },
97 | },
98 | {
99 | test: /\.js$/,
100 | exclude: /node_modules/,
101 | use: [
102 | {
103 | loader: `ifdef-loader`,
104 | options: {
105 | DEBUG: options.mode !== 'production',
106 | version: 3,
107 | 'ifdef-verbose': true, // add this for verbose output
108 | 'ifdef-triple-slash': true, // add this to use double slash comment instead of default triple slash
109 | },
110 | },
111 | ],
112 | },
113 | ],
114 | },
115 | plugins: options.plugins.concat([
116 | // Always expose NODE_ENV to webpack, in order to use `process.env.NODE_ENV`
117 | // inside your code for any environment checks; Terser will automatically
118 | // drop any unreachable code.
119 | new webpack.EnvironmentPlugin({
120 | NODE_ENV: 'development',
121 | }),
122 | ]),
123 | resolve: {
124 | modules: ['src', 'node_modules'],
125 | extensions: ['.js', '.jsx', '.react.js'],
126 | },
127 | });
128 |
--------------------------------------------------------------------------------
/example/client.lua:
--------------------------------------------------------------------------------
1 | -- Copyright © Vespura 2018
2 | -- https://github.com/TomGrobbe/JoinTransition/blob/master
3 |
4 | -- Arboratory character data. Likely fetched from a database.
5 | Characters = {
6 | {
7 | id = 1,
8 | forename = "Joe",
9 | surname = "Bloggs",
10 | description = "Recently signed a Mixer contract.",
11 | balance = 1000,
12 | position = {x = -1038.121, y = -2738.279, z = 20.16929} -- Los Santos International Airport
13 | }, {
14 | id = 2,
15 | forename = "John",
16 | surname = "Everyman",
17 | description = "Author of Roleplaying 101.",
18 | balance = 20320732,
19 | position = {x = 892.55, y = -182.25, z = 73.72} -- Downtown Cab Co.
20 | }
21 | }
22 |
23 | ------- Configurable options -------
24 |
25 | -- set the opacity of the clouds
26 | local cloudOpacity = 0.01 -- (default: 0.01)
27 |
28 | -- setting this to false will NOT mute the sound as soon as the game loads
29 | -- (you will hear background noises while on the loading screen, so not recommended)
30 | local muteSound = true -- (default: true)
31 |
32 | -- Register NUI callback
33 | RegisterNUICallback('SELECT_CHARACTER', function(data, cb)
34 | if Characters[data.id] then
35 | local pos = Characters[data.id].position
36 | -- Set player's location
37 | SetEntityCoords(PlayerPedId(), pos.x, pos.y, pos.z, false, false, false,
38 | true)
39 | -- Hide CharacterSelect NUI
40 | SendNUIMessage({type = "CHARACTERS_HIDE"})
41 | -- Remove NUI Focus
42 | SetNuiFocus(false, false)
43 |
44 | Citizen.CreateThread(function()
45 | local timer = GetGameTimer()
46 |
47 | -- Re-enable the sound in case it was muted.
48 | ToggleSound(false)
49 |
50 | SwitchInPlayer(PlayerPedId())
51 |
52 | ClearScreen()
53 |
54 | -- Wait for the player switch to be completed (state 12).
55 | while GetPlayerSwitchState() ~= 12 do
56 | Citizen.Wait(0)
57 | ClearScreen()
58 | end
59 | -- Reset the draw origin, just in case (allowing HUD elements to re-appear correctly)
60 | ClearDrawOrigin()
61 | end)
62 | end
63 | end)
64 |
65 | ------- Code -------
66 |
67 | -- Mutes or un-mutes the game's sound using a short fade in/out transition.
68 | function ToggleSound(state)
69 | if state then
70 | StartAudioScene("MP_LEADERBOARD_SCENE")
71 | else
72 | StopAudioScene("MP_LEADERBOARD_SCENE")
73 | end
74 | end
75 |
76 | -- Runs the initial setup whenever the script is loaded.
77 | function InitialSetup()
78 | -- Stopping the loading screen from automatically being dismissed.
79 | SetManualShutdownLoadingScreenNui(true)
80 | -- Disable sound (if configured)
81 | ToggleSound(muteSound)
82 | -- Switch out the player if it isn't already in a switch state.
83 | if not IsPlayerSwitchInProgress() then
84 | SwitchOutPlayer(PlayerPedId(), 0, 1)
85 | end
86 | end
87 |
88 | -- Hide radar & HUD, set cloud opacity, and use a hacky way of removing third party resource HUD elements.
89 | function ClearScreen()
90 | -- SetCloudHatOpacity(cloudOpacity)
91 | HideHudAndRadarThisFrame()
92 |
93 | -- nice hack to 'hide' HUD elements from other resources/scripts. kinda buggy though.
94 | SetDrawOrigin(0.0, 0.0, 0.0, 0)
95 | end
96 |
97 | -- Sometimes this gets called too early, but sometimes it's perfectly timed,
98 | -- we need this to be as early as possible, without it being TOO early, it's a gamble!
99 | InitialSetup()
100 |
101 | Citizen.CreateThread(function()
102 | SendNUIMessage({
103 | type = "RECIEVE_CHARACTERS",
104 | data = {characters = Characters}
105 | })
106 |
107 | -- In case it was called too early before, call it again just in case.
108 | InitialSetup()
109 |
110 | -- Wait for the switch cam to be in the sky in the 'waiting' state (5).
111 | while GetPlayerSwitchState() ~= 5 do
112 | Citizen.Wait(0)
113 | ClearScreen()
114 | end
115 |
116 | -- Shut down the game's loading screen (this is NOT the NUI loading screen).
117 | ShutdownLoadingScreen()
118 |
119 | ClearScreen()
120 | Citizen.Wait(0)
121 | DoScreenFadeOut(0)
122 |
123 | -- Shut down the NUI loading screen.
124 | ShutdownLoadingScreenNui()
125 |
126 | ClearScreen()
127 | Citizen.Wait(0)
128 | ClearScreen()
129 | DoScreenFadeIn(500)
130 | while not IsScreenFadedIn() do
131 | Citizen.Wait(0)
132 | ClearScreen()
133 | end
134 |
135 | -- Show CharacterSelect NUI
136 | SendNUIMessage({type = "CHARACTERS_SHOW"})
137 | SetNuiFocus(true, true)
138 | end)
139 |
--------------------------------------------------------------------------------