Commit f03b6912 authored by Svein Olav Styve's avatar Svein Olav Styve

Merge branch 'design_touches' into 'master'

Design touches

See merge request !20
parents 95d67906 b75b1179
## IT2810 - Group 36
Dette repoet inneholder løsningen til gruppe 36 for prosjekt 2 i IT2810.
#### Contributors
Wang, Stine Johanne Gjære. Styve, Svein Olav. Aubert, Karl Petter Egseth.
#### Applikasjonsstruktur
Nettsiden er organisert i fire hoveddeler: kategori-velger, utstilling, tabs og historie/favoritt.
Kategori-velgeren gir brukeren mulighet for å velge en kategori for hver av medietypene bilde, tekst og lyd. For hver av medietypene kan brukeren velge en av tre kategorier: “City”, “Jungle” og “Seaside”. Basert på kategori-valgene vil brukeren kunne velge mellom fire forskjellige utstillinger ved å trykke på en av de fire fanene. Utstillingen endres med en gang brukeren gjør en endring.
Utstillingen består av et bilde, en tekst og en lyd. Alle mediafilene lastes kun dersom de trengs og lagres i nettleserens cache slik de ikke trengs å lastes på nytt. Ved å inspisere nettverkstrafikken i nettleseren (Chrome DevTools) kan vi bekrefte at bruk av caching fungerer etter ønske og medieelementer kun lastes én gang. Samlingen av mediafiler er organisert i en katalogstruktur basert på medietype, med videre fordeling i tre kategorier per medietype som hver inneholder fire filer. Lastingen av filer gjøres med fetch().
Komponentene er implementert som class og function basert på hva som var mest hensiktsmessig for komponenten. Generelt sett valgte vi å implementere komponenter som function så lenge komponenten ikke trengte state.
#### Lagring
HTML Web Storage er brukt til flere formål. Vi har valgt å lagre kunstverket som brukeren så på da nettleseren ble lukket, slik at brukeren ser det samme kunstverket neste gang nettsiden besøkes. Dette blir gjort ved å lagre brukervalgene for kategorier og tab i localstorage. Første gang en bruker besøker nettsiden vil en tilfeldig utstilling vises. Brukeren har også mulighet til å lagre en favoritt-utstilling, som også er implementert ved bruk av localstorage. Disse endringene vedvarer dersom nettleseren startes på nytt, så brukeren ikke mister favoritten sin.
Session storage brukes til å lagre en historikk av alle kunstverkene brukeren har sett på i løpet av et besøk på nettsiden. Brukeren har mulighet til å navigere fram og tilbake i historikken ved bruk av navigerings-piler samt å trykke direkte på et innslag i historikken. Brukeren kan scrolle i historikken for å finne ønsket innslag. Dersom brukeren gjør et manuelt valg av kategori eller tab vil all framtidig historikk overskrives av det valget, basert på hvor i historien brukeren står.
#### Snapshot-tester
Svært enkel snapshottesting er gjort for utvalgte komponenter ved bruk av Jest. Dette sikrer at brukergrensesnittet ikke endrer seg uforventet når man gjør endringer. Dersom grensesnittet skulle endre seg vil testen for den spesifikke komponenten mislykkes, og man må ta stilling til om endringen var tilsiktet eller ikke. Dersom endringen er ønskelig oppdaterer man snapshotten, hvis ikke har man oppdaget en feil og må rette den.
#### Repsonsiv design
Vi har valgt å bruke CSS-grid til utformingen av galleriet. Grid føltes godt tilpasset arbeidsoppgaven, og plassering av elementer, samt deres størrelse ble enklere å jobbe med. Vi dro spesielt nytte av å kunne angi kolonne- og radstørrelser ved hjelp av “fr”-enheten (fraction) og å kunne angi start- og sluttposisjon på elementet i griden. Videre, siden man bygger komponenter av andre komponenter i React, kan det være hensiktsmessig å fylle “hoved-griden” (App.js) med flere “sub-grids”.
Til responsive design har vi i hovedsak brukt CSS media queries, og valgt å fokusere på de tre vanligste skjermstørrelsene (monitor, pad og telefon). Media queries samarbeider veldig godt med grid, siden man enkelt kan forandre hvor mye plass hvert grid-element skal ta opp, og hvor mange rader/kolonner en grid skal inneholde. Etter å satt opp gridene ble det også enklere å skalere innholdet. Eksempelvis satte vi bildene sitt min-width-attributt til 100%, som gjør at det skalerer i henhold til griden (og dermed størrelsen til viewporten).
Da vi begynte oppgaven, fokuserte vi hovedsakelig på nettleser-versjonen. Under produksjon var dette enklest å jobbe med, siden man da kan bruke bl. a. Chrome Dev Tools sin innebygde konsoll. Dette ble brukt til å undersøke states og kommunikasjon mellom komponenter. Etter vi var ferdig med det stadiet, kunne vi enklere fokusere på å sette sammen komponentene vi hadde laget, styling og responsiveness. Chrome Dev Tools hjalp også med å kunne se om mediafiler ble cachet eller lastet inn på nytt.
#### Responsive tester
Ved bruk av “inspect” i Chrome, har vi fått testet nettsiden i iPhone, iPad og vanlig monitor-format. I tillegg har vi testet nettsiden på hver av våre smarttelefoner, både Android og iOS. I starten hadde vi noen problemer med at tabsene i navigasjonsbaren endret bredde når de ble trykket på, og at bildet også ble større enn det skulle og ikke ble tilpasset skjermstørrelsen. Dette ble fikset og nå skal siden fungere fint på både mobil, tablet og laptop. På mobil har vi også testet både horisontal og vertikal visning av nettsiden.
<br>
......
{
"text": "City \nBY FREDERICK SEIDEL \n\nRight now, a dog tied up in the street is barking \nWith the grief of being left, \nA dog bereft. \nRight now, a car is parking. \n \nThe dog emits \nPetals of a barking flower and barking flakes of snow \nThat float upward from the street below \nTo where another victim sits: \n \nWho listens to the whole city \nAnd the dog honking like a car alarm, \nAnd doesn’t mean the dog any harm, \nAnd doesn’t feel any pity."
"text": "City \nBY FREDERICK SEIDEL \n\nRight now, a dog tied up \n in the street is barking \nWith the grief of being left, \nA dog bereft. \nRight now, a car is parking. \n \nThe dog emits \nPetals of a barking flower \nand barking flakes of snow \nThat float upward from the street below \nTo where another victim sits: \n \nWho listens to the whole city \nAnd the dog honking like a car alarm, \nAnd doesn’t mean the dog any harm, \nAnd doesn’t feel any pity."
}
\ No newline at end of file
{
"text": "Not for That City\nBY CHARLOTTE MEW\n\nNot for that city of the level sun,\nIts golden streets and glittering gates ablaze—\nThe shadeless, sleepless city of white days,\nWhite nights, or nights and days that are as one—\nWe weary, when all is said , all thought, all done.\nWe strain our eyes beyond this dusk to see\nWhat, from the threshold of eternity\nWe shall step into. No, I think we shun\nThe splendour of that everlasting glare,\nThe clamour of that never-ending song.\nAnd if for anything we greatly long,\nIt is for some remote and quiet stair\nWhich winds to silence and a space for sleep\nToo sound for waking and for dreams too deep."
"text": "Not for That City\nBY CHARLOTTE MEW\n\nNot for that city of the level sun,\nIts golden streets \nand glittering gates ablaze—\nThe shadeless, \nsleepless city of white days,\nWhite nights, \nor nights and days that are as one—\nWe weary, when all is said , \nall thought, all done.\nWe strain our eyes beyond this dusk to see\nWhat, from the threshold of eternity\nWe shall step into. No, I think we shun\nThe splendour of that everlasting glare,\nThe clamour of that never-ending song.\nAnd if for anything we greatly long,\nIt is for some remote and quiet stair\nWhich winds to silence \nand a space for sleep\nToo sound for waking \nand for dreams too deep."
}
\ No newline at end of file
{
"text": "The Pilot in the Jungle\nBY JOHN CIARDI\nI\n\nMachine stitched rivets ravel on a tree\nWhose name he does not know. Left in the sky,\nHe dangles from a silken cumulus\n(Stork’s bundle upside down\nOn the delivering wind) and sees unborn\nIncredible jungles of the lizard’s eye:\nDark fern, dark river, a shale coliseum\nMountained above one smudgepot in the trees\nThat was his surreal rug on metered skies\nAnd slid afire into this fourth dimension\nWhose infinite point of meeting parallels\nHe marks in ultra-space, suspended from\nThe chords of fifty centuries\nDescending to their past—a ripping sound\nThat snags him limb by limb. He tears and falls\nLouder than any fruit dropped from the trees,\nAnd finds himself in mud on hands and knees."
"text": "The Pilot in the Jungle\nBY JOHN CIARDI\nI\n\nMachine stitched rivets ravel on a tree\nWhose name he does not know. \nLeft in the sky,\nHe dangles from a silken cumulus\n(Stork’s bundle upside down\nOn the delivering wind) and sees unborn\nIncredible jungles of the lizard’s eye:\nDark fern, dark river, a shale coliseum\nMountained above one smudgepot in the trees\nThat was his surreal rug on metered skies\nAnd slid afire into this fourth dimension\nWhose infinite point of meeting parallels\nHe marks in ultra-space, suspended from\nThe chords of fifty centuries\nDescending to their past—a ripping sound\nThat snags him limb by limb. \n He tears and falls\nLouder than any fruit \ndropped from the trees,\nAnd finds himself in mud \non hands and knees."
}
\ No newline at end of file
{
"text": "Washing the Elephant\nBY BARBARA RAS\n\nIsn't it always the heart that wants to wash\nthe elephant, begging the body to do it\nwith soap and water, a ladder, hands,\nin tree-shade big enough for the vast savannahs\nof your sadness, the strangler fig of your guilt,\nthe cratered full moon's light fueling\nthe windy spooling memory of elephant?"
"text": "Washing the Elephant\nBY BARBARA RAS\n\nIsn't it always the heart that wants to wash\nthe elephant, begging the body to do it\nwith soap and water, a ladder, hands,\nin tree-shade big enough \nfor the vast savannahs\nof your sadness, \nthe strangler fig of your guilt,\nthe cratered full moon's light fueling\nthe windy spooling memory of elephant?"
}
\ No newline at end of file
{
"text": "Tree\nBY JANE HIRSHFIELD\n\nIt is foolish\nto let a young redwood \ngrow next to a house.\n \nEven in this \none lifetime,\n\nyou will have to choose.\n\nThat great calm being,\nthis clutter of soup pots and books— \n\nAlready the first branch-tips brush at the window. \nSoftly, calmly, immensity taps at your life."
"text": "Tree\nBY JANE HIRSHFIELD\n\nIt is foolish\nto let a young redwood \ngrow next to a house.\n \nEven in this \none lifetime,\n\nyou will have to choose.\n\nThat great calm being,\nthis clutter of soup pots and books— \n\nAlready the first branch-tips \nbrush at the window. \nSoftly, calmly, \nimmensity taps at your life."
}
\ No newline at end of file
......@@ -5,11 +5,12 @@
.chooser {
position: relative;
background: #333;
background: #343779;
grid-column-start: 1;
grid-column-end: 1;
grid-row-start: 3;
grid-row-end: 5;
box-shadow: 0.5px 0px 1px #55a;
}
.artworkArea {
......
......@@ -43,8 +43,12 @@ class App extends React.Component {
audioCat: Math.floor((Math.random() * 3)),
},
sessionHistory: [],
sessionCounter: 0
sessionCounter: 0,
};
localStorage.setItem("Text", this.state.categorySelections.textCat);
localStorage.setItem("Image", this.state.categorySelections.imageCat);
localStorage.setItem("Audio", this.state.categorySelections.audioCat);
localStorage.setItem("artwork", this.state.artwork);
}
}
......@@ -103,11 +107,11 @@ class App extends React.Component {
let newCombination = JSON.parse(sessionStorage.getItem('combinationHistory'));
if (newCombination === null) {
console.log("###");
newCombination = [];
} else {
// Remove forward history if manually selecting new artwork or category
newCombination = newCombination.splice(0, this.state.sessionCounter);
newCombination = newCombination.splice(0, this.state.sessionCounter);
}
newCombination.push(
this.state.sessionCounter + "/" +
......@@ -122,10 +126,10 @@ class App extends React.Component {
sessionCounter: (oldState.sessionCounter + 1)
}));
};
sessionBack = () => {
if (this.state.sessionCounter <= 1) return;
this.setState(oldState => {
let dataAsArray = oldState.sessionHistory[this.state.sessionCounter - 2].split(";");
return {
......@@ -157,7 +161,7 @@ class App extends React.Component {
}
});
}
};
handleArtworkChange(artworkId) {
if (this.state.artwork === artworkId) return;
......@@ -219,21 +223,22 @@ class App extends React.Component {
<div className="App">
<div className="cluster">
<div className="chooser">
<DisplayChooser categorySelections={categorySelections} handler={this}/>
<DisplayChooser categorySelections={categorySelections}
handleCategoryChange={this.handleCategoryChange} />
<FavoriteSection save={this.saveAsFavorite} open={this.openFavorite}/>
</div>
<div className="artworkArea">
<Artwork artwork={this.state.artwork}
handler={this}
handleArtworkChange={this.handleArtworkChange}
categorySelections={categorySelections}/>
</div>
</div>
<FavoriteSection save={this.saveAsFavorite} open={this.openFavorite}/>
<SessionElement sessionHistory={sessionHistory}
sessionClicked={this.sessionClicked}
sessionIndex={this.state.sessionCounter}
backClicked={this.sessionBack}
fwdClicked={this.sessionFwd}
/>
<SessionElement sessionHistory={sessionHistory}
sessionClicked={this.sessionClicked}
sessionIndex={this.state.sessionCounter}
backClicked={this.sessionBack}
fwdClicked={this.sessionFwd}
/>
</div>
);
}
......
......@@ -5,37 +5,26 @@ import Audio from './Audio';
import Tabs from './Tabs';
import '../style/Artwork.css';
export default class Artwork extends React.Component {
constructor(props) {
super(props);
export default function Artwork(props) {
this.handleArtworkChange = this.handleArtworkChange.bind(this);
}
handleArtworkChange(artwork) {
this.props.handler.handleArtworkChange(artwork);
}
render() {
const artwork = this.props.artwork;
const textCat = this.props.categorySelections.textCat;
const imgCat = this.props.categorySelections.imageCat;
const audioCat = this.props.categorySelections.audioCat;
return (
<div className="theArtwork">
<div className="tabs">
<Tabs artwork={artwork} onArtworkChange={this.handleArtworkChange}/>
</div>
<div className="text">
<Text artwork={artwork} cat={textCat}/>
</div>
<div className="theImage">
<Image id="myImage" artwork={artwork} cat={imgCat}/>
</div>
<div className="audio">
<Audio artwork={artwork} cat={audioCat}/>
</div>
const artwork = props.artwork;
const textCat = props.categorySelections.textCat;
const imgCat = props.categorySelections.imageCat;
const audioCat = props.categorySelections.audioCat;
return (
<div className="theArtwork">
<div className="tabs">
<Tabs artwork={artwork} onArtworkChange={props.handleArtworkChange}/>
</div>
<div className="text">
<Text artwork={artwork} cat={textCat}/>
</div>
<div className="theImage">
<Image id="myImage" artwork={artwork} cat={imgCat}/>
</div>
<div className="audio">
<Audio artwork={artwork} cat={audioCat}/>
</div>
);
}
</div>
);
}
......@@ -10,7 +10,7 @@ export default class DisplayChooser extends React.Component {
}
combineMediaCategories = event => {
this.props.handler.handleCategoryChange(event.target.name, event.target.value);
this.props.handleCategoryChange(event.target.name, event.target.value);
localStorage.setItem(event.target.name, event.target.value);
};
......@@ -19,11 +19,11 @@ export default class DisplayChooser extends React.Component {
return (
<div className="Display-chooser">
<MediaContainer checkedIndex={this.props.categorySelections.textCat} categoryNames={categoryNames}
media="Text" handler={this}/>
media="Text" combineMediaCategories={this.combineMediaCategories} />
<MediaContainer checkedIndex={this.props.categorySelections.imageCat} categoryNames={categoryNames}
media="Image" handler={this}/>
media="Image" combineMediaCategories={this.combineMediaCategories} />
<MediaContainer checkedIndex={this.props.categorySelections.audioCat} categoryNames={categoryNames}
media="Audio" handler={this}/>
media="Audio" combineMediaCategories={this.combineMediaCategories} />
</div>
);
}
......
......@@ -15,11 +15,12 @@ export default class FavoriteSection extends React.Component {
this.setState({
showSaveFeedback: true
});
this.timerID = setInterval(
this.timerID = setTimeout (
() => {
this.setState({
showSaveFeedback: false
});
clearInterval(this.timerID);
},
3500
);
......@@ -29,10 +30,8 @@ export default class FavoriteSection extends React.Component {
return (
<div className="Button-container">
<button onClick={this.save}>Save as favorite</button>
<button onClick={this.props.open}>Open favourite</button>
<button onClick={this.props.open}>Open favorite</button>
<p> { (this.state.showSaveFeedback) && "Favorite saved!" } </p>
</div>
);
}
......
......@@ -25,7 +25,7 @@ class Image extends React.Component {
fetchData() {
const resource = "img/" + this.props.cat + "/" + this.props.artwork + ".svg";
fetch(resource, {cache: "force-cache"})
fetch(resource)
.then(res => res.text())
.then(
(result) => {
......
......@@ -16,17 +16,17 @@ export default class MediaContainer extends React.Component {
<div className="Media-component">
<h3>{this.props.media}</h3>
<Categories value={categories[0]} mediaType={this.props.media}
handleChange={this.props.handler.combineMediaCategories}
handleChange={this.props.combineMediaCategories}
categoryName={this.props.categoryNames[0]}
selectionName={this.props.media + categories[0]}
checked={checked[0]}/>
<Categories value={categories[1]} mediaType={this.props.media}
handleChange={this.props.handler.combineMediaCategories}
handleChange={this.props.combineMediaCategories}
categoryName={this.props.categoryNames[1]}
selectionName={this.props.media + categories[1]}
checked={checked[1]}/>
<Categories value={categories[2]} mediaType={this.props.media}
handleChange={this.props.handler.combineMediaCategories}
handleChange={this.props.combineMediaCategories}
categoryName={this.props.categoryNames[2]}
selectionName={this.props.media + categories[2]}
checked={checked[2]}/>
......
......@@ -13,8 +13,8 @@ export default function SessionElement(props) {
}
return (
<div>
<h4>Combination history</h4>
<div className="sessionWrapper">
<h3>Combination history</h3>
<SessionNavigation sessionHistory={sessionHistory} sessionIndex={props.sessionIndex}
backClicked={props.backClicked} fwdClicked={props.fwdClicked} />
<div className="sessionHolder">
......
......@@ -5,7 +5,7 @@ body {
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: #fcfcfc;
}
code {
......
/* Add a black background color to the top navigation */
.navbar {
background-color: #333;
background-color: #fff;
overflow: hidden;
width: 100%;
box-shadow: 0px 2px 2px #efefef;
}
/* Style the links inside the navigation bar */
.navbar div {
float: left;
color: #f2f2f2;
color: #000;
text-align: center;
padding: 14px 16px;
text-decoration: none;
......@@ -32,6 +33,6 @@
/* Add a color to the active/current link */
.navbar div.active {
background-color: #A20C25;
background-color: #343779;
color: white;
}
......@@ -16,6 +16,7 @@
.Category-component > label {
margin: 0 0 0 5px;
user-select: none;
}
.Category-component > label:hover {
......
.Button-container {
margin: 5px auto;
margin: auto;
padding-top: 5px;
}
.Button-container button {
margin: 10px;
margin: 10px auto;
display: block;
padding: 5px;
font-size: 1rem;
background: #A20C25;
color: white;
background: #FFA646;
color: #333;
border: none;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
box-shadow:0px 2px 2px #333;
}
.Button-container button:hover {
cursor: pointer;
}
\ No newline at end of file
}
.Button-container p {
min-height: 25px;
color: white;
text-align: center;
}
@media screen and (max-width: 960px) {
.Button-container button {
display: inline-block;
margin: 10px;
}
}
.sessionWrapper {
background-color: #ffffff;
padding: 8px;
color:#333;
box-shadow:0px -2px 2px #efefef;
}
.sessionWrapper h3 {
display: inline-block;
}
.sessionWrapper .sessionNavButtons {
display: inline-block;
margin: 6px;
}
.sessionHolder {
background-color: #fff;
border-radius: 4px;
max-height: 200px;
overflow-y: scroll;
}
.sessionHolder > p {
padding: 2px;
margin: 4px;
}
.sessionHolder p:hover {
cursor: pointer;
}
.sessionHolder > .selectedSessionItem {
background-color: #eee;
background-color: #7b96e8;
border-radius: 8px;
padding: 2px;
}
@media screen and (max-width: 960px) {
.sessionHolder > p {
padding: 8px;
margin: 4px;
}
}
.sessionNavButtons > .navBtn {
display: inline-block;
background-color: #ddd;
border: 1px solid #555;
background-color: #FFA646;
border: 1px solid #333;
border-radius: 4px;
margin: 6px;
padding: 0px 8px 6px 8px;
font-size: 1.875em;
}
.sessionNavButtons > .navBtn:hover {
background-color: #bbb;
cursor: pointer;
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Old versions of Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, currently
supported by Chrome, Opera and Firefox */
}
.sessionNavButtons > .navBtn:active {
background-color: #999;
}
@media screen and (min-width: 960px) {
.sessionNavButtons > .navBtn:hover {
background-color: #Fec894;
}
}
.sessionNavButtons > .navBtn.inactive {
border: 1px solid #aaa;
background-color: #f8f8f8;
color: #aaa;
background-color: #e0e0e0;
color: #333;
}
......@@ -12,7 +12,7 @@ exports[`FavoriteSection snapshot renders 1`] = `
<button
onClick={[Function]}
>
Open favourite
Open favorite
</button>
<p>
......
......@@ -6,7 +6,7 @@ exports[`TabElement snapshot renders 1`] = `
id={1}
onClick={[Function]}
>
Category
Combo
2
</div>
`;
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment