W7 done
This commit is contained in:
parent
a101924587
commit
f662d9933f
13 changed files with 2691 additions and 0 deletions
3
w6/.dockerignore
Normal file
3
w6/.dockerignore
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules
|
||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
||||||
16
w6/Dockerfile
Normal file
16
w6/Dockerfile
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
FROM node:16.13.2-alpine3.14
|
||||||
|
|
||||||
|
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
|
||||||
|
WORKDIR /home/node/app
|
||||||
|
|
||||||
|
COPY --chown=node:node . .
|
||||||
|
|
||||||
|
RUN corepack enable
|
||||||
|
RUN corepack prepare pnpm@6.32.2 --activate
|
||||||
|
RUN pnpm install
|
||||||
|
|
||||||
|
USER node
|
||||||
|
|
||||||
|
EXPOSE 9559/tcp
|
||||||
|
|
||||||
|
CMD [ "pnpm", "start" ]
|
||||||
1097
w6/goods.json
Normal file
1097
w6/goods.json
Normal file
File diff suppressed because it is too large
Load diff
79
w6/index.js
Normal file
79
w6/index.js
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
import express from "express";
|
||||||
|
import mustache from "mustache";
|
||||||
|
import bodyParse from "body-parser";
|
||||||
|
import { readdirSync, readFileSync } from "fs";
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
app.use(express.static("./static"));
|
||||||
|
app.use(bodyParse.urlencoded({ extended: true }));
|
||||||
|
const port = 9559;
|
||||||
|
|
||||||
|
const data = JSON.parse(readFileSync("./goods.json", "utf-8"));
|
||||||
|
const templates = new Map(readdirSync("./templates").map(fn => [fn, readFileSync(`./templates/${fn}`, "utf-8")]));
|
||||||
|
|
||||||
|
app.get("/", (req, res) => {
|
||||||
|
res.send(mustache.render(templates.get("mainPage.html"), data));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/category/:category", (req, res) => {
|
||||||
|
const goodsForCategory = data.goods.filter(el => el.category === req.params.category);
|
||||||
|
if (goodsForCategory.length === 0) {
|
||||||
|
res.redirect("/");
|
||||||
|
} else {
|
||||||
|
res.send(mustache.render(templates.get("categoryPage.html"), { category: req.params.category, goods: goodsForCategory }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/goods/:goodUUID", (req, res) => {
|
||||||
|
let good = null;
|
||||||
|
let user = null;
|
||||||
|
for (let _good of data.goods) {
|
||||||
|
if (_good.guid !== req.params.goodUUID) continue;
|
||||||
|
|
||||||
|
good = _good;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (let _user of data.users) {
|
||||||
|
if (_user.id !== good.uid) continue;
|
||||||
|
|
||||||
|
user = _user;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (good === null || user === null) {
|
||||||
|
res.redirect("/");
|
||||||
|
} else {
|
||||||
|
res.send(mustache.render(templates.get("goodsPage.html"), { good, user }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/user/:userUUID", (req, res) => {
|
||||||
|
let user = null;
|
||||||
|
for (let _user of data.users) {
|
||||||
|
if (_user.guid !== req.params.userUUID) continue;
|
||||||
|
|
||||||
|
user = _user;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (user === null) {
|
||||||
|
res.redirect("/");
|
||||||
|
} else {
|
||||||
|
const goods = data.goods.filter(e => e.uid === user.id);
|
||||||
|
res.send(mustache.render(templates.get("userPage.html"), { user, goods, goodsCount: goods.length }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/search", (req, res) => {
|
||||||
|
const term = req.body.searchTerm.toLowerCase();
|
||||||
|
const st = req.body.searchType;
|
||||||
|
if (term === "") {
|
||||||
|
res.redirect("/");
|
||||||
|
} else {
|
||||||
|
const showUsers = st === "usersOnly" || st === "both";
|
||||||
|
const showGoods = st === "goodsOnly" || st === "both";
|
||||||
|
const users = showUsers? data.users.filter(u => u.fullName.toLowerCase().includes(term)) : [];
|
||||||
|
const goods = showGoods? data.goods.filter(e => e.title.toLowerCase().includes(term) || e.description.toLowerCase().includes(term)) : [];
|
||||||
|
res.send(mustache.render(templates.get("searchPage.html"), { term, users, goods, showUsers, showGoods }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(port, () => console.log(`⚡️ Serving on port ${port}`));
|
||||||
22
w6/package.json
Normal file
22
w6/package.json
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "w6",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node .",
|
||||||
|
"watch": "nodemon -e *",
|
||||||
|
"build": "docker build -t nuark/w6_bld ."
|
||||||
|
},
|
||||||
|
"author": "nuark",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"body-parser": "^1.19.2",
|
||||||
|
"express": "^4.17.3",
|
||||||
|
"mustache": "^4.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"nodemon": "^2.0.15"
|
||||||
|
}
|
||||||
|
}
|
||||||
1092
w6/pnpm-lock.yaml
generated
Normal file
1092
w6/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load diff
154
w6/static/css/styles.css
Normal file
154
w6/static/css/styles.css
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
* {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
background-color: bisque;
|
||||||
|
font-size: 16pt;
|
||||||
|
font-family: 'Smooch Sans', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: darkturquoise;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:visited {
|
||||||
|
color: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mainPageMain {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4rem;
|
||||||
|
width: 60%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jumbotronSection {
|
||||||
|
background-color: beige;
|
||||||
|
padding: 4rem;
|
||||||
|
border-radius: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchSection {
|
||||||
|
background-color: beige;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchSection > form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchSection > form > input[type="text"] {
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
background-color: transparent;
|
||||||
|
border-top: none;
|
||||||
|
border-left: none;
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: 2px black dashed;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.categories {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.categoryCard {
|
||||||
|
display: flex;
|
||||||
|
flex: 1 0 40%;
|
||||||
|
flex-direction: row;
|
||||||
|
background-color: whitesmoke;
|
||||||
|
gap: 1rem;
|
||||||
|
margin: 1rem;
|
||||||
|
text-decoration: none;
|
||||||
|
color: black;
|
||||||
|
align-items: center
|
||||||
|
}
|
||||||
|
|
||||||
|
.categoryCard > img {
|
||||||
|
clip-path: circle(81% at 0% 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.categoryCard > p {
|
||||||
|
padding-right: 4rem;
|
||||||
|
font-size: 28pt;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goodsSection {
|
||||||
|
background-color: whitesmoke;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
row-gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goodsCard {
|
||||||
|
display: flex;
|
||||||
|
flex: 1 0 30%;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: whitesmoke;
|
||||||
|
gap: 1rem;
|
||||||
|
margin: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goodsCard > .description {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goodsLink {
|
||||||
|
text-decoration: none;
|
||||||
|
color: coral;
|
||||||
|
padding: 5rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
display: inline-block;
|
||||||
|
border: solid;
|
||||||
|
border-radius: 1rem;
|
||||||
|
border-color: coral;
|
||||||
|
border-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.userCard {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 2rem;
|
||||||
|
padding-bottom: 2rem;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
height: 5rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchDataSection {
|
||||||
|
background-color: whitesmoke;
|
||||||
|
border-radius: 2rem;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goodsSearches {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
row-gap: 2rem;
|
||||||
|
}
|
||||||
31
w6/templates/categoryPage.html
Normal file
31
w6/templates/categoryPage.html
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Goods page</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Smooch+Sans:wght@300;400;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="/css/styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main id="mainPageMain">
|
||||||
|
<section class="goodsSection">
|
||||||
|
<a href="/">>>> Go home</a>
|
||||||
|
<hr>
|
||||||
|
<h2>Goods in category "{{category}}":</h2>
|
||||||
|
<div class="goods">
|
||||||
|
{{#goods}}
|
||||||
|
<div class="goodsCard">
|
||||||
|
<h3>{{title}} # {{id}}</h3>
|
||||||
|
<p class="description">{{description}}</p>
|
||||||
|
<a class="goodsLink" href="/goods/{{guid}}">More info...</a>
|
||||||
|
</div>
|
||||||
|
{{/goods}}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
35
w6/templates/goodsPage.html
Normal file
35
w6/templates/goodsPage.html
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>"{{good.title}}" good page</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Smooch+Sans:wght@300;400;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="/css/styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main id="mainPageMain">
|
||||||
|
<section class="goodsSection">
|
||||||
|
<a href="/category/{{good.category}}">>>> Go back to category "{{good.category}}"</a>
|
||||||
|
<hr>
|
||||||
|
<h2>Vieweing "{{good.title}}":</h2>
|
||||||
|
<p>In system since: {{good.registered}}</p>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
Seller profile: <a href="/user/{{user.guid}}">{{user.fullName}}</a>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h3>Description:</h3>
|
||||||
|
<p>{{good.description}}</p>
|
||||||
|
<br>
|
||||||
|
<h3>Price:</h3>
|
||||||
|
<p>{{good.price}} CR per unit</p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
58
w6/templates/mainPage.html
Normal file
58
w6/templates/mainPage.html
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Main page</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Smooch+Sans:wght@300;400;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="/css/styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main id="mainPageMain">
|
||||||
|
<section class="jumbotronSection">
|
||||||
|
<h1>Welcome to our store!</h1>
|
||||||
|
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Neque labore perspiciatis ad reprehenderit magni, fugiat eligendi! Porro dignissimos rerum accusantium!</p>
|
||||||
|
</section>
|
||||||
|
<section class="searchSection">
|
||||||
|
<form action="/search" method="post">
|
||||||
|
<input type="text" name="searchTerm" placeholder="Search term">
|
||||||
|
|
||||||
|
<h4>Search for:</h4>
|
||||||
|
<div>
|
||||||
|
<label for="both">Users and goods</label>
|
||||||
|
<input type="radio" id="both" name="searchType" value="both" checked>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<label for="userOnly">Users only</label>
|
||||||
|
<input type="radio" id="userOnly" name="searchType" value="usersOnly">
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<label for="goodsOnly">Goods only</label>
|
||||||
|
<input type="radio" id="goodsOnly" name="searchType" value="goodsOnly">
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<button type="submit">Search!</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
<section class="categoriesSection">
|
||||||
|
<h3>You can choose from a wide range of categories:</h3>
|
||||||
|
<div class="categories">
|
||||||
|
{{#categories}}
|
||||||
|
<a class="categoryCard" href="/category/{{.}}">
|
||||||
|
<img src="https://api.lorem.space/image?w=150&h=180&{{.}}" alt="{{.}}">
|
||||||
|
<p>
|
||||||
|
{{.}}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
{{/categories}}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
62
w6/templates/searchPage.html
Normal file
62
w6/templates/searchPage.html
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Search for "{{term}}"</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Smooch+Sans:wght@300;400;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="/css/styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main id="mainPageMain">
|
||||||
|
<section class="searchSection">
|
||||||
|
<h4>Search term: "{{term}}"</h4>
|
||||||
|
<h5>Searching for {{#showUsers}}users{{#showGoods}} and {{/showGoods}}{{/showUsers}}{{#showGoods}}goods{{/showGoods}}</h5>
|
||||||
|
<a href="/">>>> Go home</a>
|
||||||
|
</section>
|
||||||
|
<section class="searchDataSection">
|
||||||
|
{{#showUsers}}
|
||||||
|
<h3>Found users:</h3>
|
||||||
|
<div class="categories">
|
||||||
|
{{#users}}
|
||||||
|
<a class="categoryCard" href="/user/{{guid}}">
|
||||||
|
<img src="{{photo}}&{{guid}}" alt="{{fullName}} photo">
|
||||||
|
<p>
|
||||||
|
{{fullName}}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
{{/users}}
|
||||||
|
{{^users}}
|
||||||
|
No users found for this search!
|
||||||
|
{{/users}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#showGoods}}
|
||||||
|
<br>
|
||||||
|
<hr>
|
||||||
|
<br>
|
||||||
|
{{/showGoods}}
|
||||||
|
{{/showUsers}}
|
||||||
|
|
||||||
|
{{#showGoods}}
|
||||||
|
<h3>Found goods:</h3>
|
||||||
|
<div class="goodsSearches">
|
||||||
|
{{#goods}}
|
||||||
|
<div class="goodsCard">
|
||||||
|
<h3>{{title}} # {{id}}</h3>
|
||||||
|
<p class="description">Category: {{category}}</p>
|
||||||
|
<a class="goodsLink" href="/goods/{{guid}}">More info...</a>
|
||||||
|
</div>
|
||||||
|
{{/goods}}
|
||||||
|
{{^goods}}
|
||||||
|
No goods found for this search!
|
||||||
|
{{/goods}}
|
||||||
|
</div>
|
||||||
|
{{/showGoods}}
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
42
w6/templates/userPage.html
Normal file
42
w6/templates/userPage.html
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{user.fulName}}'s page</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Smooch+Sans:wght@300;400;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="/css/styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main id="mainPageMain">
|
||||||
|
<section class="goodsSection">
|
||||||
|
<a href="/">>>> Go home</a>
|
||||||
|
<br>
|
||||||
|
<a href="javascript:history.back()"><<< Go back</a>
|
||||||
|
<hr>
|
||||||
|
<div class="userCard">
|
||||||
|
<img src="{{user.photo}}" alt="{{user.fullName}} avatar" class="avatar">
|
||||||
|
<h2>User "{{user.fullName}}"</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<p>User has {{goodsCount}} goods in our store.</p>
|
||||||
|
<p>Their goods:</p>
|
||||||
|
<div class="goods">
|
||||||
|
{{#goods}}
|
||||||
|
<div class="goodsCard">
|
||||||
|
<h3>{{title}} # {{id}}</h3>
|
||||||
|
<h4>Category: {{category}}</h4>
|
||||||
|
<p class="description">{{description}}</p>
|
||||||
|
<a class="goodsLink" href="/goods/{{guid}}">More info...</a>
|
||||||
|
</div>
|
||||||
|
{{/goods}}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
w6/w6.zip
Normal file
BIN
w6/w6.zip
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue