mirror of
https://github.com/slynn1324/tinypin.git
synced 2026-01-23 02:25:08 +00:00
more work
This commit is contained in:
parent
8eeca69987
commit
af16c7f8f0
3 changed files with 607 additions and 185 deletions
64
server.js
64
server.js
|
|
@ -47,8 +47,9 @@ app.get("/api/boards", (req, res) => {
|
|||
});
|
||||
|
||||
// get board
|
||||
app.get("/api/boards/:boardId", (req, res) => {
|
||||
app.get("/api/boards/:boardId", async (req, res) => {
|
||||
try{
|
||||
await sleep(1000);
|
||||
let board = db.prepare("SELECT * FROM boards WHERE id = ?").get(req.params.boardId);
|
||||
if ( board ){
|
||||
|
||||
|
|
@ -70,6 +71,7 @@ app.post('/api/boards', (req, res) => {
|
|||
let result = db.prepare("INSERT INTO boards (name, createDate) VALUES (@name, @createDate)").run({name: req.body.name, createDate: new Date().toISOString()});
|
||||
let id = result.lastInsertRowid;
|
||||
let board = db.prepare("SELECT * FROM boards WHERE id = ?").get(id);
|
||||
board.titlePinId = 0;
|
||||
res.send(board);
|
||||
console.log(`Created board#${id} ${req.body.name}`);
|
||||
|
||||
|
|
@ -99,9 +101,19 @@ app.post("/api/boards/:boardId", (req, res) =>{
|
|||
});
|
||||
|
||||
// delete board
|
||||
app.delete("/api/boards/:boardId", (req, res) => {
|
||||
app.delete("/api/boards/:boardId", async (req, res) => {
|
||||
try{
|
||||
let result = db.prepare("DELETE FROM boards WHERE id = ?").run(req.params.boardId);
|
||||
await sleep(1000);
|
||||
|
||||
let pins = db.prepare("SELECT id FROM pins WHERE boardId = ?").all(req.params.boardId);
|
||||
for ( let i = 0; i < pins.length; ++i ){
|
||||
await fs.unlink(getThumbnailImagePath(pins[i].id).file);
|
||||
await fs.unlink(getOriginalImagePath(pins[i].id).file);
|
||||
}
|
||||
|
||||
let result = db.prepare("DELETE FROM pins WHERE boardId = ?").run(req.params.boardId);
|
||||
result = db.prepare("DELETE FROM boards WHERE id = ?").run(req.params.boardId);
|
||||
|
||||
if ( result.changes == 1 ){
|
||||
res.send(OK);
|
||||
} else {
|
||||
|
|
@ -128,10 +140,20 @@ app.get("/api/pins/:pinId", (req, res) => {
|
|||
}
|
||||
});
|
||||
|
||||
async function sleep(millis){
|
||||
return new Promise( (resolve,reject) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, millis)
|
||||
});
|
||||
}
|
||||
|
||||
// create pin
|
||||
app.post("/api/pins", async (req, res) => {
|
||||
try {
|
||||
|
||||
await sleep(1000);
|
||||
|
||||
console.log(req.body);
|
||||
|
||||
let image = await downloadImage(req.body.imageUrl);
|
||||
|
|
@ -212,6 +234,7 @@ app.post("/api/pins/:pinId", (req,res) => {
|
|||
});
|
||||
|
||||
if ( result.changes == 1 ){
|
||||
console.log(`updated pin#${req.params.pinId}`)
|
||||
res.send(OK);
|
||||
} else {
|
||||
res.status(404).send(NOT_FOUND);
|
||||
|
|
@ -223,6 +246,29 @@ app.post("/api/pins/:pinId", (req,res) => {
|
|||
|
||||
});
|
||||
|
||||
app.delete("/api/pins/:pinId", async (req, res) => {
|
||||
|
||||
await sleep(1000);
|
||||
|
||||
try {
|
||||
|
||||
await fs.unlink(getThumbnailImagePath(req.params.pinId).file);
|
||||
await fs.unlink(getOriginalImagePath(req.params.pinId).file);
|
||||
|
||||
let result = db.prepare('DELETE FROM pins WHERE id = ?').run(req.params.pinId);
|
||||
|
||||
if ( result.changes == 1 ){
|
||||
console.log(`deleted pin#${req.params.pinId}`);
|
||||
res.send(OK);
|
||||
} else {
|
||||
res.status(404).send(NOT_FOUND);
|
||||
}
|
||||
} catch (err){
|
||||
console.log(`Error deleting pin#${req.params.pinId}`, err);
|
||||
res.status(500).send(SERVER_ERROR);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// start listening
|
||||
app.listen(port, () => {
|
||||
|
|
@ -269,6 +315,10 @@ function initDb(){
|
|||
)
|
||||
`).run();
|
||||
|
||||
db.prepare(`
|
||||
INSERT INTO boards (id, name, createDate) VALUES (0, 'Default Board', ?)
|
||||
`).run(new Date().toISOString());
|
||||
|
||||
db.prepare("INSERT INTO migrations (id, createDate) VALUES ( @id, @createDate )").run({id:1, createDate: new Date().toISOString()});
|
||||
|
||||
} else {
|
||||
|
|
@ -309,13 +359,7 @@ async function downloadImage(imageUrl){
|
|||
}
|
||||
}
|
||||
|
||||
// function padId(id){
|
||||
// let result = id.toString();
|
||||
// while ( result.length < 12 ) {
|
||||
// result = '0' + result;
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
|
||||
|
||||
function getOriginalImagePath(pinId){
|
||||
let paddedId = pinId.toString().padStart(12, '0');
|
||||
|
|
|
|||
|
|
@ -101,4 +101,102 @@
|
|||
|
||||
.section {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* loader - https://loading.io/css/
|
||||
*/
|
||||
.lds-ring {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
.lds-ring div {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin: 8px;
|
||||
border: 8px solid #fff;
|
||||
border-radius: 50%;
|
||||
animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
||||
border-color: #fff transparent transparent transparent;
|
||||
}
|
||||
.lds-ring div:nth-child(1) {
|
||||
animation-delay: -0.45s;
|
||||
}
|
||||
.lds-ring div:nth-child(2) {
|
||||
animation-delay: -0.3s;
|
||||
}
|
||||
.lds-ring div:nth-child(3) {
|
||||
animation-delay: -0.15s;
|
||||
}
|
||||
@keyframes lds-ring {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.pin-zoom-modal-delete {
|
||||
position: fixed;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjRkZGRkZGIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGRhdGEtbmFtZT0iTGF5ZXIgMSIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHg9IjBweCIgeT0iMHB4Ij48dGl0bGU+dHJhc2g8L3RpdGxlPjxwYXRoIGQ9Ik05MC42OSwxMi4yMlYxOGEyLjkxLDIuOTEsMCwwLDEtMi45MSwyLjkxSDEyLjIyQTIuOTEsMi45MSwwLDAsMSw5LjMxLDE4VjEyLjIyYTIuOTEsMi45MSwwLDAsMSwyLjkxLTIuOTFIMzRsMS43MS0zLjRBNC43Miw0LjcyLDAsMCwxLDM5LjU5LDMuNUg2MC4zOGE0Ljc4LDQuNzgsMCwwLDEsMy45LDIuNDFMNjYsOS4zMWgyMS44QTIuOTEsMi45MSwwLDAsMSw5MC42OSwxMi4yMlpNMTUuMTMsMjYuNzVIODQuODh2NjFhOC43Miw4LjcyLDAsMCwxLTguNzIsOC43MkgyMy44NGE4LjcyLDguNzIsMCwwLDEtOC43Mi04LjcyWk0yOS42Niw4MmEyLjkxLDIuOTEsMCwwLDAsNS44MSwwVjQxLjI4YTIuOTEsMi45MSwwLDAsMC01LjgxLDBabTE3LjQ0LDBhMi45MSwyLjkxLDAsMCwwLDUuODEsMFY0MS4yOGEyLjkxLDIuOTEsMCwwLDAtNS44MSwwWm0xNy40NCwwYTIuOTEsMi45MSwwLDAsMCw1LjgxLDBWNDEuMjhhMi45MSwyLjkxLDAsMCwwLTUuODEsMFoiPjwvcGF0aD48L3N2Zz4=");
|
||||
background-repeat: no-repeat;
|
||||
background-size: 24px 24px;
|
||||
border: none;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.pin-zoom-modal-delete:hover{
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.pin-zoom-modal-site-link {
|
||||
position: fixed;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
bottom: 20px;
|
||||
right: 100px;
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjRkZGRkZGIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxuczp4b2RtPSJodHRwOi8vd3d3LmNvcmVsLmNvbS9jb3JlbGRyYXcvb2RtLzIwMDMiIHhtbDpzcGFjZT0icHJlc2VydmUiIHZlcnNpb249IjEuMSIgc3R5bGU9InNoYXBlLXJlbmRlcmluZzpnZW9tZXRyaWNQcmVjaXNpb247dGV4dC1yZW5kZXJpbmc6Z2VvbWV0cmljUHJlY2lzaW9uO2ltYWdlLXJlbmRlcmluZzpvcHRpbWl6ZVF1YWxpdHk7IiB2aWV3Qm94PSIwIDAgMzIwIDMxOS45OSIgeD0iMHB4IiB5PSIwcHgiIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIj48ZGVmcz48c3R5bGUgdHlwZT0idGV4dC9jc3MiPgogICAKICAgIC5maWwwIHtmaWxsOiNGRkZGRkY7ZmlsbC1ydWxlOm5vbnplcm99CiAgIAogIDwvc3R5bGU+PC9kZWZzPjxnPjxwYXRoIGNsYXNzPSJmaWwwIiBkPSJNMTYwIDI4My4zM2MzNC4wNiwwIDY0LjksLTEzLjgxIDg3LjIxLC0zNi4xMiAyMi4zMSwtMjIuMzEgMzYuMTIsLTUzLjE2IDM2LjEyLC04Ny4yMSAwLC0zNC4wNiAtMTMuODEsLTY0Ljg5IC0zNi4xMiwtODcuMjEgLTIyLjMxLC0yMi4zMSAtNTMuMTYsLTM2LjEzIC04Ny4yMSwtMzYuMTMgLTM0LjA2LDAgLTY0LjksMTMuODEgLTg3LjIxLDM2LjEzIC0yMi4zMSwyMi4zMSAtMzYuMTMsNTMuMTUgLTM2LjEzLDg3LjIxIDAsMzQuMDYgMTMuODEsNjQuOSAzNi4xMyw4Ny4yMSAyMi4zMSwyMi4zMSA1My4xNSwzNi4xMiA4Ny4yMSwzNi4xMnptMTAxLjM1IC0yMS45OGMtMjUuOTQsMjUuOTMgLTYxLjc4LDQxLjk4IC0xMDEuMzUsNDEuOTggLTM5LjU4LDAgLTc1LjQyLC0xNi4wNSAtMTAxLjM1LC00MS45OCAtMjUuOTQsLTI1Ljk0IC00MS45OCwtNjEuNzggLTQxLjk4LC0xMDEuMzUgMCwtMzkuNTggMTYuMDUsLTc1LjQyIDQxLjk4LC0xMDEuMzUgMjUuOTMsLTI1Ljk0IDYxLjc3LC00MS45OCAxMDEuMzUsLTQxLjk4IDM5LjU4LDAgNzUuNDIsMTYuMDUgMTAxLjM1LDQxLjk4IDI1LjkzLDI1LjkzIDQxLjk4LDYxLjc3IDQxLjk4LDEwMS4zNSAwLDM5LjU4IC0xNi4wNSw3NS40MiAtNDEuOTgsMTAxLjM1eiI+PC9wYXRoPjxwYXRoIGNsYXNzPSJmaWwwIiBkPSJNMjYuNjYgMTY5Ljg2Yy01LjUsMCAtOS45NiwtNC40NiAtOS45NiwtOS45NiAwLC01LjUgNC40NiwtOS45NiA5Ljk2LC05Ljk2bDI2Ni42NyAtMS4wNGM1LjUsMCA5Ljk2LDQuNDYgOS45Niw5Ljk2IDAsNS41IC00LjQ2LDkuOTYgLTkuOTYsOS45NmwtMjY2LjY3IDEuMDR6Ij48L3BhdGg+PHBhdGggY2xhc3M9ImZpbDAiIGQ9Ik0xNDkuNTIgMjYuNjZjMCwtNS41IDQuNDYsLTkuOTYgOS45NiwtOS45NiA1LjUsMCA5Ljk2LDQuNDYgOS45Niw5Ljk2bDEuMDQgMjY2LjY3YzAsNS41IC00LjQ2LDkuOTYgLTkuOTYsOS45NiAtNS41LDAgLTkuOTYsLTQuNDYgLTkuOTYsLTkuOTZsLTEuMDQgLTI2Ni42N3oiPjwvcGF0aD48cGF0aCBjbGFzcz0iZmlsMCIgZD0iTTE2MCAyODMuMzNjMTQuMzIsMCAyNy44OSwtMTIuODEgMzguMjQsLTMzLjUxIDExLjM5LC0yMi43NyAxOC40MywtNTQuNTIgMTguNDMsLTg5LjgzIDAsLTM1LjMxIC03LjA1LC02Ny4wNiAtMTguNDMsLTg5LjgzIC0xMC4zNSwtMjAuNyAtMjMuOTEsLTMzLjUxIC0zOC4yNCwtMzMuNTEgLTE0LjMyLDAgLTI3Ljg5LDEyLjgxIC0zOC4yNCwzMy41MSAtMTEuMzksMjIuNzcgLTE4LjQzLDU0LjUyIC0xOC40Myw4OS44MyAwLDM1LjMxIDcuMDUsNjcuMDYgMTguNDMsODkuODMgMTAuMzUsMjAuNyAyMy45MSwzMy41MSAzOC4yNCwzMy41MXptNTYuMDUgLTI0LjZjLTEzLjc3LDI3LjU2IC0zMy41NSw0NC42IC01Ni4wNSw0NC42IC0yMi41LDAgLTQyLjI4LC0xNy4wNSAtNTYuMDUsLTQ0LjYgLTEyLjc0LC0yNS40NyAtMjAuNjIsLTYwLjQxIC0yMC42MiwtOTguNzMgMCwtMzguMzMgNy44OCwtNzMuMjYgMjAuNjIsLTk4LjczIDEzLjc3LC0yNy41NiAzMy41NSwtNDQuNiA1Ni4wNSwtNDQuNiAyMi41LDAgNDIuMjgsMTcuMDUgNTYuMDUsNDQuNiAxMi43NCwyNS40NyAyMC42Miw2MC40MSAyMC42Miw5OC43MyAwLDM4LjMzIC03Ljg4LDczLjI2IC0yMC42Miw5OC43M3oiPjwvcGF0aD48cGF0aCBjbGFzcz0iZmlsMCIgZD0iTTQ1LjcgMTA0LjA0Yy01LjUsMC4wMiAtOS45OCwtNC40MiAtMTAsLTkuOTIgLTAuMDIsLTUuNSA0LjQyLC05Ljk4IDkuOTIsLTEwbDIyOC42OCAtMS4wNGM1LjUsLTAuMDIgOS45OCw0LjQyIDEwLDkuOTIgMC4wMiw1LjUgLTQuNDIsOS45OCAtOS45MiwxMGwtMjI4LjY4IDEuMDR6Ij48L3BhdGg+PHBhdGggY2xhc3M9ImZpbDAiIGQ9Ik00NS43IDIzNy40MmMtNS41LDAuMDIgLTkuOTgsLTQuNDIgLTEwLC05LjkyIC0wLjAyLC01LjUgNC40MiwtOS45OCA5LjkyLC0xMGwyMjguNjggLTEuMDRjNS41LC0wLjAyIDkuOTgsNC40MiAxMCw5LjkyIDAuMDIsNS41IC00LjQyLDkuOTggLTkuOTIsMTBsLTIyOC42OCAxLjA0eiI+PC9wYXRoPjwvZz48L3N2Zz4=");
|
||||
background-repeat: no-repeat;
|
||||
background-size: 24px 24px;
|
||||
border: none;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.pin-zoom-modal-site-link:hover{
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.pin-zoom-modal-edit {
|
||||
position: fixed;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
bottom: 20px;
|
||||
right: 60px;
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjRkZGRkZGIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGRhdGEtbmFtZT0iTGF5ZXIgMSIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHg9IjBweCIgeT0iMHB4Ij48dGl0bGU+NTE8L3RpdGxlPjxwYXRoIGQ9Ik04NC44NTAxMiw1MFY4MS43NDUxMkExMy4yNzAxMiwxMy4yNzAxMiwwLDAsMSw3MS41OTUyNCw5NUgxOC4yNTQ5MUExMy4yNzAxMiwxMy4yNzAxMiwwLDAsMSw1LDgxLjc0NTEyVjI4LjQwNTI4QTEzLjI3MDEyLDEzLjI3MDEyLDAsMCwxLDE4LjI1NDkxLDE1LjE1MDRINTBhMi41LDIuNSwwLDAsMSwwLDVIMTguMjU0OTFBOC4yNjQyMyw4LjI2NDIzLDAsMCwwLDEwLDI4LjQwNTI4VjgxLjc0NTEyQTguMjY0MjQsOC4yNjQyNCwwLDAsMCwxOC4yNTQ5MSw5MEg3MS41OTUyNGE4LjI2NDIzLDguMjY0MjMsMCwwLDAsOC4yNTQ4OC04LjI1NDg5VjUwYTIuNSwyLjUsMCwwLDEsNSwwWk04OS4xNDg0Niw2LjIzNzkyYTQuMjI2NjEsNC4yMjY2MSwwLDAsMC01Ljk3NzI5LDBsLTMzLjk2MjksMzMuOTYzTDU5Ljc5OTE2LDUwLjc5MTc2bDMzLjk2Mjg5LTMzLjk2M2E0LjIyNjUzLDQuMjI2NTMsMCwwLDAsMC01Ljk3NzIzWk00My42MjM4LDU4LjMxMjg3bDEzLjAwOTQtNC4zNTUxNkw0Ni4wNDIyNiw0My4zNjY4M2wtNC4zNTUxLDEzLjAwOTRBMS41MzAwNSwxLjUzMDA1LDAsMCwwLDQzLjYyMzgsNTguMzEyODdaIj48L3BhdGg+PC9zdmc+");
|
||||
background-repeat: no-repeat;
|
||||
background-size: 24px 24px;
|
||||
border: none;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.pin-zoom-modal-edit {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#loader:after {
|
||||
border-left-color: #3273dc;
|
||||
border-bottom-color: #3273dc;
|
||||
}
|
||||
630
static/client.js
630
static/client.js
|
|
@ -2,27 +2,94 @@ Reef.debug(true);
|
|||
|
||||
const store = new Reef.Store({
|
||||
data: {
|
||||
hash: {
|
||||
board: null
|
||||
},
|
||||
loading: false,
|
||||
boards: [],
|
||||
board: null,
|
||||
addPin: {
|
||||
active: false,
|
||||
boardId: "",
|
||||
newBoardName: null,
|
||||
imageUrl: "",
|
||||
previewReady: false,
|
||||
previewImageUrl: null,
|
||||
siteUrl: "",
|
||||
description: ""
|
||||
description: "",
|
||||
saveInProgress: false
|
||||
},
|
||||
pinZoom: {
|
||||
active: false,
|
||||
pinId: null
|
||||
pin: null
|
||||
},
|
||||
about: {
|
||||
active: false
|
||||
},
|
||||
editBoard: {
|
||||
active: false,
|
||||
name: ""
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
isAddPinValid: (data) => {
|
||||
|
||||
if ( data.addPin.boardId == "new"){
|
||||
if ( !data.addPin.newBoardName ){
|
||||
return false;
|
||||
} else if ( data.addPin.newBoardName.trim().length < 1 ){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !data.addPin.previewImageUrl ){
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
isEditBoardValid: (data) => {
|
||||
if (!data.editBoard.name){
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( data.editBoard.name.trim().length < 1 ){
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function getBoardIndexById(id){
|
||||
let idx = -1;
|
||||
for ( let i = 0; i < store.data.boards.length; ++i ){
|
||||
if ( store.data.boards[i].id == id ){
|
||||
idx = i;
|
||||
}
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
function getBoardById(id){
|
||||
return store.data.boards[getBoardIndexById(id)];
|
||||
}
|
||||
|
||||
function getPinIndexById(id){
|
||||
let idx = -1;
|
||||
for ( let i = 0; i < store.data.board.pins.length; ++i ){
|
||||
if ( store.data.board.pins[i].id == id ){
|
||||
idx = i;
|
||||
}
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
function getPinById(id){
|
||||
return store.data.board.pins[getPinIndexById(id)];
|
||||
}
|
||||
|
||||
const actions = {
|
||||
openAddPinModal: () => {
|
||||
|
||||
|
|
@ -31,7 +98,7 @@ const actions = {
|
|||
} else if ( store.data.boards && store.data.boards.length > 0 ){
|
||||
store.data.addPin.boardId = store.data.boards[0].id;
|
||||
} else {
|
||||
store.data.addPin.boardId = 0;
|
||||
store.data.addPin.boardId = "new";
|
||||
}
|
||||
|
||||
store.data.addPin.active = true;
|
||||
|
|
@ -42,11 +109,35 @@ const actions = {
|
|||
store.data.addPin.previewImageUrl = "";
|
||||
store.data.addPin.siteUrl = "";
|
||||
store.data.addPin.description = "";
|
||||
store.data.addPin.newBoardName = "";
|
||||
store.data.addPin.saveInProgress = false;
|
||||
},
|
||||
saveAddPin: async () => {
|
||||
|
||||
store.data.addPin.saveInProgress = true;
|
||||
|
||||
let boardId = store.data.addPin.boardId;
|
||||
|
||||
let newBoard = null;
|
||||
|
||||
if ( boardId == "new" ){
|
||||
let res = await fetch('api/boards', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
"name": store.data.addPin.newBoardName
|
||||
})
|
||||
});
|
||||
|
||||
if ( res.status == 200 ){
|
||||
newBoard = await res.json();
|
||||
boardId = newBoard.id;
|
||||
store.data.boards.push(newBoard);
|
||||
}
|
||||
}
|
||||
|
||||
let postData = {
|
||||
boardId: store.data.addPin.boardId,
|
||||
boardId: boardId,
|
||||
imageUrl: store.data.addPin.imageUrl,
|
||||
siteUrl: store.data.addPin.siteUrl,
|
||||
description: store.data.addPin.description
|
||||
|
|
@ -61,11 +152,19 @@ const actions = {
|
|||
});
|
||||
|
||||
if ( res.status == 200 ){
|
||||
actions.closeAddPinModal();
|
||||
|
||||
|
||||
let body = await res.json();
|
||||
store.data.board.pins.push(body);
|
||||
}
|
||||
if ( store.data.board && store.data.board.id == boardId ){
|
||||
store.data.board.pins.push(body);
|
||||
}
|
||||
|
||||
if ( newBoard ){
|
||||
newBoard.titlePinId = body.id;
|
||||
}
|
||||
|
||||
actions.closeAddPinModal();
|
||||
}
|
||||
|
||||
},
|
||||
updateAddPinPreview: () => {
|
||||
if ( store.data.addPin.imageUrl.startsWith("http") ){
|
||||
|
|
@ -87,7 +186,7 @@ const actions = {
|
|||
let pinId = el.getAttribute("data-pinid");
|
||||
|
||||
if( pinId ){
|
||||
store.data.pinZoom.pinId = pinId;
|
||||
store.data.pinZoom.pin = getPinById(pinId);
|
||||
store.data.pinZoom.active = true;
|
||||
}
|
||||
|
||||
|
|
@ -98,29 +197,47 @@ const actions = {
|
|||
},
|
||||
movePinZoomModalLeft: () => {
|
||||
|
||||
let idx = 0;
|
||||
for ( let i = 0; i < store.data.board.pins.length; ++i ){
|
||||
if ( store.data.board.pins[i].id == store.data.pinZoom.pinId ){
|
||||
idx = i;
|
||||
}
|
||||
}
|
||||
let idx = getPinIndexById(store.data.pinZoom.pin.id);
|
||||
|
||||
if ( idx > 0 ){
|
||||
store.data.pinZoom.pinId = store.data.board.pins[idx-1].id;
|
||||
store.data.pinZoom.pin = store.data.board.pins[idx-1];
|
||||
}
|
||||
|
||||
},
|
||||
movePinZoomModalRight: () => {
|
||||
|
||||
let idx = -1;
|
||||
for ( let i = 0; i < store.data.board.pins.length; ++i ){
|
||||
if ( store.data.board.pins[i].id == store.data.pinZoom.pinId ){
|
||||
idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
let idx = getPinIndexById(store.data.pinZoom.pin.id);
|
||||
|
||||
if ( idx >= 0 && (idx < store.data.board.pins.length-1) ){
|
||||
store.data.pinZoom.pinId = store.data.board.pins[idx+1].id
|
||||
store.data.pinZoom.pin = store.data.board.pins[idx+1];
|
||||
}
|
||||
},
|
||||
deletePin: async () => {
|
||||
if ( confirm("Are you sure you want to delete this pin?") ){
|
||||
|
||||
store.data.loading++;
|
||||
|
||||
let pinId = store.data.pinZoom.pin.id;
|
||||
|
||||
let idx = getPinIndexById(pin.id);
|
||||
if ( idx >= 0 ){
|
||||
store.data.board.pins.splice(idx,1);
|
||||
}
|
||||
|
||||
actions.closePinZoomModal();
|
||||
|
||||
let res = await fetch(`/api/pins/${pinId}`, {
|
||||
method: "DELETE"
|
||||
});
|
||||
|
||||
if ( res.status == 200 ){
|
||||
console.log(`deleted pin#${pinId}`);
|
||||
} else {
|
||||
console.error(`error deleting pin#${pinId}`);
|
||||
}
|
||||
|
||||
store.data.loading--;
|
||||
|
||||
}
|
||||
},
|
||||
showAboutModal: () => {
|
||||
|
|
@ -128,12 +245,85 @@ const actions = {
|
|||
},
|
||||
closeAboutModal: () => {
|
||||
store.data.about.active = false;
|
||||
},
|
||||
openEditBoardModal: () => {
|
||||
store.data.editBoard.name = store.data.board.name;
|
||||
store.data.editBoard.active = true;
|
||||
},
|
||||
closeEditBoardModal: () => {
|
||||
store.data.editBoard.name = "";
|
||||
store.data.editBoard.active = false;
|
||||
},
|
||||
saveEditBoard: async () => {
|
||||
|
||||
store.data.loading++
|
||||
|
||||
let boardId = store.data.board.id;
|
||||
let name = store.data.editBoard.name;
|
||||
|
||||
let idx = getBoardIndexById(boardId);
|
||||
console.log("idx=" + idx);
|
||||
if ( idx >= 0 ){
|
||||
store.data.boards[idx].name = name;
|
||||
store.data.board.name = name;
|
||||
}
|
||||
|
||||
let res = await fetch(`/api/boards/${boardId}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: name
|
||||
})
|
||||
});
|
||||
|
||||
if ( res.status == 200 ){
|
||||
console.log(`updated board#${boardId}`);
|
||||
store.data.editBoard.active = false;
|
||||
} else {
|
||||
console.error(`error updating board#${boardId}`);
|
||||
}
|
||||
|
||||
|
||||
store.data.loading--;
|
||||
},
|
||||
editBoardDelete: async () => {
|
||||
|
||||
if ( !confirm("Are you sure you want to delete this board and all pins on it?") ){
|
||||
return;
|
||||
}
|
||||
|
||||
store.data.loading++;
|
||||
|
||||
let boardId = store.data.board.id;
|
||||
|
||||
|
||||
let idx = getBoardIndexById(boardId);
|
||||
if ( idx >= 0 ){
|
||||
store.data.boards.splice(idx, 1);
|
||||
}
|
||||
store.data.editBoard.active = false;
|
||||
window.location.hash = "";
|
||||
|
||||
|
||||
let res = await fetch(`/api/boards/${boardId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if ( res.status == 200 ){
|
||||
console.log(`deleted board#${boardId}`);
|
||||
} else {
|
||||
console.log(`error deleting board#${boardId}`);
|
||||
}
|
||||
|
||||
store.data.loading--;
|
||||
}
|
||||
}
|
||||
|
||||
const app = new Reef("#app", {
|
||||
store: store,
|
||||
template: (store) => {
|
||||
template: (data) => {
|
||||
return /*html*/`
|
||||
<div id="navbar"></div>
|
||||
<section class="section">
|
||||
|
|
@ -142,79 +332,47 @@ const app = new Reef("#app", {
|
|||
</div>
|
||||
</section>
|
||||
<footer class="footer" id="footer">
|
||||
<div class="content has-text-right">
|
||||
<p>
|
||||
<a data-onclick="showAboutModal">about tinypin</a>
|
||||
</p>
|
||||
<div class="content">
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<a data-onclick="showAboutModal">about tinypin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<div id="add-pin-modal"></div>
|
||||
<div id="pin-zoom-modal"></div>
|
||||
<div id="edit-board-modal"></div>
|
||||
<div id="about-modal"></div>
|
||||
`
|
||||
}
|
||||
});
|
||||
|
||||
const aboutModal = new Reef("#about-modal", {
|
||||
store: store,
|
||||
template: (data) => {
|
||||
return /*html*/`
|
||||
<div class="modal ${data.about.active ? 'is-active' : ''}">
|
||||
<div class="modal-background" data-onclick="closeAboutModal"></div>
|
||||
<div class="modal-content">
|
||||
<div class="box" style="font-family: monospace;">
|
||||
<h1><strong>tinypin</strong></h1>
|
||||
<div>
|
||||
<a href="https://www.github.com">github.com/slynn1324/tinypin</a>
|
||||
<br />
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<h2><strong>credits</strong></h2>
|
||||
client
|
||||
<br />
|
||||
css framework » <a href="https://www.bulma.io">bulma.io</a>
|
||||
<br />
|
||||
ui framework » <a href="https://reefjs.com">reef</a>
|
||||
<br />
|
||||
icon » <a href="https://thenounproject.com/term/pinned/1560993/">pinned by Gregor Cresnar from the Noun Project</a>
|
||||
<br />
|
||||
server
|
||||
<br />
|
||||
language & runtime » <a href="https://nodejs.org/en/">node.js</a>
|
||||
<br />
|
||||
database » <a href="https://www.sqlite.org/index.html">sqlite</a>
|
||||
<br />
|
||||
library » <a href="https://www.npmjs.com/package/better-sqlite3">better-sqlite3</a>
|
||||
<br />
|
||||
library » <a href="https://www.npmjs.com/package/express">express</a>
|
||||
<br />
|
||||
library » <a href="https://www.npmjs.com/package/body-parser">body-parser</a>
|
||||
<br />
|
||||
library » <a href="https://www.npmjs.com/package/node-fetch">node-fetch</a>
|
||||
<br />
|
||||
library » <a href="https://www.npmjs.com/package/sharp">sharp</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="modal-close is-large" aria-label="close" data-onclick="closeAboutModal"></button>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
attachTo: app
|
||||
});
|
||||
|
||||
const navbar = new Reef("#navbar", {
|
||||
store: store,
|
||||
template: (data) => {
|
||||
|
||||
let boardName = "";
|
||||
|
||||
if ( data.board ){
|
||||
boardName = /*html*/`
|
||||
<span class="navbar-item">${data.board.name}
|
||||
<a data-onclick="openEditBoardModal" style="padding-top: 3px;"><img alt="edit" width="16" height="16" src="data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjMDAwMDAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGRhdGEtbmFtZT0iTGF5ZXIgMSIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHg9IjBweCIgeT0iMHB4Ij48dGl0bGU+NTE8L3RpdGxlPjxwYXRoIGQ9Ik04NC44NTAxMiw1MFY4MS43NDUxMkExMy4yNzAxMiwxMy4yNzAxMiwwLDAsMSw3MS41OTUyNCw5NUgxOC4yNTQ5MUExMy4yNzAxMiwxMy4yNzAxMiwwLDAsMSw1LDgxLjc0NTEyVjI4LjQwNTI4QTEzLjI3MDEyLDEzLjI3MDEyLDAsMCwxLDE4LjI1NDkxLDE1LjE1MDRINTBhMi41LDIuNSwwLDAsMSwwLDVIMTguMjU0OTFBOC4yNjQyMyw4LjI2NDIzLDAsMCwwLDEwLDI4LjQwNTI4VjgxLjc0NTEyQTguMjY0MjQsOC4yNjQyNCwwLDAsMCwxOC4yNTQ5MSw5MEg3MS41OTUyNGE4LjI2NDIzLDguMjY0MjMsMCwwLDAsOC4yNTQ4OC04LjI1NDg5VjUwYTIuNSwyLjUsMCwwLDEsNSwwWk04OS4xNDg0Niw2LjIzNzkyYTQuMjI2NjEsNC4yMjY2MSwwLDAsMC01Ljk3NzI5LDBsLTMzLjk2MjksMzMuOTYzTDU5Ljc5OTE2LDUwLjc5MTc2bDMzLjk2Mjg5LTMzLjk2M2E0LjIyNjUzLDQuMjI2NTMsMCwwLDAsMC01Ljk3NzIzWk00My42MjM4LDU4LjMxMjg3bDEzLjAwOTQtNC4zNTUxNkw0Ni4wNDIyNiw0My4zNjY4M2wtNC4zNTUxLDEzLjAwOTRBMS41MzAwNSwxLjUzMDA1LDAsMCwwLDQzLjYyMzgsNTguMzEyODdaIj48L3BhdGg+PC9zdmc+" /></a>
|
||||
</span>`;
|
||||
} else if ( !data.hash.board ) {
|
||||
boardName = /*html*/`<span class="navbar-item">Boards</span>`;
|
||||
}
|
||||
|
||||
return /*html*/`
|
||||
<nav class="navbar is-light" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="#">
|
||||
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' data-name='Layer 1' viewBox='0 0 100 125' x='0px' y='0px'%3E%3Ctitle%3EArtboard 164%3C/title%3E%3Cpath d='M56.77,3.11a4,4,0,1,0-5.66,5.66l5.17,5.17L37.23,33A23.32,23.32,0,0,0,9.42,36.8L7.11,39.11a4,4,0,0,0,0,5.66l21.3,21.29L3.23,91.23a4,4,0,0,0,5.66,5.66L34.06,71.72l21,21a4,4,0,0,0,5.66,0l2.31-2.31a23.34,23.34,0,0,0,3.81-27.82l19-19,5.17,5.18a4,4,0,0,0,5.66-5.66Zm1.16,81.16L15.61,42a15.37,15.37,0,0,1,21.19.51L57.42,63.08A15.39,15.39,0,0,1,57.93,84.27Zm4-28L43.59,37.94,61.94,19.59,80.28,37.94Z'/%3E%3C/svg%3E" width="32" height="32" />
|
||||
<!--<strong>TinyPin</strong>-->
|
||||
<img alt="boards" width="32" height="32" src="data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjMDAwMDAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2OCA0OCIgeD0iMHB4IiB5PSIwcHgiPjxwYXRoIGZpbGw9IiMwMDAwMDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTcyLDgwNiBMMTA3LDgwNiBDMTA4LjEwNDU2OSw4MDYgMTA5LDgwNi44OTU0MzEgMTA5LDgwOCBMMTA5LDgzMiBDMTA5LDgzMy4xMDQ1NjkgMTA4LjEwNDU2OSw4MzQgMTA3LDgzNCBMNzIsODM0IEM3MC44OTU0MzA1LDgzNCA3MCw4MzMuMTA0NTY5IDcwLDgzMiBMNzAsODA4IEM3MCw4MDYuODk1NDMxIDcwLjg5NTQzMDUsODA2IDcyLDgwNiBaIE0xMTIsODIyIEwxMTIsODIyIEwxMTIsODA4IEMxMTIsODA1LjIzODU3NiAxMDkuNzYxNDI0LDgwMyAxMDcsODAzIEw5Niw4MDMgTDk2LDc4OCBDOTYsNzg2Ljg5NTQzMSA5Ni44OTU0MzA1LDc4NiA5OCw3ODYgTDEyMiw3ODYgQzEyMy4xMDQ1NjksNzg2IDEyNCw3ODYuODk1NDMxIDEyNCw3ODggTDEyNCw4MjAgQzEyNCw4MjEuMTA0NTY5IDEyMy4xMDQ1NjksODIyIDEyMiw4MjIgTDExMiw4MjIgWiBNODQsODAzIEw3Miw4MDMgQzY5LjIzODU3NjMsODAzIDY3LDgwNS4yMzg1NzYgNjcsODA4IEw2Nyw4MTcgTDU4LDgxNyBDNTYuODk1NDMwNSw4MTcgNTYsODE2LjEwNDU2OSA1Niw4MTUgTDU2LDc5MSBDNTYsNzg5Ljg5NTQzMSA1Ni44OTU0MzA1LDc4OSA1OCw3ODkgTDgyLDc4OSBDODMuMTA0NTY5NSw3ODkgODQsNzg5Ljg5NTQzMSA4NCw3OTEgTDg0LDgwMyBaIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNTYgLTc4NikiPjwvcGF0aD48L3N2Zz4=" />
|
||||
|
||||
</a>
|
||||
|
||||
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false">
|
||||
|
|
@ -227,11 +385,14 @@ const navbar = new Reef("#navbar", {
|
|||
|
||||
<div class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
<span class="navbar-item" id="board-name">${data.board ? data.board.name : "Boards"}</span>
|
||||
${boardName}
|
||||
</div>
|
||||
<div class="navbar-end">
|
||||
<span class="navbar-item">
|
||||
<div id="loader" class="button is-text ${data.loading ? 'is-loading' : ''}"></div>
|
||||
</span>
|
||||
<a class="navbar-item" data-onclick="openAddPinModal">
|
||||
Add Pin
|
||||
<img alt="add pin" width="32" height="32" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' data-name='Layer 1' viewBox='0 0 100 125' x='0px' y='0px'%3E%3Ctitle%3EArtboard 164%3C/title%3E%3Cpath d='M56.77,3.11a4,4,0,1,0-5.66,5.66l5.17,5.17L37.23,33A23.32,23.32,0,0,0,9.42,36.8L7.11,39.11a4,4,0,0,0,0,5.66l21.3,21.29L3.23,91.23a4,4,0,0,0,5.66,5.66L34.06,71.72l21,21a4,4,0,0,0,5.66,0l2.31-2.31a23.34,23.34,0,0,0,3.81-27.82l19-19,5.17,5.18a4,4,0,0,0,5.66-5.66Zm1.16,81.16L15.61,42a15.37,15.37,0,0,1,21.19.51L57.42,63.08A15.39,15.39,0,0,1,57.93,84.27Zm4-28L43.59,37.94,61.94,19.59,80.28,37.94Z'/%3E%3C/svg%3E"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -242,99 +403,15 @@ const navbar = new Reef("#navbar", {
|
|||
attachTo: app
|
||||
});
|
||||
|
||||
const addPinModal = new Reef("#add-pin-modal", {
|
||||
store: store,
|
||||
template: (store) => {
|
||||
|
||||
let imagePlaceholder = 'data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22300%22%20height%3D%22300%22%3E%3Crect%20x%3D%222%22%20y%3D%222%22%20width%3D%22300%22%20height%3D%22300%22%20style%3D%22fill%3A%23dedede%3B%22%2F%3E%3Ctext%20x%3D%2250%25%22%20y%3D%2250%25%22%20font-size%3D%2218%22%20text-anchor%3D%22middle%22%20alignment-baseline%3D%22middle%22%20font-family%3D%22monospace%2C%20sans-serif%22%20fill%3D%22%23555555%22%3Eimage%3C%2Ftext%3E%3C%2Fsvg%3E';
|
||||
|
||||
let options = "";
|
||||
for ( let i = 0; i < store.boards.length; ++i ){
|
||||
options += `<option value="${store.boards[i].id}">${store.boards[i].name}</option>`;
|
||||
}
|
||||
|
||||
return /*html*/`
|
||||
<div class="modal ${store.addPin.active ? 'is-active' : ''}">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Add Pin</p>
|
||||
<button class="delete" aria-label="close" data-onclick="closeAddPinModal"></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<div class="add-pin-flex">
|
||||
<div class="add-pin-flex-left">
|
||||
<img id="add-pin-modal-img" src="${store.addPin.previewImageUrl ? store.addPin.previewImageUrl : imagePlaceholder}" />
|
||||
</div>
|
||||
<div class="add-pin-flex-right">
|
||||
<form>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Board</label>
|
||||
<div class="select">
|
||||
<select data-bind="addPin.boardId">
|
||||
${options}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Image Url</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" data-bind="addPin.imageUrl" data-onblur="updateAddPinPreview"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Website Url</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" data-bind="addPin.siteUrl" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Description</label>
|
||||
<div class="control">
|
||||
<textarea class="textarea" data-bind="addPin.description"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button class="button is-success" data-onclick="saveAddPin">Add Pin</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
attachTo: app
|
||||
});
|
||||
|
||||
const pinZoomModal = new Reef("#pin-zoom-modal", {
|
||||
store: store,
|
||||
template: (data) => {
|
||||
return /*html*/`
|
||||
<div class="modal ${data.pinZoom.active ? 'is-active' : ''}" id="pin-zoom-modal" >
|
||||
<div class="modal-background" data-onclick="closePinZoomModal"></div>
|
||||
<div class="modal-content">
|
||||
<p>
|
||||
<img src="${data.pinZoom.active ? getOriginalImagePath(data.pinZoom.pinId) : ''}" />
|
||||
</p>
|
||||
</div>
|
||||
<button class="modal-close is-large" aria-label="close" data-onclick="closePinZoomModal"></button>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
attachTo: app
|
||||
});
|
||||
|
||||
const brickwall = new Reef('#brick-wall', {
|
||||
store: store,
|
||||
template: (data, el) => {
|
||||
|
||||
// if the hash says we are supposed to be drawing a board, but it hasn't loaded yet... draw an empty div.
|
||||
if ( data.hash.board && !data.board ){
|
||||
return '<div></div>';
|
||||
}
|
||||
|
||||
let numberOfColumns = 1;
|
||||
let width = el.offsetWidth;
|
||||
// matching bulma breakpoints - https://bulma.io/documentation/overview/responsiveness/
|
||||
|
|
@ -352,7 +429,7 @@ const brickwall = new Reef('#brick-wall', {
|
|||
return /*html*/`
|
||||
<div class="brick board-brick">
|
||||
<a href="#board=${board.id}">
|
||||
<img src="${getThumbnailImagePath(board.titlePinId)}" />
|
||||
<img src="${board.titlePinId > 0 ? getThumbnailImagePath(board.titlePinId) : ''}" />
|
||||
<div class="board-brick-name">${board.name}</div>
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -415,6 +492,205 @@ const brickwall = new Reef('#brick-wall', {
|
|||
attachTo: app
|
||||
});
|
||||
|
||||
const addPinModal = new Reef("#add-pin-modal", {
|
||||
store: store,
|
||||
template: (data) => {
|
||||
|
||||
let imagePlaceholder = 'data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22300%22%20height%3D%22300%22%3E%3Crect%20x%3D%222%22%20y%3D%222%22%20width%3D%22300%22%20height%3D%22300%22%20style%3D%22fill%3A%23dedede%3B%22%2F%3E%3Ctext%20x%3D%2250%25%22%20y%3D%2250%25%22%20font-size%3D%2218%22%20text-anchor%3D%22middle%22%20alignment-baseline%3D%22middle%22%20font-family%3D%22monospace%2C%20sans-serif%22%20fill%3D%22%23555555%22%3Eimage%3C%2Ftext%3E%3C%2Fsvg%3E';
|
||||
|
||||
let options = "";
|
||||
for ( let i = 0; i < data.boards.length; ++i ){
|
||||
options += `<option value="${data.boards[i].id}">${data.boards[i].name}</option>`;
|
||||
}
|
||||
|
||||
let newBoardField = '';
|
||||
if ( data.addPin.boardId == "new" ){
|
||||
newBoardField = /*html*/`
|
||||
<div class="field">
|
||||
<label class="label">Board Name</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" data-bind="addPin.newBoardName" />
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return /*html*/`
|
||||
<div class="modal ${data.addPin.active ? 'is-active' : ''}">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Add Pin</p>
|
||||
<button class="delete" aria-label="close" data-onclick="closeAddPinModal"></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<div class="add-pin-flex">
|
||||
<div class="add-pin-flex-left">
|
||||
<img id="add-pin-modal-img" src="${data.addPin.previewImageUrl ? data.addPin.previewImageUrl : imagePlaceholder}" />
|
||||
</div>
|
||||
<div class="add-pin-flex-right">
|
||||
<form>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Board</label>
|
||||
<div class="select">
|
||||
<select data-bind="addPin.boardId">
|
||||
${options}
|
||||
<option value="new">Create New Board</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${newBoardField}
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Image Url</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" data-bind="addPin.imageUrl" data-onblur="updateAddPinPreview"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Website Url</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" data-bind="addPin.siteUrl" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Description</label>
|
||||
<div class="control">
|
||||
<textarea class="textarea" data-bind="addPin.description"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button class="button is-success ${data.addPin.saveInProgress ? 'is-loading' : ''}" ${!store.get('isAddPinValid') || data.addPin.saveInProgress ? 'disabled' : ''} data-onclick="saveAddPin">Add Pin</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
attachTo: app
|
||||
});
|
||||
|
||||
const editBoardModal = new Reef("#edit-board-modal", {
|
||||
store: store,
|
||||
template: (data) => {
|
||||
return /*html*/`
|
||||
<div class="modal ${data.editBoard.active ? 'is-active' : ''}">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Edit Board</p>
|
||||
<button class="delete" aria-label="close" data-onclick="closeEditBoardModal"></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Name</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" data-bind="editBoard.name" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button class="button is-success ${data.editBoard.saveInProgress ? 'is-loading' : '' }" ${!store.get('isEditBoardValid') || data.editBoard.saveInProgress ? 'disabled' : ''} data-onclick="saveEditBoard">Save</button>
|
||||
<button class="button is-danger" data-onclick="editBoardDelete">Delete</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
attachTo: app
|
||||
});
|
||||
|
||||
const pinZoomModal = new Reef("#pin-zoom-modal", {
|
||||
store: store,
|
||||
template: (data) => {
|
||||
|
||||
let siteLink = '';
|
||||
if ( data.pinZoom.pin && data.pinZoom.pin.siteUrl ){
|
||||
siteLink = `<a class="pin-zoom-modal-site-link" href="${data.pinZoom.pin.siteUrl}"></a>`;
|
||||
}
|
||||
|
||||
return /*html*/`
|
||||
<div class="modal ${data.pinZoom.active ? 'is-active' : ''}" id="pin-zoom-modal" >
|
||||
<div class="modal-background" data-onclick="closePinZoomModal"></div>
|
||||
<div class="modal-content">
|
||||
<p>
|
||||
<img src="${data.pinZoom.active ? getOriginalImagePath(data.pinZoom.pin.id) : ''}" />
|
||||
</p>
|
||||
</div>
|
||||
<button class="modal-close is-large" aria-label="close" data-onclick="closePinZoomModal"></button>
|
||||
${siteLink}
|
||||
<a class="pin-zoom-modal-edit" data-onclick="editPin"></a>
|
||||
<a class="pin-zoom-modal-delete" data-onclick="deletePin"></a>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
attachTo: app
|
||||
});
|
||||
|
||||
const aboutModal = new Reef("#about-modal", {
|
||||
store: store,
|
||||
template: (data) => {
|
||||
return /*html*/`
|
||||
<div class="modal ${data.about.active ? 'is-active' : ''}">
|
||||
<div class="modal-background" data-onclick="closeAboutModal"></div>
|
||||
<div class="modal-content">
|
||||
<div class="box" style="font-family: monospace;">
|
||||
<h1><strong>tinypin</strong></h1>
|
||||
<div>
|
||||
<a href="https://www.github.com">github.com/slynn1324/tinypin</a>
|
||||
<br />
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<h2><strong>credits</strong></h2>
|
||||
client
|
||||
<br />
|
||||
css framework » <a href="https://www.bulma.io">bulma.io</a>
|
||||
<br />
|
||||
ui framework » <a href="https://reefjs.com">reef</a>
|
||||
<br />
|
||||
boards icon » <a href="https://thenounproject.com/term/squares/1160031/">squares by Andrejs Kirma from the Noun Project</a>
|
||||
<br />
|
||||
pin icon » <a href="https://thenounproject.com/term/pinned/1560993/">pinned by Gregor Cresnar from the Noun Project</a>
|
||||
<br />
|
||||
trash icon » <a href="https://thenounproject.com/term/trash/2449397/">Trash by ICONZ from the Noun Project</a>
|
||||
<br />
|
||||
server
|
||||
<br />
|
||||
language & runtime » <a href="https://nodejs.org/en/">node.js</a>
|
||||
<br />
|
||||
database » <a href="https://www.sqlite.org/index.html">sqlite</a>
|
||||
<br />
|
||||
library » <a href="https://www.npmjs.com/package/better-sqlite3">better-sqlite3</a>
|
||||
<br />
|
||||
library » <a href="https://www.npmjs.com/package/express">express</a>
|
||||
<br />
|
||||
library » <a href="https://www.npmjs.com/package/body-parser">body-parser</a>
|
||||
<br />
|
||||
library » <a href="https://www.npmjs.com/package/node-fetch">node-fetch</a>
|
||||
<br />
|
||||
library » <a href="https://www.npmjs.com/package/sharp">sharp</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="modal-close is-large" aria-label="close" data-onclick="closeAboutModal"></button>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
attachTo: app
|
||||
});
|
||||
|
||||
|
||||
|
||||
document.addEventListener('click', (el) => {
|
||||
let target = el.target.closest('[data-onclick]');
|
||||
|
|
@ -479,14 +755,14 @@ window.addEventListener('resize', (evt) => {
|
|||
Reef.databind(app);
|
||||
app.render();
|
||||
|
||||
loadBoards();
|
||||
handleHash();
|
||||
|
||||
loadBoards();
|
||||
|
||||
|
||||
function handleHash(){
|
||||
|
||||
|
||||
let hash = parseQueryString(window.location.hash.substr(1));
|
||||
store.data.hash = hash;
|
||||
|
||||
if ( hash.board ){
|
||||
if ( !store.board || store.board.id != hash.board ){
|
||||
|
|
@ -503,13 +779,17 @@ function handleHash(){
|
|||
}
|
||||
|
||||
async function loadBoard(id){
|
||||
store.data.loading++
|
||||
let res = await fetch("/api/boards/" + id);
|
||||
store.data.board = await res.json();
|
||||
store.data.loading--;
|
||||
}
|
||||
|
||||
async function loadBoards(){
|
||||
store.data.loading++;
|
||||
let res = await fetch("/api/boards");
|
||||
store.data.boards = await res.json();
|
||||
store.data.loading--;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue