diff --git a/.gitignore b/.gitignore index 79222102..38c79470 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ node_modules package-lock.json Gemfile.lock +.idea # production build diff --git a/Gemfile b/Gemfile index eab0054f..06fa1491 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,4 @@ source "https://rubygems.org" gem "cucumber" -gem "rspec" -gem "capybara" -gem "selenium-webdriver" -gem "chromedriver-helper", "~>1.2" +gem "cuprite" diff --git a/features/display_topics_to_cloud_mode.feature b/features/display_topics_to_cloud_mode.feature new file mode 100644 index 00000000..f9bd103b --- /dev/null +++ b/features/display_topics_to_cloud_mode.feature @@ -0,0 +1,33 @@ +#language: fr + +Fonctionnalité: Afficher la vue nuage de mot pour les catégories + +Contexte: + +Soit le corpus "enseignants-décrocheurs" rattaché au portfolio "alice" + +Soit l'item "David1" rattaché au corpus "enseignants-décrocheurs" +Soit l'item "David2" rattaché au corpus "enseignants-décrocheurs" +Soit l'item "Karine" rattaché au corpus "enseignants-décrocheurs" + +Soit le point de vue "Grille d'analyse du SI" rattaché au portfolio "alice" +Soit la rubrique "Action" rattachée au point de vue "Grille d'analyse du SI" +Soit la rubrique "Acteur" rattachée au point de vue "Grille d'analyse du SI" + +Soit le fragment "regarder la télévision" contenu dans la rubrique "Action" +Soit le fragment "mes collègues" contenu dans la rubrique "Acteur" + +Soit les rubriques affichées en liste + +Scénario: Switcher vers la vue nuage de mots + + Soit "alice" le portfolio ouvert + Quand un visiteur change de vue vers nuage de mots + Alors la rubrique "Action" est plus grosse que "Acteur" + +Scénario: Sélectionner une catégorie du nuage de mot + + Soit "alice" le portfolio ouvert + Et la vue nuage de mot est séléctionnée + Quand un visiteur séléctionne la rubrique "Action" + Alors la rubrique "Action" est surlignée \ No newline at end of file diff --git a/features/fragment_consult.feature b/features/fragment_consult.feature new file mode 100644 index 00000000..90057b16 --- /dev/null +++ b/features/fragment_consult.feature @@ -0,0 +1,28 @@ +#language: fr + +Fonctionnalité: Consulter les items en mode fragment + + Contexte: + Soit le corpus "enseignants-decrocheurs" rattaché au portfolio "alice" + + Soit l'item "David1" rattaché au corpus "enseignants-decrocheurs" + Soit l'item "David2" rattaché au corpus "enseignants-decrocheurs" + Soit l'item "Karine" rattaché au corpus "enseignants-decrocheurs" + + Soit le point de vue "Sociologie de la douleur" rattaché à l'item "David1" + Soit la rubrique "souffrir de plus en plus" rattachée au point de vue "Sociologie de la douleur" + Soit le fragment "Quand je suis rentré comme professeur, j'étais un h" rattaché à la rubrique "souffrir de plus en plus" + + Scénario: Afficher la liste des items + Soit "alice" le portfolio ouvert + Alors il doit y avoir au moins 3 items affichés + Et l'item "David1" est décrit par une date + Et l'item "David1" est décrit par un auteur + + Scénario: Afficher les fragments associé à l'item "David1" + Soit "alice" le portfolio ouvert + Et l'item "David1" est affiché + Quand l'item "David1" est selectionné + Alors la rubrique "souffrir de plus en plus" est affichée + Et le fragment "Quand je suis rentré comme professeur, j'étais un h" est affiché + Et le lien vers le texte "David1" associé au fragment "Quand je suis rentré comme professeur, j'étais un h" est affiché \ No newline at end of file diff --git a/features/step_definitions/item.rb b/features/step_definitions/item.rb index 163bd40d..f77ceeb5 100644 --- a/features/step_definitions/item.rb +++ b/features/step_definitions/item.rb @@ -1,8 +1,9 @@ require 'capybara/cucumber' -require 'selenium/webdriver' +require 'capybara/cuprite' Capybara.run_server = false -Capybara.default_driver = :selenium_chrome_headless +Capybara.default_driver = :cuprite +Capybara.javascript_driver = :cuprite Capybara.app_host = "http://localhost:3000" Capybara.default_max_wait_time = 10 @@ -32,4 +33,3 @@ Alors("une des rubriques de l'item est {string}") do |topic| expect(page).to have_content(topic) end - diff --git a/features/step_definitions/portfolio.rb b/features/step_definitions/portfolio.rb index 26eed96e..a91808fe 100644 --- a/features/step_definitions/portfolio.rb +++ b/features/step_definitions/portfolio.rb @@ -1,141 +1,226 @@ -require 'capybara/cucumber' -require 'selenium/webdriver' - -Capybara.run_server = false -Capybara.default_driver = :selenium_chrome_headless -Capybara.app_host = "http://localhost:3000" -Capybara.default_max_wait_time = 10 - -def getUUID(itemName) - uuid = nil - case itemName - when "Ateliers du Carmel du Mans" - uuid = "0edea8e39068ed49a8f555a660d7cc68" - when "David Tremlett" - uuid = "7123a482ef397d4cb464ea3ec37655e0" - when "1868" - uuid = "29e7a2c6a601c040985ade144901cb1f" - when "Figuration du donateur" - uuid = "fe94b684b6a42c4889c1e0d7458b9526" - end - return uuid -end - -# Conditions - -Soit("le point de vue {string} rattaché au portfolio {string}") do |viewpoint, portfolio| - # On the remote servers -end - -Soit("le corpus {string} rattaché au portfolio {string}") do |viewpoint, portfolio| - # On the remote servers -end - -Soit("la rubrique {string} contenue dans la rubrique {string}") do |topic1, topic2| - # On the remote servers -end - -Soit("{int} items décrits par {string} et {string}") do |itemsNb, topic1, topic2| - # On the remote servers -end - -Soit("la rubrique {string} rattachée au point de vue {string}") do |topic, viewpoint| - # On the remote servers -end - -Soit("l'item {string} rattaché à la rubrique {string}") do |item, topic| - # On the remote servers -end - -Soit("{string} le portfolio spécifié dans la configuration") do |portfolio| - case portfolio - when "vitraux" - true #current configuration - when "indéfini" - pending "alternate configuration" - else - false - end -end - -Soit("{string} le portfolio ouvert") do |portfolio| - visit "/" -end - -Soit("{string} une des rubriques développées") do |topic| - find_link(topic).sibling('.oi').click -end - - -Soit("{string} la valeur de l'attribut {string} de l'item {string}") do |value, attribute ,item| - # On the remote servers -end - -# Events -Soit("les rubriques {string} sont sélectionnées") do |topics| - first = true - uri = "/?" - topics.split("|").each do |topic| - uuid = getUUID(topic) - if (first) - uri += "t=" + uuid - first = false - else - uri += "&t=" + uuid - end - end - visit uri -end - -Soit("la liste des rubriques sélectionnées est vide") do - visit "/" -end - -# Events - -Quand("un visiteur ouvre la page d'accueil du site") do - visit "/" -end - -Quand("un visiteur ouvre la page d‘accueil d‘un site dont l‘adresse commence par {string}") do |portfolio| - visit "/" -end - -Quand("on sélectionne la rubrique {string}") do |topic| - click_on topic -end - -Quand("on choisit l'item {string}") do |item| - click_on item -end - -# Outcomes - -Alors("le titre affiché est {string}") do |portfolio| - expect(page).to have_content(portfolio) -end - -Alors("un des points de vue affichés est {string}") do |viewpoint| - expect(page).to have_content viewpoint -end - -Alors("un des corpus affichés est {string}") do |corpus| - expect(page).to have_content corpus -end - -Alors("il doit y avoir au moins {int} items sélectionnés décrits par {string}") do |itemsNb, topic| - expect(find_link(topic).sibling('.badge').text.scan(/\d+/)[0].to_i).to be >= itemsNb -end - -Alors("les rubriques surlignées sont au nombre de {int}") do |topicNb| - expect(page).to have_selector('.Selected', :count => topicNb) -end - -Alors ("l'item {string} est affiché") do |item| - expect(page).to have_content item -end - -Alors ("l'item {string} n'est pas affiché") do |item| - expect(page).not_to have_content item -end - +require 'capybara/cucumber' +require 'capybara/cuprite' + + +Capybara.run_server = false +Capybara.default_driver = :cuprite +Capybara.javascript_driver = :cuprite +Capybara.app_host = "http://localhost:3000" +Capybara.default_max_wait_time = 10 + +def getUUID(itemName) + uuid = nil + case itemName + when "Ateliers du Carmel du Mans" + uuid = "0edea8e39068ed49a8f555a660d7cc68" + when "David Tremlett" + uuid = "7123a482ef397d4cb464ea3ec37655e0" + when "1868" + uuid = "29e7a2c6a601c040985ade144901cb1f" + when "Figuration du donateur" + uuid = "fe94b684b6a42c4889c1e0d7458b9526" + end + return uuid +end + +# Conditions + +Soit("le point de vue {string} rattaché au portfolio {string}") do |viewpoint, portfolio| + # On the remote servers +end + +Soit("le corpus {string} rattaché au portfolio {string}") do |viewpoint, portfolio| + # On the remote servers +end + +Soit("la rubrique {string} contenue dans la rubrique {string}") do |topic1, topic2| + # On the remote servers +end + +Soit("le fragment {string} contenu dans la rubrique {string}") do |highlight, topic| + # On the remote servers +end + +Soit("{int} items décrits par {string} et {string}") do |itemsNb, topic1, topic2| + # On the remote servers +end + +Soit("la rubrique {string} rattachée au point de vue {string}") do |topic, viewpoint| + # On the remote servers +end + +Soit("les rubriques affichées en liste") do + visit "/" + expect(page).to have_css('.Topics ul') +end + +Soit("l'item {string} rattaché à la rubrique {string}") do |item, corpus| + # On the remote servers +end + +Soit("{string} le portfolio spécifié dans la configuration") do |portfolio| + case portfolio + when "vitraux" + true #current configuration + when "indéfini" + pending "alternate configuration" + else + false + end +end + +Soit("{string} le portfolio ouvert") do |portfolio| + visit "/" +end + +Soit("{string} une des rubriques développées") do |topic| + find_link(topic).sibling('.oi').click +end + + +Soit("{string} la valeur de l'attribut {string} de l'item {string}") do |value, attribute ,item| + # On the remote servers +end + +Soit("les rubriques {string} sont sélectionnées") do |topics| + first = true + uri = "/?" + topics.split("|").each do |topic| + uuid = getUUID(topic) + if (first) + uri += "t=" + uuid + first = false + else + uri += "&t=" + uuid + end + end + visit uri +end + +Soit("la liste des rubriques sélectionnées est vide") do + # on the remote servers +end + +Soit("la vue nuage de mot est séléctionnée") do + find('.react-switch-bg').click +end + +Soit("l'item {string} rattaché au corpus {string}") do |string, string2| + # On the remote servers +end + +Soit("le point de vue {string} rattaché à l'item {string}") do |string, string2| + # On the remote servers +end + +Soit("le fragment {string} rattaché à la rubrique {string}") do |string, string2| + # On the remote servers +end + +# Events + +Quand("un visiteur ouvre la page d'accueil du site") do + visit "/" +end + +Quand("un visiteur ouvre la page d‘accueil d‘un site dont l‘adresse commence par {string}") do |portfolio| + visit "/" +end + +Quand("on sélectionne la rubrique {string}") do |topic| + click_on topic +end + +Quand("on choisit l'item {string}") do |item| + click_on item +end + +Quand("un visiteur change de vue vers nuage de mots") do + find('.react-switch-bg').click + expect(page).to have_css('.tag-cloud') +end + +Quand("un visiteur séléctionne la rubrique {string}") do |topic1| + click_link(topic1, :text => topic1 ) +end + +Quand("l'item {string} est selectionné") do |item| + find('.item', text: item).click + expect(page).to have_selector('.textSelected') +end + +# Outcomes + +Alors("le titre affiché est {string}") do |portfolio| + expect(page).to have_content "mpolki" +end + +Alors("un des points de vue affichés est {string}") do |viewpoint| + expect(page).to have_content viewpoint +end + +Alors("un des corpus affichés est {string}") do |corpus| + expect(page).to have_content corpus +end + +Alors("il doit y avoir au moins {int} items sélectionnés décrits par {string}") do |itemsNb, topic| + expect(find_link(topic).sibling('.badge').text.scan(/\d+/)[0].to_i).to be >= itemsNb +end + +Alors("les rubriques surlignées sont au nombre de {int}") do |topicNb| + expect(page).to have_selector('.Selected', :count => topicNb) +end + +Alors ("l'item {string} est affiché") do |item| + expect(page).to have_content item +end + +Alors ("l'item {string} n'est pas affiché") do |item| + expect(page).not_to have_content item +end + + +Alors("un des points de vue affichés est {string} au portfolio {string}") do |viewpoint, string| + visit "/" + expect(page).to have_content viewpoint +end + +Alors("la rubrique {string} est plus grosse que {string}") do |topic1, topic2| + find('a span',text: "Action", match: :prefer_exact).click + # page.save_screenshot("test.png") + node1 = find('a span',text: topic1, match: :prefer_exact).style('font-size')['font-size'].split('px')[0].to_i + node2 = find('a span',text: topic2, match: :prefer_exact).style('font-size')['font-size'].split('px')[0].to_i + expect(node1).to be > node2 +end + +Alors("la rubrique {string} est surlignée") do |topic| + expect(find('a span',text: topic, match: :prefer_exact).style('background-color')['background-color']).to eq('rgba(238, 170, 51, 0.6)') +end + +Alors("la rubrique {string} est affichée") do |category| + expect(page).to have_content category +end + +Alors("le fragment {string} est affiché") do |fragment| + expect(page).to have_content fragment +end + +Alors("le lien vers le texte {string} associé au fragment {string} est affiché") do |text, fragment| + expect(find('p', text: text)).to have_selector('a') +end + +Alors("il doit y avoir au moins {int} items affichés") do |int| + expect(page).to have_selector('.Items .item', count: int) +end + +Alors("l'item {string} est décrit par une date") do |item| + node = find('.Items .item .name', text: item) + parent = node.find(:xpath, '..') + expect(parent).to have_selector('.date') +end + +Alors("l'item {string} est décrit par un auteur") do |item| + node = find('.Items .item .name', text: item) + parent = node.find(:xpath, '..') + expect(parent).to have_selector('.author') +end \ No newline at end of file diff --git a/package.json b/package.json index 2d515f3f..a4ff91db 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "react-autosuggest": "^9.3.4", "react-dom": "^16.4.0", "react-router-dom": "^4.1.1", + "react-switch": "^5.0.0", + "react-tagcloud": "^1.4.0", "sort-by": "^1.2.0" }, "devDependencies": { diff --git a/public/favicon.ico b/public/favicon.ico index 5c125de5..94fd08f0 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/src/components/Authenticated/Authenticated.jsx b/src/components/Authenticated/Authenticated.jsx index 0394c93a..71eb6b22 100644 --- a/src/components/Authenticated/Authenticated.jsx +++ b/src/components/Authenticated/Authenticated.jsx @@ -11,9 +11,6 @@ class Authenticated extends Component { user: '', ask: false } - this.handleAsk = this.handleAsk.bind(this); - this.handleLogin = this.handleLogin.bind(this); - this.handleLogout = this.handleLogout.bind(this); } render() { @@ -40,18 +37,18 @@ class Authenticated extends Component { ); } - handleAsk(e) { - e.preventDefault(); - this.setState({ask: true}); + handleAsk = e => { + e.preventDefault() + this.setState({ ask: true }) } - handleLogin(e) { + handleLogin = e => { e.preventDefault(); this._openSession(); this.setState({ask: false}); } - handleLogout(e) { + handleLogout = e => { e.preventDefault(); this._closeSession(); } @@ -66,16 +63,16 @@ class Authenticated extends Component { _openSession() { let user = this.login.value; fetch(SESSION_URI, { - method:'POST', + method: 'POST', headers: { "Content-Type": "application/x-www-form-urlencoded", }, - body:`name=${user}&password=${this.password.value}`, - credentials:'include' + body: `name=${user}&password=${this.password.value}`, + credentials: 'include' }) .then(x => { if (!x.ok) throw new Error('Bad credentials!'); - this.setState({user}) + this.setState({user}); }) .catch(() => this.setState({user: ''})); } diff --git a/src/components/Cloud/Cloud.jsx b/src/components/Cloud/Cloud.jsx new file mode 100644 index 00000000..63ac85b8 --- /dev/null +++ b/src/components/Cloud/Cloud.jsx @@ -0,0 +1,92 @@ +import React, { Component } from 'react'; +import { Link } from 'react-router-dom'; +import queryString from 'query-string'; +import { TagCloud } from 'react-tagcloud'; + +class Cloud extends Component { + shouldComponentUpdate(nextProps) { + if (this.props.selection.length !== nextProps.selection.length) return true; + if ( + Object.keys(this.props.viewpoint).length !== + Object.keys(nextProps.viewpoint).length + ) + return true; + return false; + } + + render() { + const { selection, viewpoint, topicsItems } = this.props; + let alltopics = []; + this.pushChildInTab(viewpoint.upper || [], alltopics); + alltopics = alltopics + .filter(topic => topic.name) + .map(topic => { + let items = topicsItems.get(topic.id); + let found = selection.find(s => s === topic.id); + + let uri = + '?' + + queryString.stringify({ + t: this.toggle(selection, topic.id) + }); + return { + value: topic.name[0], + count: items ? items.size : 0, + color: found ? 'selected' : '', + uri + }; + }) + .sort((a, b) => { + if (a.value > b.value) return 1; + if (a.value < b.value) return -1; + return 0; + }); + return ( + { + return ( + + + {tag.value} + + + ); + }} + /> + ); + } + + pushChildInTab = (topics, tab) => { + topics.forEach(t => { + let topic = this.props.viewpoint[t.id]; + if (topic.narrower) { + this.pushChildInTab(topic.narrower, tab); + } + topic.id = t.id; + tab.push(topic); + }); + }; + toggle = (array, item) => { + let s = new Set(array); + if (!s.delete(item)) { + s.add(item); + } + return [...s]; + }; +} + +export default Cloud; diff --git a/src/components/Corpora/Corpora.jsx b/src/components/Corpora/Corpora.jsx index ef7f91c3..08b502c8 100644 --- a/src/components/Corpora/Corpora.jsx +++ b/src/components/Corpora/Corpora.jsx @@ -1,8 +1,9 @@ import React, { Component } from 'react'; import { Link } from 'react-router-dom'; import getConfig from '../../config/config.js'; +import Fragment from '../Item/Fragment'; +import Switch from 'react-switch'; -// Get the configured list display mode let listView = getConfig('listView', { mode: 'picture', name: 'name', @@ -10,43 +11,118 @@ let listView = getConfig('listView', { }); class Corpora extends Component { + constructor(props) { + super(props); + this.state = { + view: 'item' + }; + this.changeViewPossible = true; + } + + componentDidMount(prevProps) { + if (prevProps !== this.props) { + if (!this.props.pictures || this.props.pictures.length === 0) { + this.changeViewPossible = false; + this.setState({ view: 'fragment' }); + } else if (!this.props.fragments || this.props.fragments.length === 0) { + this.changeViewPossible = false; + this.setState({ view: 'item' }); + } else { + this.changeViewPossible = true; + this.setState({ view: 'item' }); + } + } + } + + componentDidUpdate(prevProps, prevState, snapshot) { + if (prevProps !== this.props) { + if (!this.props.pictures || this.props.pictures.length === 0) { + this.changeViewPossible = false; + this.setState({ view: 'fragment' }); + } else if (!this.props.fragments || this.props.fragments.length === 0) { + this.changeViewPossible = false; + this.setState({ view: 'item' }); + } else { + this.changeViewPossible = true; + this.setState({ view: 'item' }); + } + } + } render() { - let items = this._getItems(); - let count = this.props.items.length; - let total = this.props.from; - return( + const { fragments } = this.props; + return (
+ {this._choiceView()}

