Add event + add job form
- Modified handlers to catch errors - Added placeholder "No location added", etc. messages to Job/Event cards - Added EventForm.js and JobForm.js for adding - EventForm and JobForm are animated to slide up from bottom - Modified openProfile/closeProfile logic - Fixed SidebarItem().onClick() firing twice bug (switched to .onTap) - Profile is now animated to slide up from the bottom
This commit is contained in:
@@ -117,8 +117,10 @@ class Profile extends Shadow {
|
||||
.boxSizing("border-box")
|
||||
.width(100, pct)
|
||||
.position("fixed")
|
||||
.top(20, px)
|
||||
.zIndex(1000)
|
||||
.top(100, vh)
|
||||
.zIndex(5)
|
||||
.transition("top .3s")
|
||||
.pointerEvents("none")
|
||||
}
|
||||
|
||||
convertDate(rawDate) {
|
||||
|
||||
@@ -4,47 +4,91 @@ const handlers = {
|
||||
},
|
||||
|
||||
async addEvent(newEvent, networkId, creatorId) {
|
||||
try {
|
||||
return await global.db.events.add(newEvent, networkId, creatorId)
|
||||
} catch (e) {
|
||||
return { status: e.status, error: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
async editEvent(id, updatedEvent, networkId, userId) {
|
||||
try {
|
||||
return await global.db.events.edit(id, updatedEvent, networkId, userId);
|
||||
} catch (e) {
|
||||
return { status: e.status, error: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
async deleteEvent(id, networkId, userId) {
|
||||
try {
|
||||
return await global.db.events.delete(id, networkId, userId);
|
||||
} catch (e) {
|
||||
return { status: e.status, error: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
async getEvent(id) {
|
||||
try {
|
||||
return global.db.events.getById(id)
|
||||
} catch (e) {
|
||||
return { status: e.status, error: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
async getEvents(networkId) {
|
||||
try {
|
||||
return global.db.events.getByNetwork(networkId)
|
||||
} catch (e) {
|
||||
return { status: e.status, error: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
async addJob(newJob, networkId, creatorId) {
|
||||
try {
|
||||
return await global.db.jobs.add(newJob, networkId, creatorId);
|
||||
} catch (e) {
|
||||
return { status: e.status, error: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
async editJob(id, updatedJob, networkId, userId) {
|
||||
try {
|
||||
return await global.db.jobs.edit(id, updatedJob, networkId, userId);
|
||||
} catch (e) {
|
||||
return { status: e.status, error: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
async deleteJob(id, networkId, userId) {
|
||||
try {
|
||||
return await global.db.jobs.delete(id, networkId, userId);
|
||||
} catch (e) {
|
||||
return { status: e.status, error: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
async getJob(id) {
|
||||
try {
|
||||
return await global.db.jobs.getById(id)
|
||||
} catch (e) {
|
||||
return { status: e.status, error: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
async getJobs(networkId) {
|
||||
try {
|
||||
return global.db.jobs.getByNetwork(networkId)
|
||||
} catch (e) {
|
||||
return { status: e.status, error: e.message }
|
||||
}
|
||||
},
|
||||
|
||||
async editBio(newBio, userId) {
|
||||
try {
|
||||
return global.db.members.editBio(newBio, userId)
|
||||
} catch (e) {
|
||||
return { status: e.status, error: e.message }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,11 +35,11 @@ class EventCard extends Shadow {
|
||||
.justifyContent("space-between")
|
||||
.verticalAlign("center")
|
||||
|
||||
p(this.event.location)
|
||||
p(this.event.location ?? "No location added")
|
||||
.marginTop(0.75, em)
|
||||
p(this.convertDate(this.event.time_start))
|
||||
p(this.convertDate(this.event.time_start) ?? "No time included")
|
||||
.marginTop(0.25, em)
|
||||
p(this.event.description)
|
||||
p(this.event.description ?? "No description included")
|
||||
.marginTop(0.75, em)
|
||||
})
|
||||
.paddingVertical(1.5, em)
|
||||
|
||||
175
src/apps/Events/EventForm.js
Normal file
175
src/apps/Events/EventForm.js
Normal file
@@ -0,0 +1,175 @@
|
||||
import server from "../../_/code/bridge/serverFunctions"
|
||||
|
||||
class EventForm extends Shadow {
|
||||
inputStyles(el) {
|
||||
return el
|
||||
.background("var(--main)")
|
||||
.color("var(--text)")
|
||||
.border("1px solid var(--accent)")
|
||||
.fontSize(0.9, rem)
|
||||
.backgroundColor("var(--darkaccent)")
|
||||
.borderRadius(12, px)
|
||||
.outline("none")
|
||||
.onTouch((start) => {
|
||||
if (start) {
|
||||
this.style.backgroundColor = "var(--accent)"
|
||||
} else {
|
||||
this.style.backgroundColor = "var(--darkaccent)"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
errorMessage = ""
|
||||
|
||||
render() {
|
||||
ZStack(() => {
|
||||
p("X")
|
||||
.color("var(--darkred)")
|
||||
.fontSize(2, em)
|
||||
.position("absolute")
|
||||
.fontFamily("Arial")
|
||||
.marginTop(1, rem)
|
||||
.marginLeft(1, rem)
|
||||
.onTap(() => {
|
||||
this.toggle()
|
||||
})
|
||||
|
||||
form(() => {
|
||||
VStack(() => {
|
||||
h1("Create an Event")
|
||||
.color("var(--text)")
|
||||
.textAlign("center")
|
||||
.fontFamily("Arial")
|
||||
.marginTop(1.5, em)
|
||||
|
||||
input("Title", "70%")
|
||||
.attr({ name: "title", type: "text" })
|
||||
.margin(1, em)
|
||||
.padding(1, em)
|
||||
.styles(this.inputStyles)
|
||||
input("Location", "70%")
|
||||
.attr({ name: "location", type: "text" })
|
||||
.margin(1, em)
|
||||
.padding(1, em)
|
||||
.styles(this.inputStyles)
|
||||
input("Start Time", "70%")
|
||||
.attr({ name: "time_start", type: "datetime-local" })
|
||||
.margin(1, em)
|
||||
.padding(1, em)
|
||||
.styles(this.inputStyles)
|
||||
input("Description", "70%")
|
||||
.attr({ name: "description", type: "text" })
|
||||
.margin(1, em)
|
||||
.padding(1, em)
|
||||
.styles(this.inputStyles)
|
||||
HStack(() => {
|
||||
button("==>")
|
||||
.padding(1, em)
|
||||
.fontSize(0.9, rem)
|
||||
.borderRadius(12, px)
|
||||
.background("var(--searchbackground)")
|
||||
.color("var(--text)")
|
||||
.border("1px solid var(--accent)")
|
||||
.boxSizing("border-box")
|
||||
.onTouch(function (start) {
|
||||
if (start) {
|
||||
this.style.backgroundColor = "var(--accent)"
|
||||
} else {
|
||||
this.style.backgroundColor = "var(--searchbackground)"
|
||||
}
|
||||
})
|
||||
})
|
||||
.width(70, vw)
|
||||
.margin("auto")
|
||||
.fontSize(0.9, rem)
|
||||
.paddingLeft(0, em)
|
||||
.paddingRight(2, em)
|
||||
.marginVertical(1, em)
|
||||
.border("1px solid transparent")
|
||||
|
||||
p("")
|
||||
.dynamicText("errormessage", "{{}}")
|
||||
.margin("auto")
|
||||
.marginTop(1, em)
|
||||
.color("var(--text)")
|
||||
.fontFamily("Arial")
|
||||
.opacity(.7)
|
||||
.padding(0.5, em)
|
||||
.backgroundColor("var(--darkred)")
|
||||
.width(100, pct)
|
||||
.textAlign("center")
|
||||
.boxSizing("border-box")
|
||||
})
|
||||
.horizontalAlign("center")
|
||||
})
|
||||
.onSubmit((e) => {
|
||||
e.preventDefault()
|
||||
const data = {
|
||||
title: e.target.$('[name="title"]').value,
|
||||
location: e.target.$('[name="location"]').value,
|
||||
time_start: e.target.$('[name="time_start"]').value,
|
||||
description: e.target.$('[name="description"]').value,
|
||||
};
|
||||
this.handleSend(data)
|
||||
})
|
||||
})
|
||||
.position("fixed")
|
||||
.height(window.visualViewport.height - 20, px)
|
||||
.width(100, pct)
|
||||
.top(100, vh)
|
||||
.background("var(--main)")
|
||||
.zIndex(4)
|
||||
.borderTopLeftRadius("10px")
|
||||
.borderTopRightRadius("10px")
|
||||
.boxSizing("border-box")
|
||||
.border("1px solid var(--accent)")
|
||||
.transition("top .3s")
|
||||
}
|
||||
|
||||
async handleSend(eventData) {
|
||||
if (!eventData.title) {
|
||||
this.$(".VStack > p")
|
||||
.attr({ errorMessage: 'Events must include a title.' })
|
||||
.display("")
|
||||
|
||||
return;
|
||||
} else {
|
||||
this.$(".VStack > p").style.display = "none"
|
||||
}
|
||||
|
||||
const date = new Date(eventData.time_start);
|
||||
const timestamp = eventData.time_start ? date.toISOString() : null;
|
||||
const newEvent = {
|
||||
title: eventData.title,
|
||||
location: eventData.location ?? null,
|
||||
time_start: timestamp ?? null,
|
||||
description: eventData.description ?? null
|
||||
}
|
||||
|
||||
const { data } = await server.addEvent(newEvent, global.currentNetwork.id, global.profile.id)
|
||||
if (data.status === 200) {
|
||||
console.log("Added new event: ", data)
|
||||
this.toggle()
|
||||
window.dispatchEvent(new CustomEvent('new-event', {
|
||||
detail: { event: data.event }
|
||||
}));
|
||||
} else {
|
||||
console.log("Failed to add new event: ", data)
|
||||
this.$(".VStack > p")
|
||||
.attr({ errorMessage: data.error })
|
||||
.display("")
|
||||
}
|
||||
}
|
||||
|
||||
toggle() {
|
||||
if(this.style.top === "15vh") {
|
||||
this.style.top = "100vh"
|
||||
this.pointerEvents = "none"
|
||||
} else {
|
||||
this.style.top = "15vh"
|
||||
this.pointerEvents = "auto"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
register(EventForm)
|
||||
@@ -1,6 +1,7 @@
|
||||
import "../../components/TopBar.js"
|
||||
import "../../components/LoadingCircle.js"
|
||||
import "./EventCard.js"
|
||||
import "./EventForm.js"
|
||||
import server from "../../_/code/bridge/serverFunctions.js"
|
||||
import "../../components/SearchBar.js"
|
||||
|
||||
@@ -41,8 +42,11 @@ class Events extends Shadow {
|
||||
}
|
||||
|
||||
render() {
|
||||
VStack(() => {
|
||||
ZStack(() => {
|
||||
|
||||
EventForm()
|
||||
|
||||
VStack(() => {
|
||||
SearchBar(this.searchText)
|
||||
|
||||
VStack(() => {
|
||||
@@ -81,6 +85,14 @@ class Events extends Shadow {
|
||||
.height(100, pct)
|
||||
.width(100, pct)
|
||||
.onEvent("eventsearch", this.onEventSearch)
|
||||
.onEvent("new-event", this.onNewEvent)
|
||||
})
|
||||
}
|
||||
|
||||
onNewEvent = (e) => {
|
||||
let newEvent = e.detail.event;
|
||||
this.events.push(newEvent)
|
||||
this.rerender()
|
||||
}
|
||||
|
||||
onEventSearch = (e) => {
|
||||
|
||||
@@ -35,11 +35,11 @@ class JobCard extends Shadow {
|
||||
.justifyContent("space-between")
|
||||
.verticalAlign("center")
|
||||
|
||||
p(this.job.company)
|
||||
p(this.job.company ?? "No company added")
|
||||
.marginTop(0.75, em)
|
||||
p(this.job.location)
|
||||
p(this.job.location ?? "No location added")
|
||||
.marginTop(0.25, em)
|
||||
p(this.salaryLabel(this.job.salary_number, this.job.salary_period))
|
||||
p(this.salary_number ? this.salaryLabel(this.job.salary_number, this.job.salary_period) : "No salary added")
|
||||
.marginTop(0.75, em)
|
||||
})
|
||||
.paddingVertical(1.5, em)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import util from "../../util.js"
|
||||
import server from "../../_/code/bridge/serverFunctions"
|
||||
|
||||
class JobForm extends Shadow {
|
||||
inputStyles(el) {
|
||||
@@ -19,59 +19,185 @@ class JobForm extends Shadow {
|
||||
})
|
||||
}
|
||||
|
||||
errorMessage = ""
|
||||
|
||||
render() {
|
||||
ZStack(() => {
|
||||
p("X")
|
||||
.color("var(--darkred)")
|
||||
.fontSize(2, em)
|
||||
.position("absolute")
|
||||
.fontFamily("Arial")
|
||||
.marginTop(1, rem)
|
||||
.marginLeft(1, rem)
|
||||
.onTap(() => {
|
||||
this.toggle()
|
||||
})
|
||||
|
||||
form(() => {
|
||||
VStack(() => {
|
||||
input("Title", "70vw")
|
||||
h1("Create a Job")
|
||||
.color("var(--text)")
|
||||
.textAlign("center")
|
||||
.fontFamily("Arial")
|
||||
.marginTop(1.5, em)
|
||||
|
||||
input("Title", "70%")
|
||||
.attr({ name: "title", type: "text" })
|
||||
.margin(1, em)
|
||||
.padding(1, em)
|
||||
.styles(this.inputStyles)
|
||||
input("Location", "70vw")
|
||||
input("Location", "70%")
|
||||
.attr({ name: "location", type: "text" })
|
||||
.margin(1, em)
|
||||
.padding(1, em)
|
||||
.styles(this.inputStyles)
|
||||
input("Company", "70vw")
|
||||
input("Company", "70%")
|
||||
.attr({ name: "company", type: "text" })
|
||||
.margin(1, em)
|
||||
.padding(1, em)
|
||||
.styles(this.inputStyles)
|
||||
input("salary", "70vw")
|
||||
.attr({ name: "salary", type: "number" })
|
||||
.margin(1, em)
|
||||
HStack(() => {
|
||||
input("Salary", "30%")
|
||||
.attr({ name: "salary_number", type: "number", min: "0", step: "0.01" })
|
||||
.padding(1, em)
|
||||
.marginHorizontal(1, em)
|
||||
.styles(this.inputStyles)
|
||||
input("Description", "70vw")
|
||||
select(() => {
|
||||
option("One-time")
|
||||
.attr({ value: "one-time"})
|
||||
option("Hourly")
|
||||
.attr({ value: "hour"})
|
||||
option("Monthly")
|
||||
.attr({ value: "month"})
|
||||
option("Yearly")
|
||||
.attr({ value: "year"})
|
||||
})
|
||||
.attr({ name: "salary_period" })
|
||||
.width(40, pct)
|
||||
.padding(1, em)
|
||||
.marginHorizontal(1, em)
|
||||
.styles(this.inputStyles)
|
||||
})
|
||||
.margin(1, em)
|
||||
.boxSizing("border-box")
|
||||
.verticalAlign("center")
|
||||
.horizontalAlign("center")
|
||||
|
||||
input("Description", "70%")
|
||||
.attr({ name: "description", type: "text" })
|
||||
.margin(1, em)
|
||||
.padding(1, em)
|
||||
.styles(this.inputStyles)
|
||||
HStack(() => {
|
||||
button("==>")
|
||||
.margin(1, em)
|
||||
.padding(1, em)
|
||||
.fontSize(0.9, rem)
|
||||
.borderRadius(12, px)
|
||||
.background("var(--accent)")
|
||||
.background("var(--searchbackground)")
|
||||
.color("var(--text)")
|
||||
.border("1px solid var(--accent)")
|
||||
.boxSizing("border-box")
|
||||
.onTouch(function (start) {
|
||||
if (start) {
|
||||
this.style.backgroundColor = "var(--accent)"
|
||||
} else {
|
||||
this.style.backgroundColor = "var(--searchbackground)"
|
||||
}
|
||||
})
|
||||
})
|
||||
.position("absolute")
|
||||
.height(90, pct)
|
||||
.width(95, pct)
|
||||
.top(50, pct).left(50, pct)
|
||||
.center()
|
||||
.width(70, vw)
|
||||
.margin("auto")
|
||||
.fontSize(0.9, rem)
|
||||
.paddingLeft(0, em)
|
||||
.paddingRight(2, em)
|
||||
.marginVertical(1, em)
|
||||
.border("1px solid transparent")
|
||||
|
||||
p("")
|
||||
.dynamicText("errormessage", "{{}}")
|
||||
.margin("auto")
|
||||
.marginTop(1, em)
|
||||
.color("var(--text)")
|
||||
.fontFamily("Arial")
|
||||
.opacity(.7)
|
||||
.padding(0.5, em)
|
||||
.backgroundColor("var(--darkred)")
|
||||
.width(100, pct)
|
||||
.textAlign("center")
|
||||
.boxSizing("border-box")
|
||||
})
|
||||
.horizontalAlign("center")
|
||||
})
|
||||
.onSubmit((e) => {
|
||||
e.preventDefault()
|
||||
const data = {
|
||||
title: e.target.$('[name="title"]').value,
|
||||
location: e.target.$('[name="location"]').value,
|
||||
company: e.target.$('[name="company"]').value,
|
||||
salary_number: e.target.$('[name="salary_number"]').value,
|
||||
salary_period: e.target.$('[name="salary_period"]').value,
|
||||
description: e.target.$('[name="description"]').value,
|
||||
};
|
||||
this.handleSend(data)
|
||||
})
|
||||
})
|
||||
.position("fixed")
|
||||
.height(window.visualViewport.height - 20, px)
|
||||
.width(100, pct)
|
||||
.top(100, vh)
|
||||
.background("var(--main)")
|
||||
.zIndex(100)
|
||||
.zIndex(4)
|
||||
.borderTopLeftRadius("10px")
|
||||
.borderTopRightRadius("10px")
|
||||
.boxSizing("border-box")
|
||||
.transform(`translate(-50%, -45%)`)
|
||||
.border("1px solid var(--accent)")
|
||||
.transition("top .3s")
|
||||
}
|
||||
|
||||
tryThis() {
|
||||
console.log("hello2")
|
||||
async handleSend(jobData) {
|
||||
if (!jobData.title) {
|
||||
this.$(".VStack > p")
|
||||
.attr({ errorMessage: 'Jobs must include a title.' })
|
||||
.display("")
|
||||
|
||||
return;
|
||||
} else {
|
||||
this.$(".VStack > p").style.display = "none"
|
||||
}
|
||||
|
||||
const newJob = {
|
||||
title: jobData.title,
|
||||
location: jobData.location.trim() === '' ? null : jobData.location.trim(),
|
||||
company: jobData.company.trim() === '' ? null : jobData.company.trim(),
|
||||
salary_number: jobData.salary_number.trim() === '' ? null : jobData.salary_number,
|
||||
salary_period: jobData.salary_number.trim() === '' ? null : jobData.salary_period,
|
||||
description: jobData.description.trim() === '' ? null : jobData.description.trim()
|
||||
}
|
||||
|
||||
const { data } = await server.addJob(newJob, global.currentNetwork.id, global.profile.id)
|
||||
if (data.status === 200) {
|
||||
console.log("Added new job: ", data)
|
||||
this.toggle()
|
||||
window.dispatchEvent(new CustomEvent('new-job', {
|
||||
detail: { job: data.job }
|
||||
}));
|
||||
} else {
|
||||
console.log("Failed to add new event: ", data)
|
||||
this.$(".VStack > p")
|
||||
.attr({ errorMessage: data.error })
|
||||
.display("")
|
||||
}
|
||||
}
|
||||
|
||||
toggle() {
|
||||
if(this.style.top === "15vh") {
|
||||
this.style.top = "100vh"
|
||||
this.pointerEvents = "none"
|
||||
} else {
|
||||
this.style.top = "15vh"
|
||||
this.pointerEvents = "auto"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,10 +42,11 @@ class Jobs extends Shadow {
|
||||
}
|
||||
|
||||
render() {
|
||||
ZStack(() => {
|
||||
|
||||
JobForm()
|
||||
|
||||
VStack(() => {
|
||||
|
||||
// JobForm()
|
||||
|
||||
SearchBar(this.searchText)
|
||||
|
||||
VStack(() => {
|
||||
@@ -84,6 +85,14 @@ class Jobs extends Shadow {
|
||||
.height(100, pct)
|
||||
.width(100, pct)
|
||||
.onEvent("jobsearch", this.onJobSearch)
|
||||
.onEvent("new-job", this.onNewJob)
|
||||
})
|
||||
}
|
||||
|
||||
onNewJob = (e) => {
|
||||
let newJob = e.detail.job;
|
||||
this.jobs.push(newJob)
|
||||
this.rerender()
|
||||
}
|
||||
|
||||
onJobSearch = (e) => {
|
||||
|
||||
@@ -16,7 +16,6 @@ class AppWindowContainer extends Shadow {
|
||||
.gap(0)
|
||||
|
||||
Profile()
|
||||
.display("none")
|
||||
.zIndex(3)
|
||||
})
|
||||
.height(100, pct)
|
||||
@@ -26,11 +25,13 @@ class AppWindowContainer extends Shadow {
|
||||
}
|
||||
|
||||
openProfile() {
|
||||
this.$("profile-").display("")
|
||||
this.$("profile-").top(20, px)
|
||||
this.$("profile-").pointerEvents("auto")
|
||||
}
|
||||
|
||||
closeProfile() {
|
||||
this.$("profile-").display("none")
|
||||
this.$("profile-").top(100, vh)
|
||||
this.$("profile-").pointerEvents("none")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,9 @@ class SearchBar extends Shadow {
|
||||
.marginBottom(1, em)
|
||||
.border("1px solid var(--accent)")
|
||||
.borderRadius(15, px)
|
||||
.onTap(() => {
|
||||
this.handleAdd()
|
||||
})
|
||||
})
|
||||
.width(100, pct)
|
||||
.horizontalAlign("center")
|
||||
@@ -64,6 +67,20 @@ class SearchBar extends Shadow {
|
||||
})
|
||||
}
|
||||
|
||||
handleAdd() {
|
||||
const app = global.currentApp()
|
||||
switch (app) {
|
||||
case "Jobs":
|
||||
$("jobform-").toggle()
|
||||
break;
|
||||
case "Events":
|
||||
$("eventform-").toggle()
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dispatchSearchEvent(searchText) {
|
||||
const app = global.currentApp();
|
||||
switch (app) {
|
||||
|
||||
@@ -8,14 +8,15 @@ class Sidebar extends Shadow {
|
||||
.fontWeight("bold")
|
||||
.fontFamily("Arial")
|
||||
.marginLeft(2, em)
|
||||
.onClick(function (done) {
|
||||
.onTap(function (done) {
|
||||
console.log("hello")
|
||||
if(done) {
|
||||
if (this.innerText === "Logout") {
|
||||
global.onLogout()
|
||||
return
|
||||
} else if (this.innerText === "Profile") {
|
||||
$("appwindowcontainer-").openProfile()
|
||||
$("sidebar-").close()
|
||||
$("sidebar-").toggle()
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -41,12 +42,6 @@ class Sidebar extends Shadow {
|
||||
.zIndex(3)
|
||||
}
|
||||
|
||||
close() {
|
||||
if(this.style.right !== "-71vw") {
|
||||
this.style.right = "-71vw"
|
||||
}
|
||||
}
|
||||
|
||||
toggle() {
|
||||
if(this.style.right === "-71vw") {
|
||||
this.style.right = "0vw"
|
||||
|
||||
Reference in New Issue
Block a user