Separating all remaining code into files.

This commit is contained in:
Jorge Bolois Guerrero 2022-07-08 22:12:05 +02:00
parent 73ae583881
commit 6c473c54ea
13 changed files with 379 additions and 235 deletions

186
app.js
View File

@ -1,187 +1,7 @@
import Configuration from "./src/Configuration.js";
import Item from "./src/Item.js";
import $ from 'jquery';
import ItemHTML from "./src/ItemHTML.js";
// Idealista selectors.
const ITEM_SELECTOR = 'article.item';
// Own selectors.
const CONFIG_CLASS = 'midefos-idealista-config'
const STYLES = `
.midefos-idealista-container {
display: flex;
align-items: center;
justify-content: space-around;
width: 100%;
height: 55px;
margin-top: 5px;
background-color: white;
box-shadow: 0 3px 6px rgba(225, 245, 110, 0.16), 0 3px 6px rgba(225, 245, 110, 0.23);
}
.midefos-idealista-menu {
display: flex;
align-items: center;
justify-content: space-around;
width: 100%;
height: 40px;
background-color: white;
box-shadow: 0 3px 6px rgba(225, 245, 110, 0.16), 0 3px 6px rgba(225, 245, 110, 0.23);
}
.${CONFIG_CLASS} {
display: none;
position: fixed;
z-index: 3;
background-color: rgba(225, 245, 110, 0.90);
width: 95%;
height: 95%;
top: 2.5%;
left: 2.5%;
padding: 2rem;
}
.success {
color: darkgreen;
}
.warning {
color: darkorange;
}
.error {
color: darkred;
}
`;
import App from "./src/App.js";
function init() {
Configuration.init();
addStyles();
createMenu();
createInformation();
App.init();
}
function createMenu() {
if (window.location.href.includes('mapa-google')) return;
$('body').on('click', '#reload-midefos-idealista', () => {
createInformation();
});
$('body').on('click', '.config-open', () => {
$(`.${CONFIG_CLASS}`).toggle();
});
$('body').on('click', '.config-save', () => {
const $configNode = $(`.${CONFIG_CLASS}`);
const configNode = $configNode[0];
const newConfig = {
enabled: configNode.querySelector('#enabled').checked,
percentages: configNode.querySelector('#percentages').checked,
garage: configNode.querySelector('#garage').checked,
exterior: configNode.querySelector('#exterior').checked,
lift: configNode.querySelector('#lift').checked,
'max-price': configNode.querySelector('#max-price').value,
'max-price-per-meter': configNode.querySelector('#max-price-per-meter').value
}
setConfig(newConfig);
createInformation();
$configNode.toggle();
});
document.querySelector('#main-header').innerHTML += createConfigHTML();
const mainContent = document.querySelector('#main-content');
mainContent.innerHTML = createMenuHTML() + mainContent.innerHTML;
}
function createInformation() {
const items = document.querySelectorAll(ITEM_SELECTOR);
for (const _item of items) {
const item = new Item(_item);
const currentInformation = _item.querySelector('.midefos-idealista-container');
if (currentInformation) currentInformation.remove();
_item.innerHTML += ItemHTML.createInformation(item);
}
}
function createConfigHTML() {
return `
<div class='${CONFIG_CLASS}'>
<h2>Midefos Idealista</h2>
<h3>Configuración global:</h3>
<label>
<input type='checkbox' id='enabled' checked='${Configuration.get('enabled')}'>
<span>Habilitado</span>
</label>
<h3>Busqueda:</h3>
<label>
<input type='checkbox' id='percentages' checked='${Configuration.get('percentages')}'>
<span>Porcentajes</span>
</label>
<br>
<label>
<input type='checkbox' id='garage' checked='${Configuration.get('garage')}'>
<span>Garaje</span>
</label>
<br>
<label>
<input type='checkbox' id='exterior' checked='${Configuration.get('exterior')}'>
<span>Exterior</span>
</label>
<br>
<label>
<input type='checkbox' id='lift' checked='${Configuration.get('lift')}'>
<span>Ascensor</span>
</label>
<br>
<label>
<span>Precio máximo: </span>
<input type='number' id='max-price' value='${Configuration.get('max-price')}' placeholder='0'>
</label>
<br>
<label>
<span>Precio máximo por metro: </span>
<input type='number' id='max-price-per-meter' value='${Configuration.get('max-price-per-meter')}' placeholder='0'>
</label>
<br>
<button class='config-save'>Guardar</button>
<button class='config-open'>Cerrar</button>
</div>`
}
function createMenuHTML() {
return `
<div class='midefos-idealista-menu'>
<button class='config-open' title='Abrir configuración'></button>
<button id='reload-midefos-idealista' title='Cargar información'></button>
</div>`;
}
init();
function addStyles() {
const style = document.createElement('style');
style.textContent = STYLES;
document.head.appendChild(style);
}
init();

View File

@ -18,7 +18,6 @@
"@babel/preset-env": "^7.18.6",
"babelify": "^10.0.0",
"browserify": "^17.0.0",
"browserify-banner": "^2.0.4",
"jquery": "^3.6.0"
"browserify-banner": "^2.0.4"
}
}

20
src/App.js Normal file
View File

@ -0,0 +1,20 @@
import Preferences from './Preferences.js';
import Styles from './Styles.js';
import Menu from './Menu.js';
import Configuration from './Configuration.js';
import Information from './Information.js';
export default class App {
static init() {
Preferences.init();
Styles.add();
new Menu();
new Configuration();
Information.create();
}
}

View File

@ -1,44 +1,46 @@
import ConfigurationHTML from "./ConfigurationHTML.js";
import Event from "./Event.js";
import Information from "./Information.js";
import Log from "./Log.js";
import Preferences from "./Preferences.js";
export default class Configuration {
static NAME = 'midefos-idealista';
static _current;
static init() {
this._current = this._getConfig();
constructor() {
document.querySelector('#main-header').innerHTML += ConfigurationHTML.create();
this._initEvents();
}
static get(key) {
const config = this._getConfig();
return config[key];
_initEvents() {
Event.click(ConfigurationHTML.SAVE_CONFIG_SELECTOR, () => {
Preferences.save(this._extractConfiguration());
Information.create();
Configuration.toggle();
});
}
static save(config) {
window.localStorage.setItem(this.NAME, JSON.stringify(config));
this._current = config;
}
static _getConfig() {
const storageConfig = this._getFromLocalStorage();
if (!storageConfig) return this._default();
return storageConfig;
}
static _getFromLocalStorage() {
const storageConfig = window.localStorage.getItem(this.NAME);
if (!storageConfig) return null;
return JSON.parse(storageConfig);
}
static _default() {
_extractConfiguration() {
const container = document.querySelector(ConfigurationHTML.CONTAINER_SELECTOR);
return {
enabled: true,
percentages: true,
garage: false,
exterior: true,
lift: true,
'max-price': 120_000,
'max-price-per-meter': 1_500
enabled: container.querySelector('#enabled').checked,
percentages: container.querySelector('#percentages').checked,
garage: container.querySelector('#garage').checked,
exterior: container.querySelector('#exterior').checked,
lift: container.querySelector('#lift').checked,
'max-price': container.querySelector('#max-price').value,
'max-price-per-meter': container.querySelector('#max-price-per-meter').value
}
}
static toggle() {
const container = document.querySelector(ConfigurationHTML.CONTAINER_SELECTOR);
if (getComputedStyle(container).display === 'none') {
container.style.display = 'block';
Log.debug(`Opened configuration`);
} else {
container.style.display = 'none';
Log.debug(`Closed configuration`);
}
}

74
src/ConfigurationHTML.js Normal file
View File

@ -0,0 +1,74 @@
import Preferences from "./Preferences.js";
export default class ConfigurationHTML {
static CONTAINER_CLASS_NAME = 'midefos-idealista-config';
static get CONTAINER_SELECTOR() {
return `.${this.CONTAINER_CLASS_NAME}`;
}
static OPEN_CONFIG_CLASS_NAME = 'open-config';
static get OPEN_CONFIG_SELECTOR() {
return `.${this.OPEN_CONFIG_CLASS_NAME}`;
}
static SAVE_CONFIG_CLASS_NAME = 'save-config';
static get SAVE_CONFIG_SELECTOR() {
return `.${this.SAVE_CONFIG_CLASS_NAME}`;
}
static create() {
return `
<div class='${this.CONTAINER_CLASS_NAME}'>
<h2>Midefos Idealista</h2>
<h3>Configuración global:</h3>
<label>
<input type='checkbox' id='enabled' checked='${Preferences.get('enabled')}'>
<span>Habilitado</span>
</label>
<h3>Busqueda:</h3>
<label>
<input type='checkbox' id='percentages' checked='${Preferences.get('percentages')}'>
<span>Porcentajes</span>
</label>
<br>
<label>
<input type='checkbox' id='garage' checked='${Preferences.get('garage')}'>
<span>Garaje</span>
</label>
<br>
<label>
<input type='checkbox' id='exterior' checked='${Preferences.get('exterior')}'>
<span>Exterior</span>
</label>
<br>
<label>
<input type='checkbox' id='lift' checked='${Preferences.get('lift')}'>
<span>Ascensor</span>
</label>
<br>
<label>
<span>Precio máximo: </span>
<input type='number' id='max-price' value='${Preferences.get('max-price')}' placeholder='0'>
</label>
<br>
<label>
<span>Precio máximo por metro: </span>
<input type='number' id='max-price-per-meter' value='${Preferences.get('max-price-per-meter')}' placeholder='0'>
</label>
<br>
<button class='${this.SAVE_CONFIG_CLASS_NAME}'>Guardar</button>
<button class='${this.OPEN_CONFIG_CLASS_NAME}'>Cerrar</button>
</div>`
}
}

13
src/Event.js Normal file
View File

@ -0,0 +1,13 @@
import Log from "./Log.js";
export default class Event {
static click(selector, callback) {
document.addEventListener('click', (event) => {
if (!event.target.matches(selector)) return;
Log.debug(`Click on: ${selector}`)
callback(event.target);
});
}
}

21
src/Information.js Normal file
View File

@ -0,0 +1,21 @@
import Item from './Item.js';
import ItemHTML from './ItemHTML.js';
import Log from './Log.js';
export default class Information {
static CLASS_NAME = 'midefos-idealista-container';
static ITEM_SELECTOR = 'article.item';
static create() {
Log.debug(`Creating information...`)
const items = document.querySelectorAll(this.ITEM_SELECTOR);
for (const _item of items) {
const item = new Item(_item);
const currentInformation = _item.querySelector(`.${this.CLASS_NAME}`);
if (currentInformation) currentInformation.remove();
_item.innerHTML += ItemHTML.createInformation(item);
}
}
}

View File

@ -1,11 +1,15 @@
import Configuration from "./Configuration.js";
import Preferences from "./Preferences.js";
export default class ItemHTML {
static CONTAINER_CLASS_NAME = 'midefos-idealista-container';
static get CONTAINER_SELECTOR() {
return `.${this.CONTAINER_CLASS_NAME}`;
}
static createInformation(item) {
return `
<div class='midefos-idealista-container'>
<div class='${this.CONTAINER_CLASS_NAME}'>
${this._createPercentagePriceHTML(item)}
${this._createPriceHTML(item)}
${this._createPriceMeterHTML(item)}
@ -15,7 +19,7 @@ export default class ItemHTML {
}
static _createPercentagePriceHTML(item) {
if (!Configuration.get('percentages')) return ``;
if (!Preferences.get('percentages')) return ``;
const twentyPercent = Math.round(item.price * 20 / 100);
const thirtyPercent = Math.round(item.price * 30 / 100);
@ -28,20 +32,20 @@ export default class ItemHTML {
static _createPriceHTML(item) {
const desiredPrice = Configuration.get('max-price');
const desiredPrice = Preferences.get('max-price');
const desiredTwentyFivePercentMore = Math.round(desiredPrice * 1.25);
if (item.price <= desiredPrice) {
return `<span class='success'><strong>✓</strong> Precio</span>`;
return this._createSuccess('Precio');
} else if (item.price <= desiredTwentyFivePercentMore) {
return `<span class='warning'><strong>✓</strong> Precio</span>`;
return this._createWarning('Precio');
}
return `<span class='error'><strong>X</strong> Precio</span>`;
return this._createError('Precio');
}
static _createPriceMeterHTML(item) {
const desiredPricePerMeter = Configuration.get('max-price-per-meter');
const desiredPricePerMeter = Preferences.get('max-price-per-meter');
const desiredTwentyFivePercentMore = Math.round(desiredPricePerMeter * 1.25);
if (item.priceMeter <= desiredPricePerMeter) {
@ -53,31 +57,48 @@ export default class ItemHTML {
}
static _createLiftHTML(item) {
if (!Configuration.get('lift')) return ``;
if (!Preferences.get('lift')) return ``;
if (!item.additionalInfo) {
if (item.isFlat()) return ``;
return this.__createIndividual('?', 'Ascensor', 'warning');
return this._createMissing('Ascensor');
}
if (item.hasLift) {
return this.__createIndividual('✓', 'Ascensor', 'warning');
return this._createSuccess('Ascensor',);
}
return this.__createIndividual('X', 'Ascensor', 'error');
return this._createError('Ascensor');
}
static _createInteriorHTML(item) {
if (!Configuration.get('exterior')) return ``;
if (!Preferences.get('exterior')) return ``;
if (!item.additionalInfo) {
if (!item.isFlat()) return ``;
return `<span class='warning'><strong>?</strong> Exterior</span>`
return this._createMissing('Exterior');
}
if (item.isExterior) {
return `<span class='success'><strong>✓</strong> Exterior</span>`
return this._createSuccess('Exterior');
}
return `<span class='error'><strong>X</strong> Exterior</span>`;
return this._createError('Exterior');
}
static _createSuccess(infoText) {
return this.__createIndividual('✓', infoText, 'success');
}
static _createWarning(infoText) {
return this.__createIndividual('✓', infoText, 'warning');
}
static _createMissing(infoText) {
return this.__createIndividual('?', infoText, 'warning');
}
static _createError(infoText) {
return this.__createIndividual('✓', infoText, 'error');
}
static __createIndividual(strongText, infoText, className = '') {

7
src/Log.js Normal file
View File

@ -0,0 +1,7 @@
export default class Log {
static debug(message) {
console.log(`%c [DEBUG] ${message} `, 'background: blue; color: white; font-size: 13px;');
}
}

32
src/Menu.js Normal file
View File

@ -0,0 +1,32 @@
import Configuration from "./Configuration.js";
import ConfigurationHTML from "./ConfigurationHTML.js";
import Event from "./Event.js";
import Information from "./Information.js";
import MenuHTML from "./MenuHTML.js";
export default class Menu {
constructor() {
if (this._shouldNotLoad()) return;
const mainContent = document.querySelector('#main-content');
mainContent.innerHTML = MenuHTML.create() + mainContent.innerHTML;
this._initEvents();
}
_initEvents() {
Event.click(MenuHTML.RELOAD_INFORMATION_SELECTOR, () => {
Information.create();
})
Event.click(ConfigurationHTML.OPEN_CONFIG_SELECTOR, () => {
Configuration.toggle();
});
}
_shouldNotLoad() {
return window.location.href.includes('mapa-google');
}
}

23
src/MenuHTML.js Normal file
View File

@ -0,0 +1,23 @@
import ConfigurationHTML from "./ConfigurationHTML.js";
export default class MenuHTML {
static CONTAINER_CLASS_NAME = 'midefos-idealista-menu';
static get CONTAINER_SELECTOR() {
return `.${this.CONTAINER_CLASS_NAME}`;
}
static RELOAD_INFORMATION_CLASS_NAME = 'reload-information';
static get RELOAD_INFORMATION_SELECTOR() {
return `.${this.RELOAD_INFORMATION_CLASS_NAME}`;
}
static create() {
return `
<div class='${this.CONTAINER_CLASS_NAME}'>
<button class='${ConfigurationHTML.OPEN_CONFIG_CLASS_NAME}' title='Abrir configuración'></button>
<button class='${this.RELOAD_INFORMATION_CLASS_NAME}' title='Cargar información'></button>
</div>`;
}
}

48
src/Preferences.js Normal file
View File

@ -0,0 +1,48 @@
import Log from "./Log.js";
export default class Preferences {
static NAME = 'midefos-idealista';
static _current;
static init() {
this._current = this._getConfig();
}
static get(key) {
const config = this._getConfig();
return config[key];
}
static save(config) {
window.localStorage.setItem(this.NAME, JSON.stringify(config));
this._current = config;
Log.debug('Saved preferences')
}
static _getConfig() {
const storageConfig = this._getFromLocalStorage();
if (!storageConfig) return this._default();
return storageConfig;
}
static _getFromLocalStorage() {
const storageConfig = window.localStorage.getItem(this.NAME);
if (!storageConfig) return null;
return JSON.parse(storageConfig);
}
static _default() {
return {
enabled: true,
percentages: true,
garage: false,
exterior: true,
lift: true,
'max-price': 120_000,
'max-price-per-meter': 1_500
}
}
}

64
src/Styles.js Normal file
View File

@ -0,0 +1,64 @@
import ConfigurationHTML from "./ConfigurationHTML.js";
import ItemHTML from "./ItemHTML.js";
import MenuHTML from "./MenuHTML.js";
export default class Styles {
static APP_STYLES = `
.${ItemHTML.CONTAINER_CLASS_NAME} {
display: flex;
align-items: center;
justify-content: space-around;
width: 100%;
height: 55px;
margin-top: 5px;
background-color: white;
box-shadow: 0 3px 6px rgba(225, 245, 110, 0.16), 0 3px 6px rgba(225, 245, 110, 0.23);
}
.${MenuHTML.CONTAINER_CLASS_NAME} {
display: flex;
align-items: center;
justify-content: space-around;
width: 100%;
height: 40px;
background-color: white;
box-shadow: 0 3px 6px rgba(225, 245, 110, 0.16), 0 3px 6px rgba(225, 245, 110, 0.23);
}
.${ConfigurationHTML.CONTAINER_CLASS_NAME} {
display: none;
position: fixed;
z-index: 3;
background-color: rgba(225, 245, 110, 0.90);
width: 95%;
height: 95%;
top: 2.5%;
left: 2.5%;
padding: 2rem;
}
.success {
color: darkgreen;
}
.warning {
color: darkorange;
}
.error {
color: darkred;
}
`;
static add(style = null) {
if (!style) style = this.APP_STYLES;
const styleNode = document.createElement('style');
styleNode.textContent = style;
document.head.appendChild(styleNode);
}
}