I am trying to replicate data in the database for a specific user. It is almost 1000 rows that should be replicated. But my request gets failed with Empty response after 2 min. you can see in the picture. I am not able to identify the issue. Guide me if anyone has already solved this kind of issue.
My database is hosted on Google Cloud Storage and I am using MySQL.
This is my controller method:
public function createDataForManyToMany (Request $request)
{
$rows = TableA::where('id_analysis',
$request->analysis_id)->get();
$files_data = TableB::Where('id_analysis',
$request->analysis_id)->get();
foreach ($rows as $row)
{
$row->input_files()->saveMany($files_data);
}
return response()->json([
'status' => 'ok',
'analysis_id' => $request->analysis_id
]);
}
My Axios post request:
axios.post('/app/create-det-infiles-data', { '_token': this.csrf_token, 'analysis_id': res.data.analysis_id})
.then( res => {
if (res.data.status === 'ok') {
this.signupTried = false
this.emailSent = true
}
Vue Component:
<template>
<div class="fullwidth-container">
<div class="login-container">
<v-card class="login_form elevation-9">
<form class="login">
<input type="hidden" name="_token" :value="csrf_token"/>
<div class="header">
<h1>PIX2MAP SignUp</h1>
</div>
<v-text-field
color="dark"
prepend-icon="person"
:label="$t('name')"
required
:error-messages="nameErrors"
v-model="name">
</v-text-field>
<v-text-field
color="dark"
prepend-icon="email"
label="Email"
required
:error-messages="emailErrors"
v-model="email">
</v-text-field>
<v-text-field
color="dark"
prepend-icon="vpn_key"
:append_icon="show_pass ? 'visibility_off' : 'visibility'"
:type="show_pass ? 'text' : 'password'"
:error-messages="passwordErrors"
required
:label="$t('password')"
v-model="password"
#click:append="show_pass = !show_pass">
</v-text-field>
<v-text-field
class="pwd-confirm"
color="dark"
:append_icon="show_pass ? 'visibility_off' : 'visibility'"
:type="show_pass2 ? 'text' : 'password'"
:error-messages="repeatPasswordErrors"
required
:label="$t('repeat_password')"
v-model="password2"
#click:append="show_pass = !show_pass">
</v-text-field>
<v-layout row style="margin: 0">
<v-flex shrink style="margin-top: 0">
<v-checkbox
v-model="termsAccepted"
:label="`I accept the `"
:error-messages="acceptAgreementError"
>
</v-checkbox>
</v-flex>
<v-flex grow text-xs-left style="margin-top: 0">
contract
</v-flex>
</v-layout>
<v-btn class="btn-signup" color="accent" #click="doSignup"
:disabled="formFilled"
>Confirm <span class="float-right" :class="{'fas':signupTried, 'fa-spinner':signupTried, 'fa-spin':signupTried}"></span>
</v-btn>
<p class="privacy">{{ $t('form_privacy') }}</p>
</form>
</v-card>
<div class="back">
<v-btn flat #click="$emit('back')"><v-icon>arrow_back</v-icon>{{ $t('back_button') }}</v-btn>
</div>
</div>
<v-dialog
v-model="dialog"
max-width="650"
>
<v-card>
<v-card-title class="headline grey lighten-2">{{ contractTitile }}
<span style="margin-left: auto"><v-icon right #click="dialog = !dialog">fas fa-times</v-icon></span>
</v-card-title>
<v-card-text >
{{ contractBody }}
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
</v-card-actions>
</v-card>
</v-dialog>
<v-snackbar
v-if="signupTried"
v-model="snackbar"
dark
:bottom="y === 'bottom'"
:timeout="timeout"
:left="x === 'left'"
:color="color"
:multi-line="mode === 'multi-line'"
:right="x === 'right'"
:top="y === 'top'"
:vertical="mode === 'vertical'"
>
{{ text }}
</v-snackbar>
<v-snackbar
v-if="emailSent"
v-model="emailSnackbar"
:bottom="y === 'bottom'"
:timeout="timeout"
:left="x === 'left'"
:multi-line="mode === 'multi-line'"
:right="x === 'right'"
:top="y === 'top'"
:color="color"
:vertical="mode === 'vertical'"
>
{{ emailSentMessage }}
</v-snackbar>
</div>
</template>
<script>
import { required, email } from 'vuelidate/lib/validators'
import {TITLE, BODY_TEXT} from '../contract'
import axios from 'axios'
export default {
props: {
csrf_token: String,
name: String,
email: String,
},
data: () => ({
contractTitile: TITLE,
contractBody: BODY_TEXT,
show_pass: false,
show_pass2: false,
name: '',
email: '',
password: '',
password2: '',
signupTried: false,
emailExist: false,
dialog: false,
termsAccepted: false,
snackbar: true,
y: 'bottom',
x: null,
mode: '',
timeout: 10000,
color: '#fff',
text: 'This process will take some time, please! be patience.',
emailSent: false,
emailSnackbar: true,
emailSentMessage: 'We have sent you instructions, please verify your account by visiting your email.'
}),
validations: {
name: {
required
},
email: {
required,
email
},
password: {
required
},
password2: {
required
},
termsAccepted: {
required
}
},
methods: {
async doSignup () {
this.signupTried = true
if (this.$v.name.required && this.$v.email.required && this.$v.email.email && this.$v.password.required && this.termsAccepted ) {
axios.post('/app/register', { '_token': this.csrf_token, 'name': this.name,'email': this.email, 'password': this.password })
.then(res => {
if (res.data.status === 'ok') {
axios.post('/app/create-in-files-data', { '_token': this.csrf_token, 'analysisIds': res.data.analysis_ids})
.then(resp => {
if (resp.data.status === 'ok') {
axios.post('/app/create-mo-files-data', { '_token': this.csrf_token, 'analysisIds': resp.data.analysis_ids})
.then( res => {
if (res.data.status === 'ok') {
// window.location = '/'
// this.signupTried = false
// this.emailSent = true
axios.post('/app/create-det-infiles-data', { '_token': this.csrf_token, 'analysis_id': res.data.analysis_id})
.then( res => {
if (res.data.status === 'ok') {
this.signupTried = false
this.emailSent = true
// await axios.post('/app/create-det-mofiles-data', { '_token': this.csrf_token, 'analysis_id': res.data.analysis_id, demo: true})
// .then(async res => {
// if (res.data.status === 'ok') {
// this.showSpinner = false
// window.location = '/'
// } else {
// console.log('Files not created')
// this.showSpinner = false
// this.$emit('forgotPw', {email: this.email});
// }
// })
// .catch(e => {
// console.log('2nd error', e)
// })
} else {
console.log('Files not created')
this.showSpinner = false
this.$emit('forgotPw', {email: this.email});
}
})
.catch(e => {
console.log('2nd error', e)
})
} else {
console.log('Signup Failed')
this.signupTried = false
this.$emit('forgotPw', {email: this.email});
}
})
.catch(e => {
console.log('2nd error', e)
})
} else {
console.log('Files not created')
this.showSpinner = false
this.$emit('forgotPw', {email: this.email});
}
})
.catch(e => {
console.log('Error: ', e)
})
} else {
this.showSpinner = false
this.$emit('forgotPw', {email: this.email});
}
})
}
},
changeState () {
this.dialog = !this.dialog
}
},
computed: {
nameErrors () {
if (this.signupTried && this.name.length == 0 && !this.$v.name.required) return [this.$t('name_required')]
},
emailErrors () {
const errors = []
if (this.signupTried && !this.$v.email.required) errors.push(this.$t('email_required'))
if (!this.$v.email.email) errors.push(this.$t('not_valid_email'))
if (this.emailExist) errors.push(this.$t('existing_email'))
return errors
},
passwordErrors () {
if (this.signupTried && !this.$v.password.required) return [this.$t('password_required')]
},
repeatPasswordErrors () {
if (this.signupTried && !this.$v.password2.required) return [this.$t('password_required')]
if (this.password2 != this.password) return [this.$t('passwords_not_equal')]
},
acceptAgreementError () {
if (this.signupTried && !this.$v.termsAccepted.required) return [this.$t('agreement_required')]
},
formFilled () {
if (!this.$v.name.required || !this.$v.email.required || !this.$v.password.required || !this.$v.password2.required || !this.termsAccepted || (this.password != this.password2))
{
return true
} else {
return false
}
}
}
}
</script>
<style scoped lang="stylus">
#import '../../stylus/theme.styl'
.fullwidth-container {
width: 100%;
height: 100%;
background: $bg-login;
position: absolute;
top: 0px;
left: 0px;
z-index: 2;
.login-container {
position: relative;
width: 500px;
top: 25px;
margin: auto;
.login_form {
padding: 16pt;
text-align: center;
height: 800px;
* {
margin-top: 24pt;
}
.header {
margin: 24pt;
}
.i-am {
display: flex;
flex-direction: row;
align-items: center;
* {
margin-top: 0pt;
}
span {
width: 8%;
}
.i-am-text {
margin-right: 20px;
}
.i-am-select {
margin-left: 5%;
}
}
.btn-signup {
margin-left: auto;
margin-right: auto;
width: 80%;
}
.privacy {
margin-top: 8pt;
font-style: italic;
}
.pwd-confirm {
margin-left: 7%;
}
.checkboxAccept {
display: inline;
}
.float-right {
margin-top: -3px;
margin-left: 5px;
color: #fff;
font-size: 18px;
}
}
}
}
</style>
This issue might be caused due to MySQL instance is reaching the time out limit for your query.
In Cloud SQL you can try to modify the flag “wait_timeout” and set a bigger value, for example 50000.
Check this link as a reference to set a flag in Cloud SQL instance.
Related
I am implementing a customized dropdown becuase of the requirements we have, using Vue 2 and typescript (jquery is not an option).
It is working fine, when you click on the main box, it opens the options list downwards.
An improvement I am looking for is that, when at the end of the screen, the options list adds to the page height and thus causing the scrollbar to appear or increase scroll height.
What I am looking for is that, when popping up the div, if there's not enough space at the bottom of the screen, open it upwards instead. How do I achieve this?
(classes are using bootstrat 5)
Opened dropdown &
Closed dropdown
My code:
import Vue, {
PropType
} from 'vue';
import {
Validation
} from 'vuelidate';
let uidc = 0;
export default Vue.extend({
name: 'BaseDropdown',
props: {
value: {
type: [Number, String, Object],
default: () => ''
as string,
},
target: {
type: String,
default: '',
},
label: {
type: String,
default: '',
},
valueIsNumber: {
type: Boolean,
default: false,
},
options: {
type: Array,
default: null,
},
placeholder: {
type: String,
default: '',
},
required: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
validations: {
type: Object as PropType < Validation > ,
default: () => ({
$error: false,
$touch: () => undefined,
$params: {},
}) as Validation,
},
error: {
type: Boolean,
default: false,
},
trackEvent: {
type: String,
default: '',
},
trackField: {
type: String,
default: '',
},
trackPublic: {
type: Boolean,
default: false,
},
padLeft: {
type: Boolean,
default: false,
},
enforceBlackColour: {
type: Boolean,
default: false,
},
customStyled: {
type: Boolean,
default: false,
},
borderBottomWarning: {
type: Boolean,
default: false,
},
},
data(): {
selectedItem: any | null;
menuOpen: boolean;
searchText: string | null;
} {
return {
selectedItem: null,
menuOpen: false,
searchText: null,
};
},
mounted() {
const appElement = document.getElementById('app_home');
(appElement as any).addEventListener('click', this.handleDropdownClickOutside);
this.$nextTick(() => {
if (this.value) {
if (this.valueIsNumber) {
this.selectedItem = this.options.find((x: any) => x.value === Number(this.value)) || null;
} else {
this.selectedItem = this.options.find((x: any) => x.value.toString().toLowerCase() === this.value.toString().toLowerCase()) || null;
}
}
});
},
computed: {
v(): Validation | {} {
return this.validations;
},
errorMessage(): string {
// Validation must be cast to any to access validators
return Object.entries((this.v as Validation).$params).find(([k]) => !(this.v as any)[k]) ? .[1].message;
},
optgroups(): any {
return this.options.reduce((acc: any, o: any) => ({ ...acc,
[o.optgroup]: [...(acc[o.optgroup] || []), o]
}), {});
},
isRequired(): boolean {
return this.required !== false;
},
getSelectedItemText(): string | null {
return this.selectedItem ? this.selectedItem.text : this.placeholder || 'Please select an item';
},
filteredItems(): any[] {
const list: any[] = [];
for (let c = 0; c < 10; c += 1) {
list.push({
text: c,
value: c
});
}
// return this.searchText && this.searchText.length > 0 ? this.options.filter((x: any) => x.text.toLowerCase().indexOf(this.searchText!.toLowerCase()) > -1) : this.options;
return list;
},
},
methods: {
openMenu() {
this.menuOpen = !this.menuOpen;
if (this.menuOpen) {
this.searchText = null;
}
},
selectItem(item: any) {
this.selectedItem = item;
this.$emit('input', item.value);
this.menuOpen = false;
},
setSuppliedSelectedItem() {
this.$nextTick(() => {
if (this.value) {
this.selectedItem = this.options.find((x: any) => x.value === this.value) || null;
}
});
},
handleDropdownClickOutside(event: any): void {
const parent = document.getElementById(`select-${(this as any).uid}`);
const isParent = parent !== event.target && parent ? .contains(event.target);
if (!isParent) {
this.menuOpen = false;
// this.closeOpenendMenu();
// this.searchText = '';
}
},
},
beforeCreate() {
// eslint-disable-next-line no-plusplus
(this as any).uid = uidc++;
},
});
.dropdown {
font-size: 0.7rem;
img {
// float: right;
// padding-right: 10px;
// padding-top: 5px;
position: absolute;
top: 40%;
right: 10px;
}
.fade {
opacity: 0.5;
}
.search-box {
.form-control {
font-size: 12px !important;
height: 30px !important;
margin: 0 10px 5px 10px !important;
width: 95% !important;
}
}
.selected-item {
border-radius: 3px;
border: 1px solid #ced4da;
padding: 10px;
.selected-item-text {
text-overflow: ellipsis;
overflow: hidden;
width: 93%;
/* height: 1.2em; */
white-space: nowrap;
}
}
.items {
border: 1px solid rgb(236, 236, 236);
width: 100%;
z-index: 15;
max-height: 300px;
overflow-y: auto;
overflow-x: hidden;
background-color: white;
}
.item {
padding: 10px;
background-color: rgb(240, 240, 240);
cursor: pointer;
&:hover {
background-color: rgb(216, 216, 216);
}
}
}
.hidden {
opacity: 0.2;
}
.disabled {
background-color: #e9ecef;
opacity: 1;
pointer-events: none;
}
<template>
<div class="mt-2" :id="`select-${uid}`">
<label v-show="label" class="mb-2 label-grey" :class="{ 'required': isRequired }" :for="`select-${uid}`">{{ label }}</label>
<div class="dropdown noselect position-relative" :class="{'disabled': disabled}">
<div class="selected-item cursor-pointer" #click="openMenu">
<div class="selected-item-text" :class="{'fade': !selectedItem}">{{getSelectedItemText}}</div>
<img v-if="menuOpen" :src="constants.icons.arrowTop" />
<img v-else :src="constants.icons.arrowDown" />
</div>
<div class="items position-absolute" v-show="menuOpen">
<div v-if="filteredItems && filteredItems.length > 5 || searchText" class="search-box">
<input :size="'sm'" v-model="searchText" />
</div>
<div v-for="item in filteredItems" :key="item.value" #click="selectItem(item)">
<div class="item">
{{item.text}}
</div>
</div>
</div>
</div>
<span v-if="v.$error" class="text-error text-xs font-light">{{ errorMessage }}</span>
</div>
</template>
Suggest to use Floating-ui (well known as Poper)
Floating UI is a low-level library for positioning "floating" elements...intelligently keeping them in view
It's been using widely and cover a lot of edge cases you might encounter when try to create dropdown yourself
You can try with references here
creating-vue-component-dropdown-with-popper-js
floating-vue/dropdown
I'm pretty new to express and socket.io and I'm trying to achieve a little website:
What is it supposed to do:
You can connect to the website and enter a username
You have to select a column where you want to write (it's stored in var column)
Once on the page with the four column, you can see your username at the top of your column and start doing things there.
The other users see you in the correct column.
What it is not doing:
Actually the three points above are working quite well, my issue is with the last point :
The other users see you in the correct column.
My code is somehow not displaying every user in the correct column, in fact, it's displaying them in the same column as you are
Here is the code
$(document).ready(function () {
var socket = io();
var username = prompt("premier utilisateur : ", "nom");
var column = prompt("colonne ", "1,2,3 ou 4");
var gdhb = "";
socket.emit("new user entered his name");
socket.emit("nomUser", username);
if (column === "1") { column = ".one"; gdhb = ".dir1" }
if (column === "2") { column = ".two"; gdhb = ".dir2" }
if (column === "3") { column = ".three"; gdhb = ".dir3" }
if (column === "4") { column = ".four"; gdhb = ".dir4" }
socket.emit("user chose a column");
socket.emit("columnUser", column);
$(column).append($("<p class='username'>" + username + "</p>"))
$(document.body).click(function (b) {
var verbes = [
"appuie",
"bouscule",
"pousse"
];
var adverbes = [
"puis",
"ensuite",
"pour finir",
"alors"
];
var verbe = verbes[Math.floor(Math.random() * verbes.length)];
var adverbe = adverbes[Math.floor(Math.random() * adverbes.length)];
var verbadv = verbe + " " + adverbe;
console.log(verbadv);
socket.emit("verbadverbe");
socket.emit("verbadv", verbadv);
var div = $("<div />", {
"class": "document"
})
.css({
"left": b.pageX + 'px',
"top": b.pageY + 'px'
})
.append($("<p>" + verbadv + "</p>"))
.appendTo(column);
});
$(document.body).contextmenu(function (rc) {
var div = $("<div />", {
"class": "document"
})
.css({
"left": rc.pageX + 'px',
"top": rc.pageY + 'px'
})
.append($("<p>recule</p>"))
.appendTo(column);
});
var direction = "";
var oldx = 0;
var oldy = 0;
mousemovemethod = function (e) {
if (e.pageX > oldx && e.pageY == oldy) {
direction = "gauche";
}
else if (e.pageX == oldx && e.pageY > oldy) {
direction = "bas";
}
else if (e.pageX == oldx && e.pageY < oldy) {
direction = "haut";
}
else if (e.pageX < oldx && e.pageY == oldy) {
direction = "droite";
}
$(gdhb).append($("<p class='direction' id='direction'>" + direction + "</p>"))
$(".direction").prev().remove();
oldx = e.pageX;
oldy = e.pageY;
}
document.addEventListener('mousemove', mousemovemethod);
socket.on("columnUser", function (column) {
socket.on("nomUser", function (username) {
$(column).append($("<p class='username'>" + username + "</p>"));
socket.on("verbadv", function (verbadv) {
var div = $("<div />", {
"class": "document"
})
.append($("<p>" + verbadv + "</p>"))
.appendTo(column);
});
});
});
});
and the index.js :
const path = require('path');
const http = require('http');
const express = require('express');
const socketio = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = socketio(server);
app.use(express.static(path.join(__dirname, 'public')));
io.on('connection', (socket) => {
console.log('Nouvel utilisateur')
socket.on("nomUser", (username) => {
console.log(username);
io.emit("nomUser", username);
});
socket.on("verbadv", (verbadv) => {
console.log(verbadv);
io.emit("verbadv", verbadv);
});
socket.on("columnUser", (column) => {
console.log(column);
io.emit("columnUser", column);
});
});
server.listen(3000, () => {
console.log('listen on 3000');
})
Also if it's needed to understand better, here is the css
body {
font-family: sans-serif;
font-size: 1.3rem;
margin: 0;
background-color: DarkSlateGray;
}
.wrapper {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 0px;
grid-auto-rows: minmax(100vh, auto);
height: 100vh;
}
.one,
.two,
.three,
.four {
-ms-overflow-style: none; /* Internet Explorer 10+ */
scrollbar-width: none; /* Firefox */
position: relative;
overflow: scroll;
height: 100%;
background-color: tan;
}
.one {
grid-column: 1 / 2;
}
.two {
grid-column: 2 / 3;
}
.three {
grid-column: 3 / 4;
}
.four {
grid-column: 4 / 4;
}
.one::-webkit-scrollbar,
.two::-webkit-scrollbar,
.three::-webkit-scrollbar,
.four::-webkit-scrollbar {
display: none; /* Safari and Chrome */
}
.note {
text-align: center;
width: 100px;
height: 30px;
}
.note p{
filter: drop-shadow(0 0 0.75rem black);
}
.document{
text-align: center;
}
.document p{
padding: 0;
margin: 0;
}
.username{
text-align: center;
padding: 0;
margin: 0;
}
.direction{
position: fixed;
bottom : 0;
width: 25vw;
text-align: center;
}
Thanks a lot for the precious help.
i've solved your problem with sockets. See at my solution.
client.js
function columnIndexIsValid(index, columnsQuantity) {
return index >= 0 && index <= columnsQuantity;
}
function fullNameIsValid(fullName) {
return typeof fullName === 'string' && fullName.length > 2;
}
function reloadPage() {
window.location.reload();
}
function rand(min, max) {
return Math.floor(min + Math.random() * (max - 1 - min));
}
function getRandomColour(colours = []) {
const colour = colours[rand(0, colours.length)];
return `#${colour}`;
}
function getUserHtml(user) {
return `<div class="column__users-list__item" data-item-id="${user.id}">${user.fullName}</div>`;
}
function getDrawnUsersNodes() {
return $('.column__item');
}
function canIRenderUsers(usersQuantity) {
const $renderedUsersQuantity = getDrawnUsersNodes().length;
return $renderedUsersQuantity < usersQuantity;
}
function renderUserHtmlToNode($node, html) {
$node.html($node.html() + html);
}
function getColumnUsersList(columnNode) {
const $column = $(columnNode);
return $column.find('.column__users-list');
}
function removeDrawnUserById(userId) {
$(`[data-item-id=${userId}]`).remove();
}
class DrawnUsers {
constructor() {
this.users = new Map();
}
getUserById(id) {
return this.users.get(id);
}
add(id, state) {
this.users.set(id, state);
}
removeById(id) {
this.users.delete(id);
}
exists(id) {
return this.users.has(id);
}
}
class Storage {
static setItem(key, value) {
localStorage.setItem(key, value);
}
static getItem(key) {
return localStorage.getItem(key) || null;
}
}
function generateUserId() {
return `user-${rand(rand(0, 10000), rand(20000, 50000))}`;
}
class UserState {
constructor() {
this.state = {};
}
get() {
return {
data: this.state,
};
}
set fullName(fullName) {
this.state.fullName = fullName;
}
get fullName() {
return this.state.fullName;
}
set id(id) {
this.state.id = id;
}
get id() {
return this.state.id;
}
set columnIndex(columnIndex) {
this.state.columnIndex = columnIndex - 1;
}
get columnIndex() {
return this.state.columnIndex;
}
}
$(document).ready(function () {
const drawnUsers = new DrawnUsers();
const colours = ['F2994A', 'F2C94C', '6FCF97', '2F80ED', '56CCF2', 'DFA2F5'];
const $columns = $('.column');
const $container = $('.container');
const userState = new UserState();
$columns.each(function () {
const $self = $(this);
$self.css({ 'background-color': getRandomColour(colours) });
});
userState.fullName = prompt('Type your fullName');
userState.columnIndex = +prompt('Type your column number');
if (
!fullNameIsValid(userState.fullName) ||
!columnIndexIsValid(userState.columnIndex, $columns.length)
) {
return reloadPage();
}
$container.addClass('active');
const socket = io('ws://localhost:3000');
socket.on('connect', () => {
const generatedUserId = generateUserId();
userState.id = Storage.getItem('userId') || generatedUserId;
Storage.setItem('userId', userState.id);
socket.emit('connected', userState.get());
socket.emit('addUser', userState.get());
socket.on('updateCurrentUsers', ({ data }) => {
const { users } = data;
if (!users || !canIRenderUsers(users.length)) {
return;
}
users.forEach((user) => {
const $column = $columns[user.columnIndex];
if ($column) {
if (!drawnUsers.exists(user.id)) {
drawnUsers.add(user.id);
renderUserHtmlToNode(
getColumnUsersList($column),
getUserHtml(user)
);
}
}
});
});
socket.on('newUser', ({ data }) => {
console.log('[debug] newUser: ', data);
const $column = $columns[data.columnIndex];
if (!$column) {
return;
}
if (drawnUsers.exists(data.id)) {
drawnUsers.removeById(data.id);
removeDrawnUserById(data.id);
} else {
drawnUsers.add(data.id);
renderUserHtmlToNode(getColumnUsersList($column), getUserHtml(data));
}
});
socket.on('disconnect', () => {
socket.open();
});
});
});
server.js
const path = require('path');
const http = require('http');
const express = require('express');
const socketIo = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = socketIo(server);
app.use(express.static(path.join(__dirname, 'public')));
io.on('connection', (socket) => {
console.log('[debug] Client was connected successfully to server');
socket.on('connected', (user) => {
console.log('connected user data', user.data);
socket.data = user.data;
const users = Array.from(io.sockets.sockets.values()).map(
({ data }) => data
);
console.log('users', users);
socket.emit('updateCurrentUsers', {
data: {
users,
},
});
});
socket.on('addUser', ({ data }) => {
socket.data.columnIndex = data.columnIndex;
socket.broadcast.emit('newUser', {
data,
});
const users = Array.from(io.sockets.sockets.values()).map(
({ data }) => data
);
socket.broadcast.emit('updateCurrentUsers', {
data: {
users,
},
});
});
});
server.listen(3000, () => {
console.log('listen on 3000');
});
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="./index.css">
<title>Users Columns</title>
</head>
<body>
<div class="container">
<div class="column">
<span class="column__index">#1</span>
<div class="column__users-list"></div>
</div>
<div class="column">
<span class="column__index">#2</span>
<div class="column__users-list"></div>
</div>
<div class="column">
<span class="column__index">#3</span>
<div class="column__users-list"></div>
</div>
<div class="column">
<span class="column__index">#4</span>
<div class="column__users-list"></div>
</div>
</div>
<script
src="https://code.jquery.com/jquery-3.5.1.js"
integrity="sha256-QWo7LDvxbWT2tbbQ97B53yJnYU3WhH/C8ycbRAkjPDc="
crossorigin="anonymous"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.0.1/socket.io.js"
integrity="sha512-vGcPDqyonHb0c11UofnOKdSAt5zYRpKI4ow+v6hat4i96b7nHSn8PQyk0sT5L9RECyksp+SztCPP6bqeeGaRKg=="
crossorigin="anonymous"></script>
<script src="./client.js"></script>
</body>
</html>
index.css
html, body {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
.container {
display: none;
}
.container.active {
display: flex;
flex-direction: row;
height: 100vh;
}
.container .column {
width: calc(100% / 4);
text-align: center;
padding: 20px 0;
}
.container .column .column__users-list {
display: flex;
flex-direction: column;
}
.container .column .column__index {
color: #FFF;
font-size: 36px;
letter-spacing: 8px;
}
hey) Let's look on that case more attentive, you have created a variable what contains a column index. right? This variable does store inside var variable. That is global variable for your script. I think you need to store each user column info inside each socket. If you don't know, each socket contains some info inside itself like id and other what can be used in your project if you'd like. But for this case you need to do so:
Ask new user what column he would like to choose.
Write to his socket data/info his column index.
When you do broadcast (It's when you send an object of data to each socket in room all each socket at all) just take this column index and draw this user in correct position. But make sure what your var column has a correct value. I can advice you to use const/let in javascript :)
I want to make a grey-colored background for my list in ReactJS.
The problem is that the background seems to have a fixed height which seems to be smaller than the inside's height...
I never met this problem before, and the only way I found to have the background size bigger than the element's is to put height: XXXpx, which is not suitable...
Can someone explain me what I did wrong ?
Here is my class file :
import React, { Component } from "react";
import { ListGroupItem } from "react-bootstrap";
import { LinkContainer } from "react-router-bootstrap";
import { API } from "aws-amplify";
import "./ProjectList.css";
export default class ProjectList extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
projects: [],
reducedView: true,
projectsNumber: 1000,
numberOfElementsToDisplay: 1000
};
}
async componentDidMount() {
try {
const projects = await this.projects();
console.log(projects)
this.setState({ projects });
} catch (e) {
alert(e);
}
this.setState({ isLoading: false });
}
projects() {
return API.get("economics-endpoint", "/list");
}
handleClick(e) {
this.setState({ reducedView: false });
e.preventDefault();
}
renderNotesList(projects) {
return [{}].concat(projects).map(
(note, i) => {
if(i !== 0){
if((this.state.reducedView && i <= this.state.numberOfElementsToDisplay) || !this.state.reducedView){
return(
<div className="element">
<ListGroupItem className="mainContainer">
<div>
ProjectName#{i}
</div>
</ListGroupItem>
</div>
);
}
}
else{
if(this.state.projectsNumber === 0){
return(
<div className="element">
<LinkContainer to={`/econx/new`}>
<ListGroupItem className="new">
<div>
+ Create New Project
</div>
</ListGroupItem>
</LinkContainer>
<div className="errorMessage">
You don't have any existing project yet.
</div>
</div>
);
}
else{
return(
<div className="element">
<LinkContainer to={`/econx/new`}>
<ListGroupItem className="new">
<div>
+ Create New Project
</div>
</ListGroupItem>
</LinkContainer>
</div>
);
}
}
}
);
}
render() {
return (
<div className="ProjectList">
<div className="projects">
{!this.state.isLoading &&
this.renderNotesList(this.state.projects)}
</div>
</div>
);
}
}
And my css file :
.ProjectList .notes h4 {
font-family: "Open Sans", sans-serif;
font-weight: 600;
overflow: hidden;
line-height: 1.5;
white-space: nowrap;
text-overflow: ellipsis;
}
.projects{
background-color: #E0E0E0;
border-radius: 10px;
padding: 1%;
}
.list-group-item{
margin-bottom: 3px;
padding: 0px;
line-height: 200px;
text-align: center;
vertical-align: middle;
border-radius: 6px;
padding-bottom: 50px;
}
.list-group-item:first-child{
padding: 0px;
border-radius: 6px;
}
.list-group-item:hover{
vertical-align: baseline;
}
.element{
width: 20%;
float: left;
padding: 0% 1% 1% 1%;
}
This doesn't answer your CSS question. I did have some thoughts on the JavaScript, though. I'll offer my refactor for your consideration. Hopefully I interpreted your original logic correctly:
import React, { Component } from "react";
import { ListGroupItem } from "react-bootstrap";
import { LinkContainer } from "react-router-bootstrap";
import { API } from "aws-amplify";
import "./ProjectList.css";
export default class ProjectList extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
projects: [],
reducedView: true,
projectsNumber: 1000,
numberOfElementsToDisplay: 1000
};
}
async componentDidMount() {
try {
const projects = await this.fetchProjects();
console.log(projects);
this.setState({ projects });
} catch (e) {
alert(e);
} finally {
this.setState({ isLoading: false });
}
}
fetchProjects = () => {
return API.get("economics-endpoint", "/list");
};
handleClick = e => {
e.preventDefault();
this.setState({ reducedView: false });
};
render() {
const {
isLoading,
numberOfElementsToDisplay,
projectsNumber,
reducedView
} = this.state;
const projects = reducedView
? this.state.projects.slice(0, numberOfElementsToDisplay)
: this.state.projects;
return (
<div className="ProjectList">
<div className="projects">
{!isLoading && (
<React.Fragment>
<div className="element">
<LinkContainer to={`/econx/new`}>
<ListGroupItem className="new">
<div>+ Create New Project</div>
</ListGroupItem>
</LinkContainer>
{projectsNumber === 0 && (
<div className="errorMessage">
You don't have any existing projects yet.
</div>
)}
</div>
{projects.map((project, index) => (
<div className="element">
<ListGroupItem className="mainContainer">
<div>ProjectName#{index}</div>
</ListGroupItem>
</div>;
))}
</React.Fragment>
)}
</div>
</div>
);
}
}
This fixes your off-by-one issue by hopefully capturing your intentions as I understand them:
If it's not loading...
Show a "Create New Project" item
If they don't have any projects yet, show them a message
If they do have projects, create a new element for each one
It's worth considering whether your projectsNumber could be replaced with projects.length. If so, you can probably combine (2) and (3) above to be an either-or scenario: If they don't have projects, show a message, if they do, show the projects.
I created a button that adds emails inputs in a form (for invites), but at a certain number of inputs, they overlaps others elements on the page.
Here's some css :
.App {
height: 100%;
}
html,
body,
#root {
margin: 0;
height: 100%;
font-family: "Montserrat";
}
.page {
height: 80%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.content {
margin-top: 50px;
width: 400px;
}
(It's a JSX app)
Here's a screenshot of the page: https://paste.pics/ab860613d5fd6eb82dfe59894ee24bc0
Thanks for your help !
You can add a 'container' div around your input's.. From there you can use height and width props along with the overflow: 'scroll' attribute.
Something like this:
const { useState } = React;
// Create items
const NEW_INVITES = [...Array(10).keys()].map((item, index) => {
return {
id: index + 1,
value: index + 1
}
});
function InviteInput(props) {
const { value, onChange } = props;
const handleChange = e => {
onChange && onChange(e);
};
return (
<li>
<input
value={value}
onChange={handleChange}
className="form-input"
type="email"
placeholder="nom#exemple.com"
name="invites"
required
/>
</li>
);
}
function AddInviteButton(props) {
return (
<button onClick={props.onClick}>
Ajouter une autre personne // (Add another person)
</button>
);
}
function InviteForm({ onSubmit, height, width, addBorder }) {
const [nbInvites, setNbInvites] = useState(NEW_INVITES);
let containerRef;
const onAddInviteClick = () => {
let id = nbInvites.length + 1;
setNbInvites([
...nbInvites,
{
id,
value: id
}
]);
setTimeout((cr) => {
cr.scrollIntoView({ behavior: "smooth" });
}, 100, containerRef);
};
const handleChange = (event, index) => {
let newstate = [...nbInvites];
newstate[index].value = event.target.value;
setNbInvites(newstate);
};
const handleSubmit = event => {
onSubmit(event, nbInvites);
};
const invitesContainer = {
height,
width,
overflow: 'scroll',
border: addBorder === true ? '1px solid black' : '',
}
return (
<div>
<div style={invitesContainer}>
{nbInvites.map((item, index) => {
return (
<InviteInput
key={index}
value={item.value}
onChange={e => handleChange(e, index)}
/>
);
})}
<div ref={r => containerRef = r}/>
</div>
<AddInviteButton onClick={onAddInviteClick} />
<br />
<button onClick={handleSubmit}>Submit</button>
</div>
);
}
function App() {
const [formVals, setFormVals] = useState();
const doSubmit = (event, formValues) => {
setFormVals(formValues);
};
return (
<div className="page">
<h2 className="box-title">
Qui sont les eleves de cette classe ? // (Who are the students in this
class?)
</h2>
<p>
Vous pourrez toujours en ajouter par la suite // (You can always add
some later)
</p>
<InviteForm onSubmit={doSubmit} height="100px" width="200px" addBorder />
{formVals ? <pre>{JSON.stringify(formVals, null, 2)}</pre> : ""}
</div>
);
}
ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
I am create custom Gutenberg editor block to set YouTube video Id to <button> atribute data-video=""
UPDATE - Added working code to the below
My function.php code
// Include Css and js block files
function designa_youtube_block() {
// Scripts
wp_register_script(
'youtube-block-script', // Handle.
'/wp-content/themes/twentyfifteen/init/block.js',
array( 'wp-blocks', 'wp-element', 'wp-i18n' ),
time()
);
// Styles
wp_register_style(
'youtube-block-editor-style', // Handle.
'/wp-content/themes/twentyfifteen/init/editor.css',
array( 'wp-edit-blocks' ),
time()
);
// Register the block with WP using our namespacing
register_block_type( 'designa/youtube-block', array(
'editor_script' => 'youtube-block-script',
'editor_style' => 'youtube-block-editor-style',
) );
}
add_action( 'init', 'designa_youtube_block' );
My block.js code
( function( editor, components, element ) {
const el = element.createElement;
const registerBlockType = wp.blocks.registerBlockType;
const RichText = wp.editor.RichText;
const BlockControls = wp.editor.BlockControls;
const InspectorControls = wp.editor.InspectorControls;
const AlignmentToolbar = wp.editor.AlignmentToolbar;
const UrlInput = wp.editor.URLInput;
function getVideoId(url) {
if (url) {
let match = url.match(/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/);
if (match && match[2].length == 11)
return match[2];
else
return false;
} else {
return false;
}
}
registerBlockType( 'designa/youtube-block', {
title: 'YouTube',
description: 'Видео в всплывающем окне.',
icon: 'video-alt3',
category: 'common',
attributes: {
content: {
type: 'string',
},
url: {
type: 'string',
},
alignment: {
type: 'string',
},
},
edit: function( props, isSelected ) {
let attributes = props.attributes,
url = props.attributes.url,
alignment = props.attributes.alignment;
function onChangeAlignment( newAlignment ) {
props.setAttributes( { alignment: newAlignment } );
}
if (url)
videoTitle();
async function videoTitle(){
let id = getVideoId(url);
const response = await fetch( `https://www.googleapis.com/youtube/v3/videos?id=${ id }&key=AIzaSyBUqaVKkqdXzPQnfuuP8VPa-yqOQlJwV-w&fields=items(snippet(title))&part=snippet`, {
cache: 'no-cache',
headers: {
'user-agent': 'WP Block',
'content-type': 'application/json'
},
method: 'GET',
redirect: 'follow',
referrer: 'no-referrer',
})
.then(
returned => {
if (returned.ok) return returned;
throw new Error('Network response was not ok.');
}
);
let data = await response.json();
props.setAttributes( { title: data.items[0].snippet.title} );
};
return [
el( BlockControls, { key: 'controls' },
el( AlignmentToolbar, {
value: alignment,
onChange: onChangeAlignment,
} )
),
el( InspectorControls, { key: 'inspector' },
el( components.PanelBody, {
title: 'Превью видео',
className: 'youtube-preview-block',
initialOpen: true,
},
el( 'div', {
className: 'youtube-preview',
},
el( 'iframe', {
frameborder: '0',
allowfullscreen: 'allowfullscreen',
src: url ? 'https://www.youtube.com/embed/'+getVideoId(url)+'?rel=0&showinfo=0' : ''
},
),
),
el( 'div', { className: !props.attributes.title ? 'sceleton-youtube-title' : '' },
props.attributes.title ? props.attributes.title : ''
),
),
),
el( 'div', { className: alignment ? props.className+' justify-content-'+alignment : props.className },
el( 'svg', { className: 'play-icon', width: '50', height: '50', viewBox: '0 0 70 70' },
el( 'path', { fill: '#f6155e', d: "M35 70C54.33 70 70 54.33 70 35C70 15.67 54.33 0 35 0C15.67 0 0 15.67 0 35C0 54.33 15.67 70 35 70Z" } ),
el( 'path', { fill: '#fff', d: "M48.2988 35L28.6988 44.1L28.6988 25.9L48.2988 35Z" } ),
),
el( 'div', { className: 'youtube-wrap' },
el( RichText, {
tagName: 'div',
placeholder: 'Введите текст кнопки',
keepPlaceholderOnFocus: true,
isSelected: false,
value: props.attributes.content,
onChange: function( content ) {
props.setAttributes( {
content: content,
} );
}
}
),
el( 'div', { className: 'youtube-link-wrap' },
el( 'svg', { className: 'dashicon dashicons-admin-links', width: '20', height: '20' },
el( 'path', { d: "M17.74 2.76c1.68 1.69 1.68 4.41 0 6.1l-1.53 1.52c-1.12 1.12-2.7 1.47-4.14 1.09l2.62-2.61.76-.77.76-.76c.84-.84.84-2.2 0-3.04-.84-.85-2.2-.85-3.04 0l-.77.76-3.38 3.38c-.37-1.44-.02-3.02 1.1-4.14l1.52-1.53c1.69-1.68 4.42-1.68 6.1 0zM8.59 13.43l5.34-5.34c.42-.42.42-1.1 0-1.52-.44-.43-1.13-.39-1.53 0l-5.33 5.34c-.42.42-.42 1.1 0 1.52.44.43 1.13.39 1.52 0zm-.76 2.29l4.14-4.15c.38 1.44.03 3.02-1.09 4.14l-1.52 1.53c-1.69 1.68-4.41 1.68-6.1 0-1.68-1.68-1.68-4.42 0-6.1l1.53-1.52c1.12-1.12 2.7-1.47 4.14-1.1l-4.14 4.15c-.85.84-.85 2.2 0 3.05.84.84 2.2.84 3.04 0z" } ),
),
el( UrlInput, {
tagName: 'div',
value: url,
autoFocus: false,
onChange: function( url ) {
props.setAttributes({
url: url,
title: ''
});
}
}
),
),
),
),
];
},
save: function( props ) {
let attributes = props.attributes;
return (
el( 'div', {
className: (attributes.alignment) ? 'justify-content-'+attributes.alignment : '',
},
el( 'button', {
className: 'video-btn',
'data-video': getVideoId(attributes.url),
}, attributes.content,
),
)
);
},
} );
} )(
window.wp.editor,
window.wp.components,
window.wp.element,
);
My editor.css code
.youtube-wrap {
width: calc(80% - 70px);
}
.wp-block-designa-youtube-block {
display: flex;
align-items: center;
}
.youtube-link-wrap {
display: flex;
align-items: center;
}
.youtube-link-wrap svg {
margin-right: 10px;
fill: #8f98a1;
}
.youtube-wrap {
padding: 0 20px;
}
.youtube-preview {
position: relative;
padding-bottom: 56.25%;
height: 0;
background: #e1e9ee;
margin-bottom: 14px;
}
.gutenberg__editor .youtube-preview iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.justify-content-center {
justify-content: center;
}
.justify-content-left {
justify-content: flex-start;
}
.justify-content-right {
justify-content: flex-end;
}
.sceleton-youtube-title:before, .sceleton-youtube-title:after {
content: "";
display: block;
height: 18px;
width: 100%;
background-color: #f0f4f6;
margin-bottom: 10px;
}
.sceleton-youtube-title:after {
width: 40%;
}
.components-panel__body.youtube-preview-block {
font-weight: bold;
color: #5a5e61;
letter-spacing: 0.015em;
line-height: 22px;
}
In back end I got this result:
Everything works fine, but now I need to get the title and duration of the video from YouTube by ID getId(attributes.url) and insert to InspectorControls block.
If I try to get the title and duration using Ajax, first render all block fields to InspectorControls, and then execute my ajax request. I need something like that on ajax successful push( el( 'p', {}, '**YouTube Title**') )
Tell me how to add the information obtained with Ajax to InspectorControls after rendering and on change?