<commit_before>import $ from 'jquery';
import ModalComponent from 'ghost-admin/components/modal-base';
import copyTextToClipboard from 'ghost-admin/utils/copy-text-to-clipboard';
import {action, computed} from '@ember/object';
import {htmlSafe} from '@ember/template';
import {inject as service} from '@ember/service';
import {task, timeout} from 'ember-concurrency';
const ICON_EXTENSIONS = ['gif', 'jpg', 'jpeg', 'png', 'svg'];

export default ModalComponent.extend({
    config: service(),
    membersUtils: service(),
    settings: service(),
    store: service(),
    session: service(),
    feature: service(),
    ghostPaths: service(),
    ajax: service(),

    page: 'signup',
    iconExtensions: null,
    isShowModalLink: true,
    customIcon: null,
    showLinksPage: false,
    showLeaveSettingsModal: false,
    isPreloading: true,
    changedProducts: null,
    portalPreviewGuid: 'modal-portal-settings',

    confirm() {},

    backgroundStyle: computed('settings.accentColor', function () {
        let color = this.settings.get('accentColor') || '#ffffff';
        return htmlSafe(`background-color: ${color}`);
    }),

    disableUpdateSupportAddressButton: computed('supportAddress', function () {
        const savedSupportAddress = this.get('settings.membersSupportAddress') || '';
        if (!savedSupportAddress.includes('@') && this.config.emailDomain) {
            return !this.supportAddress || (this.supportAddress === `${savedSupportAddress}@${this.config.emailDomain}`);
        }
        return !this.supportAddress || (this.supportAddress === savedSupportAddress);
    }),

    showModalLinkOrAttribute: computed('isShowModalLink', function () {
        if (this.isShowModalLink) {
            return `#/portal`;
        }
        return `data-portal`;
    }),

    portalPreviewUrl: computed('page', 'model.products.[]', 'changedProducts.[]', 'membersUtils.{isFreeChecked,isMonthlyChecked,isYearlyChecked}', 'settings.{portalName,portalButton,portalButtonIcon,portalButtonSignupText,portalButtonStyle,accentColor,portalPlans.[]}', function () {
        const options = this.getProperties(['page']);
        options.portalProducts = this.model.products?.filter((product) => {
            return product.get('visibility') === 'public' && product.get('type') === 'paid';
        }).map((product) => {
            return product.id;
        });
        const freeProduct = this.model.products?.find((product) => {
            return product.type === 'free';
        });
        options.isFreeChecked = freeProduct?.visibility === 'public';
        return this.membersUtils.getPortalPreviewUrl(options);
    }),

    showIconSetting: computed('selectedButtonStyle', function () {
        const selectedButtonStyle = this.get('selectedButtonStyle.name') || '';
        return selectedButtonStyle.includes('icon');
    }),

    showButtonTextSetting: computed('selectedButtonStyle', function () {
        const selectedButtonStyle = this.get('selectedButtonStyle.name') || '';
        return selectedButtonStyle.includes('text');
    }),

    selectedButtonStyle: computed('settings.portalButtonStyle', function () {
        return this.buttonStyleOptions.find((buttonStyle) => {
            return (buttonStyle.name === this.settings.get('portalButtonStyle'));
        });
    }),

    isFreeChecked: computed('settings.{portalPlans.[],membersSignupAccess}', function () {
        const allowedPlans = this.settings.get('portalPlans') || [];
        return (this.settings.get('membersSignupAccess') === 'all' && allowedPlans.includes('free'));
    }),
    isMonthlyChecked: computed('settings.portalPlans.[]', 'isStripeConfigured', function () {
        const allowedPlans = this.settings.get('portalPlans') || [];
        return (this.membersUtils.isStripeEnabled && allowedPlans.includes('monthly'));
    }),
    isYearlyChecked: computed('settings.portalPlans.[]', 'isStripeConfigured', function () {
        const allowedPlans = this.settings.get('portalPlans') || [];
        return (this.membersUtils.isStripeEnabled && allowedPlans.includes('yearly'));
    }),
    products: computed('model.products.[]', 'changedProducts.[]', 'isPreloading', function () {
        const paidProducts = this.model.products?.filter(product => product.type === 'paid' && product.active === true);
        if (this.isPreloading || !paidProducts?.length) {
            return [];
        }

        const products = paidProducts.map((product) => {
            return {
                id: product.id,
                name: product.name,
                checked: product.visibility === 'public'
            };
        });
        return products;
    }),

    showPortalTiers: computed('products', 'feature.multipleProducts', function () {
        if (this.feature.get('multipleProducts')) {
            return true;
        }
        return false;
    }),

    showPortalPrices: computed('products', 'feature.multipleProducts', function () {
        if (!this.feature.get('multipleProducts')) {
            return true;
        }

        const visibleProducts = this.model.products?.filter((product) => {
            return product.visibility === 'public' && product.type === 'paid';
        });

        return !!visibleProducts?.length;
    }),

    init() {
        this._super(...arguments);
        this.buttonStyleOptions = [
            {name: 'icon-and-text', label: 'Icon and text'},
            {name: 'icon-only', label: 'Icon only'},
            {name: 'text-only', label: 'Text only'}
        ];
        this.iconExtensions = ICON_EXTENSIONS;
        this.changedProducts = [];
        this.set('supportAddress', this.parseEmailAddress(this.settings.get('membersSupportAddress')));
    },

    didInsertElement() {
        this._super(...arguments);
        this.settings.get('errors').clear();
    },

    actions: {
        toggleFreePlan(isChecked) {
            this.updateAllowedPlan('free', isChecked);
        },
        togglePlan(plan, event) {
            this.updateAllowedPlan(plan, event.target.checked);
        },
        toggleProduct(productId, event) {
            this.updateAllowedProduct(productId, event.target.checked);
        },
        togglePortalButton(showButton) {
            this.settings.set('portalButton', showButton);
        },

        togglePortalName(showSignupName) {
            this.settings.set('portalName', showSignupName);
        },

        confirm() {
            return this.saveTask.perform();
        },

        isPlanSelected(plan) {
            const allowedPlans = this.settings.get('portalPlans');
            return allowedPlans.includes(plan);
        },

        switchPreviewPage(page) {
            if (page === 'links') {
                this.set('showLinksPage', true);
                this.set('page', '');
            } else {
                this.set('showLinksPage', false);
                this.set('page', page);
            }
        },

        switchToSignupPage() {
            if (this.showLinksPage) {
                this.set('showLinksPage', false);
                this.set('page', 'signup');
            }
        },

        setButtonStyle(buttonStyle) {
            this.settings.set('portalButtonStyle', buttonStyle.name);
        },

        setSignupButtonText(event) {
            this.settings.set('portalButtonSignupText', event.target.value);
        },
        /**
         * Fired after an image upload completes
         * @param  {string} property - Property name to be set on `this.settings`
         * @param  {UploadResult[]} results - Array of UploadResult objects
         * @return {string} The URL that was set on `this.settings.property`
         */
        imageUploaded(property, results) {
            if (results[0]) {
                this.set('customIcon', results[0].url);
                this.settings.set('portalButtonIcon', results[0].url);
            }
        },
        /**
         * Opens a file selection dialog - Triggered by "Upload Image" buttons,
         * searches for the hidden file input within the .gh-setting element
         * containing the clicked button then simulates a click
         * @param  {MouseEvent} event - MouseEvent fired by the button click
         */
        triggerFileDialog(event) {
            // simulate click to open file dialog
            // using jQuery because IE11 doesn't support MouseEvent
            $(event.target)
                .closest('.gh-setting-action')
                .find('input[type="file"]')
                .click();
        },

        deleteCustomIcon() {
            this.set('customIcon', null);
            this.settings.set('portalButtonIcon', this.membersUtils.defaultIconKeys[0]);
        },

        selectDefaultIcon(icon) {
            this.settings.set('portalButtonIcon', icon);
        },

        closeLeaveSettingsModal() {
            this.set('showLeaveSettingsModal', false);
        },

        openStripeSettings() {
            this.isWaitingForStripeConnection = true;
            this.model.openStripeSettings();
        },

        leaveSettings() {
            this.closeModal();
        },

        validateFreeSignupRedirect() {
            return this._validateSignupRedirect(this.freeSignupRedirect, 'membersFreeSignupRedirect');
        },

        validatePaidSignupRedirect() {
            return this._validateSignupRedirect(this.paidSignupRedirect, 'membersPaidSignupRedirect');
        },

        setSupportAddress(supportAddress) {
            this.set('supportAddress', supportAddress);
        }
    },

    parseEmailAddress(address) {
        const emailAddress = address || 'noreply';
        // Adds default domain as site domain
        if (emailAddress.indexOf('@') < 0 && this.config.emailDomain) {
            return `${emailAddress}@${this.config.emailDomain}`;
        }
        return emailAddress;
    },

    updateAllowedPlan(plan, isChecked) {
        const portalPlans = this.settings.get('portalPlans') || [];
        const allowedPlans = [...portalPlans];
        const freeProduct = this.model.products.find(p => p.type === 'free');

        if (!isChecked) {
            this.settings.set('portalPlans', allowedPlans.filter(p => p !== plan));
            if (plan === 'free') {
                freeProduct.set('visibility', 'none');
            }
        } else {
            allowedPlans.push(plan);
            this.settings.set('portalPlans', allowedPlans);
            if (plan === 'free') {
                freeProduct.set('visibility', 'public');
            }
        }
    },

    updateAllowedProduct(productId, isChecked) {
        const portalProductsSetting = this.settings.get('portalProducts') || [];
        const allowedProducts = [...portalProductsSetting];

        const product = this.model.products.find(p => p.id === productId);
        if (!isChecked) {
            this.settings.set('portalProducts', allowedProducts.filter(p => p !== productId));
            product.set('visibility', 'none');
        } else {
            this.settings.set('portalProducts', allowedProducts);
            product.set('visibility', 'public');
        }
        let portalProducts = this.model.products.filter((p) => {
            return p.visibility === 'public';
        }).map(p => p.id);
        this.set('changedProducts', portalProducts);
    },

    _validateSignupRedirect(url, type) {
        let errMessage = `Please enter a valid URL`;
        this.settings.get('errors').remove(type);
        this.settings.get('hasValidated').removeObject(type);

        if (url === null) {
            this.settings.get('errors').add(type, errMessage);
            this.settings.get('hasValidated').pushObject(type);
            return false;
        }

        if (url === undefined) {
            // Not initialised
            return;
        }

        if (url.href.startsWith(this.siteUrl)) {
            const path = url.href.replace(this.siteUrl, '');
            this.settings.set(type, path);
        } else {
            this.settings.set(type, url.href);
        }
    },

    finishPreloading: action(async function () {
        if (this.model.preloadTask?.isRunning) {
            await this.model.preloadTask;
        }

        const portalButtonIcon = this.settings.get('portalButtonIcon') || '';
        if (portalButtonIcon && !this.membersUtils.defaultIconKeys.includes(portalButtonIcon)) {
            this.set('customIcon', this.settings.get('portalButtonIcon'));
        }

        this.siteUrl = this.config.get('blogUrl');
        this.set('isPreloading', false);
    }),

    refreshAfterStripeConnected: action(async function () {
        if (this.isWaitingForStripeConnection) {
            await this.finishPreloading();
            this.notifyPropertyChange('page'); // force preview url to recompute
            this.set('portalPreviewGuid', Date.now().valueOf()); // force preview re-render
            this.isWaitingForStripeConnection = false;
        }
    }),

    copyLinkOrAttribute: task(function* () {
        copyTextToClipboard(this.showModalLinkOrAttribute);
        yield timeout(this.isTesting ? 50 : 3000);
    }),

    saveTask: task(function* () {
        this.send('validateFreeSignupRedirect');
        this.send('validatePaidSignupRedirect');
        if (this.settings.get('errors').length !== 0) {
            return;
        }

        // Save tier visibility if changed
        yield Promise.all(
            this.model.products.filter((product) => {
                const changedAttrs = product.changedAttributes();
                return !!changedAttrs.visibility;
            }).map((product) => {
                return product.save();
            })
        );

        yield this.settings.save();

        this.closeModal();
    }).drop(),

    updateSupportAddress: task(function* () {
        let url = this.get('ghostPaths.url').api('/settings/members/email');
        try {
            const response = yield this.ajax.post(url, {
                data: {
                    email: this.supportAddress,
                    type: 'supportAddressUpdate'
                }
            });
            // this.toggleProperty('showSupportAddressConfirmation');
            return response;
        } catch (e) {
            // Failed to send email, retry
            return false;
        }
    }).drop()
});
<commit_msg>Removed usage of portal products setting<commit_after>import $ from 'jquery';
import ModalComponent from 'ghost-admin/components/modal-base';
import copyTextToClipboard from 'ghost-admin/utils/copy-text-to-clipboard';
import {action, computed} from '@ember/object';
import {htmlSafe} from '@ember/template';
import {inject as service} from '@ember/service';
import {task, timeout} from 'ember-concurrency';
const ICON_EXTENSIONS = ['gif', 'jpg', 'jpeg', 'png', 'svg'];

export default ModalComponent.extend({
    config: service(),
    membersUtils: service(),
    settings: service(),
    store: service(),
    session: service(),
    feature: service(),
    ghostPaths: service(),
    ajax: service(),

    page: 'signup',
    iconExtensions: null,
    isShowModalLink: true,
    customIcon: null,
    showLinksPage: false,
    showLeaveSettingsModal: false,
    isPreloading: true,
    changedProducts: null,
    portalPreviewGuid: 'modal-portal-settings',

    confirm() {},

    backgroundStyle: computed('settings.accentColor', function () {
        let color = this.settings.get('accentColor') || '#ffffff';
        return htmlSafe(`background-color: ${color}`);
    }),

    disableUpdateSupportAddressButton: computed('supportAddress', function () {
        const savedSupportAddress = this.get('settings.membersSupportAddress') || '';
        if (!savedSupportAddress.includes('@') && this.config.emailDomain) {
            return !this.supportAddress || (this.supportAddress === `${savedSupportAddress}@${this.config.emailDomain}`);
        }
        return !this.supportAddress || (this.supportAddress === savedSupportAddress);
    }),

    showModalLinkOrAttribute: computed('isShowModalLink', function () {
        if (this.isShowModalLink) {
            return `#/portal`;
        }
        return `data-portal`;
    }),

    portalPreviewUrl: computed('page', 'model.products.[]', 'changedProducts.[]', 'membersUtils.{isFreeChecked,isMonthlyChecked,isYearlyChecked}', 'settings.{portalName,portalButton,portalButtonIcon,portalButtonSignupText,portalButtonStyle,accentColor,portalPlans.[]}', function () {
        const options = this.getProperties(['page']);
        options.portalProducts = this.model.products?.filter((product) => {
            return product.get('visibility') === 'public' && product.get('type') === 'paid';
        }).map((product) => {
            return product.id;
        });
        const freeProduct = this.model.products?.find((product) => {
            return product.type === 'free';
        });
        options.isFreeChecked = freeProduct?.visibility === 'public';
        return this.membersUtils.getPortalPreviewUrl(options);
    }),

    showIconSetting: computed('selectedButtonStyle', function () {
        const selectedButtonStyle = this.get('selectedButtonStyle.name') || '';
        return selectedButtonStyle.includes('icon');
    }),

    showButtonTextSetting: computed('selectedButtonStyle', function () {
        const selectedButtonStyle = this.get('selectedButtonStyle.name') || '';
        return selectedButtonStyle.includes('text');
    }),

    selectedButtonStyle: computed('settings.portalButtonStyle', function () {
        return this.buttonStyleOptions.find((buttonStyle) => {
            return (buttonStyle.name === this.settings.get('portalButtonStyle'));
        });
    }),

    isFreeChecked: computed('settings.{portalPlans.[],membersSignupAccess}', function () {
        const allowedPlans = this.settings.get('portalPlans') || [];
        return (this.settings.get('membersSignupAccess') === 'all' && allowedPlans.includes('free'));
    }),
    isMonthlyChecked: computed('settings.portalPlans.[]', 'isStripeConfigured', function () {
        const allowedPlans = this.settings.get('portalPlans') || [];
        return (this.membersUtils.isStripeEnabled && allowedPlans.includes('monthly'));
    }),
    isYearlyChecked: computed('settings.portalPlans.[]', 'isStripeConfigured', function () {
        const allowedPlans = this.settings.get('portalPlans') || [];
        return (this.membersUtils.isStripeEnabled && allowedPlans.includes('yearly'));
    }),
    products: computed('model.products.[]', 'changedProducts.[]', 'isPreloading', function () {
        const paidProducts = this.model.products?.filter(product => product.type === 'paid' && product.active === true);
        if (this.isPreloading || !paidProducts?.length) {
            return [];
        }

        const products = paidProducts.map((product) => {
            return {
                id: product.id,
                name: product.name,
                checked: product.visibility === 'public'
            };
        });
        return products;
    }),

    showPortalTiers: computed('products', 'feature.multipleProducts', function () {
        if (this.feature.get('multipleProducts')) {
            return true;
        }
        return false;
    }),

    showPortalPrices: computed('products', 'feature.multipleProducts', function () {
        if (!this.feature.get('multipleProducts')) {
            return true;
        }

        const visibleProducts = this.model.products?.filter((product) => {
            return product.visibility === 'public' && product.type === 'paid';
        });

        return !!visibleProducts?.length;
    }),

    init() {
        this._super(...arguments);
        this.buttonStyleOptions = [
            {name: 'icon-and-text', label: 'Icon and text'},
            {name: 'icon-only', label: 'Icon only'},
            {name: 'text-only', label: 'Text only'}
        ];
        this.iconExtensions = ICON_EXTENSIONS;
        this.changedProducts = [];
        this.set('supportAddress', this.parseEmailAddress(this.settings.get('membersSupportAddress')));
    },

    didInsertElement() {
        this._super(...arguments);
        this.settings.get('errors').clear();
    },

    actions: {
        toggleFreePlan(isChecked) {
            this.updateAllowedPlan('free', isChecked);
        },
        togglePlan(plan, event) {
            this.updateAllowedPlan(plan, event.target.checked);
        },
        toggleProduct(productId, event) {
            this.updateAllowedProduct(productId, event.target.checked);
        },
        togglePortalButton(showButton) {
            this.settings.set('portalButton', showButton);
        },

        togglePortalName(showSignupName) {
            this.settings.set('portalName', showSignupName);
        },

        confirm() {
            return this.saveTask.perform();
        },

        isPlanSelected(plan) {
            const allowedPlans = this.settings.get('portalPlans');
            return allowedPlans.includes(plan);
        },

        switchPreviewPage(page) {
            if (page === 'links') {
                this.set('showLinksPage', true);
                this.set('page', '');
            } else {
                this.set('showLinksPage', false);
                this.set('page', page);
            }
        },

        switchToSignupPage() {
            if (this.showLinksPage) {
                this.set('showLinksPage', false);
                this.set('page', 'signup');
            }
        },

        setButtonStyle(buttonStyle) {
            this.settings.set('portalButtonStyle', buttonStyle.name);
        },

        setSignupButtonText(event) {
            this.settings.set('portalButtonSignupText', event.target.value);
        },
        /**
         * Fired after an image upload completes
         * @param  {string} property - Property name to be set on `this.settings`
         * @param  {UploadResult[]} results - Array of UploadResult objects
         * @return {string} The URL that was set on `this.settings.property`
         */
        imageUploaded(property, results) {
            if (results[0]) {
                this.set('customIcon', results[0].url);
                this.settings.set('portalButtonIcon', results[0].url);
            }
        },
        /**
         * Opens a file selection dialog - Triggered by "Upload Image" buttons,
         * searches for the hidden file input within the .gh-setting element
         * containing the clicked button then simulates a click
         * @param  {MouseEvent} event - MouseEvent fired by the button click
         */
        triggerFileDialog(event) {
            // simulate click to open file dialog
            // using jQuery because IE11 doesn't support MouseEvent
            $(event.target)
                .closest('.gh-setting-action')
                .find('input[type="file"]')
                .click();
        },

        deleteCustomIcon() {
            this.set('customIcon', null);
            this.settings.set('portalButtonIcon', this.membersUtils.defaultIconKeys[0]);
        },

        selectDefaultIcon(icon) {
            this.settings.set('portalButtonIcon', icon);
        },

        closeLeaveSettingsModal() {
            this.set('showLeaveSettingsModal', false);
        },

        openStripeSettings() {
            this.isWaitingForStripeConnection = true;
            this.model.openStripeSettings();
        },

        leaveSettings() {
            this.closeModal();
        },

        validateFreeSignupRedirect() {
            return this._validateSignupRedirect(this.freeSignupRedirect, 'membersFreeSignupRedirect');
        },

        validatePaidSignupRedirect() {
            return this._validateSignupRedirect(this.paidSignupRedirect, 'membersPaidSignupRedirect');
        },

        setSupportAddress(supportAddress) {
            this.set('supportAddress', supportAddress);
        }
    },

    parseEmailAddress(address) {
        const emailAddress = address || 'noreply';
        // Adds default domain as site domain
        if (emailAddress.indexOf('@') < 0 && this.config.emailDomain) {
            return `${emailAddress}@${this.config.emailDomain}`;
        }
        return emailAddress;
    },

    updateAllowedPlan(plan, isChecked) {
        const portalPlans = this.settings.get('portalPlans') || [];
        const allowedPlans = [...portalPlans];
        const freeProduct = this.model.products.find(p => p.type === 'free');

        if (!isChecked) {
            this.settings.set('portalPlans', allowedPlans.filter(p => p !== plan));
            if (plan === 'free') {
                freeProduct.set('visibility', 'none');
            }
        } else {
            allowedPlans.push(plan);
            this.settings.set('portalPlans', allowedPlans);
            if (plan === 'free') {
                freeProduct.set('visibility', 'public');
            }
        }
    },

    updateAllowedProduct(productId, isChecked) {
        const product = this.model.products.find(p => p.id === productId);
        if (!isChecked) {
            product.set('visibility', 'none');
        } else {
            product.set('visibility', 'public');
        }
        let portalProducts = this.model.products.filter((p) => {
            return p.visibility === 'public';
        }).map(p => p.id);
        this.set('changedProducts', portalProducts);
    },

    _validateSignupRedirect(url, type) {
        let errMessage = `Please enter a valid URL`;
        this.settings.get('errors').remove(type);
        this.settings.get('hasValidated').removeObject(type);

        if (url === null) {
            this.settings.get('errors').add(type, errMessage);
            this.settings.get('hasValidated').pushObject(type);
            return false;
        }

        if (url === undefined) {
            // Not initialised
            return;
        }

        if (url.href.startsWith(this.siteUrl)) {
            const path = url.href.replace(this.siteUrl, '');
            this.settings.set(type, path);
        } else {
            this.settings.set(type, url.href);
        }
    },

    finishPreloading: action(async function () {
        if (this.model.preloadTask?.isRunning) {
            await this.model.preloadTask;
        }

        const portalButtonIcon = this.settings.get('portalButtonIcon') || '';
        if (portalButtonIcon && !this.membersUtils.defaultIconKeys.includes(portalButtonIcon)) {
            this.set('customIcon', this.settings.get('portalButtonIcon'));
        }

        this.siteUrl = this.config.get('blogUrl');
        this.set('isPreloading', false);
    }),

    refreshAfterStripeConnected: action(async function () {
        if (this.isWaitingForStripeConnection) {
            await this.finishPreloading();
            this.notifyPropertyChange('page'); // force preview url to recompute
            this.set('portalPreviewGuid', Date.now().valueOf()); // force preview re-render
            this.isWaitingForStripeConnection = false;
        }
    }),

    copyLinkOrAttribute: task(function* () {
        copyTextToClipboard(this.showModalLinkOrAttribute);
        yield timeout(this.isTesting ? 50 : 3000);
    }),

    saveTask: task(function* () {
        this.send('validateFreeSignupRedirect');
        this.send('validatePaidSignupRedirect');
        if (this.settings.get('errors').length !== 0) {
            return;
        }

        // Save tier visibility if changed
        yield Promise.all(
            this.model.products.filter((product) => {
                const changedAttrs = product.changedAttributes();
                return !!changedAttrs.visibility;
            }).map((product) => {
                return product.save();
            })
        );

        yield this.settings.save();

        this.closeModal();
    }).drop(),

    updateSupportAddress: task(function* () {
        let url = this.get('ghostPaths.url').api('/settings/members/email');
        try {
            const response = yield this.ajax.post(url, {
                data: {
                    email: this.supportAddress,
                    type: 'supportAddressUpdate'
                }
            });
            // this.toggleProperty('showSupportAddressConfirmation');
            return response;
        } catch (e) {
            // Failed to send email, retry
            return false;
        }
    }).drop()
});
<|endoftext|>


<commit_before>import Ember from 'ember';
import permissions from 'ember-osf/const/permissions';
import CpPanelBodyComponent from 'ember-collapsible-panel/components/cp-panel-body';
import PreprintFormFieldMixin from '../mixins/preprint-form-field';

export default CpPanelBodyComponent.extend(PreprintFormFieldMixin, {
    READ: permissions.READ,
    WRITE: permissions.WRITE,
    ADMIN: permissions.ADMIN,
    permissionChanges: {},
    bibliographicChanges: {},
    valid: Ember.computed.alias('newContributorId'),
    permissionToggle: false,
    bibliographicToggle: false,
    removalToggle: false,
    stillAdmin: Ember.computed('isAdmin', function() {
        return this.get('isAdmin');
    }),
    addState: 'emptyView', // There are 3 view states on left side of Authors panel. Default state just shows search bar.
    query: null,
    // Total contributor search results
    totalSearchResults: Ember.computed('searchResults.[]', function() {
        let searchResults = this.get('searchResults');
        if (searchResults && searchResults.links) {
            return Math.ceil(searchResults.links.meta.total);
        } else {
            return;
        }
    }),
    // Total pages of contributor search results
    pages: Ember.computed('totalSearchResults', function() {
        let totalSearchResults = this.get('totalSearchResults');
        return Math.ceil(totalSearchResults / 10);
    }),
    // Search results excluding users that are already contributors
    newSearchResults: Ember.computed('searchResults.[]', 'contributors.[]', 'addState', function() {
        let searchResults = this.get('searchResults');
        let contributors = this.get('contributors');
        let userIds = contributors.map((contrib) => contrib.id.split('-')[1]);
        return searchResults.filter((result) => !Ember.A(userIds).contains(result.id));
    }),
    actions: {
        // Adds contributor then redraws view - addition of contributor may change which update/remove contributor requests are permitted
        addContributor(user) {
            this.sendAction('addContributor', user.id, 'write', true);
            this.toggleProperty('permissionToggle');
            this.toggleProperty('bibliographicToggle');
            this.toggleProperty('removalToggle');
        },
        // Adds unregistered contributor, then clears form and switches back to search view.
        // Should wait to transition until request has completed.
        addUnregisteredContributor(fullName, email) {
            let res = this.attrs.addUnregisteredContributor(fullName, email, 'write', true);
            var _this = this;
            res.then(function() {
                _this.toggleProperty('bibliographicToggle');
                _this.toggleProperty('permissionToggle');
                _this.toggleProperty('removalToggle');
            });
            this.set('addState', 'searchView');
            this.set('fullName', '');
            this.set('email', '');
        },
        updateQuery(value) {
            this.set('query', value);
        },
        // Requests a particular page of user results
        findContributors(page) {
            var query = this.get('query');
            if (query) {
                this.attrs.findContributors(query, page).then(() => {
                    this.set('addState', 'searchView');
                }, function(reason){
                    console.log(reason.errors[0].detail)
                })
            }
        },
        // Removes contributor then redraws contributor list view - removal of contributor may change
        // which additional update/remove requests are permitted.
        removeContributor(contrib) {
            this.sendAction('removeContributor', contrib);
            this.toggleProperty('removalToggle');
            this.toggleProperty('permissionToggle');
            this.toggleProperty('bibliographicToggle');
            this.removedSelfAsAdmin(contrib, contrib.get('permission'));
            this.get('contributors').removeObject(contrib);
        },
        // Updates contributor then redraws contributor list view - updating contributor
        // permissions may change which additional update/remove requests are permitted.
        updatePermissions(contributor, permission) {
            this.set(`permissionChanges.${contributor.id}`, permission.toLowerCase());
            this.sendAction(
                'editContributors',
                this.get('contributors'),
                this.get('permissionChanges'),
                {}
            );
            this.set('permissionChanges', {});
            this.toggleProperty('permissionToggle');
            this.toggleProperty('removalToggle');
            this.removedSelfAsAdmin(contributor, permission);

        },
        // Updates contributor then redraws contributor list view - updating contributor
        // bibliographic info may change which additional update/remove requests are permitted.
        updateBibliographic(contributor, isBibliographic) {
            this.set(`bibliographicChanges.${contributor.id}`, isBibliographic);
            this.sendAction(
                'editContributors',
                this.get('contributors'),
                {},
                this.get('bibliographicChanges')
            );
            this.set('bibliographicChanges', {});
            this.toggleProperty('bibliographicToggle');
            this.toggleProperty('removalToggle');
        },
        // There are 3 view states on left side of Authors panel.  This switches to add unregistered contrib view.
        unregisteredView() {
            this.set('addState', 'unregisteredView');
        },
        // There are 3 view states on left side of Authors panel.  This switches to searching contributor results view.
        searchView() {
            this.set('addState', 'searchView');
            this.set('fullName', '');
            this.set('email', '');
        },
        // There are 3 view states on left side of Authors panel.  This switches to empty view and clears search results.
        emptyView() {
            this.set('addState', 'emptyView');
            this.set('searchQuery', '');
            Ember.$('.searchQuery')[0].value = '';
        },
        // TODO Add server request when API functionality in place.
        reorderItems(itemModels, draggedModel) {
            this.set('contributors', itemModels);
        }
    },
    // Temporary popover content for authors page. Will need to find alternative to jquery selectors.
    didInsertElement: function() {
        Ember.$('#permissions-popover').popover({
            content: '
' +
                '
Read
' +
                    '

View preprint

' +
                '
Read + Write
' +
                    '

Read privileges
 ' +
                        '
Add and configure preprint
 ' +
                        '
Add and edit content

' +
                '
Administrator

' +
                    '
Read and write privileges
' +
                    '
Manage authors
' +
                    '
Public-private settings

' +
                '
'
        });
        Ember.$('#bibliographic-popover').popover({
            content: 'Only checked authors will be included in preprint citations. ' +
            'Authors not in the citation can read and modify the preprint as normal.'
        });
        Ember.$('#author-popover').popover({
            content: 'Preprints must have at least one registered administrator and one author showing in the citation at all times.  ' +
            'A registered administrator is a user who has both confirmed their account and has administrator privileges.'
        });
    },
    /**
    * If user removes their own admin permissions, many things on the page must become
    * disabled.  Changing the stillAdmin flag to false will remove many of the options
    * on the page.
    */
    removedSelfAsAdmin(contributor, permission) {
        if (this.get('currentUser').id === contributor.id.split('-')[1] && permission !== 'ADMIN') {
            this.set('stillAdmin', false);
        }
    }
});
<commit_msg>Avoid using math.ceil and use field for number of pages already calculated.<commit_after>import Ember from 'ember';
import permissions from 'ember-osf/const/permissions';
import CpPanelBodyComponent from 'ember-collapsible-panel/components/cp-panel-body';
import PreprintFormFieldMixin from '../mixins/preprint-form-field';

export default CpPanelBodyComponent.extend(PreprintFormFieldMixin, {
    READ: permissions.READ,
    WRITE: permissions.WRITE,
    ADMIN: permissions.ADMIN,
    permissionChanges: {},
    bibliographicChanges: {},
    valid: Ember.computed.alias('newContributorId'),
    permissionToggle: false,
    bibliographicToggle: false,
    removalToggle: false,
    stillAdmin: Ember.computed('isAdmin', function() {
        return this.get('isAdmin');
    }),
    addState: 'emptyView', // There are 3 view states on left side of Authors panel. Default state just shows search bar.
    query: null,
    // Total contributor search results
    totalSearchResults: Ember.computed('searchResults.[]', function() {
        let searchResults = this.get('searchResults');
        if (searchResults && searchResults.links) {
            return searchResults.meta.pagination.total;
        } else {
            return;
        }
    }),
    // Total pages of contributor search results
    pages: Ember.computed('searchResults.[]', function() {
        let searchResults = this.get('searchResults');
        if (searchResults && searchResults.links) {
            return searchResults.meta.total;
        } else {
            return;
        }
    }),
    // Search results excluding users that are already contributors
    newSearchResults: Ember.computed('searchResults.[]', 'contributors.[]', 'addState', function() {
        let searchResults = this.get('searchResults');
        let contributors = this.get('contributors');
        let userIds = contributors.map((contrib) => contrib.id.split('-')[1]);
        return searchResults.filter((result) => !Ember.A(userIds).contains(result.id));
    }),
    actions: {
        // Adds contributor then redraws view - addition of contributor may change which update/remove contributor requests are permitted
        addContributor(user) {
            this.sendAction('addContributor', user.id, 'write', true);
            this.toggleProperty('permissionToggle');
            this.toggleProperty('bibliographicToggle');
            this.toggleProperty('removalToggle');
        },
        // Adds unregistered contributor, then clears form and switches back to search view.
        // Should wait to transition until request has completed.
        addUnregisteredContributor(fullName, email) {
            let res = this.attrs.addUnregisteredContributor(fullName, email, 'write', true);
            var _this = this;
            res.then(function() {
                _this.toggleProperty('bibliographicToggle');
                _this.toggleProperty('permissionToggle');
                _this.toggleProperty('removalToggle');
            });
            this.set('addState', 'searchView');
            this.set('fullName', '');
            this.set('email', '');
        },
        updateQuery(value) {
            this.set('query', value);
        },
        // Requests a particular page of user results
        findContributors(page) {
            var query = this.get('query');
            if (query) {
                this.attrs.findContributors(query, page).then(() => {
                    this.set('addState', 'searchView');
                }, function(reason){
                    console.log(reason.errors[0].detail)
                })
            }
        },
        // Removes contributor then redraws contributor list view - removal of contributor may change
        // which additional update/remove requests are permitted.
        removeContributor(contrib) {
            this.sendAction('removeContributor', contrib);
            this.toggleProperty('removalToggle');
            this.toggleProperty('permissionToggle');
            this.toggleProperty('bibliographicToggle');
            this.removedSelfAsAdmin(contrib, contrib.get('permission'));
            this.get('contributors').removeObject(contrib);
        },
        // Updates contributor then redraws contributor list view - updating contributor
        // permissions may change which additional update/remove requests are permitted.
        updatePermissions(contributor, permission) {
            this.set(`permissionChanges.${contributor.id}`, permission.toLowerCase());
            this.sendAction(
                'editContributors',
                this.get('contributors'),
                this.get('permissionChanges'),
                {}
            );
            this.set('permissionChanges', {});
            this.toggleProperty('permissionToggle');
            this.toggleProperty('removalToggle');
            this.removedSelfAsAdmin(contributor, permission);

        },
        // Updates contributor then redraws contributor list view - updating contributor
        // bibliographic info may change which additional update/remove requests are permitted.
        updateBibliographic(contributor, isBibliographic) {
            this.set(`bibliographicChanges.${contributor.id}`, isBibliographic);
            this.sendAction(
                'editContributors',
                this.get('contributors'),
                {},
                this.get('bibliographicChanges')
            );
            this.set('bibliographicChanges', {});
            this.toggleProperty('bibliographicToggle');
            this.toggleProperty('removalToggle');
        },
        // There are 3 view states on left side of Authors panel.  This switches to add unregistered contrib view.
        unregisteredView() {
            this.set('addState', 'unregisteredView');
        },
        // There are 3 view states on left side of Authors panel.  This switches to searching contributor results view.
        searchView() {
            this.set('addState', 'searchView');
            this.set('fullName', '');
            this.set('email', '');
        },
        // There are 3 view states on left side of Authors panel.  This switches to empty view and clears search results.
        emptyView() {
            this.set('addState', 'emptyView');
            this.set('searchQuery', '');
            Ember.$('.searchQuery')[0].value = '';
        },
        // TODO Add server request when API functionality in place.
        reorderItems(itemModels, draggedModel) {
            this.set('contributors', itemModels);
        }
    },
    // Temporary popover content for authors page. Will need to find alternative to jquery selectors.
    didInsertElement: function() {
        Ember.$('#permissions-popover').popover({
            content: '
' +
                '
Read
' +
                    '

View preprint

' +
                '
Read + Write
' +
                    '

Read privileges
 ' +
                        '
Add and configure preprint
 ' +
                        '
Add and edit content

' +
                '
Administrator

' +
                    '
Read and write privileges
' +
                    '
Manage authors
' +
                    '
Public-private settings

' +
                '
'
        });
        Ember.$('#bibliographic-popover').popover({
            content: 'Only checked authors will be included in preprint citations. ' +
            'Authors not in the citation can read and modify the preprint as normal.'
        });
        Ember.$('#author-popover').popover({
            content: 'Preprints must have at least one registered administrator and one author showing in the citation at all times.  ' +
            'A registered administrator is a user who has both confirmed their account and has administrator privileges.'
        });
    },
    /**
    * If user removes their own admin permissions, many things on the page must become
    * disabled.  Changing the stillAdmin flag to false will remove many of the options
    * on the page.
    */
    removedSelfAsAdmin(contributor, permission) {
        if (this.get('currentUser').id === contributor.id.split('-')[1] && permission !== 'ADMIN') {
            this.set('stillAdmin', false);
        }
    }
});
<|endoftext|>


<commit_before>var jwt = require('jsonwebtoken');
var useragent = require('useragent');
var express=require('express');

var config=require('./config');

var database=require('./database');
var db=database.db;
var polls=database.polls;

module.exports=function(app){

// Rutas
app.get('/:id', function(req, res) {
    var ses = req.session;

    if (typeof polls[req.params.id] === 'undefined') {
        res.status(404).send('ID ' + req.params.id + ' not found');
    } else {
        var agent = useragent.parse(req.headers['user-agent']);
        var dev_id = (agent.toAgent() +
            "_" + agent.os.toString() + "_" +
            agent.device.toString()).replace(/\s/g, "");
        var value = "ID:" + req.params.id + "_" + dev_id;

        // Crea el token
        var token = jwt.sign(value, config.secret);
        ses.token = token;

        var poll_collection = polls[req.params.id];
        poll_collection.insert({
            dev_id: dev_id,
            token: token
        });
        db.saveDatabase();

	var payload = {
	    success: true,
	    message: 'Token creado',
	    token: token,
	    poll: polls[req.params.id]
        };

	console.log( req.params );
        res.json( payload );

    }
});

app.get('/:id/p/:f', function(req, res) {
    var ses = req.session;

    if (typeof polls[req.params.id] === 'undefined') {
        res.status(404).send('ID ' + req.params.id + ' not found');
    } else {
        var agent = useragent.parse(req.headers['user-agent']);
        var dev_id = (agent.toAgent() +
            "_" + agent.os.toString() + "_" +
            agent.device.toString()).replace(/\s/g, "");
        var value = "ID:" + req.params.id + "_" + dev_id;

        // Crea el token
        var token = jwt.sign(value, config.secret);
        ses.token = token;

        var poll_collection = polls[req.params.id];
        poll_collection.insert({
            dev_id: dev_id,
            token: token
        });
        db.saveDatabase();

	console.log( req.params );
	res.header("Content-Type", "application/javascript");
	res.send( req.params.f + "( " + JSON.stringify(  polls[req.params.id] ) + ")")

    }
});

app.get('/:id/:token/:respuesta', function(req, res) {
    if (typeof polls[req.params.id] === 'undefined') {
        res.status(404).send('ID ' + req.params.id + ' not found');
    } else {
        var poll_collection = polls[req.params.id];
        console.log(req.params.token);
        var dev_id = poll_collection.find({
            'token': req.params.token
        });
        res.json({
            success: true,
            message: 'token correcto'
        });
    }
});

// Rutas protegidas
var api = express.Router();
app.use('/:id', api);

// Middleware para verficiar el token
api.use(function(req, res, next) {
    // Obtener el token
    var ses = req.session;

    // Verificar el token
    if (ses.token) {
        jwt.verify(ses.token, config.secret, function(err, value) {
            if (err) {
                return res.json(403, {
                    success: false,
                    message: 'Fallo al verificar el token. Acceso no autorizado'
                });
            } else {
                // Se pasa el token con la solicitud
                req.value = value;
                next();
            }
        });
    } else {
        return res.status(404).send({
            success: false,
            message: 'Token no encontrado.'
        });
    }
});

api.get('/check', function(req, res) {
    res.json(req.value);
});
};
<commit_msg>Evitando confusiones<commit_after>var jwt = require('jsonwebtoken');
var useragent = require('useragent');
var express=require('express');

var config=require('./config');

var database=require('./database');
var db=database.db;
var polls=database.polls;

module.exports=function(app){

// Rutas
app.get('/:id', function(req, res) {
    var ses = req.session;

    if (typeof polls[req.params.id] === 'undefined') {
        res.status(404).send('ID ' + req.params.id + ' not found');
    } else {
        var agent = useragent.parse(req.headers['user-agent']);
        var dev_id = (agent.toAgent() +
            "_" + agent.os.toString() + "_" +
            agent.device.toString()).replace(/\s/g, "");
        var value = "ID:" + req.params.id + "_" + dev_id;

        // Crea el token
        var token = jwt.sign(value, config.secret);
        ses.token = token;

        var poll_collection = polls[req.params.id];
        poll_collection.insert({
            dev_id: dev_id,
            token: token
        });
        db.saveDatabase();

	var payload = {
	    success: true,
	    message: 'Token creado',
	    token: token,
	    poll: polls[req.params.id]
        };

	console.log( req.params );
        res.json( payload );

    }
});

app.get('/:id/p/:f', function(req, res) {
    var ses = req.session;

    if (typeof polls[req.params.id] === 'undefined') {
        res.status(404).send('ID ' + req.params.id + ' not found');
    } else {
        var agent = useragent.parse(req.headers['user-agent']);
        var dev_id = (agent.toAgent() +
            "_" + agent.os.toString() + "_" +
            agent.device.toString()).replace(/\s/g, "");
        var value = "ID:" + req.params.id + "_" + dev_id;

        // Crea el token
        var token = jwt.sign(value, config.secret);
        ses.token = token;

        var poll_collection = polls[req.params.id];
        poll_collection.insert({
            dev_id: dev_id,
            token: token
        });
        db.saveDatabase();

	console.log( req.params );
	res.header("Content-Type", "application/javascript");
	res.send( req.params.f + "( " + JSON.stringify(  polls[req.params.id] ) + ")")

    }
});

app.put('/:id/:token/:respuesta', function(req, res) {
    if (typeof polls[req.params.id] === 'undefined') {
        res.status(404).send('ID ' + req.params.id + ' not found');
    } else {
        var poll_collection = polls[req.params.id];
        console.log(req.params.token);
        var dev_id = poll_collection.find({
            'token': req.params.token
        });
        res.json({
            success: true,
            message: 'token correcto'
        });
    }
});

// Rutas protegidas
var api = express.Router();
app.use('/:id', api);

// Middleware para verficiar el token
api.use(function(req, res, next) {
    // Obtener el token
    var ses = req.session;

    // Verificar el token
    if (ses.token) {
        jwt.verify(ses.token, config.secret, function(err, value) {
            if (err) {
                return res.json(403, {
                    success: false,
                    message: 'Fallo al verificar el token. Acceso no autorizado'
                });
            } else {
                // Se pasa el token con la solicitud
                req.value = value;
                next();
            }
        });
    } else {
        return res.status(404).send({
            success: false,
            message: 'Token no encontrado.'
        });
    }
});

api.get('/check', function(req, res) {
    res.json(req.value);
});
};
<|endoftext|>


<commit_before>import Ember from 'ember';
import CpPanelBodyComponent from 'ember-collapsible-panel/components/cp-panel-body';
import { permissionSelector } from 'ember-osf/const/permissions';

export default CpPanelBodyComponent.extend({
    valid: Ember.computed.alias('newContributorId'),
    authorModification: false,
    // Permissions labels for dropdown
    permissionOptions: permissionSelector,
    addState: 'emptyView', // There are 3 view states on left side of Authors panel. Default state just shows search bar.
    query: null,
    // Total contributor search results
    totalSearchResults: Ember.computed('searchResults.[]', function() {
        let searchResults = this.get('searchResults');
        if (searchResults && searchResults.links) {
            return searchResults.meta.pagination.total;
        } else {
            return;
        }
    }),
    // Total pages of contributor search results
    pages: Ember.computed('searchResults.[]', function() {
        let searchResults = this.get('searchResults');
        if (searchResults && searchResults.links) {
            return searchResults.meta.total;
        } else {
            return;
        }
    }),
    actions: {
        // Adds contributor then redraws view - addition of contributor may change which update/remove contributor requests are permitted
        addContributor(user) {
            this.attrs.addContributor(user.id, 'write', true, undefined, undefined, false).then((res) => {
                this.toggleAuthorModification();
                this.get('contributors').pushObject(res);
                this.highlightSuccessOrFailure(res.id, this, 'success');
            }, () => {
                this.get('toast').error('Could not add contributor.');
                this.highlightSuccessOrFailure(user.id, this, 'error');
                user.rollbackAttributes();
            });
        },
        // Adds unregistered contributor, then clears form and switches back to search view.
        // Should wait to transition until request has completed.
        addUnregisteredContributor(fullName, email) {
            let res = this.attrs.addContributor(null, 'write', true, fullName, email, false);
            res.then((contributor) => {
                this.get('contributors').pushObject(contributor);
                this.toggleAuthorModification();
                this.set('addState', 'searchView');
                this.set('fullName', '');
                this.set('email', '');
                this.highlightSuccessOrFailure(contributor.id, this, 'success');
            }, () => {
                this.get('toast').error('Could not add unregistered contributor.');
                this.highlightSuccessOrFailure('add-unregistered-contributor-form', this, 'error');
            });

        },
        // Requests a particular page of user results
        findContributors(page) {
            var query = this.get('query');
            if (query) {
                this.attrs.findContributors(query, page).then(() => {
                    this.set('addState', 'searchView');
                }, () => {
                    this.get('toast').error('Could not perform search query.');
                    this.highlightSuccessOrFailure('author-search-box', this, 'error');
                });
            }
        },
        // Removes contributor then redraws contributor list view - removal of contributor may change
        // which additional update/remove requests are permitted.
        removeContributor(contrib) {
            this.attrs.removeContributor(contrib).then(() => {
                this.toggleAuthorModification();
                this.removedSelfAsAdmin(contrib, contrib.get('permission'));
                this.get('contributors').removeObject(contrib);
            }, () => {
                this.get('toast').error('Could not remove author');
                this.highlightSuccessOrFailure(contrib.id, this, 'error');
                contrib.rollbackAttributes();
            });
        },
        // Updates contributor then redraws contributor list view - updating contributor
        // permissions may change which additional update/remove requests are permitted.
        updatePermissions(contributor, permission) {
            this.attrs.editContributor(contributor, permission, '').then(() => {
                this.toggleAuthorModification();
                this.highlightSuccessOrFailure(contributor.id, this, 'success');
                this.removedSelfAsAdmin(contributor, permission);
            }, () => {
                this.get('toast').error('Could not modify author permissions');
                this.highlightSuccessOrFailure(contributor.id, this, 'error');
                contributor.rollbackAttributes();
            });
        },
        // Updates contributor then redraws contributor list view - updating contributor
        // bibliographic info may change which additional update/remove requests are permitted.
        updateBibliographic(contributor, isBibliographic) {
            this.attrs.editContributor(contributor, '', isBibliographic).then(() => {
                this.toggleAuthorModification();
                this.highlightSuccessOrFailure(contributor.id, this, 'success');
            }, () => {
                this.get('toast').error('Could not modify citation');
                this.highlightSuccessOrFailure(contributor.id, this, 'error');
                contributor.rollbackAttributes();
            });
        },
        // There are 3 view states on left side of Authors panel.  This switches to add unregistered contrib view.
        unregisteredView() {
            this.set('addState', 'unregisteredView');
        },
        // There are 3 view states on left side of Authors panel.  This switches to searching contributor results view.
        searchView() {
            this.set('addState', 'searchView');
            this.set('fullName', '');
            this.set('email', '');
        },
        // There are 3 view states on left side of Authors panel.  This switches to empty view and clears search results.
        resetfindContributorsView() {
            this.set('addState', 'searchView');
        },
        // Reorders contributors in UI then sends server request to reorder contributors. If request fails, reverts
        // contributor list in UI back to original.
        reorderItems(itemModels, draggedContrib) {
            var originalOrder = this.get('contributors');
            this.set('contributors', itemModels);
            var newIndex = itemModels.indexOf(draggedContrib);
            this.attrs.reorderContributors(draggedContrib, newIndex).then(() => {
                this.highlightSuccessOrFailure(draggedContrib.id, this, 'success');
            }, () => {
                this.highlightSuccessOrFailure(draggedContrib.id, this, 'error');
                this.set('contributors', originalOrder);
                this.get('toast').error('Could not reorder contributors');
                draggedContrib.rollbackAttributes();
            });
        }
    },
    // TODO find alternative to jquery selectors. Temporary popover content for authors page.
    didInsertElement: function() {
        this.$('#permissions-popover').popover({
            container: 'body',
            content: '
' +
                '
Read
' +
                    '

View preprint

' +
                '
Read + Write
' +
                    '

Read privileges
 ' +
                        '
Add and configure preprint
 ' +
                        '
Add and edit content

' +
                '
Administrator

' +
                    '
Read and write privileges
' +
                    '
Manage authors
' +
                    '
Public-private settings

' +
                '
'
        });
        this.$('#bibliographic-popover').popover({
            container: 'body',
            content: 'Only checked authors will be included in preprint citations. ' +
            'Authors not in the citation can read and modify the preprint as normal.'
        });
        this.$('#author-popover').popover({
            container: 'body',
            content: 'Preprints must have at least one registered administrator and one author showing in the citation at all times.  ' +
            'A registered administrator is a user who has both confirmed their account and has administrator privileges.'
        });
    },
    /**
    * If user removes their own admin permissions, many things on the page must become
    * disabled.  Changing the isAdmin flag to false will remove many of the options
    * on the page.
    */
    removedSelfAsAdmin(contributor, permission) {
        if (this.get('currentUser').id === contributor.get('userId') && permission !== 'ADMIN') {
            this.set('isAdmin', false);
        }
    },
    /**
    * Toggling this property, authorModification, updates several items on the page - disabling elements, enabling
    * others, depending on what requests are permitted
    */
    toggleAuthorModification() {
        this.toggleProperty('authorModification');
    }
});
<commit_msg>Change preprints back to sending emails automatically.<commit_after>import Ember from 'ember';
import CpPanelBodyComponent from 'ember-collapsible-panel/components/cp-panel-body';
import { permissionSelector } from 'ember-osf/const/permissions';

export default CpPanelBodyComponent.extend({
    valid: Ember.computed.alias('newContributorId'),
    authorModification: false,
    // Permissions labels for dropdown
    permissionOptions: permissionSelector,
    addState: 'emptyView', // There are 3 view states on left side of Authors panel. Default state just shows search bar.
    query: null,
    // Total contributor search results
    totalSearchResults: Ember.computed('searchResults.[]', function() {
        let searchResults = this.get('searchResults');
        if (searchResults && searchResults.links) {
            return searchResults.meta.pagination.total;
        } else {
            return;
        }
    }),
    // Total pages of contributor search results
    pages: Ember.computed('searchResults.[]', function() {
        let searchResults = this.get('searchResults');
        if (searchResults && searchResults.links) {
            return searchResults.meta.total;
        } else {
            return;
        }
    }),
    actions: {
        // Adds contributor then redraws view - addition of contributor may change which update/remove contributor requests are permitted
        addContributor(user) {
            this.attrs.addContributor(user.id, 'write', true, undefined, undefined, true).then((res) => {
                this.toggleAuthorModification();
                this.get('contributors').pushObject(res);
                this.highlightSuccessOrFailure(res.id, this, 'success');
            }, () => {
                this.get('toast').error('Could not add contributor.');
                this.highlightSuccessOrFailure(user.id, this, 'error');
                user.rollbackAttributes();
            });
        },
        // Adds unregistered contributor, then clears form and switches back to search view.
        // Should wait to transition until request has completed.
        addUnregisteredContributor(fullName, email) {
            let res = this.attrs.addContributor(null, 'write', true, fullName, email, true);
            res.then((contributor) => {
                this.get('contributors').pushObject(contributor);
                this.toggleAuthorModification();
                this.set('addState', 'searchView');
                this.set('fullName', '');
                this.set('email', '');
                this.highlightSuccessOrFailure(contributor.id, this, 'success');
            }, () => {
                this.get('toast').error('Could not add unregistered contributor.');
                this.highlightSuccessOrFailure('add-unregistered-contributor-form', this, 'error');
            });

        },
        // Requests a particular page of user results
        findContributors(page) {
            var query = this.get('query');
            if (query) {
                this.attrs.findContributors(query, page).then(() => {
                    this.set('addState', 'searchView');
                }, () => {
                    this.get('toast').error('Could not perform search query.');
                    this.highlightSuccessOrFailure('author-search-box', this, 'error');
                });
            }
        },
        // Removes contributor then redraws contributor list view - removal of contributor may change
        // which additional update/remove requests are permitted.
        removeContributor(contrib) {
            this.attrs.removeContributor(contrib).then(() => {
                this.toggleAuthorModification();
                this.removedSelfAsAdmin(contrib, contrib.get('permission'));
                this.get('contributors').removeObject(contrib);
            }, () => {
                this.get('toast').error('Could not remove author');
                this.highlightSuccessOrFailure(contrib.id, this, 'error');
                contrib.rollbackAttributes();
            });
        },
        // Updates contributor then redraws contributor list view - updating contributor
        // permissions may change which additional update/remove requests are permitted.
        updatePermissions(contributor, permission) {
            this.attrs.editContributor(contributor, permission, '').then(() => {
                this.toggleAuthorModification();
                this.highlightSuccessOrFailure(contributor.id, this, 'success');
                this.removedSelfAsAdmin(contributor, permission);
            }, () => {
                this.get('toast').error('Could not modify author permissions');
                this.highlightSuccessOrFailure(contributor.id, this, 'error');
                contributor.rollbackAttributes();
            });
        },
        // Updates contributor then redraws contributor list view - updating contributor
        // bibliographic info may change which additional update/remove requests are permitted.
        updateBibliographic(contributor, isBibliographic) {
            this.attrs.editContributor(contributor, '', isBibliographic).then(() => {
                this.toggleAuthorModification();
                this.highlightSuccessOrFailure(contributor.id, this, 'success');
            }, () => {
                this.get('toast').error('Could not modify citation');
                this.highlightSuccessOrFailure(contributor.id, this, 'error');
                contributor.rollbackAttributes();
            });
        },
        // There are 3 view states on left side of Authors panel.  This switches to add unregistered contrib view.
        unregisteredView() {
            this.set('addState', 'unregisteredView');
        },
        // There are 3 view states on left side of Authors panel.  This switches to searching contributor results view.
        searchView() {
            this.set('addState', 'searchView');
            this.set('fullName', '');
            this.set('email', '');
        },
        // There are 3 view states on left side of Authors panel.  This switches to empty view and clears search results.
        resetfindContributorsView() {
            this.set('addState', 'searchView');
        },
        // Reorders contributors in UI then sends server request to reorder contributors. If request fails, reverts
        // contributor list in UI back to original.
        reorderItems(itemModels, draggedContrib) {
            var originalOrder = this.get('contributors');
            this.set('contributors', itemModels);
            var newIndex = itemModels.indexOf(draggedContrib);
            this.attrs.reorderContributors(draggedContrib, newIndex).then(() => {
                this.highlightSuccessOrFailure(draggedContrib.id, this, 'success');
            }, () => {
                this.highlightSuccessOrFailure(draggedContrib.id, this, 'error');
                this.set('contributors', originalOrder);
                this.get('toast').error('Could not reorder contributors');
                draggedContrib.rollbackAttributes();
            });
        }
    },
    // TODO find alternative to jquery selectors. Temporary popover content for authors page.
    didInsertElement: function() {
        this.$('#permissions-popover').popover({
            container: 'body',
            content: '
' +
                '
Read
' +
                    '

View preprint

' +
                '
Read + Write
' +
                    '

Read privileges
 ' +
                        '
Add and configure preprint
 ' +
                        '
Add and edit content

' +
                '
Administrator

' +
                    '
Read and write privileges
' +
                    '
Manage authors
' +
                    '
Public-private settings

' +
                '
'
        });
        this.$('#bibliographic-popover').popover({
            container: 'body',
            content: 'Only checked authors will be included in preprint citations. ' +
            'Authors not in the citation can read and modify the preprint as normal.'
        });
        this.$('#author-popover').popover({
            container: 'body',
            content: 'Preprints must have at least one registered administrator and one author showing in the citation at all times.  ' +
            'A registered administrator is a user who has both confirmed their account and has administrator privileges.'
        });
    },
    /**
    * If user removes their own admin permissions, many things on the page must become
    * disabled.  Changing the isAdmin flag to false will remove many of the options
    * on the page.
    */
    removedSelfAsAdmin(contributor, permission) {
        if (this.get('currentUser').id === contributor.get('userId') && permission !== 'ADMIN') {
            this.set('isAdmin', false);
        }
    },
    /**
    * Toggling this property, authorModification, updates several items on the page - disabling elements, enabling
    * others, depending on what requests are permitted
    */
    toggleAuthorModification() {
        this.toggleProperty('authorModification');
    }
});
<|endoftext|>


<commit_before>//importing our Reading model, which has a predefined schema you can see in Readings.js
var mongoose       = require('mongoose');
var Reading        = require('./models/Readings.js');
var Usage          = require('./models/Usage.js');
var data           = require('./data.json');
var bodyParser     = require('body-parser');

module.exports = function(app) {

	// server routes ===========================================================
	// handle things like api calls
	// authentication routes
	// sample api route

var new_load = new Array();

//this should be how we add to the database from now on
/*
app.use(bodyParser.json());
app.use(bodyParser.urlEncoded({extended: true}));
*/
app.post('/load/WaterQuality', function(req, res) {
  var new_load = req.body.data;
  var return_string = "";
  for(i = 0; i < new_load.length; i++) {
    new_load[i].timestamp = new Date(new_load[i].timestamp);
  }
  Reading.create(new_load, function(err, added) {
    if(err)
      res.send(err);
    for(i = 1; i < arguments.length; i++) {
      return_string = return_string + "" + arguments[i] + "\n";
    }
  });
  res.send(return_string);
});

app.post('/load/Usage', function(req, res) {
  var new_load = req.body.data;
  var return_string = "";
  for(i = 0; i < new_load.length; i++) {
    new_load[i].timestamp = new Date(new_load[i].timestamp);
  }
  Usage.create(new_load, function(err, added) {
    if(err)
      res.send(err);
    for(i = 1; i < arguments.length; i++) {
      return_string = return_string + "" + arguments[i] + "\n";
    }
  });
  res.send(return_string);
});
/**
 * Handles the server request to retrieve n records from the database
 * Returns n records from the database... i hope these are most recent
 */
app.post('/api/data/WaterQuality', function(req, res) {
  var n = parseInt(req.body.data);

  Reading.find().sort({timestamp : -1}).limit(n).exec(function(err, readingDetails) {
   // if there is an error retrieving, send the error.
   // nothing after res.send(err) will execute
    if (err)
    res.send(err);
      res.json(readingDetails); // return all nerds in JSON format
  });
});

/**
 * Handles the server request to retrieve n records from the database
 * Returns n records from the database... i hope these are most recent
 */
app.post('/api/data/Usage', function(req, res) {
  var n = parseInt(req.body.data);

  Usage.find().sort({timestamp : -1}).limit(n).exec(function(err, readingDetails) {
   // if there is an error retrieving, send the error.
   // nothing after res.send(err) will execute
    if (err)
    res.send(err);
      res.json(readingDetails); // return all nerds in JSON format
  });
});

app.get('/reset', function(req, res) {
  /* To remove all data points in the thing */
  Reading.remove(function(err) {
    if(err)
      handleError(err);
  });
  Usage.remove(function(err) {
    if(err)
      handleError(err);
  });

  //loads json from our current data file just as a test of json
  //this will not be necessary at all when the app.post function is
  //uncommented and working
  for(i = 0; i < data.waterQuality.length; i++) {
    Reading.create({
      temperature: data.waterQuality[i].temperature,
      turbidity: data.waterQuality[i].turbidity,
      conductivity: data.waterQuality[i].conductivity,
      sodium: data.waterQuality[i].sodium,
      magnesium: data.waterQuality[i].magnesium,
      calcium: data.waterQuality[i].calcium,
      pH: data.waterQuality[i].pH,
      timestamp: new Date(data.waterQuality[i].timestamp),
      usage: data.waterQuality[i].usage
    });
  }
  for(i = 0; i < data.usageData.length; i++) {
    Usage.create({
      timestamp: new Date(data.usageData[i].timestamp),
      usage: data.usageData[i].usage
    });
  }

  res.send("success");
});

app.get('/api/data/WaterQuality', function(req, res) {
  // use mongoose to get all nerds in the database
  Reading.find(function(err, readingDetails) {
   // if there is an error retrieving, send the error.
   // nothing after res.send(err) will execute
   if (err)
    res.send(err);
  res.json(readingDetails); // return all nerds in JSON format
  });
});

app.get('/api/data/Usage', function(req, res) {
  // use mongoose to get all nerds in the database
  Usage.find(function(err, usageDetails) {
   // if there is an error retrieving, send the error.
   // nothing after res.send(err) will execute
   if (err)
    res.send(err);
  res.json(usageDetails); // return all nerds in JSON format
  });
});

 // frontend routes =========================================================
 app.get('*', function(req, res) {
  res.sendfile('./public/login.html');
 });
}<commit_msg>update post request to dynamically load data<commit_after>//importing our Reading model, which has a predefined schema you can see in Readings.js
var mongoose       = require('mongoose');
var Reading        = require('./models/Readings.js');
var Usage          = require('./models/Usage.js');
var data           = require('./data.json');
var bodyParser     = require('body-parser');

module.exports = function(app) {

	// server routes ===========================================================
	// handle things like api calls
	// authentication routes
	// sample api route

var new_load = new Array();

//this should be how we add to the database from now on
/*
app.use(bodyParser.json());
app.use(bodyParser.urlEncoded({extended: true}));
*/
app.post('/load/WaterQuality', function(req, res) {
  var new_load = req.body;
  var return_string = "success";
  for(i = 0; i < new_load.length; i++) {
    new_load[i].timestamp = new Date(new_load[i].timestamp);
  }
  Reading.create(new_load, function(err, added) {
    if(err)
      res.send(err);
    res.json(added);
  });
});

app.post('/load/Usage', function(req, res) {
  var new_load = req.body;
  var return_string = "success\n";
  for(i = 0; i < new_load.length; i++) {
    new_load[i].timestamp = new Date(new_load[i].timestamp);
  }
  Usage.create(new_load, function(err, added) {
    if(err)
      res.send(err);
    res.json(added);
  });
});
/**
 * Handles the server request to retrieve n records from the database
 * Returns n records from the database... i hope these are most recent
 */
app.post('/api/data/WaterQuality', function(req, res) {
  var n = parseInt(req.body.data);

  Reading.find().sort({timestamp : -1}).limit(n).exec(function(err, readingDetails) {
   // if there is an error retrieving, send the error.
   // nothing after res.send(err) will execute
    if (err)
    res.send(err);
      res.json(readingDetails); // return all nerds in JSON format
  });
});

/**
 * Handles the server request to retrieve n records from the database
 * Returns n records from the database... i hope these are most recent
 */
app.post('/api/data/Usage', function(req, res) {
  var n = parseInt(req.body.data);

  Usage.find().sort({timestamp : -1}).limit(n).exec(function(err, readingDetails) {
   // if there is an error retrieving, send the error.
   // nothing after res.send(err) will execute
    if (err)
    res.send(err);
      res.json(readingDetails); // return all nerds in JSON format
  });
});

app.get('/reset', function(req, res) {
  /* To remove all data points in the thing */
  Reading.remove(function(err) {
    if(err)
      handleError(err);
  });
  Usage.remove(function(err) {
    if(err)
      handleError(err);
  });

  //loads json from our current data file just as a test of json
  //this will not be necessary at all when the app.post function is
  //uncommented and working
  for(i = 0; i < data.waterQuality.length; i++) {
    Reading.create({
      temperature: data.waterQuality[i].temperature,
      turbidity: data.waterQuality[i].turbidity,
      conductivity: data.waterQuality[i].conductivity,
      sodium: data.waterQuality[i].sodium,
      magnesium: data.waterQuality[i].magnesium,
      calcium: data.waterQuality[i].calcium,
      pH: data.waterQuality[i].pH,
      timestamp: new Date(data.waterQuality[i].timestamp),
      usage: data.waterQuality[i].usage
    });
  }
  for(i = 0; i < data.usageData.length; i++) {
    Usage.create({
      timestamp: new Date(data.usageData[i].timestamp),
      usage: data.usageData[i].usage
    });
  }

  res.send("success");
});

app.get('/api/data/WaterQuality', function(req, res) {
  // use mongoose to get all nerds in the database
  Reading.find(function(err, readingDetails) {
   // if there is an error retrieving, send the error.
   // nothing after res.send(err) will execute
   if (err)
    res.send(err);
  res.json(readingDetails); // return all nerds in JSON format
  });
});

app.get('/api/data/Usage', function(req, res) {
  // use mongoose to get all nerds in the database
  Usage.find(function(err, usageDetails) {
   // if there is an error retrieving, send the error.
   // nothing after res.send(err) will execute
   if (err)
    res.send(err);
  res.json(usageDetails); // return all nerds in JSON format
  });
});

 // frontend routes =========================================================
 app.get('*', function(req, res) {
  res.sendfile('./public/login.html');
 });
}<|endoftext|>


<commit_before>/**
 * Created by teamco on 27/05/2017.
 */

import {PageRulesVisualizer} from './page.rules.visualizer';
import go from 'gojs';

/**
 * @class GenerateRules
 * @type {GenerateRules}
 */
export class GenerateRules extends PageRulesVisualizer {

  /**
   * GenerateRules
   * @class GenerateRules
   * @param {string} id
   * @param {Page} page
   * @constructor
   */
  constructor(id, page) {
    super();

    /**
     * @property GenerateRules
     * @type {string}
     */
    this.id = id;

    /**
     * @property GenerateRules
     * @type {Page}
     */
    this.page = page;

    /**
     * @property GenerateRules
     * @type {{}}
     */
    this.cache = {};
  }

  /**
   * createDiagram
   * @method createDiagram
   * @memberOf GenerateRules
   */
  createDiagram() {

    if (!go) {
      this.scope.logger.warn('Go.js should be initialized');
      return false;
    }

    const _make = go.GraphObject.make;

    /**
     * @constant
     * @type {HTMLDivElement|HTMLElement}
     */
    const div = document.getElementById(this.id);

    if (!div) {
      this.scope.logger.warn('<DIV> should be in DOM');
      return false;
    }

    /**
     * @property GenerateRules
     * @type {go.Diagram}
     */
    this.diagram = _make(go.Diagram, this.id, {
      layout: _make(go.LayeredDigraphLayout)
    });

    this.diagram.initialContentAlignment = go.Spot.Center;
    this.diagram.nodeTemplate = GenerateRules.defineTemplate(go);
    this.diagram.model = this.updateModel(go);
    this.diagram.undoManager.isEnabled = true;

    this.diagram.addDiagramListener('ObjectSingleClicked', this.objectSingleClicked.bind(this));
    this.diagram.ModelChanged = GenerateRules.modelChanged.bind(this);
    this.diagram.addDiagramListener('ChangedSelection', this.changedSelection.bind(this));

    this.updatePublishedRules();
    this.updateSubscriberRules();
  }

  /**
   * Whenever a GoJS transaction has finished modifying the model
   * @method modelChanged
   * @memberOf GenerateRules
   * @param {Event|{isTransactionFinished}} e
   * @static
   */
  static modelChanged(e) {
    if (e.isTransactionFinished) {
      //saveModel();
    }
  }

  /**
   * Update model when the Diagram.selection changes
   * @method changedSelection
   * @memberOf GenerateRules
   */
  changedSelection() {
    this.diagram.model.selectedNodeData = null;
    const it = this.diagram.selection.iterator;
    while (it.next()) {
      const node = it.value;
      // Ignore a selected link or a deleted node
      if (node instanceof go.Node && node.data) {
        this.diagram.model.selectedNodeData = node.data;
        break;
      }
    }
  }

  /**
   * @method objectSingleClicked
   * @memberOf GenerateRules
   * @param {Event|{subject}} e
   */
  objectSingleClicked(e) {
    const part = e.subject.part;
    if (part instanceof go.Link) {
    } else {
      this.diagram.clearHighlighteds();
    }
  }

  /**
   * updateConnectivity
   * @method updateConnectivity
   * @memberOf GenerateRules
   * @param node
   * @param link
   * @param port
   * @static
   */
  static updateConnectivity(node, link, port) {
    const shape = node.findObject('shape'),
        condition = node.findLinksInto().count;
    shape.stroke = condition ? 'lightcoral' : 'green';
    shape.strokeWidth = 2;
  }

  /**
   * @method defineTemplate
   * @memberOf GenerateRules
   * @param go
   * @returns {go.Part}
   * @static
   */
  static defineTemplate(go) {
    const _make = go.GraphObject.make;
    const node = _make(go.Node, 'Auto', {click: GenerateRules.showConnections},
        _make(go.Shape, new go.Binding('figure', 'figure'), {
              name: 'shape',
              strokeWidth: 0.5,
              portId: '',
              cursor: 'pointer',
              fromLinkable: true,
              toLinkable: true,
              fromLinkableSelfNode: true,
              toLinkableSelfNode: true,
              fromLinkableDuplicates: true,
              toLinkableDuplicates: true
            },
            new go.Binding('fill', 'color'),
            new go.Binding('stroke', 'isHighlighted', h => h ? 'red' : 'black').ofObject()));

    node.linkConnected = GenerateRules.updateConnectivity;
    node.linkDisconnected = GenerateRules.updateConnectivity;

    node.add(_make(go.Picture, {
          width: 32,
          height: 32,
          alignment: go.Spot.Center,
          opacity: 0.5,
          margin: new go.Margin(10, 10)
        },
        {sourceCrossOrigin: () => 'use-credentials'},
        new go.Binding('source', 'path'))
    );

    node.add(_make(go.TextBlock, {
          margin: new go.Margin(5, 10),
          font: 'normal 14px Tahoma',
          cursor: 'pointer'
        },
        new go.Binding('text', 'title'))
    );

    return node;
  }

  /**
   * @method updateModel
   * @memberOf GenerateRules
   * @param go
   * @returns {go.GraphLinksModel}
   */
  updateModel(go) {
    return new go.GraphLinksModel(GenerateRules.getWidgets(this.page));
  }

  /**
   * updateDiagram
   * @method updateDiagram
   * @memberOf GenerateRules
   * @param {Object} rule
   */
  updateDiagram(rule) {
    this.diagram.model.addNodeData({
      key: rule.key,
      figure: 'Rectangle',
      color: rule.color,
      title: `${rule.title}: (${rule.count})`,
      description: rule.title.humanize()
    });
    this.updateLink(
        this.diagram.findNodeForKey(rule.uuid),
        this.diagram.findNodeForKey(rule.key), {event: false}
    );
  }

  /**
   * @method updateLink
   * @memberOf GenerateRules
   * @param from
   * @param to
   * @param opts
   */
  updateLink(from, to, opts) {
    opts = opts || {};
    const color = opts.color || 'black',
        event = opts.event,
        _make = go.GraphObject.make;

    const _fKey = from.data.key,
        _tKey = to.data.key;

    if (event) {
      const _from = this.cache[_fKey];
      if (_from) {
        if (_from.indexOf(_tKey) > -1) {
          return false;
        } else {
          this.cache[_fKey].push(_tKey);
        }
      } else {
        this.cache[_fKey] = [_tKey];
      }
    }

    this.diagram.add(
        _make(go.Link, {
              fromNode: from,
              toNode: to,
              adjusting: go.Link.Stretch,
              routing: go.Link.AvoidsNodes,
              curve: go.Link.JumpOver,
              toShortLength: 3,
              corner: 5,
              reshapable: true,
              relinkableFrom: true,
              relinkableTo: true
            },
            _make(go.Shape, {
              isPanelMain: true,
              stroke: color,
              strokeWidth: 1
            }),
            _make(go.Shape, {
              toArrow: 'Standard',
              stroke: null,
              strokeWidth: 0
            })
        )
    );
  }

  /**
   * updatePublishedRules
   * @method updatePublishedRules
   * @memberOf GenerateRules
   */
  updatePublishedRules() {
    const published = this.getWidgetPublishedRules(this.page);
    const that = this;
    const _ = this.page.utils._;
    _.each(published, rules => _.each(rules, rule => that.updateDiagram(rule)));
  }

  /**
   * updateSubscriberRules
   * @method updateSubscriberRules
   * @memberOf GenerateRules
   */
  updateSubscriberRules() {
    const subscribed = this.getWidgetSubscriberRules(this.page);
    const that = this;
    const _ = this.page.utils._;
    _.each(subscribed, sData =>
        _.each(sData, data =>
            _.each(data.subscribers, uuids =>
                _.each(uuids, uuid =>
                    that.updateLink(
                        that.diagram.findNodeForKey(data.key),
                        that.diagram.findNodeForKey(uuid), {event: true})))));
  }

  /**
   * Highlight all Links and Nodes coming out of a given Node
   * @method showConnections
   * @memberOf GenerateRules
   * @param e
   * @param node
   * @static
   */
  static showConnections(e, node) {
    const diagram = node.diagram;
    diagram.startTransaction('highlight');
    // remove any previous highlighting
    diagram.clearHighlighteds();
    // for each Link coming out of the Node, set Link.isHighlighted
    node.findLinksOutOf().each(l => l.isHighlighted = true);
    // for each Node destination for the Node, set Node.isHighlighted
    node.findNodesOutOf().each(n => n.isHighlighted = true);
    diagram.commitTransaction('highlight');
  }
}<commit_msg>cont fix rules diagram<commit_after>/**
 * Created by teamco on 27/05/2017.
 */

import {PageRulesVisualizer} from './page.rules.visualizer';
import go from 'gojs';

/**
 * @class GenerateRules
 * @type {GenerateRules}
 */
export class GenerateRules extends PageRulesVisualizer {

  /**
   * GenerateRules
   * @class GenerateRules
   * @param {string} id
   * @param {Page} page
   * @constructor
   */
  constructor(id, page) {
    super();

    /**
     * @property GenerateRules
     * @type {string}
     */
    this.id = id;

    /**
     * @property GenerateRules
     * @type {Page}
     */
    this.page = page;

    /**
     * @property GenerateRules
     * @type {{}}
     */
    this.cache = {};
  }

  /**
   * createDiagram
   * @method createDiagram
   * @memberOf GenerateRules
   */
  createDiagram() {

    if (!go) {
      this.scope.logger.warn('Go.js should be initialized');
      return false;
    }

    const _make = go.GraphObject.make;

    /**
     * @constant
     * @type {HTMLDivElement|HTMLElement}
     */
    const div = document.getElementById(this.id);

    if (!div) {
      this.scope.logger.warn('<DIV> should be in DOM');
      return false;
    }

    /**
     * @property GenerateRules
     * @type {go.Diagram}
     */
    this.diagram = _make(go.Diagram, this.id, {
      layout: _make(go.LayeredDigraphLayout),
      initialDocumentSpot: go.Spot.TopCenter,
      initialViewportSpot: go.Spot.TopCenter,
      initialAutoScale: go.Diagram.UniformToFill
    });

    this.diagram.initialContentAlignment = go.Spot.Center;
    this.diagram.nodeTemplate = GenerateRules.defineTemplate(go);
    this.diagram.model = this.updateModel(go);
    this.diagram.undoManager.isEnabled = true;

    this.diagram.addDiagramListener('ObjectSingleClicked', this.objectSingleClicked.bind(this));
    this.diagram.ModelChanged = GenerateRules.modelChanged.bind(this);
    this.diagram.addDiagramListener('ChangedSelection', this.changedSelection.bind(this));

    this.updatePublishedRules();
    this.updateSubscriberRules();
  }

  /**
   * Whenever a GoJS transaction has finished modifying the model
   * @method modelChanged
   * @memberOf GenerateRules
   * @param {Event|{isTransactionFinished}} e
   * @static
   */
  static modelChanged(e) {
    if (e.isTransactionFinished) {
      //saveModel();
    }
  }

  /**
   * Update model when the Diagram.selection changes
   * @method changedSelection
   * @memberOf GenerateRules
   */
  changedSelection() {
    this.diagram.model.selectedNodeData = null;
    const it = this.diagram.selection.iterator;
    while (it.next()) {
      const node = it.value;
      // Ignore a selected link or a deleted node
      if (node instanceof go.Node && node.data) {
        this.diagram.model.selectedNodeData = node.data;
        break;
      }
    }
  }

  /**
   * @method objectSingleClicked
   * @memberOf GenerateRules
   * @param {Event|{subject}} e
   */
  objectSingleClicked(e) {
    const part = e.subject.part;
    if (part instanceof go.Link) {
    } else {
      this.diagram.clearHighlighteds();
    }
  }

  /**
   * updateConnectivity
   * @method updateConnectivity
   * @memberOf GenerateRules
   * @param node
   * @param link
   * @param port
   * @static
   */
  static updateConnectivity(node, link, port) {
    const shape = node.findObject('shape'),
        condition = node.findLinksInto().count;
    shape.stroke = condition ? 'lightcoral' : 'green';
    shape.strokeWidth = 2;
  }

  /**
   * @method defineTemplate
   * @memberOf GenerateRules
   * @param go
   * @returns {go.Part}
   * @static
   */
  static defineTemplate(go) {
    const _make = go.GraphObject.make;
    const node = _make(go.Node, 'Auto', {click: GenerateRules.showConnections},
        _make(go.Shape, new go.Binding('figure', 'figure'), {
              name: 'shape',
              strokeWidth: 0.5,
              portId: '',
              cursor: 'pointer',
              fromLinkable: true,
              toLinkable: true,
              fromLinkableSelfNode: true,
              toLinkableSelfNode: true,
              fromLinkableDuplicates: true,
              toLinkableDuplicates: true
            },
            new go.Binding('fill', 'color'),
            new go.Binding('stroke', 'isHighlighted', h => h ? 'red' : 'gray').ofObject()));

    node.linkConnected = GenerateRules.updateConnectivity;
    node.linkDisconnected = GenerateRules.updateConnectivity;

    node.add(_make(go.Picture, {
          width: 32,
          height: 32,
          alignment: go.Spot.Center,
          opacity: 0.5,
          margin: new go.Margin(10, 10)
        },
        {sourceCrossOrigin: () => 'use-credentials'},
        new go.Binding('source', 'path'))
    );

    node.add(_make(go.TextBlock, {
          margin: new go.Margin(5, 10),
          font: 'normal 14px Tahoma',
          cursor: 'pointer'
        },
        new go.Binding('text', 'title'))
    );

    return node;
  }

  /**
   * @method updateModel
   * @memberOf GenerateRules
   * @param go
   * @returns {go.GraphLinksModel}
   */
  updateModel(go) {
    return new go.GraphLinksModel(GenerateRules.getWidgets(this.page));
  }

  /**
   * updateDiagram
   * @method updateDiagram
   * @memberOf GenerateRules
   * @param {Object} rule
   */
  updateDiagram(rule) {
    this.diagram.model.addNodeData({
      key: rule.key,
      figure: 'Ellipse',
      color: rule.color,
      title: `${rule.title}: (${rule.count})`,
      description: rule.title.humanize()
    });
    this.updateLink(
        this.diagram.findNodeForKey(rule.uuid),
        this.diagram.findNodeForKey(rule.key), {event: false}
    );
  }

  /**
   * @method updateLink
   * @memberOf GenerateRules
   * @param from
   * @param to
   * @param opts
   */
  updateLink(from, to, opts) {
    opts = opts || {};
    const color = opts.color || 'black',
        event = opts.event,
        _make = go.GraphObject.make;

    const _fKey = from.data.key,
        _tKey = to.data.key;

    if (event) {
      const _from = this.cache[_fKey];
      if (_from) {
        if (_from.indexOf(_tKey) > -1) {
          return false;
        } else {
          this.cache[_fKey].push(_tKey);
        }
      } else {
        this.cache[_fKey] = [_tKey];
      }
    }

    this.diagram.add(
        _make(go.Link, {
              fromNode: from,
              toNode: to,
              adjusting: go.Link.Stretch,
              routing: go.Link.AvoidsNodes,
              curve: go.Link.JumpOver,
              toShortLength: 3,
              corner: 5,
              reshapable: true,
              relinkableFrom: true,
              relinkableTo: true
            },
            _make(go.Shape, {
              isPanelMain: true,
              stroke: color,
              strokeWidth: 1
            }),
            _make(go.Shape, {
              toArrow: 'Standard',
              stroke: null,
              strokeWidth: 0
            })
        )
    );
  }

  /**
   * updatePublishedRules
   * @method updatePublishedRules
   * @memberOf GenerateRules
   */
  updatePublishedRules() {
    const published = this.getWidgetPublishedRules(this.page);
    const that = this;
    const _ = this.page.utils._;
    _.each(published, rules => _.each(rules, rule => that.updateDiagram(rule)));
  }

  /**
   * updateSubscriberRules
   * @method updateSubscriberRules
   * @memberOf GenerateRules
   */
  updateSubscriberRules() {
    const subscribed = this.getWidgetSubscriberRules(this.page);
    const that = this;
    const _ = this.page.utils._;
    _.each(subscribed, sData =>
        _.each(sData, data =>
            _.each(data.subscribers, uuids =>
                _.each(uuids, uuid =>
                    that.updateLink(
                        that.diagram.findNodeForKey(data.key),
                        that.diagram.findNodeForKey(uuid), {event: true})))));
  }

  /**
   * Highlight all Links and Nodes coming out of a given Node
   * @method showConnections
   * @memberOf GenerateRules
   * @param e
   * @param node
   * @static
   */
  static showConnections(e, node) {
    const diagram = node.diagram;
    diagram.startTransaction('highlight');
    // remove any previous highlighting
    diagram.clearHighlighteds();
    // for each Link coming out of the Node, set Link.isHighlighted
    node.findLinksOutOf().each(l => l.isHighlighted = true);
    // for each Node destination for the Node, set Node.isHighlighted
    node.findNodesOutOf().each(n => n.isHighlighted = true);
    diagram.commitTransaction('highlight');
  }
}<|endoftext|>