This commit is contained in:
DmitryGantimurov 2023-08-12 01:54:40 +03:00
commit a5798cf767
9 changed files with 172 additions and 125 deletions

View File

@ -87,7 +87,7 @@ def put_in_db(name: Annotated[str, Form()], category: Annotated[str, Form()], be
temp_ancmt = models.Announcement(user_id=current_user.id, name=name, category=category, best_by=bestBy,
address=address, longtitude=longtitude, latitude=latitude, description=description, metro=metro,
trashId=trashId, booked_by=0)
trashId=trashId, src=uploaded_name, booked_by=0)
db.add(temp_ancmt) # добавляем в бд
db.commit() # сохраняем изменения
db.refresh(temp_ancmt) # обновляем состояние объекта
@ -211,8 +211,10 @@ def poems_to_front(db: Annotated[Session, Depends(utils.get_db)]): # db: Annotat
@app.get("/api/trashbox", response_model=List[schemas.TrashboxResponse])
def get_trashboxes(data: schemas.TrashboxRequest = Depends()):#крутая функция для работы с api
# json, передаваемый стороннему API
head = {'Authorization': 'Bearer {}'.format(service.my_token)}
# Данные пользователя (местоположение, количество мусорок, которое пользователь хочет видеть)
BASE_URL= "https://geointelect2.gate.petersburg.ru"
my_token="eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJhU1RaZm42bHpTdURYcUttRkg1SzN5UDFhT0FxUkhTNm9OendMUExaTXhFIn0.eyJleHAiOjE3ODYyMjUzMzMsImlhdCI6MTY5MTUzMDkzMywianRpIjoiYjU0MmU3MTQtYzJkMS00NTY2LWJkY2MtYmQ5NzA0ODY1ZjgzIiwiaXNzIjoiaHR0cHM6Ly9rYy5wZXRlcnNidXJnLnJ1L3JlYWxtcy9lZ3MtYXBpIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImJjYjQ2NzljLTU3ZGItNDU5ZC1iNWUxLWRlOGI4Yzg5MTMwMyIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFkbWluLXJlc3QtY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6IjJhOTgwMzUyLTY1M2QtNGZlZC1iMDI1LWQ1N2U0NDRjZmM3NiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiLyoiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtZWdzLWFwaSIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiIyYTk4MDM1Mi02NTNkLTRmZWQtYjAyNS1kNTdlNDQ0Y2ZjNzYiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiLQktC70LDQtNC40LzQuNGAINCv0LrQvtCy0LvQtdCyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiZTBmYzc2OGRhOTA4MjNiODgwZGQzOGVhMDJjMmQ5NTciLCJnaXZlbl9uYW1lIjoi0JLQu9Cw0LTQuNC80LjRgCIsImZhbWlseV9uYW1lIjoi0K_QutC-0LLQu9C10LIifQ.FTKiC1hpWcOkmSW9QZpC-RY7Ko50jw1mDMfXIWYxlQ-zehLm2CLmOnHvYoOoI39k2OzeCIAB9ZdRrrGZc6G9Z1eFELUjNGEqKxSC1Phj9ATemKgbOKEttk-OGc-rFr9VPA8_SnfvLts6wTI2YK33YBIxCF5nCbnr4Qj3LeEQ0d6Hy8PO4ATrBF5EOeuAZRprvIEjXe_f8N9ONKckCPB-xFB4P2pZlVXGoCNoewGEcY3zXH4khezN6zcVr6tpc6G8dBv9EqT_v92IDSg-aXQk6ysA0cO0-6x5w1-_qU0iHGIAPsLNV9IKBoFbjc0JH6cWabldPRH12NP1trvYfqKDGQ"
head = {'Authorization': 'Bearer {}'.format(my_token)}
my_data={
'x' : f"{data.Lng}",
'y' : f"{data.Lat}",
@ -237,12 +239,11 @@ def get_trashboxes(data: schemas.TrashboxRequest = Depends()):#крутая фу
case "Фрукты и овощи":
list_of_category = ["Иное"]
case "Всякая всячина":
list_of_category=["Металл", "Бумага", "Стекло","Иное", "Тетра Пак", "Батарейки", "Крышечки", "Шины", "Опасные отходы", "Лампочки", "Пластик"]
list_of_category = ["Металл", "Бумага", "Стекло", "Иное", "Тетра Пак", "Батарейки", "Крышечки", "Шины",
"Опасные отходы", "Лампочки", "Пластик"]
response = requests.post(f"{service.BASE_URL}/nearest_recycling/get", headers=head, data=my_data)
response = requests.post(f"{BASE_URL}/nearest_recycling/get", headers=head, data=my_data)
infos = response.json()
trashboxes = []
for trashbox in infos["results"]:
temp_dict = {}

View File

@ -7,7 +7,7 @@ import datetime
# Переменные для получения данных о мусорках с внешнего API
# url API
BASE_URL='https://geointelect2.gate.petersburg.ru/nearest_recycling/get'#адрес сайта и мой токин
BASE_URL='https://geointelect2.gate.petersburg.ru'#адрес сайта и мой токин
# токен для получения данных
my_token='eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJhU1RaZm42bHpTdURYcUttRkg1SzN5UDFhT0FxUkhTNm9OendMUExaTXhFIn0.eyJleHAiOjE3ODM3ODk4NjgsImlhdCI6MTY4OTA5NTQ2OCwianRpIjoiNDUzNjQzZTgtYTkyMi00NTI4LWIzYmMtYWJiYTNmYjkyNTkxIiwiaXNzIjoiaHR0cHM6Ly9rYy5wZXRlcnNidXJnLnJ1L3JlYWxtcy9lZ3MtYXBpIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImJjYjQ2NzljLTU3ZGItNDU5ZC1iNWUxLWRlOGI4Yzg5MTMwMyIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFkbWluLXJlc3QtY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6ImM2ZDJiOTZhLWMxNjMtNDAxZS05ZjMzLTI0MmE0NDcxMDY5OCIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiLyoiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtZWdzLWFwaSIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiJjNmQyYjk2YS1jMTYzLTQwMWUtOWYzMy0yNDJhNDQ3MTA2OTgiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiLQktC70LDQtNC40LzQuNGAINCv0LrQvtCy0LvQtdCyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiZTBmYzc2OGRhOTA4MjNiODgwZGQzOGVhMDJjMmQ5NTciLCJnaXZlbl9uYW1lIjoi0JLQu9Cw0LTQuNC80LjRgCIsImZhbWlseV9uYW1lIjoi0K_QutC-0LLQu9C10LIifQ.E2bW0B-c6W5Lj63eP_G8eI453NlDMnW05l11TZT0GSsAtGayXGaolHtWrmI90D5Yxz7v9FGkkCmcUZYy1ywAdO9dDt_XrtFEJWFpG-3csavuMjXmqfQQ9SmPwDw-3toO64NuZVv6qVqoUlPPj57sLx4bLtVbB4pdqgyJYcrDHg7sgwz4d1Z3tAeUfSpum9s5ZfELequfpLoZMXn6CaYZhePaoK-CxeU3KPBPTPOVPKZZ19s7QY10VdkxLULknqf9opdvLs4j8NMimtwoIiHNBFlgQz10Cr7bhDKWugfvSRsICouniIiBJo76wrj5T92s-ztf1FShJuqnQcKE_QLd2A'

View File

@ -1,5 +1,5 @@
import { Link } from 'react-router-dom'
import { CSSProperties, useEffect, useMemo, useRef, useState } from 'react'
import { useEffect, useLayoutEffect, useRef, useState } from 'react'
import { Button } from 'react-bootstrap'
import { UserCategory, composeUserCategoriesFilters, userCategoriesInfos } from '../assets/userCategories'
@ -9,64 +9,37 @@ import { URLEncodeFilters } from '../utils/filters'
import rightAngleIcon from '../assets/rightAngle.svg'
import styles from '../styles/StoriesPreview.module.css'
type StoriesPreviewProps = {
announcements: Announcement[],
category: UserCategory,
}
const styles = {
container: {
transform: 'translateX(0)',
} as CSSProperties,
ul: {
display: 'flex',
gap: 10,
listStyleType: 'none',
overflow: 'scroll',
paddingLeft: 0,
scrollBehavior: 'smooth',
} as CSSProperties,
link: {
textDecoration: 'none',
color: 'var(--bs-body-color)',
maxWidth: 'calc(25vh * 9 / 16)',
display: 'inline-block',
} as CSSProperties,
image: {
height: '25vh',
objectFit: 'contain',
borderRadius: 12,
marginBottom: 5,
maxWidth: 'inherit',
} as CSSProperties,
title: {
overflow: 'hidden',
textOverflow: 'ellipsis',
marginBottom: 5,
} as CSSProperties,
scrollButton: {
position: 'fixed',
right: 0,
top: 0,
zIndex: 100,
background: 'linear-gradient(to right, rgba(17, 17, 17, 0) 0%, rgba(17, 17, 17, 255) 100%)',
display: 'block',
height: '100%',
width: '10%',
border: 'none',
cursor: 'default',
borderRadius: 0,
} as CSSProperties,
leftScrollButton: {
left: 0,
transform: 'scaleX(-1)',
} as CSSProperties,
rightScrollButton: {
right: 0,
} as CSSProperties,
}
const StoriesPreview = ({ announcements, category }: StoriesPreviewProps) => (
announcements.map((ann, i) => (
<li key={`${category}${i}`}>
<Link to={'/?' + new URLSearchParams({
...URLEncodeFilters(composeUserCategoriesFilters[category]()),
storyIndex: i.toString(),
}).toString()} className={styles.link}>
{ann.src?.endsWith('mp4') ? (
<video src={ann.src} className={styles.image} />
) : (
<img
src={ann.src || categoryGraphics[ann.category]}
alt={'Изображение' + (ann.src ? 'предмета' : categoryNames[ann.category])}
className={styles.image}
/>
)}
<p className={styles.title}>{ann.name}</p>
<p className={styles.title}>{userCategoriesInfos[category](ann)}</p>
</Link>
</li>
))
)
function StoriesPreview({ announcements, category }: StoriesPreviewProps) {
function StoriesPreviewCarousel({ announcements, category }: StoriesPreviewProps) {
const ulElement = useRef<HTMLUListElement | null>(null)
const [showScrollButtons, setShowScrollButtons] = useState({ left: false, right: false })
@ -90,7 +63,7 @@ function StoriesPreview({ announcements, category }: StoriesPreviewProps) {
}
}, [])
useEffect(() => {
useLayoutEffect(() => {
const ul = ulElement.current
if (ul) {
@ -106,40 +79,26 @@ function StoriesPreview({ announcements, category }: StoriesPreviewProps) {
}
}
return <div style={styles.container}>
return <div className={styles.container}>
{showScrollButtons.left &&
<Button onClick={doScroll(false)} style={{ ...styles.scrollButton, ...styles.leftScrollButton }}>
<Button onClick={doScroll(false)} className={`${styles.scrollButton} ${styles.leftScrollButton}`}>
<img src={rightAngleIcon} alt='Показать ещё' />
</Button>
}
<ul style={styles.ul} className='StoriesPreview_ul' ref={ulElement}>
{useMemo(() => announcements.map((ann, i) => (
<li key={`${category}${i}`}>
<Link to={'/?' + new URLSearchParams({
...URLEncodeFilters(composeUserCategoriesFilters[category]()),
storyIndex: i.toString(),
}).toString()} style={styles.link}>
{ann.src?.endsWith('mp4') ? (
<video src={ann.src} style={styles.image} />
) : (
<img
src={ann.src || categoryGraphics[ann.category]}
alt={'Изображение' + (ann.src ? 'предмета' : categoryNames[ann.category])}
style={styles.image}
/>
)}
<p style={styles.title}>{ann.name}</p>
<p style={styles.title}>{userCategoriesInfos[category](ann)}</p>
</Link>
</li>
)), [announcements, category])}
{announcements.length > 0 ? (
<ul className={styles.list} ref={ulElement}>
<StoriesPreview announcements={announcements} category={category} />
</ul>
) : (
<p>Здесь пока пусто</p>
)}
{showScrollButtons.right &&
<Button onClick={doScroll(true)} style={{ ...styles.scrollButton, ...styles.rightScrollButton }}>
<Button onClick={doScroll(true)} className={`${styles.scrollButton} ${styles.rightScrollButton}`}>
<img src={rightAngleIcon} alt='Показать ещё' />
</Button>
}
</div>
}
export default StoriesPreview
export default StoriesPreviewCarousel

View File

@ -20,7 +20,7 @@ function useSignIn() {
body: formData,
})
if (token !== undefined) {
if (token !== null && token !== undefined) {
setToken(token)
return true

View File

@ -20,7 +20,7 @@ type UseFetchLoading = {
} & UseFetchShared
type UseFetchErrored = {
data: undefined,
data: null,
loading: false,
error: string,
} & UseFetchShared
@ -32,8 +32,7 @@ const gotError = <T>(res: UseFetchReturn<T>): res is UseFetchErrored => (
)
function fallbackError<T>(res: UseFetchSucced<T> | UseFetchErrored): T | string
function fallbackError<T>(res: UseFetchReturn<T>): T | string | undefined
function fallbackError<T>(res: UseFetchReturn<T>): T | string | undefined {
function fallbackError<T>(res: UseFetchReturn<T>): T | string | null | undefined {
return (
gotError(res) ? res.error : res.data
)
@ -62,7 +61,6 @@ function useFetch<R, T extends NonNullable<unknown>>(
needAuth,
guardResponse,
processResponse,
true,
params,
)
@ -70,11 +68,15 @@ function useFetch<R, T extends NonNullable<unknown>>(
setFetchLoading(true)
doSend().then(
data => {
if (data !== undefined) {
if (data !== undefined && data !== null) {
setData(data)
console.log('Got data', data)
}
if (data !== undefined) {
setFetchLoading(false)
}
}
).catch( // must never occur
err => import.meta.env.DEV && console.error('Failed to do fetch request', err)
)
@ -93,7 +95,7 @@ function useFetch<R, T extends NonNullable<unknown>>(
if (error !== null) {
return {
data: undefined,
data: null,
loading: fetchLoading,
error,
refetch,

View File

@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { getToken } from '../utils/auth'
import { handleHTTPErrors, isAborted } from '../utils'
import { AbortError, handleHTTPErrors, isAborted } from '../utils'
function useSend<R, T extends NonNullable<unknown>>(
url: string,
@ -10,17 +10,19 @@ function useSend<R, T extends NonNullable<unknown>>(
needAuth: boolean,
guardResponse: (data: unknown) => data is R,
processResponse: (data: R) => T,
startWithLoading = false,
defaultParams?: Omit<RequestInit, 'method'>,
) {
const [loading, setLoading] = useState(startWithLoading)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const navigate = useNavigate()
const abortControllerRef = useRef<AbortController>()
useEffect(() => () => abortControllerRef.current?.abort(), [])
useEffect(() => () => {
const reason = new AbortError('unmount')
abortControllerRef.current?.abort(reason)
}, [])
/** Don't use in useEffect. If you need request result, go with useFetch instead */
const doSend = useCallback(async (urlProps?: Record<string, string>, params?: Omit<RequestInit, 'method'>) => {
@ -28,7 +30,8 @@ function useSend<R, T extends NonNullable<unknown>>(
setError(null)
if (abortControllerRef.current) {
abortControllerRef.current.abort()
const reason = new AbortError('resent')
abortControllerRef.current.abort(reason)
}
const abortController = new AbortController()
@ -45,7 +48,7 @@ function useSend<R, T extends NonNullable<unknown>>(
if (token === null) {
navigate('/login')
return undefined
return null
}
headers.append('Authorization', `Bearer ${token}`)
@ -73,7 +76,18 @@ function useSend<R, T extends NonNullable<unknown>>(
return processResponse(data)
} catch (err) {
if (err instanceof Error && !isAborted(err)) {
if (err instanceof Error) {
if (isAborted<T>(err)) {
if (err.message !== 'resent') {
setLoading(false)
}
if (err.fallback !== undefined) {
return err.fallback
}
return undefined
} else {
if (err instanceof TypeError) {
setError('Ошибка сети')
} else {
@ -83,11 +97,12 @@ function useSend<R, T extends NonNullable<unknown>>(
if (import.meta.env.DEV) {
console.error(url, params, err)
}
}
setLoading(false)
}
}
return undefined
return null
}
}, [defaultParams, needAuth, navigate, url, method, guardResponse, processResponse])

View File

@ -11,8 +11,8 @@ function useSendButtonCaption(
const [disabled, setDisabled] = useState(false)
const [title, setTitle] = useState(initial)
const update = useCallback(<T extends NonNullable<unknown>>(data: T | undefined) => {
if (data !== undefined) {
const update = useCallback(<T extends NonNullable<unknown>>(data: T | null | undefined) => {
if (data !== undefined) { // not loading
setCaption(result)
setTitle('Отправить ещё раз')

View File

@ -0,0 +1,56 @@
.container {
transform: translateX(0);
}
.list {
display: flex;
gap: 10px;
list-style-type: none;
overflow: scroll;
padding-left: 0;
scroll-behavior: smooth;
}
.link {
text-decoration: none;
color: var(--bs-body-color);
max-width: calc(25vh * 9 / 16);
display: inline-block;
}
.image {
height: 25vh;
object-fit: contain;
border-radius: 12px;
margin-bottom: 5px;
max-width: inherit;
}
.title {
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 5px;
}
.scrollButton {
position: fixed;
right: 0;
top: 0;
z-index: 100;
background: linear-gradient(to right, rgba(17, 17, 17, 0) 0%, rgba(17, 17, 17, 255) 100%);
display: block;
height: 100%;
width: 10%;
border: none;
cursor: default;
border-radius: 0;
}
.leftScrollButton {
left: 0;
transform: scaleX(-1);
}
.rightScrollButton {
right: 0;
}

View File

@ -1,7 +1,21 @@
const isAborted = (err: Error) => (
const isAborted = <T>(err: Error): err is AbortError<T> => (
err.name === 'AbortError'
)
type AbortErrorMessage = 'resent' | 'unmount' | 'cancel'
class AbortError<T> extends DOMException {
readonly fallback: T | undefined
message: AbortErrorMessage
constructor(message: AbortErrorMessage, fallback?: T) {
super(message, 'AbortError')
this.message = message
this.fallback = fallback
}
}
function handleHTTPErrors(res: Response) {
if (!res.ok) {
switch (res.status) {
@ -16,4 +30,4 @@ function handleHTTPErrors(res: Response) {
}
}
export { isAborted, handleHTTPErrors }
export { isAborted, AbortError, handleHTTPErrors }