Subscriptions are now working and updating some parts of the view.
This commit is contained in:
parent
8687355932
commit
14ce6df7af
10 changed files with 430 additions and 127 deletions
|
@ -3,10 +3,11 @@ import { getQueryParamByName } from './utils.js';
|
||||||
import ScoutOverviewCont from './containers/GetScoutData.js';
|
import ScoutOverviewCont from './containers/GetScoutData.js';
|
||||||
import ScoutOverview from './components/ScoutOverview.js';
|
import ScoutOverview from './components/ScoutOverview.js';
|
||||||
|
|
||||||
|
const ScoutView = ScoutOverviewCont(ScoutOverview);
|
||||||
|
|
||||||
class App extends Component {
|
class App extends Component {
|
||||||
render() {
|
render() {
|
||||||
const scoutID = getQueryParamByName("id", this.props.slug);
|
const scoutID = getQueryParamByName("id", this.props.slug);
|
||||||
const ScoutView = ScoutOverviewCont(ScoutOverview);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
|
|
|
@ -1,5 +1,30 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { gql, graphql } from 'react-apollo';
|
||||||
|
|
||||||
export default({id, number, description, additionalText}) => {
|
export default({
|
||||||
return <div>{number}</div>;
|
id,
|
||||||
|
number,
|
||||||
|
completed,
|
||||||
|
letter,
|
||||||
|
description,
|
||||||
|
additionalText,
|
||||||
|
scoutID,
|
||||||
|
mutate
|
||||||
|
}) => {
|
||||||
|
if(completed) {
|
||||||
|
return <div className='completed-achievement'>
|
||||||
|
{number}{letter}
|
||||||
|
</div>;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
const variables = {
|
||||||
|
scoutID,
|
||||||
|
id,
|
||||||
|
date: (new Date(Date.now())).toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
return <button onClick={() => mutate({variables})} className='achievement'>
|
||||||
|
{number}{letter}
|
||||||
|
</button>;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,22 +1,55 @@
|
||||||
import React from 'react'
|
import React from 'react';
|
||||||
|
import Achievement from './Achievement.js';
|
||||||
|
import CompleteAchievement from '../containers/CompleteAchievement.js';
|
||||||
|
|
||||||
import Achievement from "./Achievement.js";
|
const AchievementButton = CompleteAchievement(Achievement);
|
||||||
|
|
||||||
export default ({achievements, id, completed}) => {
|
export default class AchievementList extends React.Component {
|
||||||
console.log("completed: ", completed);
|
|
||||||
if(!achievements) {
|
componentWillMount() {
|
||||||
return <div>Loading...</div>
|
this.unsubscribe = this.props.subscribeToAchievements({
|
||||||
|
scoutID: this.props.scoutID,
|
||||||
|
adventureID: this.props.adventureID
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const list = [...achievements].sort((a, b) => {
|
render() {
|
||||||
if (a.number === b.number)
|
const {
|
||||||
return a.letter > b.letter;
|
allAchievements,
|
||||||
return a.number > b.number;
|
completedAchievements,
|
||||||
}).map(achievement => (
|
scoutID
|
||||||
<Achievement key={achievement.id} {...achievement} />
|
} = this.props;
|
||||||
));
|
|
||||||
|
const completedMap = completedAchievements.reduce((map, ach) => {
|
||||||
return <div>
|
map[ach.achievement.id] = ach;
|
||||||
{list}
|
return map
|
||||||
</div>
|
}, {});
|
||||||
|
|
||||||
|
if(!allAchievements || !allAchievements.length) {
|
||||||
|
return <div>Loading...</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = [...allAchievements].sort((a, b) => {
|
||||||
|
if (a.number === b.number)
|
||||||
|
return a.letter > b.letter;
|
||||||
|
return a.number > b.number;
|
||||||
|
}).map(achievement => {
|
||||||
|
const props = {
|
||||||
|
key: achievement.id,
|
||||||
|
completed: completedMap[achievement.id],
|
||||||
|
scoutID,
|
||||||
|
...achievement
|
||||||
|
};
|
||||||
|
return <AchievementButton {...props} />
|
||||||
|
});
|
||||||
|
|
||||||
|
return <div>
|
||||||
|
{list}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,28 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import GetAchievements from "../containers/GetAchievementsForAdventure.js";
|
import GetAchievements from '../containers/GetAchievementsForAdventure.js';
|
||||||
import AchievementList from "./AchievementList.js";
|
import AchievementList from './AchievementList.js';
|
||||||
|
import {Card, CardHeader} from 'material-ui/Card';
|
||||||
|
|
||||||
export default ({id, name, number, isRequired, achievements,
|
const AchievementListComp = GetAchievements(AchievementList);
|
||||||
completedAdventures, completedAchievements}) => {
|
|
||||||
|
|
||||||
const AchievementListComp = GetAchievements(AchievementList);
|
export default ({
|
||||||
return <div>
|
id,
|
||||||
<div>{name}</div>
|
name,
|
||||||
<AchievementListComp adventureID={id} completed={completedAchievements}/>
|
number,
|
||||||
</div>
|
isRequired,
|
||||||
|
achievements,
|
||||||
|
completed,
|
||||||
|
scoutID
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const className = `adventure ${completed ? 'completed-adventure' :''}`;
|
||||||
|
return <Card className={className}>
|
||||||
|
<CardHeader title={name} />
|
||||||
|
<AchievementListComp
|
||||||
|
scoutID = {scoutID}
|
||||||
|
adventureID={id}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,39 +1,74 @@
|
||||||
import React from "react"
|
import React from 'react';
|
||||||
import { splitFilter } from '../utils.js';
|
import { splitFilter } from '../utils.js';
|
||||||
import Adventure from './Adventure.js';
|
import Adventure from './Adventure.js';
|
||||||
|
|
||||||
export default ({
|
export default class AdventureList extends React.Component {
|
||||||
allAdventures,
|
|
||||||
completedAdventures
|
componentWillMount() {
|
||||||
}) => {
|
this.unsubscribe = this.props.subscribeToAdventures({
|
||||||
if(!allAdventures) {
|
scoutID: this.props.scoutID
|
||||||
return <div> Loading... </div>
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortedAdventures = [...allAdventures].sort((a,b) => (a.number > b.number));
|
render() {
|
||||||
|
const {
|
||||||
|
scoutID,
|
||||||
|
allAdventures,
|
||||||
|
completedAdventures
|
||||||
|
} = this.props;
|
||||||
|
console.log("completed!: ", completedAdventures);
|
||||||
|
|
||||||
const [
|
if(!allAdventures || !allAdventures.length) {
|
||||||
requiredAdventures,
|
return <div> Loading... </div>
|
||||||
optionalAdventures
|
}
|
||||||
] = splitFilter(sortedAdventures, a => a.required);
|
|
||||||
|
|
||||||
const requiredAdventureList = requiredAdventures.map(adventure => {
|
const completedMap = completedAdventures.reduce((map, adv) => {
|
||||||
return <Adventure key={adventure.id} {...adventure} />;
|
map[adv.adventure.id] = adv;
|
||||||
});
|
return map;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const sortedAdventures = [...allAdventures].sort((a,b) => (a.number > b.number));
|
||||||
|
|
||||||
const optionalAdventureList = optionalAdventures.map(adventure => {
|
const [
|
||||||
return <Adventure key={adventure.id} {...adventure} />;
|
requiredAdventures,
|
||||||
});
|
optionalAdventures
|
||||||
|
] = splitFilter(sortedAdventures, a => a.required);
|
||||||
|
|
||||||
return <div>
|
const requiredAdventureList = requiredAdventures.map(adventure => {
|
||||||
<div>
|
const props = {
|
||||||
{requiredAdventureList}
|
key: adventure.id,
|
||||||
</div>
|
scoutID: scoutID,
|
||||||
<div>
|
completed: completedMap[adventure.id],
|
||||||
{
|
...adventure
|
||||||
//optionalAdventureList
|
|
||||||
}
|
}
|
||||||
</div>
|
return <Adventure {...props} />;
|
||||||
</div>;
|
});
|
||||||
|
|
||||||
|
const optionalAdventureList = optionalAdventures.map(adventure => {
|
||||||
|
console.log("ADVENTURE!: ", adventure);
|
||||||
|
const props = {
|
||||||
|
key: adventure.id,
|
||||||
|
scoutID: scoutID,
|
||||||
|
completed: completedMap[adventure.id],
|
||||||
|
...adventure
|
||||||
|
}
|
||||||
|
return <Adventure {...props} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
return <div className="adventure-list" >
|
||||||
|
<div >
|
||||||
|
{requiredAdventureList}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
//optionalAdventureList
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,25 +3,38 @@ import AllAdventures from '../containers/GetAllAdventures.js'
|
||||||
import AdventureList from './AdventureList.js';
|
import AdventureList from './AdventureList.js';
|
||||||
import AppBar from 'material-ui/AppBar';
|
import AppBar from 'material-ui/AppBar';
|
||||||
|
|
||||||
export default ({
|
const AdventureListComp = AllAdventures(AdventureList);
|
||||||
scoutID,
|
|
||||||
displayName,
|
export default class ScoutOverview extends React.Component {
|
||||||
advancementDeadline,
|
|
||||||
completedAdventures,
|
componentWillMount() {
|
||||||
completedAchievements
|
this.unsubscribe = this.props.subscribeToData({
|
||||||
}) => {
|
scoutID: this.props.scoutID
|
||||||
const appBarProps = {
|
});
|
||||||
title: displayName,
|
|
||||||
showMenuIconButton: false
|
|
||||||
}
|
}
|
||||||
const AdventureListComp = AllAdventures(AdventureList);
|
|
||||||
console.log(displayName)
|
render() {
|
||||||
return (
|
const {
|
||||||
<div>
|
scoutID,
|
||||||
<AppBar {...appBarProps} />
|
displayName,
|
||||||
<AdventureListComp
|
advancementDeadline,
|
||||||
{...{scoutID, completedAdventures, completedAchievements}}
|
} = this.props;
|
||||||
/>
|
const appBarProps = {
|
||||||
</div>
|
title: displayName,
|
||||||
);
|
showMenuIconButton: false
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<AppBar {...appBarProps} />
|
||||||
|
<AdventureListComp
|
||||||
|
scoutID = {scoutID}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
13
src/containers/CompleteAchievement.js
Normal file
13
src/containers/CompleteAchievement.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { gql, graphql } from 'react-apollo';
|
||||||
|
|
||||||
|
export default graphql(gql`
|
||||||
|
mutation MarkAchievementComplete($id:ID!, $scoutID:ID!, $date:DateTime!){
|
||||||
|
createCompletedAchievement(
|
||||||
|
completedAt: $date
|
||||||
|
achievementId: $id
|
||||||
|
scoutId: $scoutID
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
|
@ -1,32 +1,106 @@
|
||||||
import { gql, graphql } from 'react-apollo';
|
import { gql, graphql } from 'react-apollo';
|
||||||
|
|
||||||
export default graphql(gql`
|
export default graphql(gql`
|
||||||
query GetAchievementsForAdventure($adventureID:ID!){
|
query GetAchievementsForAdventure($adventureID:ID! $scoutID:ID!){
|
||||||
allAchievements(filter: {
|
all: allAchievements(filter: {
|
||||||
adventure: {
|
adventure: {id: $adventureID}
|
||||||
id: $adventureID
|
}) {
|
||||||
|
id
|
||||||
|
number
|
||||||
|
letter
|
||||||
|
description
|
||||||
|
additionalText
|
||||||
|
}
|
||||||
|
completed: allCompletedAchievements(filter:{
|
||||||
|
scout: {id: $scoutID}
|
||||||
|
achievement: {
|
||||||
|
adventure: {id: $adventureID}
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
id,
|
id
|
||||||
number,
|
completedAt
|
||||||
letter,
|
achievement {
|
||||||
description,
|
id
|
||||||
additionalText
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,{
|
`,{
|
||||||
options: ({adventureID}) => {
|
options: ({adventureID, scoutID}) => ({variables: { adventureID, scoutID }}),
|
||||||
return {
|
|
||||||
variables: {
|
|
||||||
adventureID
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
props: ({ownProps, data}) => {
|
props: ({ownProps, data}) => {
|
||||||
return data.allAchievements ? {
|
const {
|
||||||
achievements: data.allAchievements,
|
subscribeToMore,
|
||||||
...ownProps
|
all,
|
||||||
} : {...ownProps}
|
completed
|
||||||
|
} = data;
|
||||||
|
|
||||||
|
const subscribeToAchievements = params => {
|
||||||
|
return subscribeToMore({
|
||||||
|
document: gql`
|
||||||
|
subscription subscribeToCompAch($scoutID:ID!, $adventureID:ID!){
|
||||||
|
CompletedAchievement(filter: {
|
||||||
|
mutation_in: [UPDATED, CREATED, DELETED]
|
||||||
|
node: {
|
||||||
|
scout: {
|
||||||
|
id: $scoutID
|
||||||
|
}
|
||||||
|
achievement: {
|
||||||
|
adventure: {
|
||||||
|
id: $adventureID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}){
|
||||||
|
mutation
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
completedAt
|
||||||
|
achievement {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
previousValues {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
scoutID: params.scoutID,
|
||||||
|
adventureID: params.adventureID
|
||||||
|
},
|
||||||
|
updateQuery: (prev, response) => {
|
||||||
|
const subscriptionData = response.subscriptionData.data.CompletedAchievement;
|
||||||
|
console.log("UPDATE! :", response);
|
||||||
|
|
||||||
|
let updatedAchievements;
|
||||||
|
const oldAchievements = prev.completed
|
||||||
|
|
||||||
|
if(subscriptionData.mutation === 'DELETED') {
|
||||||
|
updatedAchievements = oldAchievements.filter(ach => (
|
||||||
|
ach.id !== subscriptionData.previousValues.id
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
const updatedID = subscriptionData.node.id;
|
||||||
|
updatedAchievements = [...oldAchievements];
|
||||||
|
let index = oldAchievements.findIndex(adventure => (
|
||||||
|
adventure.id === updatedID
|
||||||
|
));
|
||||||
|
if(index === -1) index = updatedAchievements.length;
|
||||||
|
updatedAchievements[index] = subscriptionData.node;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.assign({}, prev, {
|
||||||
|
completed: updatedAchievements
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
allAchievements: all || [],
|
||||||
|
completedAchievements: completed || [],
|
||||||
|
subscribeToAchievements,
|
||||||
|
...ownProps
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,22 +1,90 @@
|
||||||
import { gql, graphql } from 'react-apollo';
|
import { gql, graphql } from 'react-apollo';
|
||||||
|
|
||||||
export default graphql(gql`
|
export default graphql(gql`
|
||||||
query GetAllAdventures{
|
query GetAllAdventures($scoutID:ID!){
|
||||||
allAdventures {
|
allAdventures: allAdventures {
|
||||||
name,
|
name
|
||||||
number,
|
number
|
||||||
required,
|
required
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
completedAdventures: allCompletedAdventures(filter:{
|
||||||
|
scout: { id: $scoutID }
|
||||||
|
}) {
|
||||||
|
id
|
||||||
|
signer { id }
|
||||||
|
adventure { id }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`,{
|
`,{
|
||||||
props: ({ownProps, data}) => (
|
options: ({scoutID}) => ({variables: {scoutID}}),
|
||||||
data.allAdventures ? {
|
props: ({ownProps, data}) => {
|
||||||
allAdventures: data.allAdventures,
|
const {
|
||||||
|
subscribeToMore,
|
||||||
|
completedAdventures,
|
||||||
|
allAdventures
|
||||||
|
} = data;
|
||||||
|
const subscribeToAdventures = params => {
|
||||||
|
return subscribeToMore({
|
||||||
|
document: gql`
|
||||||
|
subscription subscribeToCompletedAdventures($scoutID:ID!){
|
||||||
|
CompletedAdventure(filter: {
|
||||||
|
mutation_in: [UPDATED, CREATED, DELETED]
|
||||||
|
node: {
|
||||||
|
scout: {
|
||||||
|
id: $scoutID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}){
|
||||||
|
mutation
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
signer { id }
|
||||||
|
adventure { id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
scoutID: params.scoutID
|
||||||
|
},
|
||||||
|
updateQuery: (prev, {subscriptionData}) => {
|
||||||
|
console.log("SubscriptionData: ", subscriptionData);
|
||||||
|
if(!subscriptionData.data) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
let updatedAdventures;
|
||||||
|
const updatedID = subscriptionData.node.id;
|
||||||
|
const oldAdventures = prev.completedAdventures;
|
||||||
|
|
||||||
|
if(subscriptionData.mutation === "DELETE") {
|
||||||
|
updatedAdventures = oldAdventures.filter(adventure => (
|
||||||
|
adventure.id !== updatedID
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
updatedAdventures = [...oldAdventures];
|
||||||
|
let index = oldAdventures.findIndex(adventure => (
|
||||||
|
adventure.id === updatedID
|
||||||
|
));
|
||||||
|
if(index === -1) index = updatedAdventures.length;
|
||||||
|
updatedAdventures[index] = subscriptionData;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.assign({}, prev, {
|
||||||
|
completedAdventures: updatedAdventures
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
allAdventures: allAdventures || [],
|
||||||
|
completedAdventures: completedAdventures || [],
|
||||||
|
subscribeToAdventures,
|
||||||
...ownProps
|
...ownProps
|
||||||
} : {...ownProps})
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,29 +5,57 @@ export default graphql(gql`
|
||||||
Scout(id: $scoutID) {
|
Scout(id: $scoutID) {
|
||||||
displayName
|
displayName
|
||||||
advancementDeadline
|
advancementDeadline
|
||||||
completedAdventures {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
}
|
|
||||||
completedAchievements {
|
|
||||||
id
|
|
||||||
number
|
|
||||||
letter
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
`,{
|
`,{
|
||||||
options : ({scoutID}) => ({variables: {scoutID}}),
|
options : ({scoutID}) => ({variables: {scoutID}}),
|
||||||
props: ({ ownProps, data }) => (
|
name: 'scoutData',
|
||||||
data.Scout ? {
|
props: ({ ownProps, data, scoutData }) => {
|
||||||
|
const subscribeToData = params => {
|
||||||
|
return scoutData.subscribeToMore({
|
||||||
|
document: gql`
|
||||||
|
subscription subscribeToScoutData($scoutID:ID!){
|
||||||
|
Scout(filter: {
|
||||||
|
mutation_in: [UPDATED]
|
||||||
|
node: {
|
||||||
|
id: $scoutID
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
mutation
|
||||||
|
node {
|
||||||
|
displayName
|
||||||
|
advancementDeadline
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
scoutID: params.scoutID
|
||||||
|
},
|
||||||
|
updateQuery: (prev, {subscriptionData}) => {
|
||||||
|
if(!subscriptionData.data) {
|
||||||
|
console.log('no data!');
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
console.log('data! : ', prev, ':', subscriptionData);
|
||||||
|
return Object.assign({}, prev, {
|
||||||
|
Scout: subscriptionData.data.Scout.node
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return scoutData.Scout ? {
|
||||||
scoutID: ownProps.scoutID,
|
scoutID: ownProps.scoutID,
|
||||||
displayName: data.Scout.displayName,
|
displayName: scoutData.Scout.displayName,
|
||||||
advancementDeadline: data.Scout.advancementDeadline,
|
advancementDeadline: scoutData.Scout.advancementDeadline,
|
||||||
completedAdventures: data.Scout.completedAdventures,
|
subscribeToData,
|
||||||
completedAchievements: data.Scout.completedAchievements
|
...ownProps
|
||||||
} : {
|
} : {
|
||||||
scoutID: ownProps.scoutID
|
scoutID: ownProps.scoutID,
|
||||||
|
subscribeToData,
|
||||||
|
...ownProps
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue