protected $casts = [
'index' => 'boolean',
+ 'requirements' => 'array',
'rulesets' => 'array',
];
--- /dev/null
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ Schema::table('techniques', function(Blueprint $table) {
+ $table->text('requirements')->nullable()->default(null);
+ $table->text('attribution')->default('');
+ });
+ Schema::table('technique_translations', function(Blueprint $table) {
+ $table->text('attribution')->default('');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('techniques', function(Blueprint $table) {
+ $table->dropColumn('requirements');
+ $table->dropColumn('attribution');
+ });
+ Schema::table('technique_translations', function(Blueprint $table) {
+ $table->dropColumn('attribution');
+ });
+ }
+};
Icon.RESULT = makePreset('ResultIcon', 'clock');
Icon.SECOND_PLACE = makePreset('SecondPlaceIcon', 'medal');
Icon.SETTINGS = makePreset('SettingsIcon', 'cog');
+Icon.SLASH = makePreset('SlashIcon', 'slash');
Icon.STREAM = makePreset('StreamIcon', ['fab', 'twitch']);
Icon.THIRD_PLACE = makePreset('ThirdPlaceIcon', 'award');
Icon.TWITCH = makePreset('TwitchIcon', ['fab', 'twitch']);
import PropTypes from 'prop-types';
import React from 'react';
-import { withTranslation } from 'react-i18next';
+import { useTranslation } from 'react-i18next';
-import i18n from '../../i18n';
+import Icon from './Icon';
const getIconURL = name => {
switch (name) {
}
};
-const ZeldaIcon = ({ name }) =>
-<span className="zelda-icon">
- <img
- alt={i18n.t(`icon.zelda.${name}`)}
- src={getIconURL(name)}
- title={i18n.t(`icon.zelda.${name}`)}
- />
-</span>;
+const ZeldaIcon = ({ name }) => {
+ const { t } = useTranslation();
+
+ const invert = name.startsWith('not-');
+ const strippedName = invert ? name.substr(4) : name;
+ const title = t(`icon.zelda.${name}`);
+
+ return <span className="zelda-icon">
+ <img
+ alt={title}
+ src={getIconURL(strippedName)}
+ title={title}
+ />
+ {invert ?
+ <span className="strike">
+ <Icon.SLASH title="" />
+ </span>
+ : null}
+ </span>;
+};
ZeldaIcon.propTypes = {
name: PropTypes.string,
};
-export default withTranslation()(ZeldaIcon);
+export default ZeldaIcon;
import PropTypes from 'prop-types';
import React from 'react';
-import { Container } from 'react-bootstrap';
+import { Alert, Container } from 'react-bootstrap';
import { withTranslation } from 'react-i18next';
import List from './List';
import Outline from './Outline';
+import Requirements from './Requirements';
import Rulesets from './Rulesets';
import RawHTML from '../common/RawHTML';
import {
: null}
</div>
<Outline technique={technique} />
+ <Requirements technique={technique} />
<RawHTML html={getTranslation(technique, 'description', i18n.language)} />
{technique.chapters ? technique.chapters.map(chapter =>
<section id={`c${chapter.id}`} key={`c${chapter.id}`}>
<h2 className="mt-5">{i18n.t('techniques.seeAlso')}</h2>
<List techniques={sorted(getRelations(technique, 'related'))} />
</> : null}
+ {getTranslation(technique, 'attribution', i18n.language) ?
+ <Alert variant="dark">
+ <RawHTML html={getTranslation(technique, 'attribution', i18n.language)} />
+ </Alert>
+ : null}
</Container>;
Detail.propTypes = {
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import ZeldaIcon from '../common/ZeldaIcon';
+
+const Requirement = ({ requirement }) =>
+ <div className="requirement">
+ {requirement.map(r =>
+ <ZeldaIcon key={r} name={r} />
+ )}
+ </div>;
+
+Requirement.propTypes = {
+ requirement: PropTypes.arrayOf(PropTypes.string),
+};
+
+export default Requirement;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+import Requirement from './Requirement';
+
+const Requirements = ({ technique }) => {
+ const { t } = useTranslation();
+
+ if (!technique.requirements || !technique.requirements.length) {
+ return null;
+ }
+
+ return <p className="tech-requirements">
+ {t('techniques.requirements')}
+ <ul>
+ {technique.requirements.map((r, i) =>
+ <li key={i}>
+ <Requirement requirement={r} />
+ </li>
+ )}
+ </ul>
+ </p>;
+};
+
+Requirements.propTypes = {
+ technique: PropTypes.shape({
+ requirements: PropTypes.arrayOf(
+ PropTypes.arrayOf(PropTypes.string),
+ ),
+ }),
+};
+
+export default Requirements;
mitts: 'Titan \'s Mitts',
moonpearl: 'Moonpearl',
mushroom: 'Mushroom',
+ 'not-flippers': 'Keine Flippers',
+ 'not-moonpearl': 'Keine Moonpearl',
powder: 'Powder',
quake: 'Quake',
'red-bomb': 'Red Bomb',
},
techniques: {
heading: 'Techniken',
+ lastModified: 'Zuletzt geƤndert: {{ date, L }}',
+ requirements: 'Erfordert: ',
rulesetCodes: {
competitive: 'COM',
mg: 'MG',
mitts: 'Titan \'s Mitts',
moonpearl: 'Moonpearl',
mushroom: 'Mushroom',
+ 'not-flippers': 'No Flippers',
+ 'not-moonpearl': 'No Moonpearl',
powder: 'Powder',
quake: 'Quake',
'red-bomb': 'Red Bomb',
},
techniques: {
heading: 'Techniques',
+ lastModified: 'Last modified: {{ date, L }}',
+ requirements: 'Requires: ',
rulesetCodes: {
competitive: 'COM',
mg: 'MG',
}
.zelda-icon {
+ position: relative;
display: inline-flex;
align-items: center;
width: 2em;
max-width: 100%;
max-height: 100%;
}
+ .strike {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 2em;
+ height: 2em;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ pointer-events: none;
+
+ svg {
+ width: 100%;
+ height: 100%;
+ }
+
+ path {
+ fill: red;
+ stroke: black;
+ stroke-width: 1px;
+ vector-effect: non-scaling-stroke;
+ }
+ }
}
path {
fill: red;
stroke: black;
- stroke-width: 2px;
+ stroke-width: 1px;
vector-effect: non-scaling-stroke;
}
}
}
}
+.tech-requirements {
+ ul {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ }
+ li {
+ display: inline;
+ list-style: none;
+ margin: 0 1ex;
+ padding: 0;
+
+ &::before {
+ display: inline;
+ content: " / ";
+ margin-right: 1ex;
+ }
+ &:first-child::before {
+ display: none;
+ }
+ }
+ .requirement {
+ display: inline-flex;
+ vertical-align: middle;
+ }
+}
+
.tech-outline {
float: right;
}