Разработана архитектура и несколько компонентов веб-интерфейса

Этот коммит содержится в:
Глеб Иваницкий 2024-08-17 22:42:50 +03:00
родитель 51e4174ad2
Коммит 1e22d40d19
31 изменённых файлов: 404 добавлений и 200 удалений

Просмотреть файл

@ -1,17 +0,0 @@
#root {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
flex-direction: column;
justify-content: space-between;
margin: 0;
background-color: var(--md-sys-color-surface-container-high);
scroll-behavior: smooth;
}
main > * {
width: 100%;
}

Просмотреть файл

@ -1,37 +0,0 @@
import React, { useEffect } from 'react'
import { styles as typescaleStyles } from '@material/web/typography/md-typescale-styles.js'
import PollList from "./PollList";
import './App.css'
function App() {
useEffect(() => {
document.adoptedStyleSheets = [typescaleStyles.styleSheet]
}, [])
return (
<main className="App">
<md-outlined-text-field id="text" label="Favorite color"></md-outlined-text-field>
<PollList />
</main>
// <div className="App">
// <header className="App-header">
// <img src={logo} className="App-logo" alt="logo"/>
// <p>
// Edit <code>src/App.js</code> and save to reload.
// </p>
// <a
// className="App-link"
// href="https://reactjs.org"
// target="_blank"
// rel="noopener noreferrer"
// >
// Learn React
// </a>
// </header>
// </div>
)
}
export default App

Просмотреть файл

