/** * Silex, live web creation * http://projects.silexlabs.org/?/silex/ * * Copyright (c) 2012 Silex Labs * http://www.silexlabs.org/ * * Silex is available under the GPL license * http://www.silexlabs.org/silex/silex-licensing/ */ /** * @fileoverview The settings dialog which handles the file settings * */ import { CloudStorage } from '../../service/cloud-storage'; import { Controller, FileInfo, Font, Model } from '../../types'; import { SilexNotification } from '../../utils/notification'; import { Url } from '../../utils/url'; import { ModalDialog } from '../ModalDialog'; /** * constant for all pane css classes */ const PANE_CSS_CLASSES: string[] = ['general-pane', 'social-pane', 'publish-pane', 'fonts-pane']; /** * the Silex SettingsDialog class */ export class SettingsDialog { /** * store the mobile checkbox */ mobileCheckbox: HTMLInputElement = null; /** * store the current publication path */ publicationPath: FileInfo = null; websiteUrl: string = null; onClose: (() => any) = null; // make this a dialog modalDialog: any; // navigation paneCssClasses: any; list: any; /** * @param element container to render the UI * @param model model class which holds * the model instances - views use it for * read operation only * @param controller structure which holds * the controller instances */ constructor(protected element: HTMLElement, protected model: Model, protected controller: Controller) { this.modalDialog = new ModalDialog({ name: 'Settings dialog', element, onOpen: (args) => { this.onClose = args.cbk; if (args.pane) { this.openPane(args.pane); } this.redraw(); }, onClose: () => { // notify the caller of this dialog if (this.onClose) { this.onClose(); } }, }); this.paneCssClasses = PANE_CSS_CLASSES; this.element.classList.add(this.paneCssClasses[0] + '-visible'); const leftPane = document.querySelector('.left-pane'); leftPane.addEventListener('click', (e) => this.onNavClick(e), false); // input text fields this.bindTextField( '.general-pane .input-title', (v) => this.controller.settingsDialogController.setTitle(v)); this.bindTextField( '.general-pane .input-lang', (v) => this.controller.settingsDialogController.setLang(v)); this.bindTextField( '.general-pane .input-site-width', (v) => this.controller.settingsDialogController.setWebsiteWidth(!!v ? parseInt(v) : null)); this.bindTextField( '.social-pane .input-title', (v) => this.controller.settingsDialogController.setTitleSocial(v)); this.bindTextField( '.general-pane .input-description', (v) => this.controller.settingsDialogController.setDescription(v)); this.bindTextField( '.social-pane .input-description', (v) => this.controller.settingsDialogController.setDescriptionSocial(v)); this.bindTextField( '.social-pane .input-twitter', (v) => this.controller.settingsDialogController.setTwitterSocial(v)); this.bindTextField( '.general-pane .input-favicon-path', (v) => this.controller.settingsDialogController.setFaviconPath(v)); this.bindTextField( '.social-pane .input-image-path', (v) => this.controller.settingsDialogController.setThumbnailSocialPath(v)); this.bindTextField('.publish-pane .input-publication-path', (v) => { const fileInfo = this.controller.settingsDialogController.getPublicationPath(); const fileInfoNew = Url.updateFileInfo(fileInfo, {path: v}); this.controller.settingsDialogController.setPublicationPath(fileInfoNew); }); this.bindTextField('.publish-pane .input-publication-service', (v) => { const fileInfo = this.controller.settingsDialogController.getPublicationPath(); const fileInfoNew = Url.updateFileInfo(fileInfo, {service: v}); this.controller.settingsDialogController.setPublicationPath(fileInfoNew); }); this.bindTextField('.publish-pane .input-website-url', (v) => { if (v === '') { v = null; } this.model.head.setWebsiteUrl(v); this.websiteUrl = v; }); // image path browse button this.bindBrowseButton('.general-pane .browse-favicon-path', () => { this.controller.settingsDialogController.browseFaviconPath( () => this.open()); }); this.bindBrowseButton('.publish-pane .browse-publication-path', () => { this.controller.settingsDialogController.browsePublishPath( () => this.open()); }); // build UI this.mobileCheckbox = this.element.querySelector('.mobile-check'); this.mobileCheckbox.addEventListener('click', () => { this.controller.settingsDialogController.toggleEnableMobile(); }, false); // fill the options of the service selector CloudStorage.getInstance().ready(() => { CloudStorage.getInstance().getServices((services) => { const select = this.element.querySelector( '.publish-pane .input-publication-service'); select.innerHTML = ''; services.forEach((service) => { const option = document.createElement('option'); option.value = service.name; option.innerHTML = service.displayName || service.name; select.appendChild(option); }); }); }); // font button (this.element.querySelector('.pane.fonts-pane .add-font-btn') as HTMLElement).onclick = (e) => this.addFont(); this.list = this.element.querySelector('.fonts-list'); this.list.onclick = (e) => { const el = (e.target as HTMLElement); if (el.classList.contains('del-btn')) { const idx = parseInt(el.getAttribute('data-idx'), 10); const fonts = this.model.head.getFonts(); const newFonts = fonts.slice(); newFonts.splice(idx, 1); this.model.head.setFonts(newFonts); } else { if (el.classList.contains('edit-btn')) { const idx = parseInt(el.getAttribute('data-idx'), 10); const fonts = this.model.head.getFonts(); this.editFont(fonts[idx], (font) => { const newFonts = fonts.slice(); newFonts[idx] = font; this.model.head.setFonts(newFonts); }); } } }; } /** * click in the navigation */ onNavClick(e: Event) { this.openPane((e.target as HTMLElement).getAttribute('data-pane')); } /** * open the given pane * adds the desired pane class + '-visible' to this.element */ openPane(paneCssClass: string) { // close all panes this.paneCssClasses.forEach( (className) => this.element.classList.remove(className + '-visible')); // open the one we want this.element.classList.add(paneCssClass + '-visible'); } /** * binds an input element with a callback */ bindTextField(cssSelector: string, cbk: (p1: string) => any) { const input = this.element.querySelector(cssSelector) as HTMLInputElement; if (!input) { throw new Error( 'Settings panel error: could not find the element to bind.'); } input.onchange = (e) => { cbk(input.value); }; } /** * binds a button element with a callback */ bindBrowseButton(cssSelector: string, cbk: () => any) { const btn = this.element.querySelector(cssSelector); if (!btn) { throw new Error( 'Settings panel error: could not find the element to bind.'); } btn.addEventListener('click', () => { cbk(); }, false); } /** * set the value to the input element * @see silex.model.Head */ setInputValue(cssSelector: string, opt_value?: string) { const input = this.element.querySelector(cssSelector) as HTMLInputElement; if (opt_value) { input.value = opt_value; } else { input.value = ''; } } /** * set the favicon path to display * @see silex.model.Head */ setFaviconPath(opt_path?: string) { this.setInputValue('.general-pane .input-favicon-path', opt_path); } /** * set the social image path to display * @see silex.model.Head */ setThumbnailSocialPath(opt_path?: string) { this.setInputValue('.social-pane .input-image-path', opt_path); } /** * set the pubication path to display * @see silex.model.Head * @param fileInfo the publication path */ setPublicationPath(fileInfo?: FileInfo) { if (fileInfo != null ) { // set input tags the values this.setInputValue( '.publish-pane .input-publication-service', fileInfo.service); this.setInputValue( '.publish-pane .input-publication-path', fileInfo.path); // display the UI with publication path set this.element.classList.remove('publication-path-not-set'); // store FileInfo value, clone FileInfo for safety this.publicationPath = (Object.assign({}, fileInfo) as FileInfo); } else { this.setInputValue('.publish-pane .input-publication-service', ''); this.setInputValue('.publish-pane .input-publication-path', ''); // display the "not set" UI this.element.classList.add('publication-path-not-set'); // store FileInfo value this.publicationPath = null; } } /** * get the pubication path from text fields * @return the publication path */ getPublicationPath(): FileInfo { const service = (this.element.querySelector('.publish-pane .input-publication-service') as HTMLInputElement).value; const path = (this.element.querySelector('.publish-pane .input-publication-path') as HTMLInputElement).value; if (this.publicationPath != null && service && path && service !== '' && path !== '') { this.publicationPath.service = service; this.publicationPath.path = path; } return this.publicationPath; } setWebsiteUrl(opt_url) { this.setInputValue('.publish-pane .input-website-url', opt_url); this.websiteUrl = opt_url; } /** * enable/disable mobile version * @see silex.model.Head */ setEnableMobile(enabled: boolean) { this.mobileCheckbox.checked = enabled; } /** * set the website width * @see silex.model.Head */ setWebsiteWidth(opt_value?: number) { this.setInputValue('.general-pane .input-site-width', opt_value ? opt_value.toString() : null); } /** * set the site title to display * @see silex.model.Head * @param opt_title the site title */ setTitle(opt_title?: string) { this.setInputValue('.general-pane .input-title', opt_title); } /** * set the site default language * @see silex.model.Head * @param opt_lang the site lang */ setLang(opt_lang?: string) { this.setInputValue('.general-pane .input-lang', opt_lang); } /** * set the site description tag * @see silex.model.Head * @param opt_description the site description */ setDescription(opt_description?: string) { this.setInputValue('.general-pane .input-description', opt_description); } /** * set the site title to display * @see silex.model.Head * @param opt_title the site title */ setTitleSocial(opt_title?: string) { this.setInputValue('.social-pane .input-title', opt_title); } /** * set the site description tag * @see silex.model.Head * @param opt_description the site description */ setDescriptionSocial(opt_description?: string) { this.setInputValue('.social-pane .input-description', opt_description); } /** * set the owner twitter account * @see silex.model.Head */ setTwitterSocial(opt_twitter?: string) { this.setInputValue('.social-pane .input-twitter', opt_twitter); } /** * open settings dialog * @param opt_cbk callback to be called when the user closes the dialog * @param opt_paneCssClass css class of the pane to open */ open(opt_cbk?: (() => any), opt_paneCssClass?: string) { this.modalDialog.open({cbk: opt_cbk, pane: opt_paneCssClass}); } /** * redraw the dialog */ redraw() { try { this.setPublicationPath(this.model.head.getPublicationPath()); this.setWebsiteUrl(this.model.head.getWebsiteUrl()); this.setFonts(this.model.head.getFonts()); } catch (e) { } } setFonts(fonts: Font[]) { this.list.innerHTML = ''; } addFont() { this.editFont( { href: 'https://fonts.googleapis.com/css?family=Roboto', family: '\'Roboto\', sans-serif', }, (newFont) => { const fonts = this.model.head.getFonts(); if (fonts.find( (font: Font) => font.href === newFont.href && font.family === newFont.family)) { console.warn('This font is already embedded in this website'); } else { fonts.push(newFont); this.model.head.setFonts(fonts); } }); } editFont(font, cbk) { SilexNotification.prompt('Edit font', 'What is the CSS stylesheet for your font, e.g. https://fonts.googleapis.com/css?family=Roboto', font.href, 'https://fonts.googleapis.com/css?family=Roboto', (ok, href) => { if (ok) { SilexNotification.prompt('Edit font', 'What is the name of your font, e.g. \'Roboto\', sans-serif', font.family, '\'Roboto\', sans-serif', (_ok, family) => { if (_ok) { cbk(({family, href} as Font)); } }, ); } }, ); } /** * close editor * this is private method, do not call it */ close() { this.modalDialog.close(); } }