]> git.localhorst.tv Git - alttp.git/commitdiff
tech attribution & requirements
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Mon, 6 Feb 2023 14:49:52 +0000 (15:49 +0100)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Mon, 6 Feb 2023 14:49:52 +0000 (15:49 +0100)
12 files changed:
app/Models/Technique.php
database/migrations/2023_02_06_124402_more_technique_properties.php [new file with mode: 0644]
resources/js/components/common/Icon.js
resources/js/components/common/ZeldaIcon.js
resources/js/components/techniques/Detail.js
resources/js/components/techniques/Requirement.js [new file with mode: 0644]
resources/js/components/techniques/Requirements.js [new file with mode: 0644]
resources/js/i18n/de.js
resources/js/i18n/en.js
resources/sass/common.scss
resources/sass/map.scss
resources/sass/techniques.scss

index c8ad91b96bc56294fb1acbbba7963f31fb5de58e..446967125c8e98ebad935fb2a45d20cbeed1ada7 100644 (file)
@@ -30,6 +30,7 @@ class Technique extends Model
 
        protected $casts = [
                'index' => 'boolean',
+               'requirements' => 'array',
                'rulesets' => 'array',
        ];
 
diff --git a/database/migrations/2023_02_06_124402_more_technique_properties.php b/database/migrations/2023_02_06_124402_more_technique_properties.php
new file mode 100644 (file)
index 0000000..803fcc5
--- /dev/null
@@ -0,0 +1,40 @@
+<?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');
+               });
+    }
+};
index 3e094ccab30643a4e2cbbe19c275176123c884d5..dd88669cc3796f6eb11b970190a98f41efc0c6b2 100644 (file)
@@ -81,6 +81,7 @@ Icon.REMOVE = makePreset('RemoveIcon', 'square-xmark');
 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']);
index 84f2220672ce3e622031b96ea6064325df774ec8..c53f4fd68def288ae2f231e11dc65b486796237f 100644 (file)
@@ -1,8 +1,8 @@
 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) {
@@ -64,17 +64,29 @@ const getIconURL = 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;
index 665392365b40120b0c9834fa01eeb5d7d145b7d2..9d9534ed31f2d6831f883fa2ce043975e90958e7 100644 (file)
@@ -1,10 +1,11 @@
 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 {
@@ -23,6 +24,7 @@ const Detail = ({ technique }) => <Container as="article">
                : 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}`}>
@@ -40,6 +42,11 @@ const Detail = ({ technique }) => <Container as="article">
                <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 = {
diff --git a/resources/js/components/techniques/Requirement.js b/resources/js/components/techniques/Requirement.js
new file mode 100644 (file)
index 0000000..5faa372
--- /dev/null
@@ -0,0 +1,17 @@
+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;
diff --git a/resources/js/components/techniques/Requirements.js b/resources/js/components/techniques/Requirements.js
new file mode 100644 (file)
index 0000000..0d4501e
--- /dev/null
@@ -0,0 +1,34 @@
+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;
index 02adc9da8ea8b35937532b1f03f3a17c18003114..f130c08ed094c32fad16cc6d95090cf9667cb2fe 100644 (file)
@@ -363,6 +363,8 @@ export default {
                                mitts: 'Titan \'s Mitts',
                                moonpearl: 'Moonpearl',
                                mushroom: 'Mushroom',
+                               'not-flippers': 'Keine Flippers',
+                               'not-moonpearl': 'Keine Moonpearl',
                                powder: 'Powder',
                                quake: 'Quake',
                                'red-bomb': 'Red Bomb',
@@ -508,6 +510,8 @@ export default {
                },
                techniques: {
                        heading: 'Techniken',
+                       lastModified: 'Zuletzt geƤndert: {{ date, L }}',
+                       requirements: 'Erfordert: ',
                        rulesetCodes: {
                                competitive: 'COM',
                                mg: 'MG',
index efb99b385fc6487f4e59ab126ec8869f2e351021..a7ca19cc1afca5c6bd45259c3e158a711fca85a8 100644 (file)
@@ -363,6 +363,8 @@ export default {
                                mitts: 'Titan \'s Mitts',
                                moonpearl: 'Moonpearl',
                                mushroom: 'Mushroom',
+                               'not-flippers': 'No Flippers',
+                               'not-moonpearl': 'No Moonpearl',
                                powder: 'Powder',
                                quake: 'Quake',
                                'red-bomb': 'Red Bomb',
@@ -508,6 +510,8 @@ export default {
                },
                techniques: {
                        heading: 'Techniques',
+                       lastModified: 'Last modified: {{ date, L }}',
+                       requirements: 'Requires: ',
                        rulesetCodes: {
                                competitive: 'COM',
                                mg: 'MG',
index 83e5b9045416cb42b541a0b6cd1f9b96dbca1be1..879253c893aba8a92603c913bdfc52b8521ffdad 100644 (file)
@@ -193,6 +193,7 @@ h1 {
 }
 
 .zelda-icon {
+       position: relative;
        display: inline-flex;
        align-items: center;
        width: 2em;
@@ -203,4 +204,27 @@ h1 {
                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;
+               }
+       }
 }
index 599cd15111a0d81184b591232d4a9dd368891ec5..38376a1b4429b47363036a5a802e08dfde1a8e7d 100644 (file)
@@ -2,7 +2,7 @@
        path {
                fill: red;
                stroke: black;
-               stroke-width: 2px;
+               stroke-width: 1px;
                vector-effect: non-scaling-stroke;
        }
 }
index 8355e70b61a8baccb281fe851b7e46fef17f2da9..eff0c62ee9ca1f7dbe04a2c5cd2f7101081bd524 100644 (file)
        }
 }
 
+.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;
 }