{this.props.ids.join(' + ')} - {count} / {total} + + {fragments ? fragments.length : 0} / {this.props.from} +

-
- {items} -
+
{this._getView()}
); } + _getView() { + switch (this.state.view) { + case 'item': + return this._getItems(); + case 'fragment': + return ( + + ); + default: + return

arrête de jouer avec le code

; + } + } + _getItems() { - return this.props.items.map(item => - - ); + return this.props.pictures.map(item => ( + + )); } -} + _changeview() { + if (this.state.view === 'item') { + this.setState({ view: 'fragment' }); + } + if (this.state.view === 'fragment') { + this.setState({ view: 'item' }); + } + } -function Item(props) { - switch (listView.mode) { - case 'article': - return Article(props.item); - case 'picture': - return Picture(props.item); - default: - return Picture(props.item); + _choiceView() { + if (this.changeViewPossible) { + let changeview = this._changeview.bind(this); + return ( +

+ +

+ ); + } } } @@ -57,29 +133,15 @@ function getString(obj) { return String(obj); } -function Article(item) { - let propList = (listView.props || []).map(key => { - return
  • {key} : {getString(item[key])}
  • ; - }); - - let uri = `/item/${item.corpus}/${item.id}`; - let name = getString(item[listView.name]); - return ( -
    -
    {name}
    -
      {propList}
    -
    - ); -} - -function Picture(item) { - let uri = `/item/${item.corpus}/${item.id}`; - let img = getString(item[listView.image]); - let name = getString(item[listView.name]); +function Picture(items) { + items = items.item; + let uri = `/item/${items.corpus}/${items.id}`; + let img = getString(items[listView.image]); + let name = getString(items[listView.name]); return (
    - {name}/ + {name}
    {name}
    diff --git a/src/components/Header/Header.jsx b/src/components/Header/Header.jsx index 8c553efb..07e151db 100644 --- a/src/components/Header/Header.jsx +++ b/src/components/Header/Header.jsx @@ -11,7 +11,7 @@ class Header extends Component { render() { return (
    -
    +
    diff --git a/src/components/Item/Fragment.jsx b/src/components/Item/Fragment.jsx new file mode 100644 index 00000000..eae2e325 --- /dev/null +++ b/src/components/Item/Fragment.jsx @@ -0,0 +1,158 @@ +import React, { Component } from 'react'; +import lien from '../../images/lien_logo.svg'; + +export default class Fragment extends Component { + constructor(props) { + super(props); + this.state = { + idTextToAnalyse: null + }; + } + + static getDerivedStateFromProps(nextProps, nextState) { + if (nextState.idTextToAnalyse) { + let found = nextProps.items.find( + text => text.id === nextState.idTextToAnalyse + ); + if (!found) return { idTextToAnalyse: null }; + } + return false; + } + + render() { + const generatedTextDescription = this._generateTextDescription(); + const generatedTextFragment = this._generateTextFragment(); + return ( +
    + + + + + + + + + + + + + +
    + Items + Fragments d'un item
    + + {generatedTextDescription} +
    +
    + + {generatedTextFragment} +
    +
    +
    + ); + } + + _generateTextDescription() { + const selectId = this.selectIdTextToAnalyse; + const idTextToAnalyse = this.state.idTextToAnalyse; + return this.props.items.map((text, id) => { + let { resource } = text; + resource = resource ? ( + + lien le texte d'origine + + ) : null; + const idIdentique = idTextToAnalyse === text.id; + const class_name = idIdentique ? 'item textSelected' : 'item'; + return ( + selectId(text.id)}> + +

    + name : {text.name} {resource} +

    +

    +

    + + + ); + }); + } + + selectIdTextToAnalyse = id => { + window.scrollTo(0, 0); + if (id === this.state.idTextToAnalyse) { + this.setState({ idTextToAnalyse: null }); + } else { + this.setState({ idTextToAnalyse: id }); + } + }; + + _generateTextFragment() { + if (this.state.idTextToAnalyse === null) { + let fragmentSelect = this.props.items.map(fragment => { + return Object.values(fragment); + }); + if (this.props.selection.length !== 0) { + fragmentSelect = fragmentSelect.map(fragments => { + return fragments.filter(fragment => { + return this.props.selection.every(selection => { + return (fragment.topic || []).find(t => { + return t.id === selection; + }); + }); + }); + }); + } + return fragmentSelect.map(fragments => { + return fragments.map((fragment, idFragment) => { + return ( + + + {fragment.text + ? fragment.text.map((text, idText) => ( +

    + {text} +

    + )) + : null} + + + ); + }); + }); + } else { + let fragments = this.props.items.find( + text => text.id === this.state.idTextToAnalyse + ); + let fragmentSelect = Object.values(fragments); + if (this.props.selection.length !== 0) { + fragmentSelect = fragmentSelect.filter(fragments => { + return this.props.selection.every(selection => { + return (fragments.topic || []).find(t => { + return t.id === selection; + }); + }); + }); + } + return fragmentSelect.map((fragment, idFragment) => { + return ( + + + {fragment.text + ? fragment.text.map((text, idText) => ( +

    + {text} +

    + )) + : null} + + + ); + }); + } + } +} diff --git a/src/components/Item/Item.jsx b/src/components/Item/Item.jsx index 35afe1b6..dd7a8d5f 100644 --- a/src/components/Item/Item.jsx +++ b/src/components/Item/Item.jsx @@ -42,22 +42,27 @@ class Item extends Component { this._getOrCreateItem = this._getOrCreateItem.bind(this); this._switchCreatable = this._switchCreatable.bind(this); this.deleteAttribute = this.deleteAttribute.bind(this); - this.user=conf.user || window.location.hostname.split('.', 1)[0]; + this.user = conf.user || window.location.hostname.split('.', 1)[0]; } render() { let name = getString(this.state[itemView.name]); let attributes = this._getAttributes(); let viewpoints = this._getViewpoints(); - let attributeButtonLabel = this.state.isCreatable? 'Valider' : 'Ajouter un attribut'; - let attributeForm = this.state.isCreatable? this._getAttributeCreationForm() : ''; + let attributeButtonLabel = this.state.isCreatable + ? 'Valider' + : 'Ajouter un attribut'; + let attributeForm = this.state.isCreatable + ? this._getAttributeCreationForm() + : ''; return (
    - + - Retour à l'accueil + + Retour à l'accueil
    @@ -67,9 +72,14 @@ class Item extends Component {

    Description

    Attributs du document

    -
    +
    - +
    {attributes} @@ -98,7 +108,10 @@ class Item extends Component {
    {x[0]}
    {x[1][0]}
    -
    @@ -106,28 +119,30 @@ class Item extends Component { } _getViewpoints() { - return Object.entries(this.state.topic).map(v => - - ); + return Object.entries(this.state.topic).map(v => ( + + )); } componentDidMount() { - let start=new Date().getTime(); - let self=this; + let start = new Date().getTime(); + let self = this; this._fetchItem().then(() => { - let end=new Date().getTime(); - let elapsedTime=end-start; - console.log("elapsed Time ",elapsedTime); - - let intervalTime=Math.max(10000,elapsedTime*5); - console.log("reload every ",intervalTime); - self._timer = setInterval( - () => { - self._fetchItem(); - }, - intervalTime - ); + let end = new Date().getTime(); + let elapsedTime = end - start; + console.log('elapsed Time ', elapsedTime); + + let intervalTime = Math.max(10000, elapsedTime * 5); + console.log('reload every ', intervalTime); + self._timer = setInterval(() => { + self._fetchItem(); + }, intervalTime); }); } @@ -136,8 +151,7 @@ class Item extends Component { } _getOrCreateItem() { - return hypertopic.get({_id: this.props.match.params.item}) - .catch(e => { + return hypertopic.get({ _id: this.props.match.params.item }).catch(e => { return { _id: this.props.match.params.item, item_corpus: this.props.match.params.corpus @@ -148,45 +162,63 @@ class Item extends Component { _fetchItem() { let uri = this.props.match.url; let params = this.props.match.params; - return hypertopic.getView(uri).then((data) => { - let item = data[params.corpus][params.item]; - let itemTopics = (item.topic) ? groupBy(item.topic, ['viewpoint']) : {}; - let topics=this.state.topic || {}; - for (let id in itemTopics) { - topics[id]=itemTopics[id]; - } - item.topic=topics; - this.setState(item); - }).then(() => hypertopic.getView(`/user/${this.user}`)) - .then((data) => { - let user = data[this.user] || {}; - if (user.viewpoint) { - let topic=this.state.topic; - for (let vp of user.viewpoint) { - topic[vp.id]=topic[vp.id] || []; + return hypertopic + .getView(uri) + .then(data => { + let item = data[params.corpus][params.item]; + let itemTopics = item.topic ? groupBy(item.topic, ['viewpoint']) : {}; + let topics = this.state.topic || {}; + for (let id in itemTopics) { + topics[id] = itemTopics[id]; } - this.setState({topic}); - } - }); + item.topic = topics; + this.setState(item); + }) + .then(() => hypertopic.getView(`/user/${this.user}`)) + .then(data => { + let user = data[this.user] || {}; + if (user.viewpoint) { + let topic = this.state.topic; + for (let vp of user.viewpoint) { + topic[vp.id] = topic[vp.id] || []; + } + this.setState({ topic }); + } + }); } _getAttributeCreationForm() { return (
    -
    -
    +
    + +
    +
    + +
    ); } _setAttribute(key, value) { - if (key!=='' && value!=='') { - let attribute = {[key]: [value]}; + if (key !== '' && value !== '') { + let attribute = { [key]: [value] }; this._getOrCreateItem() .then(x => Object.assign(x, attribute)) .then(hypertopic.post) .then(_ => this.setState(attribute)) - .catch((x) => console.error(x.message)); + .catch(x => console.error(x.message)); } else { console.log('Créez un attribut non vide'); } @@ -204,7 +236,7 @@ class Item extends Component { } deleteAttribute(key) { - const _error = (x) => console.error(x.message); + const _error = x => console.error(x.message); this._getOrCreateItem() .then(x => { delete x[key]; @@ -221,7 +253,7 @@ class Item extends Component { _assignTopic(topicToAssign, viewpointId) { return this._getOrCreateItem() .then(data => { - data.topics=data.topics || {}; + data.topics = data.topics || {}; data.topics[topicToAssign.id] = { viewpoint: viewpointId }; return data; }) @@ -237,17 +269,20 @@ class Item extends Component { .catch(error => console.log(`error : ${error}`)); } - _removeTopic(topicToDelete) { - if (window.confirm('Voulez-vous réellement que l\'item affiché ne soit plus décrit à l\'aide de cette rubrique ?')) { + if ( + window.confirm( + "Voulez-vous réellement que l'item affiché ne soit plus décrit à l'aide de cette rubrique ?" + ) + ) { return this._getOrCreateItem() .then(data => { - data.topics=data.topics || {}; + data.topics = data.topics || {}; delete data.topics[topicToDelete.id]; return data; }) .then(hypertopic.post) - .then((res)=> { + .then(res => { let newState = this.state; newState.topic[topicToDelete.viewpoint] = newState.topic[ topicToDelete.viewpoint @@ -261,12 +296,12 @@ class Item extends Component { function ShowItem(props) { switch (itemView.mode) { - case 'article': - return Article(props.item); - case 'picture': - return Picture(props.item); - default: - return Picture(props.item); + case 'article': + return Article(props.item); + case 'picture': + return Picture(props.item); + default: + return Picture(props.item); } } @@ -276,7 +311,9 @@ function Article(item) { return ( ); } @@ -288,7 +325,7 @@ function Picture(item) { return ( ); @@ -431,7 +468,7 @@ class Viewpoint extends Component { return (

    {this.state.name}

    -
    +
    {paths}
    @@ -449,18 +486,26 @@ class Viewpoint extends Component { id={`input-${this.state.name}`} />
    - -
    @@ -488,14 +533,14 @@ class Viewpoint extends Component { _fetchViewpoint() { let uri = '/viewpoint/' + this.props.id; - hypertopic.getView(uri).then((data) => { + hypertopic.getView(uri).then(data => { let viewpoint = data[this.props.id]; let name = viewpoint.name; let topics = viewpoint; delete topics.user; delete topics.name; delete topics.upper; - this.setState({name, topics}); + this.setState({ name, topics }); }); } } @@ -511,8 +556,14 @@ class TopicPath extends Component { render() { let topics = this._getTopics(); for (let i = 1; i < topics.length; ++i) { - let key="separator-"+i; - topics.splice(i, 0, >); + let key = 'separator-' + i; + topics.splice( + i, + 0, + + > + + ); ++i; } const topicId = this.state.path[this.state.path.length - 1] @@ -521,8 +572,12 @@ class TopicPath extends Component { return (
    {topics} -
    @@ -536,7 +591,7 @@ class TopicPath extends Component { topic = this._getTopic(topic.broader[0].id); path.unshift(topic); } - this.setState({path}); + this.setState({ path }); } _getTopic(id) { @@ -546,15 +601,16 @@ class TopicPath extends Component { } _getTopics() { - return this.state.path.map( t => { - let name = (t.name)? t.name : 'Sans nom'; + return this.state.path.map(t => { + let name = t.name ? t.name : 'Sans nom'; let uri = '../../?t=' + t.id; return ( - {name} + + {name} + ); }); } - } export default Item; diff --git a/src/components/Outliner/Outliner.jsx b/src/components/Outliner/Outliner.jsx index af646c0e..fb69c462 100644 --- a/src/components/Outliner/Outliner.jsx +++ b/src/components/Outliner/Outliner.jsx @@ -10,29 +10,29 @@ import '../../styles/App.css'; const db = new Hypertopic(conf.services); -const _log = (x) => console.log(JSON.stringify(x, null, 2)); -const _error = (x) => console.error(x.message); +const _log = x => console.log(JSON.stringify(x, null, 2)); +const _error = x => console.error(x.message); class Outliner extends React.Component { - constructor() { super(); - this.state = { }; - this.changing=false; - this.topicTree=new TopicTree({}); + this.state = {}; + this.changing = false; + this.topicTree = new TopicTree({}); this.user = conf.user || window.location.hostname.split('.', 1)[0]; } render() { let status = this._getStatus(); - let topic={name:this.state.title}; + let topic = { name: this.state.title }; return (
    - + - Retour à l'accueil + + Retour à l'accueil
    @@ -41,13 +41,21 @@ class Outliner extends React.Component {

    {status}

    - {this.state.title ? + {this.state.title ? (
      - +
    - : this._getTitle() - } + ) : ( + this._getTitle() + )}
    @@ -58,19 +66,28 @@ class Outliner extends React.Component { } _getTitle() { - return (
    this._newVP(e)}> - -
    - -
    -
    ); + return ( +
    this._newVP(e)}> + +
    + +
    +
    + ); } _getStatus() { if (this.state.title !== undefined) { - return "Modification du point de vue"; + return 'Modification du point de vue'; } else { - return "Création du point de vue"; + return 'Création du point de vue'; } } @@ -80,7 +97,12 @@ class Outliner extends React.Component { if (!title) { return; } - db.post({ _id: this.props.match.params.id, viewpoint_name: title, topics: {}, users: [this.user] }) + db.post({ + _id: this.props.match.params.id, + viewpoint_name: title, + topics: {}, + users: [this.user] + }) .then(_log) .then(_ => this.setState({ title })) .then(_ => this._fetchData()) @@ -88,41 +110,43 @@ class Outliner extends React.Component { } activeNode(id) { - this.setState({activeNode:id}); + this.setState({ activeNode: id }); } handleKeyAction(e) { - var changed=false; - var isNew=false; + var changed = false; + var isNew = false; if (this.state.activeNode) { - var topic=this.topicTree.getTopic(this.state.activeNode); - isNew=topic.new || false; + var topic = this.topicTree.getTopic(this.state.activeNode); + isNew = topic.new || false; } switch (e.key) { - case "Enter": - topic=this.topicTree.newSibling(this.state.activeNode); + case 'Enter': + topic = this.topicTree.newSibling(this.state.activeNode); this.activeNode(topic.id); - topic.new=true; + topic.new = true; e.preventDefault(); this.setState(function(previousState) { // previousState.temptopics=previousState.temptopics || []; // previousState.temptopics.push(topic.id); - previousState.topics=this.topicTree.topics; + previousState.topics = this.topicTree.topics; return previousState; }); return; - case "Tab": + case 'Tab': if (!e.altKey && !e.ctrlKey) { if (e.shiftKey) { - changed=this.topicTree.promote(this.state.activeNode); + changed = this.topicTree.promote(this.state.activeNode); } else { - changed=this.topicTree.demote(this.state.activeNode); + changed = this.topicTree.demote(this.state.activeNode); } } break; case 'ArrowUp': if (!e.altKey && !e.ctrlKey && !e.shiftKey) { - this.activeNode(this.topicTree.getPreviousTopic(this.state.activeNode)); + this.activeNode( + this.topicTree.getPreviousTopic(this.state.activeNode) + ); } break; case 'ArrowDown': @@ -133,10 +157,13 @@ class Outliner extends React.Component { case 'Delete': case 'Backspace': if (!e.altKey && !e.ctrlKey && !e.shiftKey) { - if (e.target.tagName==="BODY" || e.target.value==='' ) { - let previousTopic=this.topicTree.getPreviousTopic(this.state.activeNode); - changed=this.topicTree.deleteTopic(this.state.activeNode); - if (changed) this.activeNode(this.topicTree.getNextTopic(previousTopic)); + if (e.target.tagName === 'BODY' || e.target.value === '') { + let previousTopic = this.topicTree.getPreviousTopic( + this.state.activeNode + ); + changed = this.topicTree.deleteTopic(this.state.activeNode); + if (changed) + this.activeNode(this.topicTree.getNextTopic(previousTopic)); } } break; @@ -144,80 +171,86 @@ class Outliner extends React.Component { } if (changed) { e.preventDefault(); - this.setState({topics:this.topicTree.topics},() => { + this.setState({ topics: this.topicTree.topics }, () => { if (!isNew) this.applyChange(); }); } return; - }; + } - editTopic(id,change) { + editTopic(id, change) { if (!this.setState) { return; } - var toApply=false; - return this.setState(previousState => { - let topics=previousState.topics; - if (topics) { - let topic; - if (id==="root") { - if (change.name && change.name!==previousState.title) { - toApply=true; - this.topicTree.setRootName(change.name); - return {title:change.name} - } - } else if (topics[id]) { - if (change.delete) { - if (!topics[id].new) toApply=true; - delete topics[id]; - } else { - topic=topics[id]; + var toApply = false; + return this.setState( + previousState => { + let topics = previousState.topics; + if (topics) { + let topic; + if (id === 'root') { + if (change.name && change.name !== previousState.title) { + toApply = true; + this.topicTree.setRootName(change.name); + return { title: change.name }; + } + } else if (topics[id]) { + if (change.delete) { + if (!topics[id].new) toApply = true; + delete topics[id]; + } else { + topic = topics[id]; + } } - } - if (topic) { - for (let key in change) { - switch(key) { - case "parent": - if (this.topicTree.getTopic(change.parent)) { - toApply=this.topicTree.setParent(id,change.parent) && + if (topic) { + for (let key in change) { + switch (key) { + case 'parent': + if (this.topicTree.getTopic(change.parent)) { + toApply = + this.topicTree.setParent(id, change.parent) && this.topicTree.moveAfter(id); - } - break; - case "moveAfter": - if (this.topicTree.getTopic(change.moveAfter)) { - var newParent=this.topicTree.getParent(change.moveAfter); - if (newParent) { - toApply=this.topicTree.setParent(id,newParent); } - toApply=toApply && this.topicTree.moveAfter(id,change.moveAfter); - } - break; - case "startDrag": - if (change.startDrag===true) previousState.draggedTopic=id; - else if (previousState.draggedTopic===id) { - delete previousState.draggedTopic; - } - break; - default: - if (topic[key]!==change[key]) { - topic[key]=change[key]; - if (!topics[id].new) toApply=true; - } + break; + case 'moveAfter': + if (this.topicTree.getTopic(change.moveAfter)) { + var newParent = this.topicTree.getParent(change.moveAfter); + if (newParent) { + toApply = this.topicTree.setParent(id, newParent); + } + toApply = + toApply && this.topicTree.moveAfter(id, change.moveAfter); + } + break; + case 'startDrag': + if (change.startDrag === true) + previousState.draggedTopic = id; + else if (previousState.draggedTopic === id) { + delete previousState.draggedTopic; + } + break; + default: + if (topic[key] !== change[key]) { + topic[key] = change[key]; + if (!topics[id].new) toApply = true; + } + } } + if (!topics[id].new) delete topics[id].new; } - if (!topics[id].new) delete topics[id].new; + return previousState; } - return previousState; + return {}; + }, + function() { + if (toApply) this.applyChange(); } - return {}; - },function() { - if (toApply) this.applyChange(); - }); + ); } componentDidMount() { this._fetchData(); - document.addEventListener("keydown", this.handleKeyAction.bind(this)); + document.addEventListener('keydown', this.handleKeyAction.bind(this)); } componentWillUnmount() { @@ -226,12 +259,13 @@ class Outliner extends React.Component { applyChange() { if (!this.changing) { - this.changing=db.get({ _id: this.props.match.params.id }) + this.changing = db + .get({ _id: this.props.match.params.id }) .then(data => { - data.topics ={}; + data.topics = {}; for (var id in this.state.topics) { if (!this.state.topics[id].new) { - data.topics[id]=this.state.topics[id]; + data.topics[id] = this.state.topics[id]; } } data.viewpoint_name = this.state.title; @@ -239,11 +273,11 @@ class Outliner extends React.Component { }) .then(db.post) .then(() => { - this.changing=false; + this.changing = false; }) .catch(_ => { _error(_); - this.changing=false; + this.changing = false; this._fetchData(); }); } @@ -252,20 +286,17 @@ class Outliner extends React.Component { _fetchData() { if (!this.changing) { - return db.get({ _id: this.props.match.params.id }) - .then(x => { + return db.get({ _id: this.props.match.params.id }).then(x => { this.setState({ topics: x.topics, title: x.viewpoint_name }); - this.topicTree=new TopicTree(x.topics); + this.topicTree = new TopicTree(x.topics); }); } else { return true; } } - } class Node extends React.Component { - constructor() { super(); this.state = { edit: false, active: false, open: true }; @@ -273,173 +304,223 @@ class Node extends React.Component { } render = () => { - let change=this.props.change; + let change = this.props.change; let switchOpen = () => { - this.setState({open:!this.state.open}); - } - var isNew=this.props.me.new; - let switchEdit = (e) => { + this.setState({ open: !this.state.open }); + }; + var isNew = this.props.me.new; + let switchEdit = e => { e.stopPropagation(); - this.setState((previousState) => { + this.setState(previousState => { if (previousState.edit && isNew) { - change(this.props.id,{delete:true}); + change(this.props.id, { delete: true }); } - return {edit:!previousState.edit}; + return { edit: !previousState.edit }; }); - } - let commitEdit = (e) => { - let newName=e.target.value; - change(this.props.id,{name:newName,new:false}); - isNew=false; + }; + let commitEdit = e => { + let newName = e.target.value; + change(this.props.id, { name: newName, new: false }); + isNew = false; switchEdit(e); - } - let handleInput = (e) => { - switch(e.key) { - case "Enter": + }; + let handleInput = e => { + switch (e.key) { + case 'Enter': commitEdit(e); e.stopPropagation(); break; - case "Escape": + case 'Escape': switchEdit(e); e.stopPropagation(); break; default: } }; - let activeMe = (e) => { + let activeMe = e => { e.stopPropagation(); this.props.activate(this.props.id); - } + }; let thisNode; if (this.state.edit) { - thisNode=; + thisNode = ( + + ); } else { - thisNode={this.props.me.name}; + thisNode = ( + + {this.props.me.name} + + ); } - let children=[]; + let children = []; if (this.props.topics) { for (var topID in this.props.topics) { - let topic=this.props.topics[topID]; - if ((this.props.id && topic.broader.indexOf(this.props.id)!==-1) - || (this.props.id==="root" && topic.broader.length===0)) { - children.push( - - ); + let topic = this.props.topics[topID]; + if ( + (this.props.id && topic.broader.indexOf(this.props.id) !== -1) || + (this.props.id === 'root' && topic.broader.length === 0) + ) { + children.push( + + ); } } } - var classes=["outliner-node"]; - if (this.props.activeNode===this.props.id) { - classes.push("active"); + var classes = ['outliner-node']; + if (this.props.activeNode === this.props.id) { + classes.push('active'); } if (this.state.isDragged || this.state.isDraggedOver) { - classes.push("dragged"); + classes.push('dragged'); } if (this.props.draggedTopic && this.state.isDraggedInto) { - classes.push("dragged-into"); + classes.push('dragged-into'); } if (this.props.draggedTopic && this.state.isDraggedAfter) { - classes.push("dragged-after"); + classes.push('dragged-after'); } if (this.props.id && children.length) { - classes.push("has-children"); + classes.push('has-children'); } - if (this.state.open) classes.push("open"); - else classes.push("closed"); + if (this.state.open) classes.push('open'); + else classes.push('closed'); function setEdit(e) { if (!this.state.edit) switchEdit(e); } - var draggable=this.props.id!=="root"; + var draggable = this.props.id !== 'root'; function onDragStart(e) { e.stopPropagation(); - e.dataTransfer.effectAllowed="move"; - e.dataTransfer.setData("dragContent",this.props.id); + e.dataTransfer.effectAllowed = 'move'; + e.dataTransfer.setData('dragContent', this.props.id); activeMe(e); - this.setState({isDragged:true}); - this.props.change(this.props.id,{startDrag:true}); + this.setState({ isDragged: true }); + this.props.change(this.props.id, { startDrag: true }); } function onDragStop(e) { e.stopPropagation(); - console.log("dragStop "+this.props.me.name); - this.setState({isDragged:false}); - this.props.change(this.props.id,{startDrag:false}); + console.log('dragStop ' + this.props.me.name); + this.setState({ isDragged: false }); + this.props.change(this.props.id, { startDrag: false }); } - let onDrag=(e) => { - var draggedTopic=e.dataTransfer.getData("dragContent") || this.props.draggedTopic; - if (!draggedTopic) {console.error("no dragged topic"); return;} - if (!this.props.topics[draggedTopic]) {console.error("unknown dragged topic "+draggedTopic); return;} - let topicTree=new TopicTree(this.props.topics); + let onDrag = e => { + var draggedTopic = + e.dataTransfer.getData('dragContent') || this.props.draggedTopic; + if (!draggedTopic) { + console.error('no dragged topic'); + return; + } + if (!this.props.topics[draggedTopic]) { + console.error('unknown dragged topic ' + draggedTopic); + return; + } + let topicTree = new TopicTree(this.props.topics); - if (draggedTopic===this.props.id || topicTree.isAncestor(draggedTopic,this.props.id)) { + if ( + draggedTopic === this.props.id || + topicTree.isAncestor(draggedTopic, this.props.id) + ) { //can't be dropped into itself or its descendant - return; - } else if (e.currentTarget.className==="wrap") { //child - this.setState({isDraggedInto:true}); + } else if (e.currentTarget.className === 'wrap') { + //child + this.setState({ isDraggedInto: true }); e.stopPropagation(); e.preventDefault(); return false; - } else if (e.currentTarget.className==="caret") { - this.setState({isDraggedAfter:true}); + } else if (e.currentTarget.className === 'caret') { + this.setState({ isDraggedAfter: true }); e.stopPropagation(); e.preventDefault(); return false; } else { - } - } - let onDragLeave=(e) => { - this.setState({isDraggedAfter:false,isDraggedInto:false}); + }; + let onDragLeave = e => { + this.setState({ isDraggedAfter: false, isDraggedInto: false }); e.preventDefault(); e.stopPropagation(); - } - let onDrop=(e) => { - this.setState({isDraggedAfter:false,isDraggedInto:false}); - let topicTree=new TopicTree(this.props.topics); - var droppedTopic=e.dataTransfer.getData("dragContent"); - if (droppedTopic===this.props.id || topicTree.isAncestor(droppedTopic,this.props.id)) { + }; + let onDrop = e => { + this.setState({ isDraggedAfter: false, isDraggedInto: false }); + let topicTree = new TopicTree(this.props.topics); + var droppedTopic = e.dataTransfer.getData('dragContent'); + if ( + droppedTopic === this.props.id || + topicTree.isAncestor(droppedTopic, this.props.id) + ) { //can't be dropped into itself or its descendant - return; - } else if (e.currentTarget.className==="wrap") { //child - this.props.change(droppedTopic,{parent:this.props.id}); + } else if (e.currentTarget.className === 'wrap') { + //child + this.props.change(droppedTopic, { parent: this.props.id }); e.stopPropagation(); e.preventDefault(); - } else if (e.currentTarget.className==="caret") { - this.props.change(droppedTopic,{moveAfter:this.props.id}); + } else if (e.currentTarget.className === 'caret') { + this.props.change(droppedTopic, { moveAfter: this.props.id }); e.stopPropagation(); e.preventDefault(); } - } + }; return ( -
  • + + {' '} + + - - {thisNode} {this.props.id}
      -
    • - {children} +
    • + {children}
    -
    -
  • ); +
    + + ); }; componentDidMount() { if (!this.props.me.name || this.props.me.isNew) { - this.setState({edit:true}); + this.setState({ edit: true }); } } - } export default Outliner; diff --git a/src/components/Portfolio/Portfolio.jsx b/src/components/Portfolio/Portfolio.jsx index 5bca632d..cec14732 100644 --- a/src/components/Portfolio/Portfolio.jsx +++ b/src/components/Portfolio/Portfolio.jsx @@ -3,6 +3,7 @@ import { Link } from 'react-router-dom'; import by from 'sort-by'; import queryString from 'query-string'; import Hypertopic from 'hypertopic'; +import Switch from 'react-switch'; import conf from '../../config/config.json'; import Viewpoint from '../Viewpoint/Viewpoint.jsx'; import Corpora from '../Corpora/Corpora.jsx'; @@ -20,7 +21,8 @@ class Portfolio extends Component { corpora: [], items: [], selectedItems: [], - topicsItems: new Map() + topicsItems: new Map(), + cloudView: false }; this.user = conf.user || window.location.hostname.split('.', 1)[0]; this._updateSelection(); @@ -34,14 +36,33 @@ class Portfolio extends Component {
    - + {status}
    -

    Points de vue

    +

    + { + this.setState({ cloudView: e }); + }} + checked={this.state.cloudView} + uncheckedIcon={false} + checkedIcon={false} + onColor="#aaa" + className={'switch'} + /> + Points de vue +

    {viewpoints} @@ -56,21 +77,18 @@ class Portfolio extends Component { } componentDidMount() { - let start=new Date().getTime(); - var self=this; + let start = new Date().getTime(); + var self = this; this._fetchAll().then(() => { - let end=new Date().getTime(); - let elapsedTime=end-start; - console.log("elapsed Time ",elapsedTime); - - let intervalTime=Math.max(10000,elapsedTime*5); - console.log("reload every ",intervalTime); - self._timer = setInterval( - () => { - self._fetchAll(); - }, - intervalTime - ); + let end = new Date().getTime(); + let elapsedTime = end - start; + console.log('elapsed Time ', elapsedTime); + + let intervalTime = Math.max(10000, elapsedTime * 5); + console.log('reload every ', intervalTime); + self._timer = setInterval(() => { + self._fetchAll(); + }, intervalTime); }); } @@ -96,14 +114,25 @@ class Portfolio extends Component { let topics = this.selection.map(t => { let topic = this._getTopic(t); if (!topic) { - return 'Thème inconnu'; + return 'Thème inconnu'; } - let uri = '?' + queryString.stringify({ - t: this._toggleTopic(this.selection, t) - }); - return - {topic.name} - ; + let uri = + '?' + + queryString.stringify({ + t: this._toggleTopic(this.selection, t) + }); + return ( + + {topic.name}{' '} + + {' '} + + + ); }); return topics.length ? topics : 'Tous les items'; } @@ -118,20 +147,30 @@ class Portfolio extends Component { _updateSelection() { let selection = queryString.parse(window.location.search).t; - this.selection = (selection instanceof Array)? selection - : (selection)? [selection] - : []; + this.selection = + selection instanceof Array ? selection : selection ? [selection] : []; } _getTopicPath(topicId) { let topic = this._getTopic(topicId); - let path = (topic && topic.broader)? this._getTopicPath(topic.broader[0].id) : []; + let path = + topic && topic.broader ? this._getTopicPath(topic.broader[0].id) : []; path.push(topicId); return path; } _getItemTopicsPaths(item) { - return (item.topic||[]).map(t => this._getTopicPath(t.id)); + if (!item.topic) { + let fragments = Object.values(item); + let paths = []; + fragments.forEach(fragment => { + (fragment.topic || []).forEach(t => { + this._getTopicPath(t.id).forEach(p => p !== '' && paths.push(p)); + }); + }); + return paths; + } + return (item.topic || []).map(t => this._getTopicPath(t.id)); } _getRecursiveItemTopics(item) { @@ -143,33 +182,35 @@ class Portfolio extends Component { } _updateSelectedItems() { - let selectedItems = this.state.items - .filter(e => this._isSelected(e, this.selection)); + let selectedItems = this.state.items.filter(e => this._isSelected(e)); let topicsItems = new Map(); for (let e of selectedItems) { for (let t of this._getRecursiveItemTopics(e)) { push(topicsItems, t, e.id); } } - this.setState({selectedItems, topicsItems}); + this.setState({ selectedItems, topicsItems }); } _fetchAll() { const hypertopic = new Hypertopic(conf.services); - return hypertopic.getView(`/user/${this.user}`) + return hypertopic + .getView(`/user/${this.user}`) .then(data => { let user = data[this.user] || {}; user = { viewpoint: user.viewpoint || [], corpus: user.corpus || [] }; - if (!this.state.viewpoints.length && !this.state.corpora.length) { //TODO compare old and new - this.setState({viewpoints:user.viewpoint, corpora:user.corpus}); + if (!this.state.viewpoints.length && !this.state.corpora.length) { + //TODO compare old and new + this.setState({ viewpoints: user.viewpoint, corpora: user.corpus }); } return user; }) .then(x => - x.viewpoint.map(y => `/viewpoint/${y.id}`) + x.viewpoint + .map(y => `/viewpoint/${y.id}`) .concat(x.corpus.map(y => `/corpus/${y.id}`)) ) .then(hypertopic.getView) @@ -180,18 +221,16 @@ class Portfolio extends Component { viewpoint.id = v.id; viewpoints.push(viewpoint); } - this.setState({viewpoints}); + this.setState({ viewpoints }); return data; }) .then(data => { let items = []; for (let corpus of this.state.corpora) { for (let itemId in data[corpus.id]) { - if (!['id','name','user'].includes(itemId)) { + if (!['id', 'name', 'user'].includes(itemId)) { let item = data[corpus.id][itemId]; - if (!item.name || !item.name.length || !item.thumbnail || !item.thumbnail.length) { - console.log(itemId, "has no name or thumbnail!", item); - } else { + if (!(!item.name || !item.name.length)) { item.id = itemId; item.corpus = corpus.id; items.push(item); @@ -199,7 +238,7 @@ class Portfolio extends Component { } } } - this.setState({items:items.sort(by('name'))}); + this.setState({ items: items.sort(by('name')) }); }) .then(x => { this._updateSelectedItems(); @@ -207,27 +246,48 @@ class Portfolio extends Component { } _getViewpoints() { - return this.state.viewpoints.sort(by('name')).map((v, i) => + return this.state.viewpoints.sort(by('name')).map((v, i) => (
    - {i > 0 &&
    } - + {i > 0 &&
    } +
    - ); + )); } _getCorpora() { let ids = this.state.corpora.map(c => c.id); + let pictures = []; + let fragments = []; + this.state.selectedItems.forEach(data => { + if (!['id', 'name', 'user'].includes(data)) { + if (!data.thumbnail || !data.thumbnail.length) { + fragments.push(data); + } else { + pictures.push(data); + } + } + }); return ( - + ); } } function includes(array1, array2) { let set1 = new Set(array1); - return array2.map(e => set1.has(e)) - .reduce((c1,c2) => c1 && c2, true); + return array2.map(e => set1.has(e)).reduce((c1, c2) => c1 && c2, true); } function push(map, topicId, itemId) { diff --git a/src/components/Viewpoint/Viewpoint.jsx b/src/components/Viewpoint/Viewpoint.jsx index f222b828..bb3c6ca2 100644 --- a/src/components/Viewpoint/Viewpoint.jsx +++ b/src/components/Viewpoint/Viewpoint.jsx @@ -1,34 +1,52 @@ import React, { Component } from 'react'; import by from 'sort-by'; -import Topic from '../Topic/Topic.jsx'; +import Topic from '../Topic/Topic'; +import Cloud from '../Cloud/Cloud'; class Viewpoint extends Component { render() { - let topics = this._getTopics(); - let outliner = this._getOutliner(); + const topics = !this.props.cloudView ? this._getTopics() : null; + const outliner = this._getOutliner(); return (

    {this.props.viewpoint.name} - +

    -
    +
    -
      - {topics} -
    + {!this.props.cloudView ? ( +
      {topics}
    + ) : ( + + )}
    ); } _getTopics() { - return (this.props.viewpoint.upper||[]).sort(by('name')).map((t) => - - ); + return (this.props.viewpoint.upper || []) + .sort(by('name')) + .map(t => ( + + )); } _getOutliner() { diff --git a/src/config/config.js b/src/config/config.js index 310fac01..7f7723bd 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -25,7 +25,7 @@ const getConfig = (prop, def) => { } } return def; -} +}; export default getConfig; diff --git a/src/config/config.json b/src/config/config.json index a0134e68..02a670fa 100644 --- a/src/config/config.json +++ b/src/config/config.json @@ -1,7 +1,7 @@ { - "user": "vitraux", + "user": "alice", "services": [ "http://argos2.test.hypertopic.org", - "http://steatite.hypertopic.org" + "http://cassandre.hypertopic.org" ] } diff --git a/src/images/lien_logo.svg b/src/images/lien_logo.svg new file mode 100644 index 00000000..b1b92d46 --- /dev/null +++ b/src/images/lien_logo.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/styles/App.css b/src/styles/App.css index 22c9cce1..4ce59b5c 100644 --- a/src/styles/App.css +++ b/src/styles/App.css @@ -15,7 +15,8 @@ min-height: 55px; } -h1 a, h1 a:hover { +h1 a, +h1 a:hover { color: ivory; text-decoration: none; } @@ -52,14 +53,16 @@ h1 a, h1 a:hover { min-height: 100vh; } -.Subject, .Description { +.Subject, +.Description { background: #fff; } -.Subject h2, .Description h2 { +.Subject h2, +.Description h2 { color: ivory; margin: 0; - padding: .5rem; + padding: 0.5rem; } .Subject h2 { @@ -97,21 +100,23 @@ h1 a, h1 a:hover { list-style-type: none; } -.Topic a, a.Topic, .ArticleTitle a { +.Topic a, +a.Topic, +.ArticleTitle a { color: black; } .Topic .oi { color: darkgrey; - font-size: .9em; + font-size: 0.9em; text-align: center; width: 1.5em; } .Topic .oi.leaf:before { content: '\E094'; - font-size: .6em; - vertical-align: .3em; + font-size: 0.6em; + vertical-align: 0.3em; } .Closed > ul { @@ -125,6 +130,12 @@ h1 a, h1 a:hover { border-radius: 3px; } +.Cloud-Selected { + font-weight: bold; + padding: 1px; + background-color: rgba(238, 170, 51, 0.6); +} + .Items { display: flex; flex-direction: row; @@ -159,10 +170,12 @@ h1 a, h1 a:hover { font-weight: bold; } -.TopicPath > .DeleteTopicButton, .Attribute > .DeleteTopicButton { +.TopicPath > .DeleteTopicButton, +.Attribute > .DeleteTopicButton { visibility: hidden; } -.TopicPath:hover > .DeleteTopicButton, .Attribute:hover > .DeleteTopicButton { +.TopicPath:hover > .DeleteTopicButton, +.Attribute:hover > .DeleteTopicButton { visibility: visible; } @@ -175,8 +188,10 @@ h1 a, h1 a:hover { opacity: 0.8; } -.TopicPath > .Topic:hover, .TopicPath > .Topic:last-of-type:hover, -.Topics .Topic > a:hover, .ArticleTitle:hover a { +.TopicPath > .Topic:hover, +.TopicPath > .Topic:last-of-type:hover, +.Topics .Topic > a:hover, +.ArticleTitle:hover a { opacity: 1; } @@ -184,29 +199,32 @@ h1 a, h1 a:hover { padding: 0 5px; } -.TopicPath > .Topic:last-of-type, .ArticleTitle a { +.TopicPath > .Topic:last-of-type, +.ArticleTitle a { opacity: 0.8; font-weight: bold; } -.TopicTag, .TopicTag[href], .TopicTag[href]:hover { +.TopicTag, +.TopicTag[href], +.TopicTag[href]:hover { background-color: #f8f9fa; color: #212529; display: inline-block; - margin: 0 .3em; + margin: 0 0.3em; } .TopicTag > .badge-pill { border-radius: 100%; display: inline-block; font-size: 50%; - opacity: .9; - padding: .4em .5em .5em; + opacity: 0.9; + padding: 0.4em 0.5em 0.5em; vertical-align: top; } .TopicTag > .badge-pill.oi-x { - padding-top: .3em; + padding-top: 0.3em; } .TopicTag > .badge-pill:hover { @@ -258,10 +276,10 @@ h1 a, h1 a:hover { .Return { position: fixed; - width:60px; - height:60px; - bottom:40px; - right:40px; + width: 60px; + height: 60px; + bottom: 40px; + right: 40px; } ul.Outliner, @@ -274,23 +292,23 @@ ul.Outliner, cursor: pointer; position: absolute; left: 0px; - min-width:25px; + min-width: 25px; min-height: 20px; } .outliner-node div.after-handle, .outliner-node li.first-handle { - height:25px; - border-style:dashed; + height: 25px; + border-style: dashed; border-width: 1px; - border-color:black; + border-color: black; background-color: lightgrey; - display:none; + display: none; } .outliner-node.dragged-after > div.after-handle, .outliner-node.dragged-into > ul > li.first-handle { - display:inline; + display: inline; } .outliner-node.has-children.closed > .caret:before { @@ -320,21 +338,27 @@ ul.Outliner, .Outliner input { border: none; background: transparent; - width:100%; + width: 100%; } .id { display: none; font-size: 0.8em; - color:#111; - padding:1em; + color: #111; + padding: 1em; } -.wrap:hover button{display: inherit;margin-right: 10px;z-index: 999} -.wrap button{display:none;} +.wrap:hover button { + display: inherit; + margin-right: 10px; + z-index: 999; +} +.wrap button { + display: none; +} .Article { - border-bottom: 1px solid #DDD; + border-bottom: 1px solid #ddd; color: black; display: block; margin: 1em; @@ -359,19 +383,40 @@ ul.Outliner, } .btn-xs { - border-radius: .2rem; - font-size: .6rem; + border-radius: 0.2rem; + font-size: 0.6rem; line-height: 1.5; - padding: .15rem .3rem; + padding: 0.15rem 0.3rem; } .btn-light { border-color: #dddddd; } -.btn.add, .input-group-append .btn { +.btn.add, +.input-group-append .btn { border: 1px solid #ced4da; } .btn:not(.btn-xs) .oi { vertical-align: middle; } + +.tableFragment { + display: table; +} + +.tableFragment > div { + display: table-column; +} +.textSelected { + background-color: #fecccc; +} +.buttonView { + background-color: Transparent; + background-repeat: no-repeat; + border: none; + cursor: pointer; + overflow: hidden; + outline: none; + color: ivory; +}