mirror of
https://gitlab.com/MrFry/qmining-page
synced 2025-04-01 20:23:44 +02:00
Caching fetched resources
This commit is contained in:
parent
7e93e41edc
commit
3daeef184a
12 changed files with 213 additions and 85 deletions
|
@ -66,7 +66,6 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
width: 150px;
|
width: 150px;
|
||||||
background-color: #222426;
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
|
@ -13,7 +13,7 @@ const byVotes = (a, b) => {
|
||||||
return b.votes.length - a.votes.length
|
return b.votes.length - a.votes.length
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Todos() {
|
export default function Todos({ globalState, setGlobalState }) {
|
||||||
const [loaded, setLoaded] = useState(false)
|
const [loaded, setLoaded] = useState(false)
|
||||||
const [columns, setColumns] = useState(null)
|
const [columns, setColumns] = useState(null)
|
||||||
const [cards, setCards] = useState(null)
|
const [cards, setCards] = useState(null)
|
||||||
|
@ -26,6 +26,9 @@ export default function Todos() {
|
||||||
const [activeGroups, setActiveGroups] = useState(null)
|
const [activeGroups, setActiveGroups] = useState(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (globalState.todos) {
|
||||||
|
setTodos(globalState.todos)
|
||||||
|
} else {
|
||||||
fetch(`${constants.apiUrl}todos`, {
|
fetch(`${constants.apiUrl}todos`, {
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
})
|
})
|
||||||
|
@ -34,7 +37,11 @@ export default function Todos() {
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setTodos(data)
|
setTodos(data)
|
||||||
|
setGlobalState({
|
||||||
|
todos: data,
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const setTodos = (data) => {
|
const setTodos = (data) => {
|
||||||
|
@ -188,6 +195,9 @@ export default function Todos() {
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setTodos(data)
|
setTodos(data)
|
||||||
|
setGlobalState({
|
||||||
|
todos: data,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -300,7 +300,7 @@ select:hover {
|
||||||
|
|
||||||
.selectContainer > select,
|
.selectContainer > select,
|
||||||
.selectContainer > input {
|
.selectContainer > input {
|
||||||
width: 20%;
|
width: 30%;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
border: 1.5px solid white;
|
border: 1.5px solid white;
|
||||||
background-color: #9c9c98;
|
background-color: #9c9c98;
|
||||||
|
|
|
@ -10,6 +10,7 @@ function MyApp({ Component, pageProps, router }) {
|
||||||
const [userId, setUserId] = useState()
|
const [userId, setUserId] = useState()
|
||||||
const [motd, setMotd] = useState()
|
const [motd, setMotd] = useState()
|
||||||
const [unreads, setUnreads] = useState()
|
const [unreads, setUnreads] = useState()
|
||||||
|
const [globalState, setGlobalState] = useState({})
|
||||||
|
|
||||||
const getGlobalProps = () => {
|
const getGlobalProps = () => {
|
||||||
fetch(`${constants.apiUrl}infos?motd=true`, {
|
fetch(`${constants.apiUrl}infos?motd=true`, {
|
||||||
|
@ -50,6 +51,13 @@ function MyApp({ Component, pageProps, router }) {
|
||||||
getGlobalProps()
|
getGlobalProps()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const updateGlobalState = (newState) => {
|
||||||
|
setGlobalState({
|
||||||
|
...globalState,
|
||||||
|
...newState,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const globalData = {
|
const globalData = {
|
||||||
userId: userId,
|
userId: userId,
|
||||||
motd: motd,
|
motd: motd,
|
||||||
|
@ -65,12 +73,16 @@ function MyApp({ Component, pageProps, router }) {
|
||||||
router={router}
|
router={router}
|
||||||
globalData={globalData}
|
globalData={globalData}
|
||||||
refetchGlobalData={getGlobalProps}
|
refetchGlobalData={getGlobalProps}
|
||||||
|
setGlobalState={updateGlobalState}
|
||||||
|
globalState={globalState}
|
||||||
>
|
>
|
||||||
<Component
|
<Component
|
||||||
{...pageProps}
|
{...pageProps}
|
||||||
router={router}
|
router={router}
|
||||||
globalData={globalData}
|
globalData={globalData}
|
||||||
refetchGlobalData={getGlobalProps}
|
refetchGlobalData={getGlobalProps}
|
||||||
|
setGlobalState={updateGlobalState}
|
||||||
|
globalState={globalState}
|
||||||
/>
|
/>
|
||||||
</Layout>
|
</Layout>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -70,7 +70,7 @@ function fetchDbs() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AllQuestions({ router }) {
|
export default function AllQuestions({ router, globalState, setGlobalState }) {
|
||||||
const [subjectsShowing, setSubjectsShowing] = useState(false)
|
const [subjectsShowing, setSubjectsShowing] = useState(false)
|
||||||
const [searchTerm, setSearchTerm] = useState('')
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
const [activeSubjName, setActiveSubjName] = useState('')
|
const [activeSubjName, setActiveSubjName] = useState('')
|
||||||
|
@ -112,15 +112,60 @@ export default function AllQuestions({ router }) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (dbs && selectedDb && (selectedDb === 'all' || dbs[selectedDb])) {
|
if (dbs && selectedDb && (selectedDb === 'all' || dbs[selectedDb])) {
|
||||||
if (selectedDb === 'all') {
|
if (selectedDb === 'all') {
|
||||||
|
const hasAll =
|
||||||
|
globalState.dbs &&
|
||||||
|
dbs.every((db) => {
|
||||||
|
return (
|
||||||
|
Object.keys(globalState.dbs).findIndex((key) => {
|
||||||
|
return db.name === key
|
||||||
|
}) !== -1
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (hasAll) {
|
||||||
|
const asd = Object.keys(globalState.dbs).map((key) => {
|
||||||
|
return {
|
||||||
|
dbName: key,
|
||||||
|
data: globalState.dbs[key],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setData(mergeData(asd))
|
||||||
|
setFetchingData(false)
|
||||||
|
} else {
|
||||||
fetchAllData(dbs).then((res) => {
|
fetchAllData(dbs).then((res) => {
|
||||||
setData(mergeData(res))
|
setData(mergeData(res))
|
||||||
setFetchingData(false)
|
setFetchingData(false)
|
||||||
|
|
||||||
|
let cacheRes = {}
|
||||||
|
res.forEach((db) => {
|
||||||
|
cacheRes = {
|
||||||
|
...cacheRes,
|
||||||
|
[db.dbName]: db.data,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
setGlobalState({
|
||||||
|
dbs: cacheRes,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const selected = dbs[selectedDb]
|
||||||
|
if (globalState.dbs && globalState.dbs[selected.name]) {
|
||||||
|
setData(globalState.dbs[selected.name])
|
||||||
|
setFetchingData(false)
|
||||||
} else {
|
} else {
|
||||||
fetchData(dbs[selectedDb]).then((res) => {
|
fetchData(dbs[selectedDb]).then((res) => {
|
||||||
setData(res.data)
|
setData(res.data)
|
||||||
setFetchingData(false)
|
setFetchingData(false)
|
||||||
|
|
||||||
|
setGlobalState({
|
||||||
|
dbs: {
|
||||||
|
...globalState.dbs,
|
||||||
|
[res.dbName]: res.data,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [selectedDb, dbs])
|
}, [selectedDb, dbs])
|
||||||
|
|
|
@ -8,10 +8,13 @@ import LoadingIndicator from '../components/LoadingIndicator'
|
||||||
|
|
||||||
import styles from './contact.module.css'
|
import styles from './contact.module.css'
|
||||||
|
|
||||||
export default function Contact() {
|
export default function Contact({ globalState, setGlobalState }) {
|
||||||
const [contacts, setContacts] = useState()
|
const [contacts, setContacts] = useState()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (globalState.contacts) {
|
||||||
|
setContacts(globalState.contacts)
|
||||||
|
} else {
|
||||||
fetch(constants.apiUrl + 'contacts.json', {
|
fetch(constants.apiUrl + 'contacts.json', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
|
@ -25,7 +28,11 @@ export default function Contact() {
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
setContacts(res)
|
setContacts(res)
|
||||||
|
setGlobalState({
|
||||||
|
contacts: res,
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -9,7 +9,7 @@ import constants from '../constants.json'
|
||||||
import styles from './contribute.module.css'
|
import styles from './contribute.module.css'
|
||||||
import repos from '../data/repos.json'
|
import repos from '../data/repos.json'
|
||||||
|
|
||||||
export default function contribute() {
|
export default function Contribute({ globalState, setGlobalState }) {
|
||||||
const [showFeedback, setShowFeedback] = useState(false)
|
const [showFeedback, setShowFeedback] = useState(false)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -52,7 +52,7 @@ export default function contribute() {
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<hr />
|
<hr />
|
||||||
<Todos />
|
<Todos globalState={globalState} setGlobalState={setGlobalState} />
|
||||||
<hr />
|
<hr />
|
||||||
<div id={'gitrepo'} className={styles.gitRepos}>
|
<div id={'gitrepo'} className={styles.gitRepos}>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -76,7 +76,7 @@ function updateForumPost(forum, postKey, postData) {
|
||||||
}, {})
|
}, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Index({ globalData }) {
|
export default function Index({ globalData, globalState, setGlobalState }) {
|
||||||
const userId = globalData.userId
|
const userId = globalData.userId
|
||||||
const motd = globalData.motd
|
const motd = globalData.motd
|
||||||
const [news, setNews] = useState(null)
|
const [news, setNews] = useState(null)
|
||||||
|
@ -84,13 +84,20 @@ export default function Index({ globalData }) {
|
||||||
const [fetchingForum, setFetchingForum] = useState(false)
|
const [fetchingForum, setFetchingForum] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (globalState.news) {
|
||||||
|
const { entries, nextKey } = globalState.news
|
||||||
|
setNextEntryKey(nextKey)
|
||||||
|
setNews(entries)
|
||||||
|
} else {
|
||||||
setFetchingForum(true)
|
setFetchingForum(true)
|
||||||
fetchForum().then((res) => {
|
fetchForum().then((res) => {
|
||||||
setFetchingForum(false)
|
setFetchingForum(false)
|
||||||
const { entries, nextKey } = res
|
const { entries, nextKey } = res
|
||||||
setNextEntryKey(nextKey)
|
setNextEntryKey(nextKey)
|
||||||
setNews(entries)
|
setNews(entries)
|
||||||
|
setGlobalState({ news: res })
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const renderNews = () => {
|
const renderNews = () => {
|
||||||
|
@ -281,6 +288,12 @@ export default function Index({ globalData }) {
|
||||||
const { entries, nextKey } = res
|
const { entries, nextKey } = res
|
||||||
setNextEntryKey(nextKey)
|
setNextEntryKey(nextKey)
|
||||||
setNews({ ...news, ...entries })
|
setNews({ ...news, ...entries })
|
||||||
|
setGlobalState({
|
||||||
|
news: {
|
||||||
|
entries: { ...news, ...entries },
|
||||||
|
nextKey: nextKey,
|
||||||
|
},
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -40,7 +40,7 @@ function requestPw() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PwRequest({ globalData }) {
|
export default function PwRequest({ globalData, globalState, setGlobalState }) {
|
||||||
const userId = globalData.userId || '...'
|
const userId = globalData.userId || '...'
|
||||||
const [result, setResult] = useState([])
|
const [result, setResult] = useState([])
|
||||||
const [daysTillNext, setDaysTillNext] = useState('...')
|
const [daysTillNext, setDaysTillNext] = useState('...')
|
||||||
|
@ -65,9 +65,16 @@ export default function PwRequest({ globalData }) {
|
||||||
} = data
|
} = data
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (globalState.availablePWS) {
|
||||||
|
setData(globalState.availablePWS)
|
||||||
|
} else {
|
||||||
fetchAvailablePWS().then((data) => {
|
fetchAvailablePWS().then((data) => {
|
||||||
setData(data)
|
setData(data)
|
||||||
|
setGlobalState({
|
||||||
|
availablePWS: data,
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
const nextDate = 1
|
const nextDate = 1
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
if (now.getDate() === nextDate) {
|
if (now.getDate() === nextDate) {
|
||||||
|
@ -154,6 +161,9 @@ export default function PwRequest({ globalData }) {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
requestPw().then((res) => {
|
requestPw().then((res) => {
|
||||||
setData(res)
|
setData(res)
|
||||||
|
setGlobalState({
|
||||||
|
availablePWS: res,
|
||||||
|
})
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
setResult([...result, res.pw])
|
setResult([...result, res.pw])
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -84,7 +84,7 @@ function sortDataBy(data, key) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RankList({ globalData }) {
|
export default function RankList({ globalData, globalState, setGlobalState }) {
|
||||||
const userId = globalData.userId
|
const userId = globalData.userId
|
||||||
const [ranklist, setRanklist] = useState(null)
|
const [ranklist, setRanklist] = useState(null)
|
||||||
const [selfUserId, setSelfUserId] = useState('...')
|
const [selfUserId, setSelfUserId] = useState('...')
|
||||||
|
@ -96,18 +96,31 @@ export default function RankList({ globalData }) {
|
||||||
const getList = () => {
|
const getList = () => {
|
||||||
setSum()
|
setSum()
|
||||||
setRanklist(null)
|
setRanklist(null)
|
||||||
|
if (globalState.ranklist) {
|
||||||
|
const { list, sum, selfuserId } = globalState.ranklist
|
||||||
|
setRanklist(list || [])
|
||||||
|
setSum(sum)
|
||||||
|
if (selfuserId) {
|
||||||
|
setSelfUserId(selfuserId)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
getListFromServer(since)
|
getListFromServer(since)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setRanklist(data.list || [])
|
const { list, sum, selfuserId } = data
|
||||||
setSum(data.sum)
|
setRanklist(list || [])
|
||||||
if (data.selfuserId) {
|
setSum(sum)
|
||||||
setSelfUserId(data.selfuserId)
|
if (selfuserId) {
|
||||||
|
setSelfUserId(selfuserId)
|
||||||
}
|
}
|
||||||
|
setGlobalState({
|
||||||
|
ranklist: data,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setRanklist(null)
|
setRanklist(null)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getList()
|
getList()
|
||||||
|
|
|
@ -22,13 +22,20 @@ function fetchSupportedSites() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Script() {
|
export default function Script({ globalState, setGlobalState }) {
|
||||||
const [supportedSites, setSupportedSites] = useState()
|
const [supportedSites, setSupportedSites] = useState()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (globalState.supportedSites) {
|
||||||
|
setSupportedSites(globalState.supportedSites)
|
||||||
|
} else {
|
||||||
fetchSupportedSites().then((res) => {
|
fetchSupportedSites().then((res) => {
|
||||||
setSupportedSites(res)
|
setSupportedSites(res)
|
||||||
|
setGlobalState({
|
||||||
|
supportedSites: res,
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -184,7 +184,12 @@ function uploadFile(dir, file) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function UserFiles({ router, globalData }) {
|
export default function UserFiles({
|
||||||
|
router,
|
||||||
|
globalData,
|
||||||
|
globalState,
|
||||||
|
setGlobalState,
|
||||||
|
}) {
|
||||||
const userId = globalData.userId
|
const userId = globalData.userId
|
||||||
const [dirs, setDirs] = useState()
|
const [dirs, setDirs] = useState()
|
||||||
const [sortBy, setSortBy] = useState('name')
|
const [sortBy, setSortBy] = useState('name')
|
||||||
|
@ -210,23 +215,48 @@ export default function UserFiles({ router, globalData }) {
|
||||||
setSearchTerm()
|
setSearchTerm()
|
||||||
|
|
||||||
if (router.query.dir) {
|
if (router.query.dir) {
|
||||||
|
getDir(dir)
|
||||||
|
} else {
|
||||||
|
getRootDir()
|
||||||
|
}
|
||||||
|
}, [router.query.dir])
|
||||||
|
|
||||||
|
const getDir = (dir, nocache) => {
|
||||||
|
if (!nocache && globalState.userFiles && globalState.userFiles[dir]) {
|
||||||
|
setDirs(globalState.userFiles[dir])
|
||||||
|
} else {
|
||||||
listUserDir(dir)
|
listUserDir(dir)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
setDirs(res.files)
|
setDirs(res.files)
|
||||||
|
setGlobalState({
|
||||||
|
userFiles: {
|
||||||
|
...globalState.userFiles,
|
||||||
|
[dir]: res.files,
|
||||||
|
},
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.catch((res) => {
|
.catch((res) => {
|
||||||
alert(res.msg)
|
alert(res.msg)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRootDir = (nocache) => {
|
||||||
|
if (!nocache && globalState.userFilesRoot) {
|
||||||
|
setDirs(globalState.userFilesRoot)
|
||||||
} else {
|
} else {
|
||||||
listUserDir()
|
listUserDir()
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
setGlobalState({
|
||||||
|
userFilesRoot: res.dirs,
|
||||||
|
})
|
||||||
setDirs(res.dirs)
|
setDirs(res.dirs)
|
||||||
})
|
})
|
||||||
.catch((res) => {
|
.catch((res) => {
|
||||||
alert(res.msg)
|
alert(res.msg)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [router.query.dir])
|
}
|
||||||
|
|
||||||
const dirSorter = (a, b) => {
|
const dirSorter = (a, b) => {
|
||||||
if (a[sortBy] < b[sortBy]) {
|
if (a[sortBy] < b[sortBy]) {
|
||||||
|
@ -391,13 +421,7 @@ export default function UserFiles({ router, globalData }) {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
if (confirm(`Biztos törlöd '${name}'-t ?`)) {
|
if (confirm(`Biztos törlöd '${name}'-t ?`)) {
|
||||||
deleteFile(currDir, name).then(() => {
|
deleteFile(currDir, name).then(() => {
|
||||||
listUserDir(currDir)
|
getDir(currDir, true)
|
||||||
.then((res) => {
|
|
||||||
setDirs(res.files)
|
|
||||||
})
|
|
||||||
.catch((res) => {
|
|
||||||
alert(res.msg)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
@ -479,14 +503,8 @@ export default function UserFiles({ router, globalData }) {
|
||||||
setUploading(true)
|
setUploading(true)
|
||||||
uploadFile(currDir, file).then(() => {
|
uploadFile(currDir, file).then(() => {
|
||||||
setUploading(false)
|
setUploading(false)
|
||||||
listUserDir(currDir)
|
|
||||||
.then((res) => {
|
|
||||||
setDirs(res.files)
|
|
||||||
setAddingNew(null)
|
setAddingNew(null)
|
||||||
})
|
getDir(currDir, true)
|
||||||
.catch((res) => {
|
|
||||||
alert(res.msg)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -507,14 +525,8 @@ export default function UserFiles({ router, globalData }) {
|
||||||
setUploading(true)
|
setUploading(true)
|
||||||
newSubj(newSubjName).then(() => {
|
newSubj(newSubjName).then(() => {
|
||||||
setUploading(false)
|
setUploading(false)
|
||||||
listUserDir()
|
|
||||||
.then((res) => {
|
|
||||||
setDirs(res.dirs)
|
|
||||||
setAddingNew(null)
|
setAddingNew(null)
|
||||||
})
|
getRootDir()
|
||||||
.catch((res) => {
|
|
||||||
alert(res.msg)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue