Nous ne développons plus activement de fonctionnalités pour cette application. Les PR seront acceptés pour les corrections de bogues, les traductions et les mises à jour de contenu. Le développement actif de fonctionnalités est en cours sur https://github.com/zooniverse/front-end-monorepo/
Pour éviter d'avoir à installer Node.js ou toute autre dépendance, vous pouvez tout exécuter avec Docker et Docker Compose.
docker-compose build
créera une image Docker locale et exécutera npm ci
. Exécutez-le chaque fois que vous modifiez les dépendances dans package.json
.
docker-compose up
démarre un serveur Web de développement qui écoute sur le port 3735.
docker-compose down
arrête le serveur de développement.
docker-compose run --rm shell
démarre un conteneur exécutant un shell, par exemple. pour exécuter des tests.
Assurez-vous d'avoir Node 8 et npm
5 ou supérieur. Il est recommandé de gérer vos installations Node avec nvm .
npm ci
installe les dépendances.
npm start
construit et exécute le site localement.
npm ci --legacy-peer-deps
. Veuillez consulter le numéro 6155 pour plus de détails.
La racine /
est redirigée vers www.zooniverse.org car cette application frontend n'est plus utilisée pour la page d'accueil. Pointez votre navigateur vers un sous-chemin pour afficher cette application exécutée localement.
Ouvrez le navigateur Web de votre choix et accédez à https://localhost:3735/lab
Si vous souhaitez vous connecter via l'API Panoptes et afficher les pages authentifiées, vous devrez alors configurer et utiliser https://local.zooniverse.org:3735/lab
au lieu d'utiliser localhost:3735. Sinon, vous rencontrerez des erreurs CORS. (Vous devez ajouter le nom d'hôte à votre fichier hosts, en pointant vers local. Les instructions se trouvent sur notre Stackoverflow.)
Dépannage : le navigateur Web bloque le site Web local
Le problème : lorsque j'essaie d'afficher localhost:3735 ou local.zooniverse.org:3735, mon navigateur Web m'arrête et affiche un écran d'avertissement.
Exemples d'erreurs : « Votre connexion n'est pas privée / NET::ERR_CERT_AUTHORITY_INVALID » sur Chrome 104 ; « Avertissement : risque de sécurité potentiel à venir » sur Firefox 103 ; "Cette connexion n'est pas privée" sur Safari 15.4.
La cause : le serveur Web local exécute HTTPS et utilise un certificat auto-signé. Les navigateurs Web modernes considèrent ces certificats comme très peu fiables et constituent un indicateur possible d'une attaque de l'homme du milieu.
La ou les solutions :
thisisunsafe
) n'importe où dans la fenêtre pour contourner temporairement l'avertissement ; ouL'application peut être configurée à l'aide des variables d'environnement suivantes :
NODE_ENV
- définit l'environnement du code et détermine s'il convient d'appliquer des optimisations de production au code fourni, et quel ensemble de valeurs par défaut appliquer, par exemple l'URL de l'hôte API, l'URL de l'hôte Talk, etc.PANOPTES_API_APPLICATION
- définit l'ID d'application à utiliser lors des demandes d'authentification à l'API Panoptes. La valeur par défaut est celle définie par NODE_ENV
.PANOPTES_API_HOST
- définit l'URL de l'instance de l'API Panoptes. La valeur par défaut est celle définie par NODE_ENV
.STAT_HOST
- définit l'URL de l'instance de l'API Stats. La valeur par défaut est celle définie par NODE_ENV
.SUGAR_HOST
- définit l'URL de l'instance de l'API Sugar. La valeur par défaut est celle définie par NODE_ENV
.TALK_HOST
- définit l'URL de l'instance de l'API Talk. La valeur par défaut est celle définie par NODE_ENV
. scripts
package.json
; afin de les remplacer, vous devrez modifier package.json
.NODE_ENV
, voir config.js
dans panoptes-javascript-client.Les nouveaux PR GitHub au sein de l'organisation Zooniverse seront organisés par Jenkins dans le cadre du processus CI. Une fois CI terminé, vos modifications doivent être transférées sur https://pr-{PR-Number}.pfe-preview.zooniverse.org. Jenkins expire parfois avant de terminer la construction. Si une build PR échoue, utilisez le lien vers Jenkins (depuis votre PR) pour vous connecter et essayez de redémarrer la build.
Pour tester avec des données de production, vous pouvez ajouter env=production
à votre URL de développement, par exemple localhost:3735/projects?env=production
. Notez qu’il est supprimé à chaque actualisation de page.
Toutes les bonnes choses sont dans ./app . Commencez à ./app/main.cjsx
Nous avons comparé notre code JavaScript à une version modifiée du guide de style AirBnB. Veuillez pelucher vos modifications avec eslint, en utilisant le fichier .eslintrc à la racine de ce dépôt. Si vous avez des questions, n'hésitez pas à nous les poser sur GitHub.
Lors de l'édition, faites de votre mieux pour suivre les conventions de style et d'architecture déjà utilisées par le projet. La base de code est vaste et les styles ont évolué au cours de son développement. Jetez un œil à zooniverse/front-end-monorepo pour avoir une idée de nos conventions d'organisation des composants.
Essayez npm ci
pour rafraîchir vos dépendances. Et lisez les avertissements, ils devraient vous indiquer si vous utilisez la mauvaise version de Node ou de npm ou s'il vous manque des dépendances. Si vous utilisez docker-compose
pour créer et tester le site, vous ne devriez rencontrer aucun problème avec la version Node, mais docker-compose build
construira une nouvelle image avec un nouveau npm ci
.
Si vous écrivez un nouveau composant, écrivez un test. Chaque composant doit avoir son propre fichier .spec.js
. Le lanceur de tests est Mocha et Enzyme est disponible pour tester les composants React. Mocha génère une erreur ( Illegal import declaration
) lors de la compilation de fichiers coffeescript contenant des instructions d'importation ES6 avec des chaînes de modèle. Convertissez ces importations en instructions require
. Vous pouvez exécuter les tests avec npm test
.
Le déploiement est géré par Github Action.
À l’ouverture des demandes d’extraction, une action Github est déclenchée pour se déployer vers un emplacement intermédiaire de branche. L'emplacement de stockage du blob dépend du numéro de demande d'extraction, par exemple https://pr-5926.pfe-preview.zooniverse.org
.
Lors du push to master, une action Github est déclenchée pour être déployée sur la mise en scène principale trouvée sur https://master.pfe-preview.zooniverse.org
.
Les déploiements en production sont déclenchés par une mise à jour vers laquelle commit la balise production-release
est pointée. Cette balise doit être mise à jour via des opérations de chat, puis une action Github s'exécutera pour créer et télécharger les fichiers sur notre fournisseur de cloud disponible sur https://www.zooniverse.org
. Le déploiement de production peut être exécuté ad hoc dans l'onglet actions selon les besoins si vous disposez des autorisations appropriées sur le référentiel, mais ne le faites qu'en cas d'urgence.
Tout ce qui concerne le classificateur.
Composants liés aux collections.
Divers composants génériques et réutilisables.
Les éléments de mise en page au niveau de l'application vont ici. Si cela affecte l’en-tête principal du site, le pied de page principal du site ou la présentation du contenu principal du site, c’est ici qu’il se trouve.
Fonctions et données individuelles réutilisées dans tous les composants.
C’est là que réside la majeure partie de l’application. Idéalement, chaque route pointe vers un composant de page chargé de récupérer les données et de gérer toutes les actions que l'utilisateur peut effectuer sur ces données. Ce composant de page utilise ces données pour restituer l'interface utilisateur avec des composants stupides, en transmettant les actions si nécessaire.
Initialement destiné à contenir des composants isolés qui ne seraient en réalité réutilisés nulle part. Ceux-ci appartiennent probablement plus près de l’endroit où ils sont réellement utilisés.
Vues du sujet (TODOC : quel est le rapport avec Talk/collections ?)
Composants liés à la conversation.
Les fichiers ici seront copiés dans le répertoire de sortie lors de la construction.
Chaque classe de composants de tâche doit avoir quelques composants statiques :
Summary
: Affiche le résumé post-classification de l'annotation des tâches.
Editor
: Le composant utilisé pour modifier la tâche de workflow dans le générateur de projet.
Il existe également quelques points d'ancrage dans le reste de l'interface de classification, si la tâche doit être rendue en dehors de la zone de tâches.
BeforeSubject
: Contenu HTML à afficher avant l'image du sujet pendant la tâche.
InsideSubject
: Contenu SVG à afficher sur l'image du sujet pendant la tâche.
Contenu HTML AfterSubject
devant apparaître après l'image du sujet pendant la tâche.
Ces hooks peuvent être préfixés par Persist
, ce qui les fera apparaître avec la tâche et persistera même après que l'utilisateur soit passé à la tâche suivante.
Persist{Before,After}Task
fonctionne de la même manière, mais pour la zone de tâches. Les hooks non persistants sont inutiles pour la zone de tâches.
Chaque composant nécessite également quelques méthodes statiques :
getDefaultTask
: renvoie la description de la tâche à utiliser par défaut lorsqu'un utilisateur ajoute la tâche à un workflow dans le générateur de projet.
getTaskText
: étant donné une tâche, cela renvoie une description textuelle de base de la tâche (par exemple la question dans une tâche de question, l'instruction dans une tâche de dessin, etc.)
getDefaultAnnotation
: L'annotation à générer lorsque le classificateur commence la tâche
isAnnotationComplete
: Étant donné une tâche et une annotation, cela détermine si le classificateur permettra ou non à l'utilisateur de passer à la tâche suivante.
testAnnotationQuality
: étant donné l'annotation de l'utilisateur et une annotation "gold standard" connue pour la même tâche, cela renvoie un nombre compris entre 0 (totalement faux) et 1 (totalement correct) indiquant à quel point l'annotation de l'utilisateur est proche de la norme.
Assurez-vous d'appeler this.props.onChange
avec la tâche mise à jour lorsqu'elle change.
Certaines méthodes statiques, appelées depuis le composant MarkInitializer
, qui contrôlent les valeurs de la marque lors de la première action de création de marque de l'utilisateur :
defaultValues
: Juste quelques valeurs par défaut pour la marque.
initStart
: Pour chaque mousedown/touchstart jusqu'à ce que isComplete
renvoie true, renvoie les valeurs de la marque.
initMove
: Pour chaque mousemove/touchmove, renvoie de nouvelles valeurs pour la marque.
initRelease
: Pour chaque mouseup/touchend, renvoie de nouvelles valeurs pour la marque.
isComplete
: La marque est-elle terminée ? Certaines marques nécessitent plusieurs interactions avant que l'initialiseur n'abandonne le contrôle.
initValid
: Si une marque n'est pas valide (par exemple un rectangle de largeur ou de hauteur nulle), elle sera automatiquement détruite.
Quelques composants d'assistance sont DrawingToolRoot
qui gère les états sélectionnés/désactivés et restitue les fenêtres contextuelles de sous-tâches, ainsi que DeleteButton
et DragHandle
, qui restituent des contrôles cohérents pour les outils de dessin. Il existe également une fonction deleteIfOutOfBounds
qui doit être appelée après tout déplacement d'une marque entière.
React exige que chaque composant d'un tableau ait une key
unique pour les frères et sœurs. Lors du rendu de tableaux d'éléments qui n'ont pas d'ID (annotations, réponses), fournissez une propriété _key
aléatoire si elle n'existe pas. Assurez-vous que les propriétés préfixées par un trait de soulignement ne sont pas conservées. C'est automatique avec la classe JSONAPIClient.Model
.
< ul >
{ for item in things
item . _key ?= Math . random ()
< li key = { item . _key }>{ item . label }</ li >}
</ ul >
Il y en a bon composants malheureux (avec le recul) pour aider avec les valeurs asynchrones. Ils prennent la fonction @props.children
, ce qui semble un peu chevalin mais fonctionne plutôt bien. La plupart des données demandées sont mises en cache localement, elles sont donc généralement sûres, mais si vous remarquez que la même demande est effectuée plusieurs fois de suite, c'est un bon point de départ pour commencer à rechercher les appels redondants. Voici un exemple de nouveau rendu lorsqu'un projet change, ce qui entraîne une vérification des propriétaires du projet.
< ChangeListener target = { @props . project }>{ =>
< PromiseRenderer promise = { @props . project . get ( ' owners ' )}>{([owner]) =>
if owner is @props . user
< p > This project is yours.</ p >
else
< p > This project belongs to { owner . display_name }.</ p >
}</ PromiseRenderer >
}</ ChangeListener >
N'écrivez pas de nouveau code à l'aide de ChangeListener
ou PromiseRenderer
.
Si cela est raisonnable, remplacez les instances ChangeListener
et PromiseRenderer
par l'état du composant dans le code sur lequel vous travaillez. C'est plus détaillé, mais c'est plus lisible, et cela nous rapprochera du rendu sur le serveur à l'avenir.
Incluez tout CSS requis pour la fonctionnalité d'un composant en ligne avec le composant, sinon conservez-le dans un fichier séparé, un par composant. Pour un composant donné, choisissez un nom de classe de niveau supérieur unique pour ce composant et imbriquez les classes enfants en dessous. Conservez les styles de base et les variables communs dans common.styl . Formatage du stylet : oui, deux points, pas de points-virgules, pas d'accolades. @extends
en haut, puis les propriétés (par ordre alphabétique), puis les sélecteurs descendants. Préférez l’utilisation de display: flex
et flex-wrap: wrap
des requêtes multimédias explicites dans la mesure du possible.
Notre CSS est devenu vraiment énorme, nous essayons donc BEM pour l'organisation.
// <special-button.styl>
.special-button
background : red
color : white
.special-button__icon
width : 1 em ;
// <special-container.styl>
.special-container
margin : 1 em 1 vw
.special-container__button
border : 1 px solid
Nous migrons de coffeescript vers ES6. Cela peut être fait progressivement en écrivant un nouveau composant ou en réécrivant un composant existant dans ES6. Quelques pièges doivent être mentionnés :
L'opérateur existentiel n'existe pas dans ES6. Soit comparez explicitement à null
, soit utilisez !!thing
si cela doit juste être véridique.
Les classes ES6 natives sont préférées car React.createClass()
est obsolète. Cependant, si le composant existant s'appuie sur des mixins, envisagez d'utiliser createReactClass()
.
Les mixins sont obsolètes et ne sont pas pris en charge avec les classes natives, ne les utilisez donc pas dans de nouveaux composants.
Utilisez des backticks pour importer des composants ES6 dans des composants coffeescript :
`import NewComponent from './new-component'`
Un fichier de configuration ESLint est configuré à la racine du référentiel que vous pouvez utiliser avec votre éditeur de texte pour lint ES6 et utiliser le guide de style React d'Airbnb.
Un guide sur l'écriture de classes natives par rapport à l'utilisation createReactClass()
Voir la bibliothèque panoptes-client : https://www.npmjs.com/package/panoptes-client.
Le format de la valeur d'une annotation dépend de la tâche utilisée pour la générer.
single : L'index de la réponse choisie.
multiple : Un tableau des indices des réponses choisies (dans l'ordre dans lequel elles ont été choisies).
dessin : un tableau de marques d'outils de dessin (dont les descriptions suivent ci-dessous).
enquête : un ensemble d'identifications en tant qu'objets. A chaque identification un choice
(l'identifiant de l'animal identifié) et answers
, un objet. Chaque clé dans answers
est l'identifiant d'une question. Si cette question autorise plusieurs réponses, la valeur sera un tableau d’ID de réponse, sinon un seul ID de réponse.
crop : un objet contenant les x
, y
, width
et height
de la région recadrée.
texte : une chaîne.
combo : un sous-tableau d'annotations.
liste déroulante : un tableau d'objets où la value
de chaîne fait référence à la réponse à la question correspondante et l' option
booléenne indique que la réponse figurait dans la liste des options.
Toutes les coordonnées sont relatives au coin supérieur gauche de l'image.
Toutes les marques ont un tool
, qui est l'index de l'outil (par exemple workflow.tasks.T0.tools[0]
) utilisé pour créer la marque.
Toutes les marques contiennent un frame
, qui est l'index du cadre sujet (par exemple subject.locations[0]
) sur lequel la marque a été faite.
Si des tâches details
sont définies pour un outil, ses marques auront un tableau details
de sous-classifications (chacune avec une value
, suivant les descriptions ci-dessus).
La valeur de l'annotation du dessin est la suivante :
point : les coordonnées x
et y
.
ligne : les coordonnées de début ( x1
, y1
) et de fin ( x2
, y2
).
polygone : un tableau d'objets, chacun contenant les coordonnées x
et y
d'un sommet. Si la marque n'a pas été explicitement fermée par l'utilisateur, auto_closed
vaut true
.
rectangle : la coordonnée x
, y
du point supérieur gauche du rectangle ainsi que sa width
et height
.
cercle : Les coordonnées x
et y
du centre du cercle et son rayon r
.
ellipse : les coordonnées x
et y
du centre de l'ellipse, ses rayons rx
et ry
et l' angle
de rx
par rapport à l'axe x en degrés (dans le sens inverse des aiguilles d'une montre à partir de 3h00).
bézier : identique à un polygone, mais chaque point à index impair est la coordonnée du point de contrôle d'une courbe de Bézier quadratique.
colonne : le pixel x
le plus à gauche et la width
de la sélection de colonne.
Merci à BrowserStack d'avoir pris en charge l'open source et de nous avoir permis de tester ce projet sur plusieurs plateformes.