Skip to content

Commit

Permalink
feat: adds build page
Browse files Browse the repository at this point in the history
  • Loading branch information
AssisrMatheus committed Oct 29, 2021
1 parent cc5c805 commit e52c669
Show file tree
Hide file tree
Showing 22 changed files with 646 additions and 44 deletions.
9 changes: 9 additions & 0 deletions .env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Backend
NEXT_PUBLIC_GRAPHQL_URI="https://dev.api.marathon.perimetre.co/graphql"

# Unity
NEXT_PUBLIC_UNITY_PUBLIC_FOLDER="unity"
NEXT_PUBLIC_UNITY_BUILD_NAME="build-0.11"
NEXT_PUBLIC_UNITY_COMPANY_NAME="Marathon"
NEXT_PUBLIC_UNITY_PRODUCT_NAME="Space Planner"
NEXT_PUBLIC_UNITY_PRODUCT_VERSION="0.11"
8 changes: 4 additions & 4 deletions .lintstagedrc.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const path = require('path');

module.exports = {
'*': 'prettier --ignore-unknown --write',
'**/*.{js,jsx,ts,tsx,html,md,mdx}': (filenames) => {
'src/**/*': 'prettier --ignore-unknown --write',
'./src/**/*.+(js|jsx|ts|tsx)': (filenames) => {
const relativeFilenames = filenames
// Completely removes the current path(at __dirname) to an absolute path
// By replacing the initial part of the string to nothing
Expand All @@ -11,6 +11,6 @@ module.exports = {
return `next lint --fix --file ${relativeFilenames}`;
},
// Make sure to keep forward slash on stylelint
'/**/*.{js,jsx,ts,tsx,css}': 'stylelint --fix',
'**/*.ts?(x)': 'eslint --fix --plugin tsc --rule \'tsc/config: [2, {configFile: "./tsconfig.json"}]\''
'./src/**/*.+(css|scss)': 'stylelint --fix',
'src/**/*.ts?(x)': 'eslint --fix --plugin tsc --rule \'tsc/config: [2, {configFile: "./tsconfig.json"}]\''
};
5 changes: 0 additions & 5 deletions .prettierignore

This file was deleted.

16 changes: 0 additions & 16 deletions .stylelintrc.json

This file was deleted.

10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ ARG NEXT_PUBLIC_DEFAULT_LOCALE
ENV NEXT_PUBLIC_DEFAULT_LOCALE=${NEXT_PUBLIC_DEFAULT_LOCALE}
ARG NEXT_PUBLIC_GRAPHQL_URI
ENV NEXT_PUBLIC_GRAPHQL_URI=${NEXT_PUBLIC_GRAPHQL_URI}
ARG NEXT_PUBLIC_UNITY_PUBLIC_FOLDER
ENV NEXT_PUBLIC_UNITY_PUBLIC_FOLDER=${NEXT_PUBLIC_UNITY_PUBLIC_FOLDER}
ARG NEXT_PUBLIC_UNITY_BUILD_NAME
ENV NEXT_PUBLIC_UNITY_BUILD_NAME=${NEXT_PUBLIC_UNITY_BUILD_NAME}
ARG NEXT_PUBLIC_UNITY_COMPANY_NAME
ENV NEXT_PUBLIC_UNITY_COMPANY_NAME=${NEXT_PUBLIC_UNITY_COMPANY_NAME}
ARG NEXT_PUBLIC_UNITY_PRODUCT_NAME
ENV NEXT_PUBLIC_UNITY_PRODUCT_NAME=${NEXT_PUBLIC_UNITY_PRODUCT_NAME}
ARG NEXT_PUBLIC_UNITY_PRODUCT_VERSION
ENV NEXT_PUBLIC_UNITY_PRODUCT_VERSION=${NEXT_PUBLIC_UNITY_PRODUCT_VERSION}

WORKDIR /build

Expand Down
25 changes: 20 additions & 5 deletions graphql.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -33941,7 +33941,12 @@
"name": "deprecated",
"description": "Marks an element of a GraphQL schema as no longer supported.",
"isRepeatable": false,
"locations": ["ARGUMENT_DEFINITION", "ENUM_VALUE", "FIELD_DEFINITION", "INPUT_FIELD_DEFINITION"],
"locations": [
"ARGUMENT_DEFINITION",
"ENUM_VALUE",
"FIELD_DEFINITION",
"INPUT_FIELD_DEFINITION"
],
"args": [
{
"name": "reason",
Expand All @@ -33961,7 +33966,11 @@
"name": "include",
"description": "Directs the executor to include this field or fragment only when the `if` argument is true.",
"isRepeatable": false,
"locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"],
"locations": [
"FIELD",
"FRAGMENT_SPREAD",
"INLINE_FRAGMENT"
],
"args": [
{
"name": "if",
Expand All @@ -33985,7 +33994,11 @@
"name": "skip",
"description": "Directs the executor to skip this field or fragment when the `if` argument is true.",
"isRepeatable": false,
"locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"],
"locations": [
"FIELD",
"FRAGMENT_SPREAD",
"INLINE_FRAGMENT"
],
"args": [
{
"name": "if",
Expand All @@ -34009,7 +34022,9 @@
"name": "specifiedBy",
"description": "Exposes a URL that specifies the behaviour of this scalar.",
"isRepeatable": false,
"locations": ["SCALAR"],
"locations": [
"SCALAR"
],
"args": [
{
"name": "url",
Expand All @@ -34031,4 +34046,4 @@
}
]
}
}
}
7 changes: 6 additions & 1 deletion locales/en.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
{
"locale": "en"
"locale": "en",
"title": "Space Planner",
"description": "Marathon space planner",
"build": {
"title": "Build"
}
}
15 changes: 15 additions & 0 deletions src/components/Elements/DefaultPageTitle/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Head from 'next/head';
import React from 'react';
import { useIntl } from 'react-intl';

const DefaultPageTitle: React.FC = () => {
const intl = useIntl();
return (
<Head>
<title>{intl.formatMessage({ id: 'title' })}</title>
<meta name="description" content={intl.formatMessage({ id: 'description' })} />
</Head>
);
};

export default DefaultPageTitle;
153 changes: 153 additions & 0 deletions src/components/Elements/UnityPlayer/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Explicitly disable because we're dealing with unity objects and we don't have their type
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
import classnames from 'classnames';

import Script from 'next/script';
import env from '../../../env';
import { useUnityPlayerContext } from '../../Providers/UnityPlayerProvider';

export type UnityPlayerRef = {
sendMessage: (gameObjectName: string, methodName: string, value: number | string) => void;
};

export type UnityPlayerProps = {
className?: string;
};

const UnityPlayer = forwardRef<UnityPlayerRef, UnityPlayerProps>(function UnityPlayer({ className }, ref) {
const { hasProvider, setErrorMessage, setLoadingProgress, state, setState, unityInstance } = useUnityPlayerContext();

if (!hasProvider) {
throw 'Called UnityPlayer. But no UnityPlayerProvider was found. Wrap your UnityPlayer with the UnityPlayerProvider component';
}

const { buildName, buildUrl, loaderUrl, companyName, productName, productVersion } = useMemo(() => {
const {
NEXT_PUBLIC_UNITY_BUILD_NAME,
NEXT_PUBLIC_UNITY_COMPANY_NAME,
NEXT_PUBLIC_UNITY_PRODUCT_NAME,
NEXT_PUBLIC_UNITY_PRODUCT_VERSION,
NEXT_PUBLIC_UNITY_PUBLIC_FOLDER
} = env();
// Unity folder inside /public folder.
const unityPublicServePath = NEXT_PUBLIC_UNITY_PUBLIC_FOLDER;
const buildName = NEXT_PUBLIC_UNITY_BUILD_NAME;

const buildUrl = `${unityPublicServePath}/Build`;
const loaderUrl = `${buildUrl}/${buildName}.loader.js`;

return {
unityPublicServePath,
buildName,
buildUrl,
loaderUrl,
companyName: NEXT_PUBLIC_UNITY_COMPANY_NAME,
productName: NEXT_PUBLIC_UNITY_PRODUCT_NAME,
productVersion: NEXT_PUBLIC_UNITY_PRODUCT_VERSION
};
}, []);

/*
* Initialization
* */
const [isScriptLoaded, setIsScriptLoaded] = useState(false);
const unityCanvas = useRef<HTMLCanvasElement>(null);

//! This has not been converted from plain js to react yet
// const showBanner = useCallback((msg, type) => {
// function updateBannerVisibility() {
// warningBanner.style.display = warningBanner.children.length ? 'block' : 'none';
// }
// var div = document.createElement('div');
// div.innerHTML = msg;
// warningBanner.appendChild(div);
// if (type == 'error') div.style = 'background: red; padding: 10px;';
// else {
// if (type == 'warning') div.style = 'background: yellow; padding: 10px;';
// setTimeout(function() {
// warningBanner.removeChild(div);
// updateBannerVisibility();
// }, 5000);
// }
// updateBannerVisibility();
// }, []);

useEffect(() => {
const setup = async () => {
if (isScriptLoaded && unityCanvas.current && state === 'initializing') {
// Initial unity config
const config: any = {
dataUrl: `${buildUrl}/${buildName}.data`,
frameworkUrl: `${buildUrl}/${buildName}.framework.js`,
codeUrl: `${buildUrl}/${buildName}.wasm`,
streamingAssetsUrl: 'StreamingAssets',
companyName,
productName,
productVersion
// showBanner: unityShowBanner,
// By default Unity keeps WebGL canvas render target size matched with
// the DOM size of the canvas element (scaled by window.devicePixelRatio)
// Set this to false if you want to decouple this synchronization from
// happening inside the engine, and you would instead like to size up
// the canvas DOM size and WebGL render target sizes yourself.
//matchWebGLToCanvasSize: false
};

// Test if mobile
if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
config.devicePixelRatio = 1;
}

unityCanvas.current.style.width = '100%';
unityCanvas.current.style.height = '100%';

// Let's setup unity
try {
// Copy the method created by the script
const createUnityInstance = (window as any).createUnityInstance;

setState('loading');

// Setup
const localUnityInstance = await createUnityInstance(unityCanvas.current, config, (progress: number) => {
setLoadingProgress(progress);
});

// Store unity instance for later use
unityInstance.current = localUnityInstance;

setState('complete');
} catch (error: any) {
setState('error');
setErrorMessage(error as string);
}
}
};

setup();
// We really only want this to run once
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isScriptLoaded]);

/*
* External ref
* */
useImperativeHandle(
ref,
() => ({
sendMessage: (gameObjectName, methodName, value) =>
unityInstance.current?.SendMessage(gameObjectName, methodName, value)
}),
[unityInstance]
);

return (
<div className={classnames('w-full h-full', className)}>
<canvas ref={unityCanvas} width={960} height={600} />
<Script src={loaderUrl} strategy="afterInteractive" onLoad={() => setIsScriptLoaded(true)} />
</div>
);
});

export default UnityPlayer;
91 changes: 91 additions & 0 deletions src/components/Templates/Build/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import Head from 'next/head';
import React, { useState } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import BuilderSidebar from '../../UI/BuilderSidebar';
import UnityPlayer from '../../Elements/UnityPlayer';
import Image from 'next/image';
import classnames from 'classnames';
import ProgressBar from '../../UI/ProgressBar';
import Spinner from '../../UI/Spinner';
import { UnityPlayerProvider, useUnityPlayerContext } from '../../Providers/UnityPlayerProvider';

const LoadingState: React.FC = () => {
const [imageLoaded, setImageLoaded] = useState(false);
const { loadingProgress } = useUnityPlayerContext();

return (
<div
className={classnames('w-full h-full flex flex-col justify-center items-center', {
'opacity-0 animate-fade-in': loadingProgress < 1, // Animate in when progress starts
'animate-fade-out': loadingProgress >= 1 // Fade out when loading is finished
})}
>
<div
className={classnames('h-28 w-28 relative translate-y-2 opacity-0', {
'animate-fade-into': imageLoaded // Animate up when image gets loaded
})}
>
<Image
layout="fill"
src="/images/logo.webp"
alt={'marathon'}
sizes="50vw"
objectFit="cover"
onLoadingComplete={() => setImageLoaded(true)}
/>
</div>
<div className="flex items-center justify-center gap-4 my-6">
<p className="text-2xl font-semibold uppercase text-mui-dark">
<FormattedMessage id="title" />
</p>
<Spinner className="h-6 w-6" />
</div>
<ProgressBar color="mui-color-mui-dark" className="max-w-xs" progress={loadingProgress * 100} />
</div>
);
};

type DrawerBuilderProps = {};

const DrawerBuilder: React.FC<DrawerBuilderProps> = () => {
const { loadingProgress, state } = useUnityPlayerContext();

return (
<div className="flex min-h-screen">
{/* Left sidebar, fixed width */}
<BuilderSidebar />
{/* Right section, takes remaining space(flex-grow) */}
<div className="flex-grow relative">
<UnityPlayer className={classnames('opacity-0', { 'animate-fade-in': loadingProgress >= 1 })} />
{/* Content on top of unity player */}
<div className="absolute inset-0 pointer-events-none">{state === 'loading' && <LoadingState />}</div>
</div>
</div>
);
};

type BuildTemplateProps = {};

const BuildTemplate: React.FC<BuildTemplateProps> = () => {
const intl = useIntl();

return (
<>
<Head>
{/*TODO: display project name*/}
<title>
{intl.formatMessage({ id: 'build.title' })} | {intl.formatMessage({ id: 'title' })}
</title>
{/*TODO: display project description*/}
{/*<meta name="description" content="Generated by create next app" />*/}
</Head>
<div id="build-template">
<UnityPlayerProvider>
<DrawerBuilder />
</UnityPlayerProvider>
</div>
</>
);
};

export default BuildTemplate;
Loading

0 comments on commit e52c669

Please sign in to comment.