wikimedia/mediawiki-extensions-UploadWizard

View on GitHub
resources/metadata/uw.MetadataContent.js

Summary

Maintainability
C
7 hrs
Test Coverage
( function ( uw ) {
    'use strict';

    /*
     * External dependencies (WikibaseMediaInfo, Wikibase datamodel & config vars) are a
     * soft dependency and may not be available here. We'll attempt to lazy-load them from
     * uw.MetadataContent constructor and will then populate these vars on success.
     */
    var AddPropertyWidget,
        StatementWidget,
        dataTypesMap,
        propertyDataValuesTypes,
        defaultProperties,
        propertyTypes,
        wikibaseDatamodel;

    /**
     * @class
     * @param {mw.UploadWizardUpload} upload
     * @param {Object} [config] Configuration options
     */
    uw.MetadataContent = function UWMetadataContent( upload, config ) {
        var $titleDiv,
            $filenameDiv,
            $thumbnailDiv,
            self = this;

        uw.MetadataContent.super.call(
            this,
            $.extend( { classes: [ 'mwe-upwiz-metadata-content' ] }, config )
        );

        // Set up widget data
        this.upload = upload;
        this.entityId = undefined;
        this.statementWidgets = {};

        // Build the UI
        $titleDiv = $( '<h2>' )
            .addClass( 'mwe-upwiz-metadata-content-caption' )
            .text( upload.details.getThumbnailCaption() );
        $filenameDiv = $( '<p>' )
            .addClass( 'mwe-upwiz-metadata-content-filename' )
            .text( upload.details.getTitle().getMain() );
        $thumbnailDiv = $( '<div>' )
            .addClass( 'mwe-upwiz-metadata-content-thumbnail' );
        this.$statementsDiv = $( '<div>' )
            .addClass( 'mwe-upwiz-metadata-content-statements' );

        // Copy all button
        this.copyAllStatementsButton = new OO.ui.ButtonWidget( {
            label: mw.message( 'mwe-upwiz-copy-statements-button' ).text(),
            disabled: true,
            classes: [ 'mwe-upwiz-metadata-content-copy-all' ]
        } );

        this.copyAllStatementsButton.connect( this, { click: 'copyStatementsToAllFiles' } );

        // Load thumbnail and append elements to page once ready
        upload.getThumbnail( 630, 360 ).then( ( thumb ) => {
            mw.UploadWizard.placeThumbnail( $thumbnailDiv, thumb );
            self.$element.prepend(
                $titleDiv,
                $filenameDiv,
                $thumbnailDiv
            );
        } );

        // Call the setup method and append statementWidgets once ready
        this.$element.append( this.$statementsDiv );
        mw.loader.using( [
            'wikibase.mediainfo.statements',
            'wikibase.datamodel'
        ] ).then( ( require ) => {
            AddPropertyWidget = require( 'wikibase.mediainfo.statements' ).AddPropertyWidget;
            StatementWidget = require( 'wikibase.mediainfo.statements' ).StatementWidget;
            wikibaseDatamodel = require( 'wikibase.datamodel' );
            dataTypesMap = mw.config.get( 'wbDataTypes' );
            defaultProperties = mw.config.get( 'wbmiDefaultProperties' ) || [];
            propertyTypes = mw.config.get( 'wbmiPropertyTypes' ) || {};
            propertyDataValuesTypes = [];

            Object.keys( propertyTypes ).forEach( ( propertyId ) => {
                propertyDataValuesTypes[ propertyId ] = dataTypesMap[ propertyTypes[ propertyId ] ].dataValueType;
            } );

            ( mw.UploadWizard.config.defaults.statements || [] ).forEach( ( data ) => {
                if ( defaultProperties.indexOf( data.propertyId ) < 0 ) {
                    defaultProperties.push( data.propertyId );
                }
                propertyDataValuesTypes[ data.propertyId ] = data.dataType;
            } );

            Object.freeze( defaultProperties );
        } ).then( self.setup() ).then( () => {
            Object.keys( self.statementWidgets ).forEach( ( propertyId ) => {
                var statementWidget = self.statementWidgets[ propertyId ];
                self.$statementsDiv.append( statementWidget.$element );
            } );

            self.createAddPropertyWidgetIfNecessary();

            if ( config.allowCopy ) {
                self.$element.append( self.copyAllStatementsButton.$element );
            }
        } );
    };

    OO.inheritClass( uw.MetadataContent, OO.ui.Widget );

    /**
     * Setup method. Fetch the mediainfo ID of the relevant file and create
     * widgets for default statements. If default statements are added,
     * a "change" event is emitted so that the user can immediately publish
     * using the suggested data.
     *
     * @return {jQuery.Promise}
     * @fires change
     */
    uw.MetadataContent.prototype.setup = function () {
        var self = this;

        return this.upload.details.getMediaInfoEntityId().then( ( entityId ) => {
            self.entityId = entityId;

            // Create a statement widget for each default property and set its datatype
            defaultProperties.forEach( ( propertyId ) => {
                var defaultData = self.getDefaultDataForProperty( propertyId ),
                    widget = self.createStatementWidget(
                        propertyId,
                        propertyDataValuesTypes[ propertyId ],
                        defaultData
                    );

                self.$statementsDiv.append( widget.$element );

                // pre-populate statements with data if necessary (campaigns only);
                // default values are still considered "changes" to be published
                if ( !defaultData.isEmpty() ) {
                    self.emit( 'change' );
                }
            } );
        } );
    };

    /**
     * @param {string} propertyId
     * @return {wikibaseDatamodel.StatementList}
     */
    uw.MetadataContent.prototype.getDefaultDataForProperty = function ( propertyId ) {
        var defaultStatements = mw.UploadWizard.config.defaults.statements,
            defaultData;

        if ( !defaultStatements ) {
            return new wikibaseDatamodel.StatementList();
        }

        defaultData = defaultStatements.filter( ( statement ) => statement.propertyId === propertyId && statement.values )[ 0 ];

        if ( !defaultData || !defaultData.values ) {
            return new wikibaseDatamodel.StatementList();
        }

        return new wikibaseDatamodel.StatementList(
            defaultData.values.map( ( itemId ) => new wikibaseDatamodel.Statement(
                new wikibaseDatamodel.Claim(
                    new wikibaseDatamodel.PropertyValueSnak(
                        propertyId,
                        new wikibaseDatamodel.EntityId( itemId )
                    ),
                    null,
                    null
                )
            ) )
        );
    };

    /**
     * Creates and sets up the AddPropertyWidget if other statements are enabled.
     */
    uw.MetadataContent.prototype.createAddPropertyWidgetIfNecessary = function () {
        // let's always create the widget, that way we don't have to check for its
        // existence everywhere - but we won't add it to DOM if it's not wanted
        this.addPropertyWidget = new AddPropertyWidget( {
            propertyIds: Object.keys( this.statementWidgets )
        } );

        this.addPropertyWidget.connect( this, { choose: 'onPropertyAdded' } );

        if ( mw.UploadWizard.config.wikibase.nonDefaultStatements !== false ) {
            this.$element.append( this.addPropertyWidget.$element );
        }
    };

    /**
     * Creates and returns a new StatementWidget, with appropriate event handlers
     * attached. Also stashes the statement's property ID.
     *
     * @param {string} propId P123, etc.
     * @param {string} valueType 'wikibase-entityid', 'string', etc.
     * @param {wikibaseDatamodel.Statement} [data]
     * @return {Object} StatementWidget
     */
    uw.MetadataContent.prototype.createStatementWidget = function ( propId, valueType, data ) {
        var self = this,
            widget;

        data = data || new wikibaseDatamodel.StatementList();

        widget = new StatementWidget( {
            editing: true,
            entityId: this.entityId,
            propertyId: propId,
            valueType: valueType,
            isDefaultProperty: defaultProperties.indexOf( propId ) >= 0,
            helpUrls: mw.config.get( 'wbmiHelpUrls' ) || {}
        } );

        // don't start subscribing to events until statementwidget has been
        // pre-populated with initial data
        widget.setData( data ).then( () => {
            widget.connect( self, { widgetRemoved: 'onStatementWidgetRemoved' } );
            widget.connect( self, { change: 'enableCopyAllButton' } );
            widget.connect( self, { change: [ 'emit', 'change' ] } );
        } );

        this.statementWidgets[ propId ] = widget;
        return widget;
    };

    /**
     * @param {Object.<StatementWidget>} statementWidgets Map of { property id: StatementWidget }
     */
    uw.MetadataContent.prototype.applyCopiedStatements = function ( statementWidgets ) {
        var self = this;

        // 1. remove all existing statementWidgets
        Object.keys( this.statementWidgets ).forEach( ( propId ) => {
            self.onStatementWidgetRemoved( propId );
        } );

        // 2. re-create statement widgets for each PID in the statementWidgets array
        // NOTE: this currently loses "default-ness"
        Object.keys( statementWidgets ).forEach( ( propertyId ) => {
            var statementWidget = statementWidgets[ propertyId ],
                sourceData = statementWidget.getData(),
                targetData,
                valueType;

            if ( sourceData.isEmpty() ) {
                // skip empty widgets
                return;
            }

            // construct a new StatementList which is a copy of the existing list,
            // just a new instance (like, different GUID)
            targetData = new wikibaseDatamodel.StatementList(
                sourceData.toArray().map( ( statement ) => new wikibaseDatamodel.Statement(
                    new wikibaseDatamodel.Claim(
                        statement.getClaim().getMainSnak(),
                        statement.getClaim().getQualifiers(),
                        null
                    ),
                    statement.getReferences(),
                    statement.getRank()
                ) )
            );

            valueType = sourceData.toArray()[ 0 ].getClaim().getMainSnak().getValue().getType();

            self.createStatementWidget( propertyId, valueType, targetData );
        } );

        // 3. Append newly-copied statementWidgets to the page
        Object.keys( this.statementWidgets ).forEach( ( propertyId ) => {
            var statementWidget = self.statementWidgets[ propertyId ];
            self.$statementsDiv.append( statementWidget.$element );
        } );
    };

    /**
     * Handles the selection by user of a new property to add to page. Creates a
     * new appropriate StatementWidget, updates internal datatype map, and
     * appends widget to DOM.
     *
     * @param {Object} item
     * @param {Object} data
     */
    uw.MetadataContent.prototype.onPropertyAdded = function ( item, data ) {
        var propertyId = data.id,
            statementWidget;

        statementWidget = this.createStatementWidget( propertyId, dataTypesMap[ data.datatype ].dataValueType );
        this.addPropertyWidget.$element.before( statementWidget.$element );
    };

    /**
     * Handles the removal of StatementWidgets.
     * Also cleans up the propertyID and statementWidget arrays.
     *
     * @param {string} propertyId
     * @throws {Error} Raises error if any item to remove is not found
     */
    uw.MetadataContent.prototype.onStatementWidgetRemoved = function ( propertyId ) {
        var removedWidget;

        if ( !( propertyId in this.statementWidgets ) ) {
            throw new Error( 'Statement widget for property ' + propertyId + ' not found' );
        }
        removedWidget = this.statementWidgets[ propertyId ];

        removedWidget.$element.remove();
        delete this.statementWidgets[ propertyId ];
        this.addPropertyWidget.onStatementPanelRemoved( propertyId );
    };

    uw.MetadataContent.prototype.copyStatementsToAllFiles = function () {
        var self = this;

        OO.ui.confirm(
            mw.msg( 'mwe-upwiz-copy-statements-dialog' ),
            {
                actions: [
                    {
                        action: 'accept',
                        label: mw.msg( 'mwe-upwiz-copy-statements-dialog-accept' ),
                        flags: [ 'primary', 'progressive' ]
                    },
                    {
                        action: 'reject',
                        label: mw.msg( 'ooui-dialog-message-reject' ),
                        flags: 'safe'
                    }
                ]
            }
        ).then( ( confirmed ) => {
            var statements = self.getStatements();
            if ( confirmed ) {
                self.emit( 'copyToAll', statements, self.upload.file.name );
                self.disableCopyAllButton();
            }
        } );
    };

    uw.MetadataContent.prototype.enableCopyAllButton = function () {
        this.copyAllStatementsButton.setLabel( mw.message( 'mwe-upwiz-copy-statements-button' ).text() );
        this.copyAllStatementsButton.setDisabled( false );
    };

    uw.MetadataContent.prototype.disableCopyAllButton = function () {
        this.copyAllStatementsButton.setLabel( mw.message( 'mwe-upwiz-copy-statements-button-done' ).text() );
        this.copyAllStatementsButton.setDisabled( true );
    };

    /**
     * @return {Object.<StatementWidget>} Map of { property id: StatementWidget }
     */
    uw.MetadataContent.prototype.getStatements = function () {
        return this.statementWidgets;
    };

}( mw.uploadWizard ) );