@ -1,45 +0,0 @@
.full-screen-dialog {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: var(--md-sys-color-surface-container);
z-index: 1;
animation: .1s show ease;
}
.full-screen-dialog > .top-app-bar {
position: absolute;
top: 0;
right: 0;
left: 0;
display: flex;
align-items: center;
gap: .5rem;
padding: .5rem;
}
.full-screen-dialog > .top-app-bar > * {
flex-shrink: 0;
}
.full-screen-dialog > .top-app-bar > .title {
flex-grow: 1;
flex-shrink: 1;
color: var(--md-sys-color-on-surface);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
@keyframes show {
from {
opacity: 0;
scale: 0;
}
to {
opacity: 1;
scale: 1;
}
}

Просмотреть файл

@ -1,28 +0,0 @@
import React, { useEffect, useState } from 'react'
import '@material/web/iconbutton/icon-button.js'
import '@material/web/icon/icon.js'
import '@material/web/button/filled-button'
import './FullScreenDialog.css'
export default function ({
editPoll,
setEditPoll,
}) {
// const [close, onClose] = useState(false)
return (
<div className="full-screen-dialog">
<div className="top-app-bar">
<md-icon-button onClick={() => setEditPoll(false)}>
<md-icon>close</md-icon>
</md-icon-button>
<div className="title md-typescale-title-large">Изменить опрос</div>
<md-filled-button onClick={() => setEditPoll()}>Сохранить</md-filled-button>
<md-icon-button>
<md-icon>more_vert</md-icon>
</md-icon-button>
</div>
</div>
)
}

Просмотреть файл

@ -1,4 +0,0 @@
.poll-list {
display: flex;
flex-direction: column;
}

Просмотреть файл

@ -1,37 +0,0 @@
import React, { useEffect, useState } from 'react'
import PollListItem from "./PollListItem"
import FullScreenDialog from "./FullScreenDialog"
import './PollList.css'
const PollContext = React.createContext({
polls: [],
fetchPolls: () => {},
})
export default function PollList() {
const [polls, setPolls] = useState([])
const [editPoll, setEditPoll] = useState(false)
const fetchPolls = async () => {
const response = await fetch('/api/polls')
const polls = await response.json()
setPolls(polls)
}
useEffect(() => {
fetchPolls()
}, [])
return (
<div className="poll-list">
<PollContext.Provider value={{polls, fetchPolls}}>
{polls.map((poll) => (
PollListItem(poll, setEditPoll)
))}
</PollContext.Provider>
{editPoll ? <FullScreenDialog editPoll={editPoll} setEditPoll={setEditPoll} /> : null}
</div>
)
}

Просмотреть файл

@ -1,22 +0,0 @@
import '@material/web/switch/switch.js'
import './PollListItem.css'
export default function PollListItem(data, setEditPoll) {
return (
<div className="poll-list-item" onClick={() => setEditPoll(true)}>
<md-ripple></md-ripple>
<div>
<div className="name md-typescale-title-large">{data.name}</div>
<div className="caption md-typescale-body-medium">
<span>{data.days_of_week}</span>
&nbsp;&bull;&nbsp;
<span>{data.time}</span>
&nbsp;&bull;&nbsp;
<span>{data.question_number}</span>
</div>
</div>
<md-switch></md-switch>
</div>
)
}

3
frontend/src/activities/MainActivity.css Обычный файл
Просмотреть файл

@ -0,0 +1,3 @@
main > * {
width: 100%;
}

35
frontend/src/activities/MainActivity.js Обычный файл
Просмотреть файл

@ -0,0 +1,35 @@
import React, { useEffect } from 'react'
import { styles as typescaleStyles } from '@material/web/typography/md-typescale-styles.js'
import PollsScreen from '../screens/PollsScreen'
import Scaffold from '../components/Scaffold'
import NavigationBar from '../components/NavigationBar'
import './MainActivity.css'
const bottomBar = (
<NavigationBar />
)
const floatingActionButton = (
<md-fab id="create-poll-button" label="Создать опрос">
<md-icon slot="icon">add</md-icon>
</md-fab>
)
const content = (
<PollsScreen />
)
export default function MainActivity() {
useEffect(() => {
document.adoptedStyleSheets = [typescaleStyles.styleSheet]
}, [])
return (
<Scaffold
bottomBar={bottomBar}
floatingActionButton={floatingActionButton}
content={content} />
)
}

Просмотреть файл

@ -0,0 +1,36 @@
.full-screen-dialog {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
flex-direction: column;
background-color: var(--md-sys-color-surface);
z-index: 2;
transition:
opacity .1s ease-in-out,
scale .1s ease-in-out;
}
.full-screen-dialog.open {
opacity: 1;
scale: 1;
}
.full-screen-dialog.close {
opacity: 0;
scale: 0;
}
.full-screen-dialog > .content {
flex-grow: 1;
flex-shrink: 1;
display: flex;
flex-direction: column;
overflow-y: auto;
}
.full-screen-dialog > .content > * {
padding: 1rem;
}

22
frontend/src/components/FullScreenDialog.js Обычный файл
Просмотреть файл

@ -0,0 +1,22 @@
import '@material/web/iconbutton/icon-button.js'
import '@material/web/icon/icon.js'
import '@material/web/button/filled-button'
import TopAppBar from './TopAppBar'
import './FullScreenDialog.css'
export default function ({
setEditPoll,
classList,
children,
}) {
return (
<div className={`full-screen-dialog ${classList}`}>
<TopAppBar onDismissRequest={() => setEditPoll(false)} />
<div className="content">
{children}
</div>
</div>
)
}

Просмотреть файл

@ -1,7 +1,7 @@
nav {
display: flex;
flex-direction: column;
background-color: var(--md-sys-color-surface-container);
background-color: var(--md-sys-color-surface-container-high);
}
nav ul {

Просмотреть файл

@ -1,9 +1,9 @@
import '@material/web/iconbutton/icon-button.js'
import '@material/web/icon/icon.js'
import './BottomNavBar.css'
import './NavigationBar.css'
function BottomNavBar() {
export default function NavigationBar() {
return (
<nav className="md-typescale-body-small">
<ul>
@ -35,5 +35,3 @@ function BottomNavBar() {
</nav>
)
}
export default BottomNavBar

Просмотреть файл

@ -3,7 +3,6 @@
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
}
.poll-list-item .name {

35
frontend/src/components/PollListItem.js Обычный файл
Просмотреть файл

@ -0,0 +1,35 @@
import '@material/web/list/list-item'
import '@material/web/ripple/ripple'
import '@material/web/switch/switch.js'
import './PollListItem.css'
import React from "react";
export default function PollListItem(
poll,
setEditPoll,
updatePoll,
) {
return (
<md-list-item onClick={() => setEditPoll(poll)}>
<md-ripple></md-ripple>
<div className="poll-list-item">
<div>
<div className="name md-typescale-title-large">{poll.name}</div>
<div className="caption md-typescale-body-medium">
<span>{poll.daysOfWeek}</span>
&nbsp;&bull;&nbsp;
<span>{poll.time}</span>
&nbsp;&bull;&nbsp;
<span>{poll.questionNumber}</span>
</div>
</div>
<md-switch onClickCapture={(event) => {
event.nativeEvent.stopPropagation()
poll.isEnabled = !poll.isEnabled
updatePoll(poll)
}} {...(poll.isEnabled ? {selected: true} : {})}></md-switch>
</div>
</md-list-item>
)
}

46
frontend/src/components/Scaffold.css Обычный файл
Просмотреть файл

@ -0,0 +1,46 @@
.scaffold {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
background-color: var(--md-sys-color-surface);
}
.scaffold > .top-bar {
flex-shrink: 0;
}
.scaffold > .content {
flex-grow: 1;
flex-shrink: 1;
overflow-y: auto;
}
.scaffold > .bottom-bar {
flex-shrink: 0;
position: relative;
}
.scaffold > .bottom-bar > aside {
position: absolute;
right: 0;
bottom: 100%;
left: 0;
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1rem;
pointer-events: none;
}
.scaffold > .bottom-bar > aside > * {
z-index: 1;
}
.scaffold > .bottom-bar > aside > * > * {
pointer-events: auto;
}
.scaffold > .bottom-bar > aside > .floating-action-button {
text-align: right;
}

25
frontend/src/components/Scaffold.js Обычный файл
Просмотреть файл

@ -0,0 +1,25 @@
import './Scaffold.css'
export default function Scaffold({
topBar,
bottomBar,
snackbarHost,
floatingActionButton,
content,
}) {
return (
<div className="scaffold">
<div className="top-bar">{topBar}</div>
<div className="content">
{content}
</div>
<div className="bottom-bar">
<aside>
<div className="snackbar">{snackbarHost}</div>
<div className="floating-action-button">{floatingActionButton}</div>
</aside>
{bottomBar}
</div>
</div>
)
}

0
frontend/src/components/Snackbar.css Обычный файл
Просмотреть файл

0
frontend/src/components/Snackbar.js Обычный файл
Просмотреть файл

20
frontend/src/components/TopAppBar.css Обычный файл
Просмотреть файл

@ -0,0 +1,20 @@
.top-app-bar {
flex-shrink: 0;
display: flex;
align-items: center;
gap: .5rem;
padding: .5rem;
}
.top-app-bar > * {
flex-shrink: 0;
}
.top-app-bar > .title {
flex-grow: 1;
flex-shrink: 1;
color: var(--md-sys-color-on-surface);
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
}

22
frontend/src/components/TopAppBar.js Обычный файл
Просмотреть файл

@ -0,0 +1,22 @@
import '@material/web/iconbutton/icon-button'
import '@material/web/icon/icon'
import '@material/web/button/filled-button'
import './TopAppBar.css'
export default function TopAppBar({
onDismissRequest,
}) {
return (
<div className="top-app-bar">
<md-icon-button onClick={() => onDismissRequest()}>
<md-icon>close</md-icon>
</md-icon-button>
<div className="title md-typescale-title-large">Изменить опрос</div>
<md-filled-button onClick={() => onDismissRequest()}>Сохранить</md-filled-button>
<md-icon-button>
<md-icon>more_vert</md-icon>
</md-icon-button>
</div>
)
}

Просмотреть файл

@ -6,3 +6,17 @@ body {
margin: 0;
scroll-behavior: smooth;
}
#root {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
flex-direction: column;
justify-content: space-between;
margin: 0;
background-color: var(--md-sys-color-surface);
scroll-behavior: smooth;
}

Просмотреть файл

@ -1,13 +1,14 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import '@material/web/fab/fab'
import './index.css'
import App from './App'
import BottomNavBar from "./BottomNavBar"
import MainActivity from "./activities/MainActivity";
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<React.StrictMode>
<App />
<BottomNavBar />
<MainActivity />
</React.StrictMode>
)

0
frontend/src/screens/ChatsScreen.css Обычный файл
Просмотреть файл

0
frontend/src/screens/ChatsScreen.js Обычный файл
Просмотреть файл

0
frontend/src/screens/DashboardScreen.css Обычный файл
Просмотреть файл

0
frontend/src/screens/DashboardScreen.js Обычный файл
Просмотреть файл

0
frontend/src/screens/PollsScreen.css Обычный файл
Просмотреть файл

138
frontend/src/screens/PollsScreen.js Обычный файл
Просмотреть файл

@ -0,0 +1,138 @@
import React, {useEffect, useState} from "react"
import '@material/web/textfield/outlined-text-field'
import '@material/web/fab/fab'
import '@material/web/icon/icon'
import '@material/web/select/outlined-select'
import '@material/web/select/select-option'
import '@material/web/list/list'
import '@material/web/list/list-item'
import FullScreenDialog from "../components/FullScreenDialog"
import PollListItem from "../components/PollListItem"
import './PollsScreen.css'
const PollContext = React.createContext({
polls: [],
fetchPolls: () => {},
})
const apiUrl = new URL(location)
apiUrl.pathname = '/api/polls'
export default function PollsScreen() {
const [polls, setPolls] = useState([])
const [editPoll, setEditPoll] = useState(false)
const fetchPolls = async () => {
const response = await fetch('/api/polls')
const polls = await response.json()
setPolls(polls)
}
const updatePoll = async (poll) => {
await fetch(apiUrl, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(poll)
})
await fetchPolls()
}
useEffect(() => {
fetchPolls()
}, [])
return (
<section id="polls-screen">
<md-list>
<PollContext.Provider value={{polls, fetchPolls}}>
{polls.map((poll) => (
PollListItem(poll, setEditPoll, updatePoll)
))}
</PollContext.Provider>
</md-list>
<FullScreenDialog
setEditPoll={setEditPoll}
classList={editPoll ? 'open' : 'close'}>
<md-outlined-text-field
label="Название"
value={editPoll ? editPoll.name : ''}
required="required">
<md-icon slot="leading-icon">title</md-icon>
</md-outlined-text-field>
<md-outlined-select label="Дни недели" required="required">
<md-icon slot="leading-icon">date_range</md-icon>
<md-select-option value="mon">
<div slot="headline">Понедельник</div>
</md-select-option>
<md-select-option value="tue">
<div slot="headline">Вторник</div>
</md-select-option>
<md-select-option value="wed">
<div slot="headline">Среда</div>
</md-select-option>
<md-select-option value="thu">
<div slot="headline">Четверг</div>
</md-select-option>
<md-select-option value="fri">
<div slot="headline">Пятница</div>
</md-select-option>
<md-select-option value="sat">
<div slot="headline">Суббота</div>
</md-select-option>
<md-select-option value="sun">
<div slot="headline">Воскресенье</div>
</md-select-option>
</md-outlined-select>
<section id="poll-messages">
<div className="md-typescale-title-medium">Сообщения</div>
<md-list>
<md-list-item>
Fruits
</md-list-item>
<md-divider></md-divider>
<md-list-item>
Apple
</md-list-item>
<md-list-item>
Banana
</md-list-item>
<md-list-item>
<div slot="headline">Cucumber</div>
<div slot="supporting-text">Cucumbers are long green fruits that are just as long as this
multi-line description
</div>
</md-list-item>
<md-list-item
type="link"
href="https://google.com/search?q=buy+kiwis&tbm=shop"
target="_blank">
<div slot="headline">Shop for Kiwis</div>
<div slot="supporting-text">This will link you out in a new tab</div>
<md-icon slot="end">open_in_new</md-icon>
</md-list-item>
</md-list>
</section>
</FullScreenDialog>
</section>
)
}

0
frontend/src/screens/UsersScreen.css Обычный файл
Просмотреть файл

0
frontend/src/screens/UsersScreen.js Обычный файл
Просмотреть файл