Merge pull request #9 from dennisschoepf/timeline
This commit is contained in:
commit
5e96f1892c
19 changed files with 4250 additions and 74 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -3,3 +3,4 @@ node_modules
|
|||
dist
|
||||
.yarn
|
||||
pnp.js
|
||||
sourceproject
|
||||
|
|
|
|||
275
index.html
275
index.html
|
|
@ -7,6 +7,11 @@
|
|||
</head>
|
||||
<body>
|
||||
<div class="ui">
|
||||
<div id="score">
|
||||
<div id="score-found">0</div>
|
||||
<div id="score-divider">/</div>
|
||||
<div id="score-total">6</div>
|
||||
</div>
|
||||
<div id="companion">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="companion-shape" viewBox="0 0 80 80">
|
||||
<polygon fill="#A60303" points="40,0 68,12 80,40 68,68 40,80 12,68 0,40 12,12"></polygon>
|
||||
|
|
@ -24,11 +29,20 @@
|
|||
</div>
|
||||
<div id="info-message">
|
||||
<div id="info-message-header">
|
||||
<h2 id="info-message-headline">Test</h2>
|
||||
<img id="info-message-img" src="" />
|
||||
<div id="info-message-header-text">
|
||||
<h4 id="info-message-subheadline"></h4>
|
||||
<h2 id="info-message-headline">Test</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div id="info-message-contents">Test</div>
|
||||
<a id="info-message-link" href="" target="_blank">Check this out on Github</a>
|
||||
<div id="info-message-contents">
|
||||
<div id="info-message-contents-text"></div>
|
||||
<div id="info-message-contents-version"></div>
|
||||
<div id="info-message-contents-legacy"></div>
|
||||
<h4 id="info-message-content-commits-headline">Last Commits</h4>
|
||||
<div id="info-message-contents-commits"></div>
|
||||
</div>
|
||||
<a id="info-message-link" href="" target="_blank">Check this out in detail</a>
|
||||
<div id="info-message-close">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<path
|
||||
|
|
@ -38,8 +52,263 @@
|
|||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div id="intro">
|
||||
<div id="intro-step1">
|
||||
<h1>Hello there 👋,</h1>
|
||||
<p>
|
||||
thanks for participating in an interactive study for this research project. If there are
|
||||
any problems with this study do not hesitate to contact me via mail:
|
||||
<a href="mailto:me@dnsc.io">me@dnsc.io</a>
|
||||
</p>
|
||||
<p>
|
||||
You are participating in a study on Playful Experiences within the Onboarding Process of
|
||||
Software Development Projects. In the following you are going to be guided through an
|
||||
interactive visualization of a software project. Within that visualization you are going
|
||||
to be able to reveal certain parts of the project, that might be of value in an
|
||||
onboarding process. The visualizations are going to be accompanied by some questions on
|
||||
the visualization itself but also on the broader aspect of using game mechanics in
|
||||
software development. There is additional information on the study background and the
|
||||
study procedure in the next step.
|
||||
</p>
|
||||
<div id="intro-disclaimer">
|
||||
<h3>Disclaimer</h3>
|
||||
<p>
|
||||
Do not expect a real game experience within this study. This study aims to gather
|
||||
initial feedback on certain aspects of which information could be interesting to
|
||||
include within software development onboarding and how its presentation changes how it
|
||||
is perceived. The visualization of the in-game elements is therefore very abstract.
|
||||
The arguably more important part of this study are the answers to the questions you
|
||||
will be asked in the end, so please answer them carefully. Thanks in advance, let's
|
||||
start now!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="intro-step2">
|
||||
<h1>Information about data protection and declaration of consent</h1>
|
||||
<p>Dear participant,</p>
|
||||
<p>
|
||||
thank you for your interest in participating in this study on ”Playful Experiences in
|
||||
the Onboarding Process of Software Development Projects”. This study takes place as a
|
||||
part of my master thesis on “Onboarding in Software Development Projects as a Space of
|
||||
Play”. The thesis is written as a part of the new Joint Master "Human-Computer
|
||||
Interaction" of the FH Salzburg and the University of Salzburg. In order to use your
|
||||
personal data, we would need your written declaration of consent. Please, carefully read
|
||||
the following information.
|
||||
</p>
|
||||
<h3>General Study Information</h3>
|
||||
<p>
|
||||
The goal of this study is to research how software developers experience playful
|
||||
approaches on the onboarding process into new projects. The results of this study are
|
||||
going to contribute to the research body of both the software development field but also
|
||||
the field of “Play”. From the study results conclusions are going to be drawn about: The
|
||||
openness towards playful experiences in the software development field, possible hurdles
|
||||
on implementing such experiences, the general attitude of developers towards play in the
|
||||
field and the selection of what information to include in onboarding processes.
|
||||
</p>
|
||||
<h3>Overall study procedure</h3>
|
||||
<p>
|
||||
The participant will take part in a hybrid-study setting. Part of the study consists of
|
||||
going through an interactive visualization of an open-source software project and
|
||||
revealing parts of information within some parts of the project. The other part of the
|
||||
study consists of questions on the visualization and general questions on the
|
||||
intersection of Play and software development. The study can be accessed online and can
|
||||
be finished online without additional work on the participants side. Overall the
|
||||
procedure should take between 15 and 25 minutes.
|
||||
</p>
|
||||
<h3>Rights</h3>
|
||||
<p>
|
||||
The participation in this study is voluntary. You may withdraw or stop your
|
||||
participation at any time without incurring any consequences. Moreover, you can request
|
||||
information about the processed personal data at any time, even after the termination of
|
||||
the study (for further information see also Confidentiality and data processing). If you
|
||||
have any questions please contact the study leader.
|
||||
</p>
|
||||
<h3>Risks</h3>
|
||||
<p>
|
||||
You do not incur any risks by participating in this study. If you feel uncomfortable
|
||||
during the study you can cancel at any time without giving reasons and without incurring
|
||||
any consequences.
|
||||
</p>
|
||||
<h3>Confidentiality and Data Processing</h3>
|
||||
<p>
|
||||
Your data will only be processed within the aforementioned project if you give your
|
||||
written consent. The data is going to be processed via the Firebase platform
|
||||
(https://firebase.google.com/) on european servers. For additional information on their
|
||||
privacy and data protection guidelines visit their documentation at
|
||||
https://policies.google.com/u/0/privacy. The following data is going to be processed
|
||||
within the study:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
Demographic Information (Name, Age, Professional Background, Years of Experience,
|
||||
Gender)
|
||||
</li>
|
||||
<li>
|
||||
Your actions within the visualization (Timestamps of Clicks and Mouse moves together
|
||||
with the timestamps of certain events)
|
||||
</li>
|
||||
<li>Your answers to the questions that are part of the study</li>
|
||||
</ul>
|
||||
<p>
|
||||
After the end of the study, the data is going to be exported from Firebase and the
|
||||
underlying app instance is going to be deleted. This data is going to be kept strictly
|
||||
confidential and is not going to be passed on to further third parties. The data is
|
||||
going to be analyzed and then used within my master thesis. If you only want to be
|
||||
mentioned anonymously in my thesis please indicate so at the bottom. If you are doing
|
||||
that your name and age are going to be hidden in my thesis and your data is only going
|
||||
to be connected to a random ID.
|
||||
</p>
|
||||
<h3>Renumeration</h3>
|
||||
<p>You will not receive an expense allowance for participation.</p>
|
||||
<h3>Consent</h3>
|
||||
<p>
|
||||
I have carefully read and understood the declaration of consent. Any additional
|
||||
questions were answered to my satisfaction. My participation is voluntary, and I know
|
||||
that I can withdraw at any time, even without giving reasons, without incurring any
|
||||
disadvantages. By clicking on “Agree” below I agree to the processing of my personal
|
||||
data collected for this study and I am willing to participate.
|
||||
</p>
|
||||
<label for="intro-anonymous">
|
||||
<input name="intro-anonymous" type="checkbox" id="intro-anonymous" />
|
||||
I want to participate anonymously (the data is not going to be connected to your name)
|
||||
</label>
|
||||
</div>
|
||||
<div id="intro-step3">
|
||||
<h1>General Questions</h1>
|
||||
<p>Please provide some additional information about you below:</p>
|
||||
<label id="name-label" for="name">
|
||||
Your name
|
||||
<input type="text" id="intro-name" name="name" placeholder="e.g. Max" />
|
||||
</label>
|
||||
<label for="age">
|
||||
Your age
|
||||
<input type="number" id="intro-age" name="age" placeholder="e.g. 28" />
|
||||
</label>
|
||||
<label for="background">
|
||||
Your Professional Background
|
||||
<input
|
||||
type="text"
|
||||
id="intro-background"
|
||||
name="background"
|
||||
placeholder="e.g. Web Developer"
|
||||
/>
|
||||
</label>
|
||||
<label for="experience">
|
||||
Your Professional Experience
|
||||
<select
|
||||
type="text"
|
||||
id="intro-experience"
|
||||
name="experience"
|
||||
placeholder="e.g. Web Developer"
|
||||
>
|
||||
<option selected="true" default disabled>Choose an option ...</option>
|
||||
<option value="0-1">0-1 years</option>
|
||||
<option value="1-3">1-3 years</option>
|
||||
<option value="3-5">3-5 years</option>
|
||||
<option value="5-10">5-10 years</option>
|
||||
<option value="10+">10 or more years</option>
|
||||
</select>
|
||||
</label>
|
||||
<div id="intro-error">Please provide a value for all inputs</div>
|
||||
</div>
|
||||
<div id="intro-step4">
|
||||
<h1>The study itself</h1>
|
||||
<p>
|
||||
Now we're ready to dive into this study. After clicking on the button below you are
|
||||
dropped into a visualization of a software development project. This project is a
|
||||
monorepo, more specifically the
|
||||
<a href="https://github.com/ethereumjs/ethereumjs-monorepo">ethereumjs monorepo</a>. You
|
||||
are controlling a little player character with your mouse. The character consists of a
|
||||
head and tail. As soon as its head touches elements on the screen you are able to
|
||||
interact with them. That should be all you need for now, just drop in now and take a
|
||||
look at what is there.
|
||||
</p>
|
||||
<p>Have fun! 🤟</p>
|
||||
</div>
|
||||
<div id="intro-step5">
|
||||
<h1>Questions about this visualization sepcifically</h1>
|
||||
<p>
|
||||
After the project-specific "knowledge" questions in the last step I want to get your
|
||||
valuable input as a developer for the following questions:
|
||||
</p>
|
||||
<label for="k-1">
|
||||
1) Do you remember any of the contributors of this project? Please name them below
|
||||
<textarea id="k-1" name="k-1"></textarea>
|
||||
</label>
|
||||
</div>
|
||||
<div id="intro-step6">
|
||||
<h1>Questions about Play in Software Development</h1>
|
||||
<p>
|
||||
After the project-specific "knowledge" questions in the last step I want to get your
|
||||
valuable input as a developer for the following questions:
|
||||
</p>
|
||||
<label for="fb-1">
|
||||
1) Would you say, that you have learned something about the underlying project from
|
||||
going through this interactive visualization? If yes, what have you learned? If no, what
|
||||
was missing from the visualization in your opinion?
|
||||
<textarea id="fb-1" name="fb-1"></textarea>
|
||||
</label>
|
||||
<label for="fb-2">
|
||||
2) What was your overall experience going through this visualization? What did you like
|
||||
or did not like? Is there anything that stood out for you?
|
||||
<textarea id="fb-2" name="fb-2"></textarea>
|
||||
</label>
|
||||
<label for="fb-3">
|
||||
3) How did you experience the companion (Lower right)? Was it helpful or rather
|
||||
annoying?
|
||||
<textarea id="fb-3" name="fb-3"></textarea>
|
||||
</label>
|
||||
<label for="fb-4">
|
||||
4) Could you imagine yourself using a visualization or something similar on different
|
||||
projects to learn about them? If so, on which projects would you want to try it out? If
|
||||
not, what would you prefer instead, just going throught the files?
|
||||
<textarea id="fb-4" name="fb-4"></textarea>
|
||||
</label>
|
||||
<label for="fb-5">
|
||||
5) Have you felt like any information was missing on the things that were shown within
|
||||
the visualization?
|
||||
<textarea id="fb-5" name="fb-5"></textarea>
|
||||
</label>
|
||||
<label for="fb-6">
|
||||
6) Would you have liked to see additional information on the underlying project? If so,
|
||||
what kind of information and how would you have liked?
|
||||
<textarea id="fb-6" name="fb-6"></textarea>
|
||||
</label>
|
||||
<label for="fb-7">
|
||||
7) Do you have any additional ideas on how playful elements or game mechanics could be
|
||||
used within the onboarding phase of software development projects?
|
||||
<textarea id="fb-7" name="fb-7"></textarea>
|
||||
</label>
|
||||
<label for="fb-8">
|
||||
8) What is your general stance on using games/game mechanics or playful elements within
|
||||
software development?
|
||||
<textarea id="fb-8" name="fb-8"></textarea>
|
||||
</label>
|
||||
<label for="fb-9">
|
||||
9) Do you have any additional ideas on how playful elements or game mechanics could be
|
||||
used within the onboarding phase of software development projects? Any elements from
|
||||
games you play that you think could be reused when making yourself familiar with new
|
||||
projects?
|
||||
<textarea id="fb-9" name="fb-9"></textarea>
|
||||
</label>
|
||||
<label for="fb-10">
|
||||
10) Anything else you want to mention?
|
||||
<textarea id="fb-10" name="fb-10"></textarea>
|
||||
</label>
|
||||
</div>
|
||||
<div id="intro-step7">
|
||||
<h2>Thank you so much! 🙏</h2>
|
||||
<p>
|
||||
Thank you for your participation. If you have additional feedback please contact me:
|
||||
<a href="mailto:me@dnsc.io">me@dnsc.io</a>. You can now safely close the browser window.
|
||||
</p>
|
||||
</div>
|
||||
<button id="intro-button">Let's start!</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="backdrop"></div>
|
||||
<div id="comp-backdrop"></div>
|
||||
<div id="intro-backdrop"></div>
|
||||
<script src="./main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
11
main.ts
11
main.ts
|
|
@ -1,4 +1,5 @@
|
|||
import p5 from 'p5';
|
||||
import firebase from 'firebase/app';
|
||||
import { SCREEN_WIDTH, SCREEN_HEIGHT } from './src/constants/screen';
|
||||
import { DetailScene } from './src/scenes/DetailScene';
|
||||
import { OverviewScene } from './src/scenes/OverviewScene';
|
||||
|
|
@ -6,6 +7,8 @@ import { Scenes } from './src/scenes/scenes';
|
|||
import store from './src/store';
|
||||
import { Companion, CompanionState } from './src/ui/companion';
|
||||
import { InfoMessage } from './src/ui/info';
|
||||
import { Intro } from './src/ui/intro';
|
||||
import { Score } from './src/ui/score';
|
||||
|
||||
const sketch = (s: p5) => {
|
||||
// Scenes
|
||||
|
|
@ -16,8 +19,10 @@ const sketch = (s: p5) => {
|
|||
s.createCanvas(SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||
s.noCursor();
|
||||
|
||||
new Intro();
|
||||
new Companion();
|
||||
new InfoMessage();
|
||||
new Score();
|
||||
|
||||
overviewScene = new OverviewScene();
|
||||
detailScene = new DetailScene();
|
||||
|
|
@ -36,7 +41,11 @@ const sketch = (s: p5) => {
|
|||
s.mousePressed = () => {
|
||||
const { currentScene, companionState, infoMessageShown } = store.getState();
|
||||
|
||||
if (companionState !== CompanionState.ACTIVE || !infoMessageShown) {
|
||||
if (
|
||||
companionState !== CompanionState.ACTIVE &&
|
||||
!infoMessageShown &&
|
||||
store.getState().currentIntroStep === 0
|
||||
) {
|
||||
if (currentScene === Scenes.OVERVIEW) {
|
||||
overviewScene.onSceneClick();
|
||||
} else if (currentScene === Scenes.DETAIL) {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
1610
package-lock.json
generated
1610
package-lock.json
generated
File diff suppressed because it is too large
Load diff
12
package.json
12
package.json
|
|
@ -5,15 +5,23 @@
|
|||
"repository": "git@github.com:dennisschoepf/codewanderer.git",
|
||||
"author": "Dennis Schoepf <dennis@schoepf.co>",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "parcel index.html",
|
||||
"build": "parcel build index.html",
|
||||
"deploy": "./deploy.sh"
|
||||
"deploy": "./deploy.sh",
|
||||
"dev:meta": "nodemon --ignore '**/*.json' ./scripts/get-metadata.js",
|
||||
"generate": "node ./scripts/get-metadata.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@octokit/core": "^3.5.1",
|
||||
"@types/animejs": "^3.1.4",
|
||||
"@types/lodash": "^4.14.171",
|
||||
"@types/p5": "^1.3.0",
|
||||
"get-folder-size": "^3.1.0",
|
||||
"glob": "^7.1.7",
|
||||
"node-fetch": "^2.6.1",
|
||||
"nodemon": "^2.0.12",
|
||||
"parcel-bundler": "^1.12.5",
|
||||
"prettier": "^2.3.2",
|
||||
"sass": "^1.35.2",
|
||||
|
|
@ -21,7 +29,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"animejs": "^3.2.1",
|
||||
"firebase": "^8.8.1",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.1",
|
||||
"p5": "^1.4.0",
|
||||
"rxjs": "^7.2.0",
|
||||
"zustand": "^3.5.7"
|
||||
|
|
|
|||
66
scripts/get-metadata.js
Normal file
66
scripts/get-metadata.js
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import { resolve } from 'path';
|
||||
import { readdir, writeFile } from 'fs/promises';
|
||||
import getItemSize from 'get-folder-size';
|
||||
import {
|
||||
getLegaciesForSubproject,
|
||||
getLinksForSubproject,
|
||||
getPackagesForSubproject,
|
||||
getProjectContributors,
|
||||
} from './helpers.js';
|
||||
|
||||
const __dirname = resolve();
|
||||
export const PROJECT_PATH = resolve(__dirname, 'sourceproject/ethereumjs-monorepo');
|
||||
export const SUBPACKAGE_PATH = resolve(PROJECT_PATH, 'packages');
|
||||
|
||||
const main = async () => {
|
||||
const subprojectPaths = await readdir(SUBPACKAGE_PATH);
|
||||
const subprojectOverviewData = await Promise.all(
|
||||
subprojectPaths.map(async (subprojectPath) => {
|
||||
const size = await getItemSize.loose(resolve(PROJECT_PATH, SUBPACKAGE_PATH, subprojectPath));
|
||||
|
||||
return {
|
||||
name: subprojectPath,
|
||||
path: `packages/${subprojectPath}`,
|
||||
filePath: resolve(PROJECT_PATH, SUBPACKAGE_PATH, subprojectPath),
|
||||
size: Math.floor(size * 0.001),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const subprojects = subprojectOverviewData.filter(
|
||||
(subprojectData) => subprojectData.name !== 'vm' && subprojectData.name !== 'ethereum-tests'
|
||||
);
|
||||
|
||||
const projectContributors = await getProjectContributors();
|
||||
|
||||
const subprojectsWithRevealables = await Promise.all(
|
||||
subprojects.map(async (subproject, i) => {
|
||||
const packages = await getPackagesForSubproject(subproject);
|
||||
const links = await getLinksForSubproject(subproject);
|
||||
const legacies = await getLegaciesForSubproject(subproject);
|
||||
const contributors = [
|
||||
projectContributors[0 + i],
|
||||
projectContributors[10 + i],
|
||||
projectContributors[20 + i],
|
||||
];
|
||||
|
||||
return {
|
||||
...subproject,
|
||||
links,
|
||||
revealables: [...packages, ...contributors, ...legacies],
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const jsonToWrite = JSON.stringify(
|
||||
{
|
||||
subprojects: subprojectsWithRevealables,
|
||||
},
|
||||
null,
|
||||
2
|
||||
);
|
||||
|
||||
writeFile(resolve(__dirname, 'metadata/project.json'), jsonToWrite);
|
||||
};
|
||||
|
||||
main();
|
||||
154
scripts/helpers.js
Normal file
154
scripts/helpers.js
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
import { resolve, basename } from 'path';
|
||||
import { readFile } from 'fs/promises';
|
||||
import { promisify } from 'util';
|
||||
import { Octokit } from '@octokit/core';
|
||||
import child_process from 'child_process';
|
||||
import nodeGlob from 'glob';
|
||||
import { PROJECT_PATH, SUBPACKAGE_PATH } from './get-metadata.js';
|
||||
import getItemSize from 'get-folder-size';
|
||||
|
||||
const exec = promisify(child_process.exec);
|
||||
const glob = promisify(nodeGlob);
|
||||
|
||||
const octokit = new Octokit({
|
||||
auth: '',
|
||||
});
|
||||
|
||||
export async function getLegaciesForSubproject(subproject) {
|
||||
// Get all paths to project files
|
||||
const files = await glob(resolve(SUBPACKAGE_PATH, subproject.name, '**/*.ts'));
|
||||
const filteredFilePaths = files.filter(
|
||||
(filePath) =>
|
||||
!filePath.includes('spec.') && !filePath.includes('/test/') && !filePath.includes('@types')
|
||||
);
|
||||
|
||||
// Get line counts for files
|
||||
const filesWithWordCounts = await Promise.all(
|
||||
filteredFilePaths.map(async (filteredFilePath) => {
|
||||
const { stdout } = await exec(`wc -l < ${filteredFilePath}`);
|
||||
|
||||
return {
|
||||
path: filteredFilePath,
|
||||
count: Number(stdout.replace('\n', '')),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const largestFiles = filesWithWordCounts
|
||||
.sort((fwcA, fwcB) => fwcB.count - fwcA.count)
|
||||
.slice(0, 2);
|
||||
|
||||
// Transform to legacy object
|
||||
const legacies = await Promise.all(
|
||||
largestFiles.map(async (largeFile) => await createLegacy(largeFile))
|
||||
);
|
||||
|
||||
// Return 2 highest line counts
|
||||
return legacies;
|
||||
}
|
||||
|
||||
export async function getProjectContributors() {
|
||||
const contribs = await octokit.request('/repos/ethereumjs/ethereumjs-monorepo/contributors');
|
||||
const contributors = await Promise.all(
|
||||
contribs.data.map(async (contrib) => await createContributor(contrib))
|
||||
);
|
||||
|
||||
return contributors;
|
||||
}
|
||||
|
||||
export async function getLinksForSubproject(subproject) {
|
||||
const subprojectPackageJson = await readFile(
|
||||
resolve(subproject.filePath, 'package.json'),
|
||||
'utf8'
|
||||
);
|
||||
const { dependencies } = JSON.parse(subprojectPackageJson);
|
||||
const links = Object.keys(dependencies).filter((dependency) =>
|
||||
dependency.includes('@ethereumjs')
|
||||
);
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
export async function getPackagesForSubproject(subproject) {
|
||||
const subprojectPackageJson = await readFile(
|
||||
resolve(subproject.filePath, 'package.json'),
|
||||
'utf8'
|
||||
);
|
||||
const { dependencies } = JSON.parse(subprojectPackageJson);
|
||||
const relevantDependencies = Object.keys(dependencies)
|
||||
.filter((dependency) => !dependency.includes('ethereumjs') && !dependency.includes('@types'))
|
||||
.slice(0, 3)
|
||||
.map((dependencyKey) => ({
|
||||
name: dependencyKey,
|
||||
version: dependencies[dependencyKey],
|
||||
}));
|
||||
|
||||
const formattedDependencies = relevantDependencies.map((dep) => createPackage(dep));
|
||||
|
||||
return formattedDependencies;
|
||||
}
|
||||
|
||||
const createContributor = async (contrib) => {
|
||||
if (!contrib) return;
|
||||
|
||||
let commits;
|
||||
|
||||
try {
|
||||
const rawCommits = await octokit.request(`/repos/ethereumjs/ethereumjs-monorepo/commits`, {
|
||||
author: contrib.login,
|
||||
});
|
||||
|
||||
const lastRawCommits = rawCommits.data.slice(0, 3);
|
||||
|
||||
commits = lastRawCommits.map((commit) => ({
|
||||
url: commit.html_url,
|
||||
message: commit.commit.message,
|
||||
time: commit.commit.author.date,
|
||||
}));
|
||||
} catch (e) {}
|
||||
|
||||
return {
|
||||
type: 'CONTRIBUTOR',
|
||||
name: contrib.login,
|
||||
url: contrib.html_url,
|
||||
size: contrib.contributions,
|
||||
imageUrl: contrib.avatar_url,
|
||||
contents:
|
||||
'This is one of the main contributors to the overall repository and this part of the repository specifically. Below you can see the contributors latest commits and the button on the right will take you straight to the respective Github profile.',
|
||||
commits,
|
||||
};
|
||||
};
|
||||
|
||||
const createPackage = ({ name, version }) => {
|
||||
const path = 'path.to.package.json';
|
||||
const size = Math.floor(Math.random() * 250) + 50;
|
||||
|
||||
return {
|
||||
type: 'PACKAGE',
|
||||
name,
|
||||
path,
|
||||
version,
|
||||
size,
|
||||
contents: `This package is used throughout this part of the repository. Below you can see the version that is installed currently. If you want to take a look at the package documentation, past versions and much more, click the button in the lower right corner. It'll lead you to the npmjs website of the package.`,
|
||||
url: `https://www.npmjs.com/package/${name}`,
|
||||
};
|
||||
};
|
||||
|
||||
const createLegacy = async ({ path, count }) => {
|
||||
const fileSize = await getItemSize(path);
|
||||
const rawFileContents = await readFile(path, 'utf-8');
|
||||
const projectPath = path.replace(PROJECT_PATH, '');
|
||||
|
||||
const fileContents = rawFileContents.replace(/\r\n/g, '\n').split('\n').slice(30, 40).join('\n');
|
||||
|
||||
return {
|
||||
type: 'LEGACY',
|
||||
name: basename(projectPath),
|
||||
path: projectPath,
|
||||
size: count,
|
||||
contents: `This file seems pretty big compared to the other files in this part of the project. It's ${fileSize.size} bytes and has ${count} lines of code. That could make it hard to read for new contributors and people looking at the repository. You might want to look into a refactoring in order to keep it at a more readable size. What you could also do is contact one of the contributors to help in understanding what's going on. They must be hiding somewhere in this package as well, try to reveal them to access their Github profiles.`,
|
||||
fileContents,
|
||||
fileSize: fileSize.size,
|
||||
url: `https://github.com/ethereumjs/ethereumjs-monorepo/blob/master${projectPath}`,
|
||||
};
|
||||
};
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { mp5 } from '../main';
|
||||
import { SCREEN_HEIGHT, SCREEN_WIDTH } from './constants/screen';
|
||||
import { Edge } from './sketchObjects/Edge';
|
||||
import { RevealableInterface, RevealableTypes } from './sketchObjects/Revealable';
|
||||
import { Revealable, RevealableInterface, RevealableTypes } from './sketchObjects/Revealable';
|
||||
import { Coordinates, JSONSubproject, SubProject } from './types';
|
||||
|
||||
export function getEdgeDimensions({ size }: JSONSubproject): number {
|
||||
|
|
@ -11,8 +11,8 @@ export function getEdgeDimensions({ size }: JSONSubproject): number {
|
|||
|
||||
export function generateRandomEdgeCoordinates(): Coordinates {
|
||||
return {
|
||||
x: mp5.random(150, SCREEN_WIDTH - 150),
|
||||
y: mp5.random(150, SCREEN_HEIGHT - 150),
|
||||
x: mp5.random(200, SCREEN_WIDTH - 200),
|
||||
y: mp5.random(200, SCREEN_HEIGHT - 200),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -90,17 +90,37 @@ export function getRevealablesforSubproject(
|
|||
}));
|
||||
}
|
||||
|
||||
export function generateRevealableCoords(): Coordinates[] {
|
||||
const areaWidth = mp5.width / 3;
|
||||
const rowHeight = mp5.height / 2;
|
||||
export function generateRevealableCoords(existingRevealables: Revealable[]): Coordinates {
|
||||
let newCoords: Coordinates;
|
||||
const existingCoordinates = existingRevealables.map(({ area }) => ({ x: area.x, y: area.y }));
|
||||
|
||||
// Max. 6 revealables one in each area
|
||||
return [
|
||||
{ x: mp5.random(25, areaWidth), y: mp5.random(25, rowHeight) },
|
||||
{ x: mp5.random(areaWidth, areaWidth * 2), y: mp5.random(25, rowHeight) },
|
||||
{ x: mp5.random(areaWidth * 2, areaWidth * 3), y: mp5.random(25, rowHeight) },
|
||||
{ x: mp5.random(25, areaWidth), y: mp5.random(rowHeight, rowHeight * 2) },
|
||||
{ x: mp5.random(areaWidth, areaWidth * 2), y: mp5.random(rowHeight, rowHeight * 2) },
|
||||
{ x: mp5.random(areaWidth * 2, areaWidth * 3), y: mp5.random(rowHeight, rowHeight * 2) },
|
||||
];
|
||||
if (existingRevealables.length === 0) {
|
||||
return generateRandomEdgeCoordinates();
|
||||
} else {
|
||||
do {
|
||||
newCoords = generateRandomEdgeCoordinates();
|
||||
} while (isColliding(newCoords, existingCoordinates));
|
||||
}
|
||||
|
||||
return newCoords;
|
||||
}
|
||||
|
||||
export function generateRevealables(revealables: RevealableInterface[]): Revealable[] {
|
||||
let revObjs = [];
|
||||
|
||||
revealables.forEach((revealable) => {
|
||||
const coordinates = generateRevealableCoords(revObjs);
|
||||
revObjs.push({
|
||||
revealable,
|
||||
area: {
|
||||
x: coordinates.x,
|
||||
y: coordinates.y,
|
||||
w: revealable.size < 50 ? 50 : revealable.size > 400 ? 400 : revealable.size,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
console.log(revObjs);
|
||||
|
||||
return revObjs.map((revObj) => new Revealable(revObj.revealable, revObj.area));
|
||||
}
|
||||
|
|
|
|||
89
src/logger.ts
Normal file
89
src/logger.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import firebase from 'firebase/app';
|
||||
import 'firebase/database';
|
||||
import store from './store';
|
||||
|
||||
const firebaseConfig = {
|
||||
apiKey: 'AIzaSyCORIIdFkDBagiVf0IeK0UxZL1qv0m90_E',
|
||||
authDomain: 'codewanderer-d9212.firebaseapp.com',
|
||||
databaseURL: 'https://codewanderer-d9212-default-rtdb.europe-west1.firebasedatabase.app',
|
||||
projectId: 'codewanderer-d9212',
|
||||
storageBucket: 'codewanderer-d9212.appspot.com',
|
||||
messagingSenderId: '143940874668',
|
||||
appId: '1:143940874668:web:d2f860b4fc5d4292cf8a09',
|
||||
};
|
||||
|
||||
type LogEventType =
|
||||
| 'CC'
|
||||
| 'OC'
|
||||
| 'RC'
|
||||
| 'LI'
|
||||
| 'LR'
|
||||
| 'LS'
|
||||
| 'LC'
|
||||
| 'PI'
|
||||
| 'PR'
|
||||
| 'PS'
|
||||
| 'PC'
|
||||
| 'NI'
|
||||
| 'NR'
|
||||
| 'NS'
|
||||
| 'NC'
|
||||
| 'GF'
|
||||
| 'SF'
|
||||
| 'OTHER';
|
||||
|
||||
export interface LogEvent {
|
||||
timestamp: number;
|
||||
type: LogEventType;
|
||||
message?: string;
|
||||
additionalData?: any;
|
||||
}
|
||||
|
||||
export class Logger {
|
||||
database: firebase.database.Database;
|
||||
|
||||
constructor() {
|
||||
firebase.initializeApp(firebaseConfig);
|
||||
this.database = firebase.database();
|
||||
}
|
||||
|
||||
public log(ev: LogEvent) {
|
||||
console.log('Logging event');
|
||||
const uid = store.getState().uid;
|
||||
|
||||
if (uid) {
|
||||
const logEvKey = this.database.ref(uid).child('logs').push().key;
|
||||
|
||||
this.database.ref(uid).update({ [`logs/${logEvKey}`]: ev });
|
||||
}
|
||||
}
|
||||
|
||||
public logPersonalData(
|
||||
name: string,
|
||||
age: number,
|
||||
background: string,
|
||||
experience: string,
|
||||
anonymous: boolean
|
||||
) {
|
||||
const uid = store.getState().uid;
|
||||
|
||||
this.database.ref(uid).set({
|
||||
name,
|
||||
age,
|
||||
background,
|
||||
experience,
|
||||
anonymous,
|
||||
});
|
||||
}
|
||||
|
||||
public logQuestions(answers: string[], isKQ: boolean = false) {
|
||||
const uid = store.getState().uid;
|
||||
|
||||
this.database
|
||||
.ref(uid)
|
||||
.child(`${isKQ ? 'knowledge' : 'general'}Questions`)
|
||||
.set(answers);
|
||||
}
|
||||
}
|
||||
|
||||
export const logger = new Logger();
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
import _ from 'lodash';
|
||||
import { mp5 } from '../../main';
|
||||
import { colors } from '../constants/colors';
|
||||
import { generateRevealableCoords } from '../helpers';
|
||||
import { generateRevealables } from '../helpers';
|
||||
import { logger } from '../logger';
|
||||
import { Player } from '../sketchObjects/Player';
|
||||
import { Revealable, RevealableInterface } from '../sketchObjects/Revealable';
|
||||
import { Revealable, RevealableInterface, RevealableTypes } from '../sketchObjects/Revealable';
|
||||
import store from '../store';
|
||||
import { Coordinates } from '../types';
|
||||
import { CompanionState } from '../ui/companion';
|
||||
|
|
@ -14,6 +15,9 @@ export class DetailScene {
|
|||
revealables: RevealableInterface[];
|
||||
revealableCoords: Coordinates[];
|
||||
revealableObjects: Revealable[];
|
||||
startTime?: number = null;
|
||||
wasInteractedWith: boolean = false;
|
||||
wasHovered: boolean = false;
|
||||
|
||||
constructor() {
|
||||
this.player = new Player();
|
||||
|
|
@ -21,20 +25,35 @@ export class DetailScene {
|
|||
store.subscribe((state, prevState) => {
|
||||
if (!_.isEqual(state.revealables, prevState.revealables)) {
|
||||
this.revealables = state.revealables;
|
||||
this.revealableCoords = generateRevealableCoords();
|
||||
this.revealableObjects = this.revealables.map(
|
||||
(revealable, i) =>
|
||||
new Revealable(revealable, {
|
||||
x: this.revealableCoords[i].x,
|
||||
y: this.revealableCoords[i].y,
|
||||
w: this.revealables[i].size,
|
||||
})
|
||||
);
|
||||
this.revealableObjects = generateRevealables(this.revealables);
|
||||
console.log(this.revealableObjects);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
draw() {
|
||||
if (this.startTime === null) {
|
||||
this.startTime = mp5.millis();
|
||||
}
|
||||
|
||||
if (mp5.millis() > this.startTime + 6000 && !this.wasInteractedWith) {
|
||||
this.wasInteractedWith = true;
|
||||
store.getState().addUserMessage({
|
||||
inputWanted: false,
|
||||
text: 'Trouble knowing what to do? You should try clicking somewhere in order to spawn reveal bubbles. Try this in different parts of the canvas to see what you can find',
|
||||
});
|
||||
} else if (
|
||||
mp5.millis() > this.startTime + 16000 &&
|
||||
!this.wasHovered &&
|
||||
this.wasInteractedWith
|
||||
) {
|
||||
this.wasHovered = true;
|
||||
store.getState().addUserMessage({
|
||||
inputWanted: false,
|
||||
text: "Good job with your reveal bubbles, in order to truly find out what is important in this part of the project, try to catch the revealed objects with your character's head to be able to interact with them.",
|
||||
});
|
||||
}
|
||||
|
||||
mp5.background(mp5.color(colors.greyLighter));
|
||||
|
||||
this.player.drawOnReveal();
|
||||
|
|
@ -44,20 +63,36 @@ export class DetailScene {
|
|||
revObj.draw();
|
||||
});
|
||||
|
||||
store.setState({
|
||||
revealablesFinished: this.revealableObjects.filter((revObj) => revObj.wasInteractedWith)
|
||||
.length,
|
||||
});
|
||||
|
||||
this.player.move();
|
||||
|
||||
if (
|
||||
this.revealableObjects.every((revObj) => revObj.wasInteractedWith) &&
|
||||
!(store.getState().companionState === CompanionState.ACTIVE)
|
||||
!(store.getState().companionState === CompanionState.ACTIVE) &&
|
||||
!store.getState().infoMessageShown
|
||||
) {
|
||||
store.setState((state) => ({
|
||||
finishedSubProjects: [...state.finishedSubProjects, state.currentSubproject],
|
||||
}));
|
||||
|
||||
if (!store.getState().finishedSubProjects.includes(store.getState().currentSubproject)) {
|
||||
store.setState((state) => ({
|
||||
finishedSubProjects: [...state.finishedSubProjects, state.currentSubproject],
|
||||
}));
|
||||
}
|
||||
store.getState().addUserMessage({
|
||||
text: "Yaay! You've found all of the important parts of this part of the repository. You will be returned to the subproject overview now. Pick the next subproject you want to take a look at there.",
|
||||
inputWanted: false,
|
||||
onNext: () => store.setState({ currentScene: Scenes.OVERVIEW }),
|
||||
onNext: () => {
|
||||
logger.log({
|
||||
type: 'SF',
|
||||
timestamp: Date.now(),
|
||||
message: `Finished subprojects: ${JSON.stringify(
|
||||
store.getState().finishedSubProjects
|
||||
)}`,
|
||||
});
|
||||
store.setState({ showScore: false, currentScene: Scenes.OVERVIEW });
|
||||
},
|
||||
showIdle: false,
|
||||
});
|
||||
}
|
||||
|
|
@ -66,9 +101,27 @@ export class DetailScene {
|
|||
onSceneClick() {
|
||||
this.revealableObjects.forEach((revObj) => {
|
||||
if (revObj.isHovered) {
|
||||
logger.log({
|
||||
type:
|
||||
revObj.type === RevealableTypes.CONTRIBUTOR
|
||||
? 'NI'
|
||||
: revObj.type === RevealableTypes.LEGACY
|
||||
? 'LI'
|
||||
: 'PI',
|
||||
timestamp: Date.now(),
|
||||
message: `Identified ${revObj.name}`,
|
||||
});
|
||||
|
||||
this.wasHovered = true;
|
||||
revObj.onClick();
|
||||
} else {
|
||||
logger.log({
|
||||
type: 'RC',
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
this.player.reveal();
|
||||
this.wasInteractedWith = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,14 @@ import store from '../store';
|
|||
import { generateEdges } from '../helpers';
|
||||
import { Scenes } from './scenes';
|
||||
import projectMetadata from '../../metadata/project.json';
|
||||
import { playerHead$ } from '../area';
|
||||
import { Area } from '../types';
|
||||
import { logger } from '../logger';
|
||||
|
||||
export class OverviewScene {
|
||||
player: Player;
|
||||
playerHead: Area;
|
||||
edges: Edge[];
|
||||
sfLogged: boolean;
|
||||
|
||||
constructor() {
|
||||
this.edges = generateEdges(projectMetadata.subprojects);
|
||||
|
|
@ -30,14 +31,47 @@ export class OverviewScene {
|
|||
public onSceneClick() {
|
||||
this.edges.forEach((edge, i) => {
|
||||
const dist = mp5.dist(mp5.mouseX, mp5.mouseY, edge.x, edge.y);
|
||||
if (dist < edge.r) {
|
||||
if (dist < edge.currentSize) {
|
||||
logger.log({
|
||||
type: 'OC',
|
||||
timestamp: Date.now(),
|
||||
message: 'Click inside edge',
|
||||
});
|
||||
|
||||
store.getState().setProjectMetadata(edge.name);
|
||||
store.setState({ currentSubproject: edge.name, currentScene: Scenes.DETAIL });
|
||||
store.setState({
|
||||
showScore: true,
|
||||
currentSubproject: edge.name,
|
||||
currentScene: Scenes.DETAIL,
|
||||
});
|
||||
} else {
|
||||
logger.log({
|
||||
type: 'OC',
|
||||
timestamp: Date.now(),
|
||||
message: 'Click outside edge',
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private drawLocations() {
|
||||
if (store.getState().finishedSubProjects.length === 3 && !store.getState().finishedGame) {
|
||||
store.setState({ finishedGame: true });
|
||||
|
||||
setTimeout(() => {
|
||||
logger.log({
|
||||
timestamp: Date.now(),
|
||||
type: 'GF',
|
||||
});
|
||||
|
||||
store.getState().addUserMessage({
|
||||
text: "Nice! 😎 You made it all the way through. Now I would be very thankful if you could take some time to answer the following questions. Don't overthink the answers and write down everything that comes to your mind. The more input you give, the better no matter how well it is formulated!",
|
||||
inputWanted: false,
|
||||
onNext: () => store.setState({ currentIntroStep: 5 }),
|
||||
});
|
||||
}, 800);
|
||||
}
|
||||
|
||||
this.edges.forEach((edgeShape) => {
|
||||
if (store.getState().finishedSubProjects.some((fsp) => fsp === edgeShape.name)) {
|
||||
edgeShape.finished = true;
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ import { combineLatest } from 'rxjs';
|
|||
import { mp5 } from '../../main';
|
||||
import { areasColliding, playerHead$, revealedArea$ } from '../area';
|
||||
import { colors } from '../constants/colors';
|
||||
import { logger } from '../logger';
|
||||
import store from '../store';
|
||||
import { Area } from '../types';
|
||||
import { Commit } from '../ui/info';
|
||||
|
||||
export enum RevealableTypes {
|
||||
LEGACY = 'LEGACY',
|
||||
|
|
@ -19,6 +21,9 @@ export interface RevealableInterface {
|
|||
size: number;
|
||||
path?: string;
|
||||
imageUrl?: string;
|
||||
version?: string;
|
||||
commits?: Commit[];
|
||||
fileContents?: string;
|
||||
}
|
||||
|
||||
enum RevealableStates {
|
||||
|
|
@ -38,10 +43,14 @@ export class Revealable {
|
|||
contents: string;
|
||||
url: string;
|
||||
imageUrl: string;
|
||||
version: string;
|
||||
commits: Commit[];
|
||||
fileContents: string;
|
||||
|
||||
isHovered: boolean;
|
||||
isRevealed: boolean;
|
||||
wasInteractedWith: boolean;
|
||||
wasRevealed: boolean;
|
||||
|
||||
minSize: number = 5;
|
||||
currentSize: number;
|
||||
|
|
@ -51,12 +60,28 @@ export class Revealable {
|
|||
pulseOpacity: number = 255;
|
||||
pulseCountUp: boolean;
|
||||
|
||||
constructor({ type, name, path, contents, url, imageUrl }: RevealableInterface, area: Area) {
|
||||
constructor(
|
||||
{
|
||||
type,
|
||||
name,
|
||||
path,
|
||||
contents,
|
||||
url,
|
||||
imageUrl,
|
||||
version,
|
||||
commits,
|
||||
fileContents,
|
||||
}: RevealableInterface,
|
||||
area: Area
|
||||
) {
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.path = path;
|
||||
this.contents = contents;
|
||||
this.url = url;
|
||||
this.version = version;
|
||||
this.commits = commits;
|
||||
this.fileContents = fileContents;
|
||||
this.imageUrl = imageUrl;
|
||||
this.area = area;
|
||||
this.currentSize = this.minSize;
|
||||
|
|
@ -85,6 +110,21 @@ export class Revealable {
|
|||
this.state = RevealableStates.FOUND;
|
||||
} else if (isRevealed && !isHovered) {
|
||||
this.state = RevealableStates.REVEALED;
|
||||
|
||||
if (!this.wasRevealed) {
|
||||
logger.log({
|
||||
type:
|
||||
this.type === RevealableTypes.CONTRIBUTOR
|
||||
? 'NR'
|
||||
: this.type === RevealableTypes.LEGACY
|
||||
? 'LR'
|
||||
: 'PR',
|
||||
timestamp: Date.now(),
|
||||
message: `Revealed ${this.name}`,
|
||||
});
|
||||
}
|
||||
|
||||
this.wasRevealed = true;
|
||||
} else {
|
||||
this.state = RevealableStates.HIDDEN;
|
||||
}
|
||||
|
|
@ -108,10 +148,27 @@ export class Revealable {
|
|||
mp5.fill(mp5.color(colors.red));
|
||||
mp5.ellipse(this.area.x, this.area.y, this.currentSize);
|
||||
} else if (this.state === RevealableStates.INACTIVE) {
|
||||
this.minSize = 35;
|
||||
this.reduceSize();
|
||||
|
||||
mp5.fill(mp5.color(colors.greyDark));
|
||||
mp5.fill(mp5.color(colors.redDark));
|
||||
mp5.ellipse(this.area.x, this.area.y, this.currentSize);
|
||||
mp5.strokeWeight(4);
|
||||
mp5.stroke(mp5.color(colors.greyLighter));
|
||||
|
||||
mp5.line(
|
||||
this.area.x + this.currentSize / 5,
|
||||
this.area.y - this.currentSize / 5,
|
||||
this.area.x - this.currentSize / 5,
|
||||
this.area.y + this.currentSize / 5
|
||||
);
|
||||
|
||||
mp5.line(
|
||||
this.area.x - this.currentSize / 5,
|
||||
this.area.y - this.currentSize / 5,
|
||||
this.area.x + this.currentSize / 5,
|
||||
this.area.y + this.currentSize / 5
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -119,11 +176,26 @@ export class Revealable {
|
|||
if (this.isHovered && !this.wasInteractedWith) {
|
||||
this.wasInteractedWith = true;
|
||||
|
||||
logger.log({
|
||||
type:
|
||||
this.type === RevealableTypes.CONTRIBUTOR
|
||||
? 'NS'
|
||||
: this.type === RevealableTypes.LEGACY
|
||||
? 'LS'
|
||||
: 'PS',
|
||||
timestamp: Date.now(),
|
||||
message: `Showing info message for ${this.name}`,
|
||||
});
|
||||
|
||||
store.getState().addInfoMessage({
|
||||
type: this.type,
|
||||
headline: this.name,
|
||||
innerHTML: this.contents,
|
||||
imgUrl: this.imageUrl,
|
||||
url: this.url,
|
||||
version: this.version,
|
||||
commits: this.commits,
|
||||
fileContents: this.fileContents,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
13
src/store.ts
13
src/store.ts
|
|
@ -6,9 +6,9 @@ import project from '../metadata/project.json';
|
|||
import { InfoMessageType } from './ui/info';
|
||||
import { RevealableInterface, RevealableTypes } from './sketchObjects/Revealable';
|
||||
import { getRevealablesforSubproject } from './helpers';
|
||||
import { SubProject } from './types';
|
||||
|
||||
export interface State {
|
||||
currentIntroStep: number;
|
||||
currentScene: Scenes;
|
||||
currentSubproject?: string;
|
||||
companionState: CompanionState;
|
||||
|
|
@ -20,15 +20,26 @@ export interface State {
|
|||
revealables: RevealableInterface[];
|
||||
finishedSubProjects: string[];
|
||||
setProjectMetadata: (projectName: string) => void;
|
||||
participantAnonymous: boolean;
|
||||
finishedGame: boolean;
|
||||
revealablesFinished: number;
|
||||
showScore: boolean;
|
||||
uid: string;
|
||||
}
|
||||
|
||||
const store = create<State>(
|
||||
devtools((set) => ({
|
||||
uid: null,
|
||||
showScore: false,
|
||||
currentIntroStep: 1,
|
||||
revealablesFinished: 0,
|
||||
currentScene: Scenes.OVERVIEW,
|
||||
currentSubproject: null,
|
||||
participantAnonymous: false,
|
||||
companionState: CompanionState.IDLE,
|
||||
infoMessageShown: false,
|
||||
infoMessages: [],
|
||||
finishedGame: false,
|
||||
addInfoMessage: (newMessage) =>
|
||||
set((state) => ({ ...state, infoMessages: [...state.infoMessages, newMessage] })),
|
||||
userMessages: [],
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import anime from 'animejs/lib/anime.es';
|
||||
import { logger } from '../logger';
|
||||
import store from '../store';
|
||||
|
||||
export enum CompanionState {
|
||||
|
|
@ -22,6 +23,7 @@ export class Companion {
|
|||
messageTextRef: HTMLElement;
|
||||
messageInputRef: HTMLElement;
|
||||
messageButtonRef: HTMLElement;
|
||||
backdrop: HTMLElement;
|
||||
hoverAnimation: any;
|
||||
message: CompanionMessage;
|
||||
|
||||
|
|
@ -31,6 +33,7 @@ export class Companion {
|
|||
this.messageTextRef = document.getElementById('message-text');
|
||||
this.messageInputRef = document.getElementById('message-input');
|
||||
this.messageButtonRef = document.getElementById('message-confirm');
|
||||
this.backdrop = document.getElementById('comp-backdrop');
|
||||
|
||||
this.ref.addEventListener('click', () => this.handleClick());
|
||||
this.ref.addEventListener('mouseover', () => this.handleMouseEnter());
|
||||
|
|
@ -43,11 +46,13 @@ export class Companion {
|
|||
store.subscribe(
|
||||
(companionState) => {
|
||||
if (companionState === CompanionState.ACTIVE) {
|
||||
this.backdrop.style.display = 'block';
|
||||
this.stopAwaitAnimation();
|
||||
this.showActiveShape();
|
||||
this.scaleUpCompanion();
|
||||
this.showMessage(this.message);
|
||||
} else if (companionState === CompanionState.IDLE) {
|
||||
this.backdrop.style.display = 'none';
|
||||
this.scaleDownCompanion();
|
||||
this.showIdleShape();
|
||||
this.stopAwaitAnimation();
|
||||
|
|
@ -93,6 +98,11 @@ export class Companion {
|
|||
// Hide Message
|
||||
this.messageRef.style.display = 'none';
|
||||
store.setState({ companionState: CompanionState.IDLE });
|
||||
logger.log({
|
||||
type: 'CC',
|
||||
timestamp: Date.now(),
|
||||
message: 'Close message',
|
||||
});
|
||||
|
||||
if (this.message.onNext) {
|
||||
this.message.onNext();
|
||||
|
|
@ -185,7 +195,7 @@ export class Companion {
|
|||
}
|
||||
|
||||
handleClick() {
|
||||
const { companionState } = store.getState();
|
||||
/*const { companionState } = store.getState();
|
||||
let newCompanionState: CompanionState;
|
||||
|
||||
if (companionState === CompanionState.ACTIVE) {
|
||||
|
|
@ -194,7 +204,11 @@ export class Companion {
|
|||
newCompanionState = CompanionState.ACTIVE;
|
||||
}
|
||||
|
||||
store.setState({ companionState: newCompanionState });
|
||||
store.setState({ companionState: newCompanionState });*/
|
||||
logger.log({
|
||||
type: 'CC',
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseEnter() {
|
||||
|
|
|
|||
115
src/ui/info.ts
115
src/ui/info.ts
|
|
@ -1,28 +1,55 @@
|
|||
import moment from 'moment';
|
||||
import { logger } from '../logger';
|
||||
import { RevealableTypes } from '../sketchObjects/Revealable';
|
||||
import store from '../store';
|
||||
|
||||
export interface Commit {
|
||||
url: string;
|
||||
message: string;
|
||||
time: string;
|
||||
}
|
||||
export interface InfoMessageType {
|
||||
type: RevealableTypes;
|
||||
headline: string;
|
||||
innerHTML: string;
|
||||
imgUrl?: string;
|
||||
url?: string;
|
||||
commits?: Commit[];
|
||||
version?: string;
|
||||
fileContents?: string;
|
||||
}
|
||||
|
||||
export class InfoMessage {
|
||||
type: RevealableTypes;
|
||||
name: string;
|
||||
infoMessage: HTMLElement;
|
||||
infoMessageSubheadline: HTMLElement;
|
||||
infoMessageHeadline: HTMLElement;
|
||||
infoMessageContents: HTMLElement;
|
||||
infoMessageClose: HTMLElement;
|
||||
infoMessageImgRef: HTMLImageElement;
|
||||
infoMessageLinkRef: HTMLAnchorElement;
|
||||
|
||||
infoMessageCommitsHeadlineRef: HTMLElement;
|
||||
infoMessageCommitsRef: HTMLElement;
|
||||
infoMessageVersionRef: HTMLElement;
|
||||
infoMessageLegacyRef: HTMLElement;
|
||||
backdrop: HTMLElement;
|
||||
|
||||
constructor() {
|
||||
this.infoMessage = document.getElementById('info-message');
|
||||
this.infoMessageHeadline = document.getElementById('info-message-headline');
|
||||
this.infoMessageContents = document.getElementById('info-message-contents');
|
||||
this.infoMessageSubheadline = document.getElementById('info-message-subheadline');
|
||||
this.infoMessageContents = document.getElementById('info-message-contents-text');
|
||||
this.infoMessageClose = document.getElementById('info-message-close');
|
||||
this.infoMessageImgRef = document.getElementById('info-message-img') as HTMLImageElement;
|
||||
this.infoMessageLinkRef = document.getElementById('info-message-link') as HTMLAnchorElement;
|
||||
this.infoMessageCommitsRef = document.getElementById('info-message-contents-commits');
|
||||
this.infoMessageCommitsHeadlineRef = document.getElementById(
|
||||
'info-message-content-commits-headline'
|
||||
);
|
||||
this.infoMessageVersionRef = document.getElementById('info-message-contents-version');
|
||||
this.infoMessageLegacyRef = document.getElementById('info-message-contents-legacy');
|
||||
this.backdrop = document.getElementById('backdrop');
|
||||
|
||||
this.backdrop.addEventListener('click', this.onBackdropClick);
|
||||
|
|
@ -39,6 +66,11 @@ export class InfoMessage {
|
|||
const newMessage = state.infoMessages[state.infoMessages.length - 1];
|
||||
this.setContents(newMessage.headline, newMessage.innerHTML);
|
||||
|
||||
this.type = newMessage.type;
|
||||
this.name = newMessage.headline;
|
||||
|
||||
this.infoMessageSubheadline.innerHTML = this.getTextForType();
|
||||
|
||||
if (newMessage.imgUrl) {
|
||||
this.setImg(newMessage.imgUrl);
|
||||
} else {
|
||||
|
|
@ -51,16 +83,77 @@ export class InfoMessage {
|
|||
this.infoMessageLinkRef.style.display = 'none';
|
||||
}
|
||||
|
||||
if (this.type === RevealableTypes.CONTRIBUTOR) {
|
||||
this.infoMessageCommitsRef.style.display = 'block';
|
||||
this.infoMessageCommitsHeadlineRef.style.display = 'block';
|
||||
this.setCommits(newMessage.commits);
|
||||
} else {
|
||||
this.infoMessageCommitsRef.style.display = 'none';
|
||||
this.infoMessageCommitsHeadlineRef.style.display = 'none';
|
||||
}
|
||||
|
||||
if (this.type === RevealableTypes.PACKAGE) {
|
||||
this.infoMessageVersionRef.style.display = 'block';
|
||||
this.setVersion(newMessage.version);
|
||||
} else {
|
||||
this.infoMessageVersionRef.style.display = 'none';
|
||||
}
|
||||
|
||||
if (this.type === RevealableTypes.LEGACY) {
|
||||
this.infoMessageLegacyRef.style.display = 'block';
|
||||
this.setLegacy(newMessage.fileContents);
|
||||
} else {
|
||||
this.infoMessageLegacyRef.style.display = 'none';
|
||||
}
|
||||
|
||||
store.setState({ infoMessageShown: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getTextForType(): string {
|
||||
if (this.type === RevealableTypes.CONTRIBUTOR) {
|
||||
return 'Contributor';
|
||||
} else if (this.type === RevealableTypes.PACKAGE) {
|
||||
return 'NPM Package';
|
||||
} else {
|
||||
return 'Legacy Alert';
|
||||
}
|
||||
}
|
||||
|
||||
private setContents(headline: string, innerHTML: string) {
|
||||
this.infoMessageHeadline.innerText = headline;
|
||||
this.infoMessageContents.innerHTML = innerHTML;
|
||||
}
|
||||
|
||||
private setVersion(version: string) {
|
||||
this.infoMessageVersionRef.innerHTML = `This package is installed at<br/><h3>${version}</h3>`;
|
||||
}
|
||||
|
||||
private setLegacy(fileContents: string) {
|
||||
this.infoMessageLegacyRef.innerHTML = `
|
||||
<p>Excerpt from the file:</p>
|
||||
<pre>${fileContents}</pre>
|
||||
`;
|
||||
}
|
||||
|
||||
private setCommits(commits: Commit[]) {
|
||||
const commitEls = commits.map(
|
||||
({ message, url, time }) =>
|
||||
`<div class="info-message-contents-commit">
|
||||
<p>${message}</p>
|
||||
<div>
|
||||
<span>${moment(time).format(
|
||||
'LLL'
|
||||
)}</span> • <a href="${url}" target="_blank">Take a look on Github</a>
|
||||
</div>
|
||||
</div>`
|
||||
);
|
||||
console.log(commitEls);
|
||||
|
||||
this.infoMessageCommitsRef.innerHTML = commitEls.join('');
|
||||
}
|
||||
|
||||
private setImg(imgUrl: string) {
|
||||
this.infoMessageImgRef.src = imgUrl;
|
||||
this.infoMessageImgRef.style.display = 'block';
|
||||
|
|
@ -82,10 +175,30 @@ export class InfoMessage {
|
|||
}
|
||||
|
||||
private onBackdropClick() {
|
||||
logger.log({
|
||||
type:
|
||||
this.type === RevealableTypes.CONTRIBUTOR
|
||||
? 'NC'
|
||||
: this.type === RevealableTypes.LEGACY
|
||||
? 'LC'
|
||||
: 'PC',
|
||||
timestamp: Date.now(),
|
||||
message: `Closing info message for ${this.name}`,
|
||||
});
|
||||
store.setState({ infoMessageShown: false });
|
||||
}
|
||||
|
||||
private onCloseClick() {
|
||||
logger.log({
|
||||
type:
|
||||
this.type === RevealableTypes.CONTRIBUTOR
|
||||
? 'NC'
|
||||
: this.type === RevealableTypes.LEGACY
|
||||
? 'LC'
|
||||
: 'PC',
|
||||
timestamp: Date.now(),
|
||||
message: `Closing info message for ${this.name}`,
|
||||
});
|
||||
store.setState({ infoMessageShown: false });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
269
src/ui/intro.ts
Normal file
269
src/ui/intro.ts
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
import { logger } from '../logger';
|
||||
import { Scenes } from '../scenes/scenes';
|
||||
import store from '../store';
|
||||
|
||||
export class Intro {
|
||||
currentStep: number;
|
||||
anonymous: boolean;
|
||||
|
||||
nextButton: HTMLElement;
|
||||
anonymousCheckbox: HTMLInputElement;
|
||||
introContainer: HTMLElement;
|
||||
introBackdrop: HTMLElement;
|
||||
step1Container: HTMLElement;
|
||||
step2Container: HTMLElement;
|
||||
step3Container: HTMLElement;
|
||||
step4Container: HTMLElement;
|
||||
step5Container: HTMLElement;
|
||||
step6Container: HTMLElement;
|
||||
step7Container: HTMLElement;
|
||||
|
||||
nameRef: HTMLInputElement;
|
||||
ageRef: HTMLInputElement;
|
||||
backgroundRef: HTMLInputElement;
|
||||
experienceRef: HTMLSelectElement;
|
||||
|
||||
fb1: HTMLTextAreaElement;
|
||||
fb2: HTMLTextAreaElement;
|
||||
fb3: HTMLTextAreaElement;
|
||||
fb4: HTMLTextAreaElement;
|
||||
fb5: HTMLTextAreaElement;
|
||||
fb6: HTMLTextAreaElement;
|
||||
fb7: HTMLTextAreaElement;
|
||||
fb8: HTMLTextAreaElement;
|
||||
fb9: HTMLTextAreaElement;
|
||||
fb10: HTMLTextAreaElement;
|
||||
|
||||
k1: HTMLTextAreaElement;
|
||||
|
||||
errorRef: HTMLElement;
|
||||
|
||||
constructor() {
|
||||
this.step1Container = document.getElementById('intro-step1');
|
||||
this.step2Container = document.getElementById('intro-step2');
|
||||
this.step3Container = document.getElementById('intro-step3');
|
||||
this.step4Container = document.getElementById('intro-step4');
|
||||
this.step5Container = document.getElementById('intro-step5');
|
||||
this.step6Container = document.getElementById('intro-step6');
|
||||
this.step7Container = document.getElementById('intro-step7');
|
||||
this.nextButton = document.getElementById('intro-button');
|
||||
this.anonymousCheckbox = document.querySelector('#intro-anonymous');
|
||||
this.introContainer = document.querySelector('#intro');
|
||||
this.introBackdrop = document.querySelector('#intro-backdrop');
|
||||
|
||||
this.nameRef = document.querySelector('#intro-name');
|
||||
this.ageRef = document.querySelector('#intro-age');
|
||||
this.backgroundRef = document.querySelector('#intro-background');
|
||||
this.experienceRef = document.querySelector('#intro-experience');
|
||||
|
||||
this.fb1 = document.querySelector('#fb-1');
|
||||
this.fb2 = document.querySelector('#fb-2');
|
||||
this.fb3 = document.querySelector('#fb-3');
|
||||
this.fb4 = document.querySelector('#fb-4');
|
||||
this.fb5 = document.querySelector('#fb-5');
|
||||
this.fb6 = document.querySelector('#fb-6');
|
||||
this.fb7 = document.querySelector('#fb-7');
|
||||
this.fb8 = document.querySelector('#fb-8');
|
||||
this.fb9 = document.querySelector('#fb-9');
|
||||
this.fb10 = document.querySelector('#fb-10');
|
||||
|
||||
this.k1 = document.querySelector('#k-1');
|
||||
|
||||
this.errorRef = document.querySelector('#intro-error');
|
||||
|
||||
this.nextButton.addEventListener('click', () => this.onNextClick());
|
||||
|
||||
this.currentStep = store.getState().currentIntroStep;
|
||||
|
||||
if (this.currentStep === 0) {
|
||||
this.introContainer.style.display = 'none';
|
||||
this.introBackdrop.style.display = 'none';
|
||||
} else {
|
||||
this.showStep();
|
||||
}
|
||||
|
||||
store.subscribe((state) => {
|
||||
this.currentStep = state.currentIntroStep;
|
||||
this.anonymous = state.participantAnonymous;
|
||||
|
||||
if (state.currentIntroStep === 2) {
|
||||
this.nextButton.innerHTML = 'Agree';
|
||||
} else if (state.currentIntroStep === 3) {
|
||||
this.nextButton.innerHTML = 'Confirm';
|
||||
} else if (state.currentIntroStep === 4) {
|
||||
this.nextButton.innerHTML = 'Start already!';
|
||||
} else if (state.currentIntroStep === 7) {
|
||||
this.nextButton.style.display = 'none';
|
||||
} else if (state.currentIntroStep === 0) {
|
||||
this.introContainer.style.display = 'none';
|
||||
this.introBackdrop.style.display = 'none';
|
||||
} else {
|
||||
this.nextButton.innerHTML = 'Continue';
|
||||
}
|
||||
|
||||
if (state.currentIntroStep !== 0) {
|
||||
this.introContainer.style.display = 'block';
|
||||
this.introBackdrop.style.display = 'block';
|
||||
}
|
||||
|
||||
if (this.anonymous) {
|
||||
this.hideNameInput();
|
||||
}
|
||||
|
||||
this.showStep();
|
||||
});
|
||||
}
|
||||
|
||||
private onNextClick() {
|
||||
const currentStep = store.getState().currentIntroStep;
|
||||
|
||||
// Track if particpant wants to be anon
|
||||
if (currentStep === 2) {
|
||||
store.setState({ participantAnonymous: this.anonymousCheckbox.checked });
|
||||
}
|
||||
|
||||
// Validate input
|
||||
if (currentStep === 3) {
|
||||
const name = this.nameRef.value;
|
||||
const age = Number(this.ageRef.value);
|
||||
const background = this.backgroundRef.value;
|
||||
const experience = this.experienceRef.value;
|
||||
|
||||
if (
|
||||
(this.anonymous ? false : !name) ||
|
||||
!age ||
|
||||
!background ||
|
||||
experience === 'Choose an option...'
|
||||
) {
|
||||
this.errorRef.style.display = 'block';
|
||||
return;
|
||||
} else {
|
||||
this.errorRef.style.display = 'none';
|
||||
|
||||
store.setState({
|
||||
uid: `${Date.now()}_${name.replace(/[^a-zA-Z ]/g, '').toLowerCase()}`,
|
||||
});
|
||||
|
||||
this.sendDemographicData(name, age, background, experience);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentStep === 4) {
|
||||
store.setState({ currentIntroStep: 0 });
|
||||
|
||||
setTimeout(() => {
|
||||
if (store.getState().currentScene !== Scenes.DETAIL) {
|
||||
store.getState().addUserMessage({
|
||||
inputWanted: false,
|
||||
text: "Hey there! Need help here? You'll have to touch the parts of the project you want to take a look at with you character's head. As soon as the project part (packages/...) is highlighted, you can click it to dive deeper into what lies behind 🔬",
|
||||
});
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentStep === 6) {
|
||||
this.sendGeneralQuestionAnswers();
|
||||
}
|
||||
|
||||
if (currentStep === 5) {
|
||||
this.sendKnowledgeQuestionAnswers();
|
||||
}
|
||||
|
||||
store.setState((state) => ({ currentIntroStep: state.currentIntroStep + 1 }));
|
||||
}
|
||||
|
||||
private sendDemographicData(name: string, age: number, background: string, experience: string) {
|
||||
logger.logPersonalData(name, age, background, experience, this.anonymous);
|
||||
}
|
||||
|
||||
private sendGeneralQuestionAnswers() {
|
||||
const answers = [
|
||||
this.fb1.value,
|
||||
this.fb2.value,
|
||||
this.fb3.value,
|
||||
this.fb4.value,
|
||||
this.fb5.value,
|
||||
this.fb6.value,
|
||||
this.fb7.value,
|
||||
this.fb8.value,
|
||||
this.fb9.value,
|
||||
this.fb10.value,
|
||||
];
|
||||
|
||||
logger.logQuestions(answers);
|
||||
}
|
||||
|
||||
private sendKnowledgeQuestionAnswers() {
|
||||
const answers = [this.k1.value];
|
||||
|
||||
logger.logQuestions(answers, true);
|
||||
}
|
||||
|
||||
private hideNameInput() {
|
||||
this.nameRef.style.display = 'none';
|
||||
const label = document.querySelector('#name-label') as HTMLElement;
|
||||
label.style.display = 'none';
|
||||
}
|
||||
|
||||
private showStep() {
|
||||
if (this.currentStep === 1) {
|
||||
this.step1Container.style.display = 'flex';
|
||||
this.step2Container.style.display = 'none';
|
||||
this.step3Container.style.display = 'none';
|
||||
this.step4Container.style.display = 'none';
|
||||
this.step5Container.style.display = 'none';
|
||||
this.step6Container.style.display = 'none';
|
||||
this.step7Container.style.display = 'none';
|
||||
} else if (this.currentStep === 2) {
|
||||
this.step1Container.style.display = 'none';
|
||||
this.step2Container.style.display = 'flex';
|
||||
this.step3Container.style.display = 'none';
|
||||
this.step4Container.style.display = 'none';
|
||||
this.step5Container.style.display = 'none';
|
||||
this.step6Container.style.display = 'none';
|
||||
this.step7Container.style.display = 'none';
|
||||
} else if (this.currentStep === 3) {
|
||||
this.step1Container.style.display = 'none';
|
||||
this.step2Container.style.display = 'none';
|
||||
this.step3Container.style.display = 'flex';
|
||||
this.step4Container.style.display = 'none';
|
||||
this.step5Container.style.display = 'none';
|
||||
this.step6Container.style.display = 'none';
|
||||
this.step7Container.style.display = 'none';
|
||||
} else if (this.currentStep === 4) {
|
||||
this.step1Container.style.display = 'none';
|
||||
this.step2Container.style.display = 'none';
|
||||
this.step3Container.style.display = 'none';
|
||||
this.step4Container.style.display = 'flex';
|
||||
this.step5Container.style.display = 'none';
|
||||
this.step6Container.style.display = 'none';
|
||||
this.step7Container.style.display = 'none';
|
||||
} else if (this.currentStep === 5) {
|
||||
this.step1Container.style.display = 'none';
|
||||
this.step2Container.style.display = 'none';
|
||||
this.step3Container.style.display = 'none';
|
||||
this.step4Container.style.display = 'none';
|
||||
this.step5Container.style.display = 'flex';
|
||||
this.step6Container.style.display = 'none';
|
||||
this.step7Container.style.display = 'none';
|
||||
} else if (this.currentStep === 6) {
|
||||
this.step1Container.style.display = 'none';
|
||||
this.step2Container.style.display = 'none';
|
||||
this.step3Container.style.display = 'none';
|
||||
this.step4Container.style.display = 'none';
|
||||
this.step5Container.style.display = 'none';
|
||||
this.step6Container.style.display = 'flex';
|
||||
this.step7Container.style.display = 'none';
|
||||
} else if (this.currentStep === 7) {
|
||||
this.step1Container.style.display = 'none';
|
||||
this.step2Container.style.display = 'none';
|
||||
this.step3Container.style.display = 'none';
|
||||
this.step4Container.style.display = 'none';
|
||||
this.step5Container.style.display = 'none';
|
||||
this.step6Container.style.display = 'none';
|
||||
this.step7Container.style.display = 'flex';
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/ui/score.ts
Normal file
42
src/ui/score.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import store from '../store';
|
||||
|
||||
export class Score {
|
||||
showScore: boolean;
|
||||
|
||||
scoreFound: number;
|
||||
scoreTotal: number;
|
||||
|
||||
scoreRef: HTMLElement;
|
||||
scoreFoundRef: HTMLElement;
|
||||
scoreTotalRef: HTMLElement;
|
||||
|
||||
constructor() {
|
||||
this.scoreRef = document.querySelector('#score');
|
||||
this.scoreFoundRef = document.querySelector('#score-found');
|
||||
this.scoreTotalRef = document.querySelector('#score-total');
|
||||
|
||||
this.scoreFound = 0;
|
||||
this.scoreTotal = store.getState().revealables.length;
|
||||
|
||||
this.scoreFoundRef.innerHTML = this.scoreFound.toString();
|
||||
this.scoreTotalRef.innerHTML = this.scoreTotal.toString();
|
||||
|
||||
if (store.getState().showScore) {
|
||||
this.scoreRef.style.display = 'flex';
|
||||
}
|
||||
|
||||
store.subscribe((state) => {
|
||||
if (state.showScore) {
|
||||
this.scoreRef.style.display = 'flex';
|
||||
} else {
|
||||
this.scoreRef.style.display = 'none';
|
||||
}
|
||||
|
||||
this.scoreTotal = state.revealables.length;
|
||||
this.scoreFound = state.revealablesFinished;
|
||||
|
||||
this.scoreFoundRef.innerHTML = this.scoreFound.toString();
|
||||
this.scoreTotalRef.innerHTML = this.scoreTotal.toString();
|
||||
});
|
||||
}
|
||||
}
|
||||
281
styles.scss
281
styles.scss
|
|
@ -23,6 +23,60 @@ main {
|
|||
button {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
|
||||
'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
font-size: 16px;
|
||||
|
||||
padding: 6px 15px;
|
||||
background-color: transparent;
|
||||
border: 2px solid black;
|
||||
border-radius: 5px;
|
||||
font-weight: bold;
|
||||
max-width: 180px;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: bold;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
input[type='text'],
|
||||
input[type='number'] {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
font-size: 18px;
|
||||
padding: 8px 12px;
|
||||
outline: 0;
|
||||
border: 2px solid black;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
select {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
font-size: 18px;
|
||||
padding: 8px 12px;
|
||||
outline: 0;
|
||||
border: 2px solid black;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-top: 25px;
|
||||
min-height: 8rem;
|
||||
font-size: 18px;
|
||||
padding: 8px 12px;
|
||||
outline: 0;
|
||||
border: 2px solid black;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.ui {
|
||||
|
|
@ -32,9 +86,11 @@ button {
|
|||
right: 40px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
|
||||
#companion {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
|
|
@ -90,6 +146,7 @@ button {
|
|||
#message {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
right: 60px;
|
||||
bottom: 50px;
|
||||
margin-right: 100px;
|
||||
|
|
@ -143,6 +200,25 @@ button {
|
|||
}
|
||||
}
|
||||
|
||||
#score {
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
align-items: flex-end;
|
||||
color: #b0b7bf;
|
||||
font-size: 32px;
|
||||
|
||||
#score-found {
|
||||
font-size: 60px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#score-divider {
|
||||
font-size: 42px;
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
#info-message {
|
||||
display: none;
|
||||
position: fixed;
|
||||
|
|
@ -159,10 +235,81 @@ button {
|
|||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
|
||||
#info-message-contents {
|
||||
#info-message-contents-text {
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
#info-message-contents-legacy {
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #4e4e4e;
|
||||
font-style: italic;
|
||||
}
|
||||
pre {
|
||||
background-color: black;
|
||||
color: white;
|
||||
padding: 0.5rem;
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
font-weight: bold;
|
||||
color: #b0b7bf;
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
|
||||
#info-message-contents-version {
|
||||
h3 {
|
||||
display: inline-block;
|
||||
margin: 1rem 0 0 0;
|
||||
padding: 0.4rem 0.6rem;
|
||||
background-color: #4e4e4e;
|
||||
color: #fff;
|
||||
border-radius: 10px;
|
||||
font-size: 30px;
|
||||
letter-spacing: 2px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
#info-message-contents-commits {
|
||||
.info-message-contents-commit {
|
||||
background-color: #d0d5d9;
|
||||
border-radius: 10px;
|
||||
padding: 1rem;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div {
|
||||
span {
|
||||
font-size: 14px;
|
||||
color: #4e4e4e;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0d0d0d;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#info-message-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-bottom: 25px;
|
||||
|
||||
#info-message-img {
|
||||
|
|
@ -170,11 +317,22 @@ button {
|
|||
height: 100px;
|
||||
width: 100px;
|
||||
border-radius: 50%;
|
||||
margin-right: 1.5rem;
|
||||
}
|
||||
|
||||
#info-message-headline {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
#info-message-header-text {
|
||||
#info-message-subheadline {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
font-weight: bold;
|
||||
color: #b0b7bf;
|
||||
margin: 0;
|
||||
}
|
||||
#info-message-headline {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -211,6 +369,97 @@ button {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#intro {
|
||||
background-color: white;
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
min-width: 60%;
|
||||
max-width: 90%;
|
||||
max-height: 90%;
|
||||
overflow-y: auto;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: #fff;
|
||||
padding: 2.5rem 4rem;
|
||||
border-radius: 15px;
|
||||
--tw-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
|
||||
#intro-step1 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
h3 {
|
||||
margin: 0.6rem 0 0rem 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0.6rem 0 1rem;
|
||||
}
|
||||
|
||||
#intro-disclaimer {
|
||||
background-color: #ffedd5;
|
||||
color: #7c2d12;
|
||||
padding: 4px 20px;
|
||||
border-radius: 5px;
|
||||
border-left: 7px solid #7c2d12;
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
#intro-step2 {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#intro-step3 {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#intro-step4 {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#intro-step5 {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#intro-step6 {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
|
||||
label {
|
||||
margin-top: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
#intro-step7 {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#intro-button {
|
||||
float: right;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
#intro-error {
|
||||
display: none;
|
||||
margin-top: 25px;
|
||||
background-color: #fecaca;
|
||||
color: #7f1d1d;
|
||||
padding: 8px 20px;
|
||||
border-radius: 5px;
|
||||
border-left: 7px solid #7f1d1d;
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#backdrop {
|
||||
|
|
@ -223,3 +472,25 @@ button {
|
|||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
#comp-backdrop {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 3;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
#intro-backdrop {
|
||||
display: block;
|
||||
position: fixed;
|
||||
z-index: 3;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
}
|
||||
|
|
|
|||
Reference in a new issue