<commit_before>/**
 * Created by ksinger on 26.11.2015.
 */

import { actionTypes } from '../actions/actions';
import { repeatVar } from '../utils';
import Immutable from 'immutable';
import { createReducer } from 'redux-immutablejs'
import { combineReducersStable } from '../redux-utils';

export const draftsReducer = createReducer(
    //initial state
    Immutable.Map(),

    //handlers
    {
        /**
         * @param {*} draftId : the draft that's type has changed
         * @param {string} type : the short-hand won-type (e.g. `'won:Demand'`)
         */
        [actionTypes.drafts.change.type]: (drafts, {payload:{draftId, type}}) => {
            //TODO use json-ld for state
            const stateWithDraft = guaranteeDraftExistence(drafts, draftId);
            return type ?
                stateWithDraft.setIn([draftId, 'type'], type) :
                stateWithDraft.deleteIn([draftId, 'type'])

        },
        /**
         * @param {*} draftId : the draft that's title has changed
         * @param {string} title : any user-entered text, e.g. `'I am moving and need a new couch.'`
         */
        [actionTypes.drafts.change.title]: (drafts, {payload:{draftId, title}}) => {
            //TODO use json-ld for state
            const stateWithDraft = guaranteeDraftExistence(drafts, draftId);
            return title ?
                stateWithDraft.setIn([draftId, 'title'], title) :
                stateWithDraft.deleteIn([draftId, 'title'])
        },
        /**
         * @param {*} draftId : the draft that's thumbnail has changed
         * @param {object} image : e.g. `{ name: 'somepic.png', type: 'image/png', data: 'iVBORw0...gAAI1=' }`
         */
        [actionTypes.drafts.change.thumbnail]: (drafts, {payload:{draftId, image}}) => {
            //TODO use json-ld for state
            const stateWithDraft = guaranteeDraftExistence(drafts, draftId);
            console.log('changed thumbnail ', image);
            return stateWithDraft.setIn([draftId, 'thumbnail'], Immutable.fromJS(image));
        },

        [actionTypes.drafts.publish]: (drafts, {payload:{draftId, needUri}}) =>
            guaranteeDraftExistence(drafts, draftId)
                .setIn([draftId, 'pendingPublishingAs'], needUri),

        [actionTypes.drafts.publishSuccessful]: (drafts, {payload:{ needUri }}) => {
            const draftId = drafts
                .filter(draft => draft.get('pendingPublishingAs') === needUri)
                .map(draft => draft.get('draftId'))
                .first();
            return drafts.remove(draftId);
        }

        //TODO delete draft once it's completely empty
        //TODO init drafts from server
        //TODO isValidNeed function to check compliance to won-ontology (and throw errors early)
    }
)

/**
 * Adds an empty draft to the state if it doesn't exist yet
 * @param drafts
 * @param draftId
 * @returns {*}
 */
function guaranteeDraftExistence(drafts, draftId) {
    if(drafts.get(draftId)) {
        return drafts
    } else {
        const defaultDraft = Immutable.fromJS({ draftId });
        return drafts.set(draftId, defaultDraft);
    }
}
<commit_msg>clear drafts state on logout<commit_after>/**
 * Created by ksinger on 26.11.2015.
 */

import { actionTypes } from '../actions/actions';
import { repeatVar } from '../utils';
import Immutable from 'immutable';
import { createReducer } from 'redux-immutablejs'
import { combineReducersStable } from '../redux-utils';

const initialState = Immutable.fromJS({});

export const draftsReducer = createReducer(
    //initial state
    Immutable.Map(),

    //handlers
    {
        [actionTypes.logout]: () => {
            return initialState;
        },
        /**
         * @param {*} draftId : the draft that's type has changed
         * @param {string} type : the short-hand won-type (e.g. `'won:Demand'`)
         */
        [actionTypes.drafts.change.type]: (drafts, {payload:{draftId, type}}) => {
            //TODO use json-ld for state
            const stateWithDraft = guaranteeDraftExistence(drafts, draftId);
            return type ?
                stateWithDraft.setIn([draftId, 'type'], type) :
                stateWithDraft.deleteIn([draftId, 'type'])

        },
        /**
         * @param {*} draftId : the draft that's title has changed
         * @param {string} title : any user-entered text, e.g. `'I am moving and need a new couch.'`
         */
        [actionTypes.drafts.change.title]: (drafts, {payload:{draftId, title}}) => {
            //TODO use json-ld for state
            const stateWithDraft = guaranteeDraftExistence(drafts, draftId);
            return title ?
                stateWithDraft.setIn([draftId, 'title'], title) :
                stateWithDraft.deleteIn([draftId, 'title'])
        },
        /**
         * @param {*} draftId : the draft that's thumbnail has changed
         * @param {object} image : e.g. `{ name: 'somepic.png', type: 'image/png', data: 'iVBORw0...gAAI1=' }`
         */
        [actionTypes.drafts.change.thumbnail]: (drafts, {payload:{draftId, image}}) => {
            //TODO use json-ld for state
            const stateWithDraft = guaranteeDraftExistence(drafts, draftId);
            console.log('changed thumbnail ', image);
            return stateWithDraft.setIn([draftId, 'thumbnail'], Immutable.fromJS(image));
        },

        [actionTypes.drafts.publish]: (drafts, {payload:{draftId, needUri}}) =>
            guaranteeDraftExistence(drafts, draftId)
                .setIn([draftId, 'pendingPublishingAs'], needUri),

        [actionTypes.drafts.publishSuccessful]: (drafts, {payload:{ needUri }}) => {
            const draftId = drafts
                .filter(draft => draft.get('pendingPublishingAs') === needUri)
                .map(draft => draft.get('draftId'))
                .first();
            return drafts.remove(draftId);
        }

        //TODO delete draft once it's completely empty
        //TODO init drafts from server
        //TODO isValidNeed function to check compliance to won-ontology (and throw errors early)
    }
)

/**
 * Adds an empty draft to the state if it doesn't exist yet
 * @param drafts
 * @param draftId
 * @returns {*}
 */
function guaranteeDraftExistence(drafts, draftId) {
    if(drafts.get(draftId)) {
        return drafts
    } else {
        const defaultDraft = Immutable.fromJS({ draftId });
        return drafts.set(draftId, defaultDraft);
    }
}
<|endoftext|>


<commit_before>import inherited from 'mastodon/locales/ja.json';

const messages = {
  'getting_started.open_source_notice': 'Glitchsocは{Mastodon}によるフリーなオープンソースソフトウェアです。誰でもGitHub（{github}）から開発に參加したり、問題を報告したりできます。',
  'layout.auto': '自動',
  'layout.current_is': 'あなたの現在のレイアウト:',
  'layout.desktop': 'デスクトップ',
  'layout.single': 'モバイル',
  'navigation_bar.app_settings': 'アプリ設定',
  'getting_started.onboarding': '解説を表示',
  'onboarding.page_one.federation': '{domain}はMastodonのインスタンスです。Mastodonとは、独立したサーバが連携して作るソーシャルネットワークです。これらのサーバーをインスタンスと呼びます。',
  'onboarding.page_one.welcome': '{domain}へようこそ！',
  'onboarding.page_six.github': '{domain}はGlitchsocを使用しています。Glitchsocは{Mastodon}のフレンドリーな{fork}で、どんなMastodonアプリやインスタンスとも互換性があります。Glitchsocは完全に無料で、オープンソースです。{github}でバグ報告や機能要望あるいは貢獻をすることが可能です。',
  'settings.always_show_spoilers_field': '常にコンテンツワーニング設定を表示する(指定がない場合は通常投稿)',
  'settings.auto_collapse': '自動折りたたみ',
  'settings.auto_collapse_all': 'すべて',
  'settings.auto_collapse_lengthy': '長いトゥート',
  'settings.auto_collapse_media': 'メディア付きトゥート',
  'settings.auto_collapse_notifications': '通知',
  'settings.auto_collapse_reblogs': 'ブースト',
  'settings.auto_collapse_replies': '返信',
  'settings.close': '閉じる',
  'settings.collapsed_statuses': 'トゥート',
  'settings.confirm_missing_media_description': '画像に対する補助記載がないときに投稿前の警告を表示する',
  'settings.content_warnings': 'コンテンツワーニング',
  'settings.content_warnings_filter': '説明に指定した文字が含まれているものを自動で展開しないようにする',
  'settings.content_warnings.regexp': '正規表現',
  'settings.enable_collapsed': 'トゥート折りたたみを有効にする',
  'settings.enable_content_warnings_auto_unfold': 'コンテンツワーニング指定されている投稿を常に表示する',
  'settings.general': '一般',
  'settings.image_backgrounds': '画像背景',
  'settings.image_backgrounds_media': '折りたまれたメディア付きトゥートをプレビュー',
  'settings.image_backgrounds_users': '折りたまれたトゥートの背景を変更する',
  'settings.media': 'メディア',
  'settings.media_letterbox': 'メディアをレターボックス式で表示',
  'settings.media_fullwidth': '全幅メディアプレビュー',
  'settings.navbar_under': 'ナビを画面下部に移動させる(モバイル レイアウトのみ)',
  'settings.notifications.favicon_badge': '通知アイコンに未読件数を表示する',
  'settings.notifications_opts': '通知の設定',
  'settings.notifications.tab_badge': '未読の通知があるとき、通知アイコンにマークを表示する',
  'settings.preferences': 'ユーザー設定',
  'settings.wide_view': 'ワイドビュー(デスクトップ レイアウトのみ)',
  'settings.compose_box_opts': 'コンポーズボックス設定',
  'settings.show_reply_counter': '投稿に対するリプライの数を表示する',
  'settings.side_arm': 'セカンダリートゥートボタン',
  'settings.side_arm.none': '表示しない',
  'settings.side_arm_reply_mode': '返信時の投稿範囲',
  'settings.side_arm_reply_mode.copy': '返信先の投稿範囲を利用する',
  'settings.side_arm_reply_mode.keep': 'セカンダリートゥートボタンの設定を維持する',
  'settings.side_arm_reply_mode.restrict': '返信先の投稿範囲に制限する',
  'settings.layout': 'レイアウト',
  'settings.layout_opts': 'レイアウトの設定',
  'status.collapse': '折りたたむ',
  'status.uncollapse': '折りたたみを解除',

  'confirmations.missing_media_description.message': '少なくとも1つの画像に視聴覚障害者のための画像説明が付与されていません。すべての画像に対して説明を付与することを望みます。',
  'confirmations.missing_media_description.confirm': 'このまま投稿',

  'favourite_modal.combo': '次からは {combo} を押せば、これをスキップできます。',

  'home.column_settings.show_direct': 'DMを表示',

  'notification.markForDeletion': '選択',
  'notifications.clear': '通知を全てクリアする',
  'notifications.marked_clear_confirmation': '削除した全ての通知を完全に削除してもよろしいですか？',
  'notifications.marked_clear': '選択した通知を削除する',

  'notification_purge.btn_all': 'すべて\n選択',
  'notification_purge.btn_none': '選択\n解除',
  'notification_purge.btn_invert': '選択を\n反転',
  'notification_purge.btn_apply': '選択したものを\n削除',

  'compose.attach.upload': 'ファイルをアップロード',
  'compose.attach.doodle': '落書きをする',
  'compose.attach': 'アタッチ...',

  'advanced_options.local-only.short': 'ローカル限定',
  'advanced_options.local-only.long': '他のインスタンスには投稿されません',
  'advanced_options.local-only.tooltip': 'この投稿はローカル限定投稿です',
  'advanced_options.icon_title': '高度な設定',
  'advanced_options.threaded_mode.short': 'スレッドモード',
  'advanced_options.threaded_mode.long': '投稿時に自動的に返信するように設定します',
  'advanced_options.threaded_mode.tooltip': 'スレッドモードを有効にする',

  'navigation_bar.direct': 'ダイレクトメッセージ',
  'navigation_bar.bookmarks': 'ブックマーク',
  'column.bookmarks': 'ブックマーク'
};

export default Object.assign({}, inherited, messages);<commit_msg>translation glitch's client setting interface [ja]<commit_after>import inherited from 'mastodon/locales/ja.json';

const messages = {
  'getting_started.open_source_notice': 'Glitchsocは{Mastodon}によるフリーなオープンソースソフトウェアです。誰でもGitHub（{github}）から開発に參加したり、問題を報告したりできます。',
  'layout.auto': '自動',
  'layout.current_is': 'あなたの現在のレイアウト:',
  'layout.desktop': 'デスクトップ',
  'layout.single': 'モバイル',
  'navigation_bar.app_settings': 'アプリ設定',
  'getting_started.onboarding': '解説を表示',
  'onboarding.page_one.federation': '{domain}はMastodonのインスタンスです。Mastodonとは、独立したサーバが連携して作るソーシャルネットワークです。これらのサーバーをインスタンスと呼びます。',
  'onboarding.page_one.welcome': '{domain}へようこそ！',
  'onboarding.page_six.github': '{domain}はGlitchsocを使用しています。Glitchsocは{Mastodon}のフレンドリーな{fork}で、どんなMastodonアプリやインスタンスとも互換性があります。Glitchsocは完全に無料で、オープンソースです。{github}でバグ報告や機能要望あるいは貢獻をすることが可能です。',
  'settings.always_show_spoilers_field': '常にコンテンツワーニング設定を表示する(指定がない場合は通常投稿)',
  'settings.auto_collapse': '自動折りたたみ',
  'settings.auto_collapse_all': 'すべて',
  'settings.auto_collapse_lengthy': '長いトゥート',
  'settings.auto_collapse_media': 'メディア付きトゥート',
  'settings.auto_collapse_notifications': '通知',
  'settings.auto_collapse_reblogs': 'ブースト',
  'settings.auto_collapse_replies': '返信',
  'settings.close': '閉じる',
  'settings.collapsed_statuses': 'トゥート折りたたみ',
  'settings.confirm_missing_media_description': '画像に対する補助記載がないときに投稿前の警告を表示する',
  'settings.content_warnings': 'コンテンツワーニング',
  'settings.content_warnings_filter': '説明に指定した文字が含まれているものを自動で展開しないようにする',
  'settings.content_warnings.regexp': '正規表現',
  'settings.enable_collapsed': 'トゥート折りたたみを有効にする',
  'settings.enable_content_warnings_auto_unfold': 'コンテンツワーニング指定されている投稿を常に表示する',
  'settings.general': '一般',
  'settings.image_backgrounds': '画像背景',
  'settings.image_backgrounds_media': '折りたまれたメディア付きトゥートをプレビュー',
  'settings.image_backgrounds_users': '折りたまれたトゥートの背景を変更する',
  'settings.media': 'メディア',
  'settings.media_letterbox': 'メディアをレターボックス式で表示',
  'settings.media_fullwidth': '全幅メディアプレビュー',
  'settings.navbar_under': 'ナビを画面下部に移動させる(モバイル レイアウトのみ)',
  'settings.notifications.favicon_badge': '通知アイコンに未読件数を表示する',
  'settings.notifications_opts': '通知の設定',
  'settings.notifications.tab_badge': '未読の通知があるとき、通知アイコンにマークを表示する',
  'settings.preferences': 'ユーザー設定',
  'settings.wide_view': 'ワイドビュー(デスクトップ レイアウトのみ)',
  'settings.compose_box_opts': 'コンポーズボックス設定',
  'settings.show_reply_counter': '投稿に対するリプライの数を表示する',
  'settings.side_arm': 'セカンダリートゥートボタン',
  'settings.side_arm.none': '表示しない',
  'settings.side_arm_reply_mode': '返信時の投稿範囲',
  'settings.side_arm_reply_mode.copy': '返信先の投稿範囲を利用する',
  'settings.side_arm_reply_mode.keep': 'セカンダリートゥートボタンの設定を維持する',
  'settings.side_arm_reply_mode.restrict': '返信先の投稿範囲に制限する',
  'settings.layout': 'レイアウト',
  'settings.layout_opts': 'レイアウトの設定',
  'status.collapse': '折りたたむ',
  'status.uncollapse': '折りたたみを解除',

  'confirmations.missing_media_description.message': '少なくとも1つの画像に視聴覚障害者のための画像説明が付与されていません。すべての画像に対して説明を付与することを望みます。',
  'confirmations.missing_media_description.confirm': 'このまま投稿',

  'favourite_modal.combo': '次からは {combo} を押せば、これをスキップできます。',

  'home.column_settings.show_direct': 'DMを表示',

  'notification.markForDeletion': '選択',
  'notifications.clear': '通知を全てクリアする',
  'notifications.marked_clear_confirmation': '削除した全ての通知を完全に削除してもよろしいですか？',
  'notifications.marked_clear': '選択した通知を削除する',

  'notification_purge.btn_all': 'すべて\n選択',
  'notification_purge.btn_none': '選択\n解除',
  'notification_purge.btn_invert': '選択を\n反転',
  'notification_purge.btn_apply': '選択したものを\n削除',

  'compose.attach.upload': 'ファイルをアップロード',
  'compose.attach.doodle': '落書きをする',
  'compose.attach': '添付...',

  'advanced_options.local-only.short': 'ローカル限定',
  'advanced_options.local-only.long': '他のインスタンスには投稿されません',
  'advanced_options.local-only.tooltip': 'この投稿はローカル限定投稿です',
  'advanced_options.icon_title': '高度な設定',
  'advanced_options.threaded_mode.short': 'スレッドモード',
  'advanced_options.threaded_mode.long': '投稿時に自動的に返信するように設定します',
  'advanced_options.threaded_mode.tooltip': 'スレッドモードを有効にする',

  'navigation_bar.direct': 'ダイレクトメッセージ',
  'navigation_bar.bookmarks': 'ブックマーク',
  'column.bookmarks': 'ブックマーク',

  'compose.content-type.html': 'HTML',
  'compose.content-type.markdown': 'マークダウン',
  'compose.content-type.plain': 'プレーンテキスト',
  'compose_form.poll.multiple_choices': '複数回答を許可',
  'compose_form.poll.single_choice': '単一回答を許可',
  'compose_form.spoiler': '本文は警告の後ろに隠す',
  'confirmation_modal.do_not_ask_again': 'もう１度尋ねない',
  'confirmations.discard_edit_media.confirm': '破棄',
  'confirmations.discard_edit_media.message': 'メディアの説明・プレビューに保存していない変更があります。破棄してもよろしいですか？',
  'content-type.change': 'コンテンツ形式を変更',
  'settings.show_action_bar': 'アクションバーを表示',
  'settings.filtering_behavior': 'フィルターの振る舞い',
  'settings.filtering_behavior.cw': '警告文にフィルターされた単語を付加して表示します',
  'settings.filtering_behavior.drop': 'フィルターされたトゥートを完全に隠します',
  'settings.filtering_behavior.hide': '\'フィルターされました\'とその理由を確認するボタンを表示する',
  'settings.filtering_behavior.upstream': '\'フィルターされました\'とバニラMastodonと同じように表示する',
  'settings.filters': 'フィルター',
  'settings.hicolor_privacy_icons': 'ハイカラーの公開範囲アイコン',
  'settings.hicolor_privacy_icons.hint': '公開範囲アイコンを明るく表示し見分けやすい色にします',
  'confirmations.missing_media_description.message': 'このトゥートには説明のないメディアが含まれています',
  'settings.confirm_boost_missing_media_description': 'メディアの説明が欠けているトゥートをブーストする前に確認ダイアログを表示する',
  'settings.tag_misleading_links': '誤解を招くリンクにタグをつける',
  'settings.tag_misleading_links.hint': '明示的に言及していないすべてのリンクに、リンクターゲットホストを含む視覚的な表示を追加します',
  'settings.rewrite_mentions': '表示されたトゥートの返信先表示を書き換える',
  'settings.rewrite_mentions_acct': 'ユーザー名とドメイン名（アカウントがリモートの場合）を表示するように書き換える',
  'settings.rewrite_mentions_no': '書き換えない',
  'settings.rewrite_mentions_username': 'ユーザー名を表示するように書き換える',
  'settings.swipe_to_change_columns': 'スワイプでカラムを切り替え可能にする（モバイルのみ）',
  'settings.prepend_cw_re': '返信するとき警告に "re: "を付加する',
  'settings.preselect_on_reply': '返信するときユーザー名を事前選択する',
  'settings.confirm_before_clearing_draft': '作成しているメッセージが上書きされる前に確認ダイアログを表示する',
  'settings.show_content_type_choice': 'トゥートを書くときコンテンツ形式の選択ボタンを表示する',
  'settings.inline_preview_cards': '外部リンクに埋め込みプレビューを有効にする',
  'settings.media_reveal_behind_cw': '既定で警告指定されているトゥートの閲覧注意メディアを表示する',
  'settings.pop_in_left': '左',
  'settings.pop_in_player': 'ポップインプレイヤーを有効化する',
  'settings.pop_in_position': 'ポップインプレーヤーの位置:',
  'settings.pop_in_right': '右',

};

export default Object.assign({}, inherited, messages);
<|endoftext|>


<commit_before>import React from 'react';
import NotificationsContainer from './containers/notifications_container';
import PropTypes from 'prop-types';
import LoadingBarContainer from './containers/loading_bar_container';
import TabsBar from './components/tabs_bar';
import ModalContainer from './containers/modal_container';
import { connect } from 'react-redux';
import { Redirect, withRouter } from 'react-router-dom';
import { isMobile } from '../../is_mobile';
import { debounce } from 'lodash';
import { uploadCompose, resetCompose } from '../../actions/compose';
import { refreshHomeTimeline } from '../../actions/timelines';
import { refreshNotifications } from '../../actions/notifications';
import { clearHeight } from '../../actions/height_cache';
import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
import UploadArea from './components/upload_area';
import ColumnsAreaContainer from './containers/columns_area_container';
import classNames from 'classnames';
import {
  Compose,
  Status,
  GettingStarted,
  PublicTimeline,
  CommunityTimeline,
  AccountTimeline,
  AccountGallery,
  HomeTimeline,
  Followers,
  Following,
  Reblogs,
  Favourites,
  DirectTimeline,
  HashtagTimeline,
  Notifications,
  FollowRequests,
  GenericNotFound,
  FavouritedStatuses,
  Blocks,
  Mutes,
  PinnedStatuses,
} from './util/async-components';
import { HotKeys } from 'react-hotkeys';
import { me } from '../../initial_state';
import { defineMessages, injectIntl } from 'react-intl';

// Dummy import, to make sure that <Status /> ends up in the application bundle.
// Without this it ends up in ~8 very commonly used bundles.
import '../../../glitch/components/status';

const messages = defineMessages({
  beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave Mastodon.' },
});

const mapStateToProps = state => ({
  isComposing: state.getIn(['compose', 'is_composing']),
  hasComposingText: state.getIn(['compose', 'text']) !== '',
});

const keyMap = {
  new: 'n',
  search: 's',
  forceNew: 'option+n',
  focusColumn: ['1', '2', '3', '4', '5', '6', '7', '8', '9'],
  reply: 'r',
  favourite: 'f',
  boost: 'b',
  mention: 'm',
  open: ['enter', 'o'],
  openProfile: 'p',
  moveDown: ['down', 'j'],
  moveUp: ['up', 'k'],
  back: 'backspace',
  goToHome: 'g h',
  goToNotifications: 'g n',
  goToLocal: 'g l',
  goToFederated: 'g t',
  goToDirect: 'g d',
  goToStart: 'g s',
  goToFavourites: 'g f',
  goToPinned: 'g p',
  goToProfile: 'g u',
  goToBlocked: 'g b',
  goToMuted: 'g m',
};

@connect(mapStateToProps)
@injectIntl
@withRouter
export default class UI extends React.Component {

  static contextTypes = {
    router: PropTypes.object.isRequired,
  };

  static propTypes = {
    dispatch: PropTypes.func.isRequired,
    children: PropTypes.node,
    layout: PropTypes.string,
    isWide: PropTypes.bool,
    systemFontUi: PropTypes.bool,
    navbarUnder: PropTypes.bool,
    isComposing: PropTypes.bool,
    hasComposingText: PropTypes.bool,
    location: PropTypes.object,
    intl: PropTypes.object.isRequired,
  };

  state = {
    width: window.innerWidth,
    draggingOver: false,
  };

  handleBeforeUnload = (e) => {
    const { intl, isComposing, hasComposingText } = this.props;

    if (isComposing && hasComposingText) {
      // Setting returnValue to any string causes confirmation dialog.
      // Many browsers no longer display this text to users,
      // but we set user-friendly message for other browsers, e.g. Edge.
      e.returnValue = intl.formatMessage(messages.beforeUnload);
    }
  }

  handleResize = debounce(() => {
    // The cached heights are no longer accurate, invalidate
    this.props.dispatch(clearHeight());

    this.setState({ width: window.innerWidth });
  }, 500, {
    trailing: true,
  });

  handleDragEnter = (e) => {
    e.preventDefault();

    if (!this.dragTargets) {
      this.dragTargets = [];
    }

    if (this.dragTargets.indexOf(e.target) === -1) {
      this.dragTargets.push(e.target);
    }

    if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
      this.setState({ draggingOver: true });
    }
  }

  handleDragOver = (e) => {
    e.preventDefault();
    e.stopPropagation();

    try {
      e.dataTransfer.dropEffect = 'copy';
    } catch (err) {

    }

    return false;
  }

  handleDrop = (e) => {
    e.preventDefault();

    this.setState({ draggingOver: false });

    if (e.dataTransfer && e.dataTransfer.files.length === 1) {
      this.props.dispatch(uploadCompose(e.dataTransfer.files));
    }
  }

  handleDragLeave = (e) => {
    e.preventDefault();
    e.stopPropagation();

    this.dragTargets = this.dragTargets.filter(el => el !== e.target && this.node.contains(el));

    if (this.dragTargets.length > 0) {
      return;
    }

    this.setState({ draggingOver: false });
  }

  closeUploadModal = () => {
    this.setState({ draggingOver: false });
  }

  handleServiceWorkerPostMessage = ({ data }) => {
    if (data.type === 'navigate') {
      this.context.router.history.push(data.path);
    } else {
      console.warn('Unknown message type:', data.type);
    }
  }

  componentWillMount () {
    window.addEventListener('beforeunload', this.handleBeforeUnload, false);
    window.addEventListener('resize', this.handleResize, { passive: true });
    document.addEventListener('dragenter', this.handleDragEnter, false);
    document.addEventListener('dragover', this.handleDragOver, false);
    document.addEventListener('drop', this.handleDrop, false);
    document.addEventListener('dragleave', this.handleDragLeave, false);
    document.addEventListener('dragend', this.handleDragEnd, false);

    if ('serviceWorker' in  navigator) {
      navigator.serviceWorker.addEventListener('message', this.handleServiceWorkerPostMessage);
    }

    this.props.dispatch(refreshHomeTimeline());
    this.props.dispatch(refreshNotifications());
  }

  componentDidMount () {
    this.hotkeys.__mousetrap__.stopCallback = (e, element) => {
      return ['TEXTAREA', 'SELECT', 'INPUT'].includes(element.tagName);
    };
  }

  shouldComponentUpdate (nextProps) {
    if (nextProps.isComposing !== this.props.isComposing) {
      // Avoid expensive update just to toggle a class
      this.node.classList.toggle('is-composing', nextProps.isComposing);
      this.node.classList.toggle('navbar-under', nextProps.navbarUnder);

      return false;
    }

    // Why isn't this working?!?
    // return super.shouldComponentUpdate(nextProps, nextState);
    return true;
  }

  componentDidUpdate (prevProps) {
    if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) {
      this.columnsAreaNode.handleChildrenContentChange();
    }
  }

  componentWillUnmount () {
    window.removeEventListener('beforeunload', this.handleBeforeUnload);
    window.removeEventListener('resize', this.handleResize);
    document.removeEventListener('dragenter', this.handleDragEnter);
    document.removeEventListener('dragover', this.handleDragOver);
    document.removeEventListener('drop', this.handleDrop);
    document.removeEventListener('dragleave', this.handleDragLeave);
    document.removeEventListener('dragend', this.handleDragEnd);
  }

  setRef = c => {
    this.node = c;
  }

  setColumnsAreaRef = c => {
    this.columnsAreaNode = c.getWrappedInstance().getWrappedInstance();
  }

  handleHotkeyNew = e => {
    e.preventDefault();

    const element = this.node.querySelector('.compose-form__autosuggest-wrapper textarea');

    if (element) {
      element.focus();
    }
  }

  handleHotkeySearch = e => {
    e.preventDefault();

    const element = this.node.querySelector('.search__input');

    if (element) {
      element.focus();
    }
  }

  handleHotkeyForceNew = e => {
    this.handleHotkeyNew(e);
    this.props.dispatch(resetCompose());
  }

  handleHotkeyFocusColumn = e => {
    const index  = (e.key * 1) + 1; // First child is drawer, skip that
    const column = this.node.querySelector(`.column:nth-child(${index})`);

    if (column) {
      const status = column.querySelector('.focusable');

      if (status) {
        status.focus();
      }
    }
  }

  handleHotkeyBack = () => {
    if (window.history && window.history.length === 1) {
      this.context.router.history.push('/');
    } else {
      this.context.router.history.goBack();
    }
  }

  setHotkeysRef = c => {
    this.hotkeys = c;
  }

  handleHotkeyGoToHome = () => {
    this.context.router.history.push('/timelines/home');
  }

  handleHotkeyGoToNotifications = () => {
    this.context.router.history.push('/notifications');
  }

  handleHotkeyGoToLocal = () => {
    this.context.router.history.push('/timelines/public/local');
  }

  handleHotkeyGoToFederated = () => {
    this.context.router.history.push('/timelines/public');
  }

  handleHotkeyGoToDirect = () => {
    this.context.router.history.push('/timelines/direct');
  }

  handleHotkeyGoToStart = () => {
    this.context.router.history.push('/getting-started');
  }

  handleHotkeyGoToFavourites = () => {
    this.context.router.history.push('/favourites');
  }

  handleHotkeyGoToPinned = () => {
    this.context.router.history.push('/pinned');
  }

  handleHotkeyGoToProfile = () => {
    this.context.router.history.push(`/accounts/${me}`);
  }

  handleHotkeyGoToBlocked = () => {
    this.context.router.history.push('/blocks');
  }

  handleHotkeyGoToMuted = () => {
    this.context.router.history.push('/mutes');
  }

  render () {
    const { width, draggingOver } = this.state;
    const { children, layout, isWide, navbarUnder } = this.props;

    const columnsClass = layout => {
      switch (layout) {
      case 'single':
        return 'single-column';
      case 'multiple':
        return 'multi-columns';
      default:
        return 'auto-columns';
      }
    };

    const className = classNames('ui', columnsClass(layout), {
      'wide': isWide,
      'system-font': this.props.systemFontUi,
      'navbar-under': navbarUnder,
    });

    const handlers = {
      new: this.handleHotkeyNew,
      search: this.handleHotkeySearch,
      forceNew: this.handleHotkeyForceNew,
      focusColumn: this.handleHotkeyFocusColumn,
      back: this.handleHotkeyBack,
      goToHome: this.handleHotkeyGoToHome,
      goToNotifications: this.handleHotkeyGoToNotifications,
      goToLocal: this.handleHotkeyGoToLocal,
      goToFederated: this.handleHotkeyGoToFederated,
      goToDirect: this.handleHotkeyGoToDirect,
      goToStart: this.handleHotkeyGoToStart,
      goToFavourites: this.handleHotkeyGoToFavourites,
      goToPinned: this.handleHotkeyGoToPinned,
      goToProfile: this.handleHotkeyGoToProfile,
      goToBlocked: this.handleHotkeyGoToBlocked,
      goToMuted: this.handleHotkeyGoToMuted,
    };

    return (
      <HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef}>
        <div className={className} ref={this.setRef}>
          {navbarUnder ? null : (<TabsBar />)}

          <ColumnsAreaContainer ref={this.setColumnsAreaRef} singleColumn={isMobile(width, layout)}>
            <WrappedSwitch>
              <Redirect from='/' to='/getting-started' exact />
              <WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
              <WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
              <WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
              <WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
              <WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} />
              <WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />

              <WrappedRoute path='/notifications' component={Notifications} content={children} />
              <WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
              <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />

              <WrappedRoute path='/statuses/new' component={Compose} content={children} />
              <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
              <WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
              <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />

              <WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
              <WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
              <WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
              <WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />

              <WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
              <WrappedRoute path='/blocks' component={Blocks} content={children} />
              <WrappedRoute path='/mutes' component={Mutes} content={children} />

              <WrappedRoute component={GenericNotFound} content={children} />
            </WrappedSwitch>
          </ColumnsAreaContainer>

          <NotificationsContainer />
          {navbarUnder ? (<TabsBar />) : null}
          <LoadingBarContainer className='loading-bar' />
          <ModalContainer />
          <UploadArea active={draggingOver} onClose={this.closeUploadModal} />
        </div>
      </HotKeys>
    );
  }

}
<commit_msg>Throw layout, isWide, navbarUnder back into mapStateToProps.<commit_after>import React from 'react';
import NotificationsContainer from './containers/notifications_container';
import PropTypes from 'prop-types';
import LoadingBarContainer from './containers/loading_bar_container';
import TabsBar from './components/tabs_bar';
import ModalContainer from './containers/modal_container';
import { connect } from 'react-redux';
import { Redirect, withRouter } from 'react-router-dom';
import { isMobile } from '../../is_mobile';
import { debounce } from 'lodash';
import { uploadCompose, resetCompose } from '../../actions/compose';
import { refreshHomeTimeline } from '../../actions/timelines';
import { refreshNotifications } from '../../actions/notifications';
import { clearHeight } from '../../actions/height_cache';
import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
import UploadArea from './components/upload_area';
import ColumnsAreaContainer from './containers/columns_area_container';
import classNames from 'classnames';
import {
  Compose,
  Status,
  GettingStarted,
  PublicTimeline,
  CommunityTimeline,
  AccountTimeline,
  AccountGallery,
  HomeTimeline,
  Followers,
  Following,
  Reblogs,
  Favourites,
  DirectTimeline,
  HashtagTimeline,
  Notifications,
  FollowRequests,
  GenericNotFound,
  FavouritedStatuses,
  Blocks,
  Mutes,
  PinnedStatuses,
} from './util/async-components';
import { HotKeys } from 'react-hotkeys';
import { me } from '../../initial_state';
import { defineMessages, injectIntl } from 'react-intl';

// Dummy import, to make sure that <Status /> ends up in the application bundle.
// Without this it ends up in ~8 very commonly used bundles.
import '../../../glitch/components/status';

const messages = defineMessages({
  beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave Mastodon.' },
});

const mapStateToProps = state => ({
  isComposing: state.getIn(['compose', 'is_composing']),
  hasComposingText: state.getIn(['compose', 'text']) !== '',
  layout: state.getIn(['local_settings', 'layout']),
  isWide: state.getIn(['local_settings', 'stretch']),
  navbarUnder: state.getIn(['local_settings', 'navbar_under'])
});

const keyMap = {
  new: 'n',
  search: 's',
  forceNew: 'option+n',
  focusColumn: ['1', '2', '3', '4', '5', '6', '7', '8', '9'],
  reply: 'r',
  favourite: 'f',
  boost: 'b',
  mention: 'm',
  open: ['enter', 'o'],
  openProfile: 'p',
  moveDown: ['down', 'j'],
  moveUp: ['up', 'k'],
  back: 'backspace',
  goToHome: 'g h',
  goToNotifications: 'g n',
  goToLocal: 'g l',
  goToFederated: 'g t',
  goToDirect: 'g d',
  goToStart: 'g s',
  goToFavourites: 'g f',
  goToPinned: 'g p',
  goToProfile: 'g u',
  goToBlocked: 'g b',
  goToMuted: 'g m',
};

@connect(mapStateToProps)
@injectIntl
@withRouter
export default class UI extends React.Component {

  static contextTypes = {
    router: PropTypes.object.isRequired,
  };

  static propTypes = {
    dispatch: PropTypes.func.isRequired,
    children: PropTypes.node,
    layout: PropTypes.string,
    isWide: PropTypes.bool,
    systemFontUi: PropTypes.bool,
    navbarUnder: PropTypes.bool,
    isComposing: PropTypes.bool,
    hasComposingText: PropTypes.bool,
    location: PropTypes.object,
    intl: PropTypes.object.isRequired,
  };

  state = {
    width: window.innerWidth,
    draggingOver: false,
  };

  handleBeforeUnload = (e) => {
    const { intl, isComposing, hasComposingText } = this.props;

    if (isComposing && hasComposingText) {
      // Setting returnValue to any string causes confirmation dialog.
      // Many browsers no longer display this text to users,
      // but we set user-friendly message for other browsers, e.g. Edge.
      e.returnValue = intl.formatMessage(messages.beforeUnload);
    }
  }

  handleResize = debounce(() => {
    // The cached heights are no longer accurate, invalidate
    this.props.dispatch(clearHeight());

    this.setState({ width: window.innerWidth });
  }, 500, {
    trailing: true,
  });

  handleDragEnter = (e) => {
    e.preventDefault();

    if (!this.dragTargets) {
      this.dragTargets = [];
    }

    if (this.dragTargets.indexOf(e.target) === -1) {
      this.dragTargets.push(e.target);
    }

    if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
      this.setState({ draggingOver: true });
    }
  }

  handleDragOver = (e) => {
    e.preventDefault();
    e.stopPropagation();

    try {
      e.dataTransfer.dropEffect = 'copy';
    } catch (err) {

    }

    return false;
  }

  handleDrop = (e) => {
    e.preventDefault();

    this.setState({ draggingOver: false });

    if (e.dataTransfer && e.dataTransfer.files.length === 1) {
      this.props.dispatch(uploadCompose(e.dataTransfer.files));
    }
  }

  handleDragLeave = (e) => {
    e.preventDefault();
    e.stopPropagation();

    this.dragTargets = this.dragTargets.filter(el => el !== e.target && this.node.contains(el));

    if (this.dragTargets.length > 0) {
      return;
    }

    this.setState({ draggingOver: false });
  }

  closeUploadModal = () => {
    this.setState({ draggingOver: false });
  }

  handleServiceWorkerPostMessage = ({ data }) => {
    if (data.type === 'navigate') {
      this.context.router.history.push(data.path);
    } else {
      console.warn('Unknown message type:', data.type);
    }
  }

  componentWillMount () {
    window.addEventListener('beforeunload', this.handleBeforeUnload, false);
    window.addEventListener('resize', this.handleResize, { passive: true });
    document.addEventListener('dragenter', this.handleDragEnter, false);
    document.addEventListener('dragover', this.handleDragOver, false);
    document.addEventListener('drop', this.handleDrop, false);
    document.addEventListener('dragleave', this.handleDragLeave, false);
    document.addEventListener('dragend', this.handleDragEnd, false);

    if ('serviceWorker' in  navigator) {
      navigator.serviceWorker.addEventListener('message', this.handleServiceWorkerPostMessage);
    }

    this.props.dispatch(refreshHomeTimeline());
    this.props.dispatch(refreshNotifications());
  }

  componentDidMount () {
    this.hotkeys.__mousetrap__.stopCallback = (e, element) => {
      return ['TEXTAREA', 'SELECT', 'INPUT'].includes(element.tagName);
    };
  }

  shouldComponentUpdate (nextProps) {
    if (nextProps.isComposing !== this.props.isComposing) {
      // Avoid expensive update just to toggle a class
      this.node.classList.toggle('is-composing', nextProps.isComposing);
      this.node.classList.toggle('navbar-under', nextProps.navbarUnder);

      return false;
    }

    // Why isn't this working?!?
    // return super.shouldComponentUpdate(nextProps, nextState);
    return true;
  }

  componentDidUpdate (prevProps) {
    if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) {
      this.columnsAreaNode.handleChildrenContentChange();
    }
  }

  componentWillUnmount () {
    window.removeEventListener('beforeunload', this.handleBeforeUnload);
    window.removeEventListener('resize', this.handleResize);
    document.removeEventListener('dragenter', this.handleDragEnter);
    document.removeEventListener('dragover', this.handleDragOver);
    document.removeEventListener('drop', this.handleDrop);
    document.removeEventListener('dragleave', this.handleDragLeave);
    document.removeEventListener('dragend', this.handleDragEnd);
  }

  setRef = c => {
    this.node = c;
  }

  setColumnsAreaRef = c => {
    this.columnsAreaNode = c.getWrappedInstance().getWrappedInstance();
  }

  handleHotkeyNew = e => {
    e.preventDefault();

    const element = this.node.querySelector('.compose-form__autosuggest-wrapper textarea');

    if (element) {
      element.focus();
    }
  }

  handleHotkeySearch = e => {
    e.preventDefault();

    const element = this.node.querySelector('.search__input');

    if (element) {
      element.focus();
    }
  }

  handleHotkeyForceNew = e => {
    this.handleHotkeyNew(e);
    this.props.dispatch(resetCompose());
  }

  handleHotkeyFocusColumn = e => {
    const index  = (e.key * 1) + 1; // First child is drawer, skip that
    const column = this.node.querySelector(`.column:nth-child(${index})`);

    if (column) {
      const status = column.querySelector('.focusable');

      if (status) {
        status.focus();
      }
    }
  }

  handleHotkeyBack = () => {
    if (window.history && window.history.length === 1) {
      this.context.router.history.push('/');
    } else {
      this.context.router.history.goBack();
    }
  }

  setHotkeysRef = c => {
    this.hotkeys = c;
  }

  handleHotkeyGoToHome = () => {
    this.context.router.history.push('/timelines/home');
  }

  handleHotkeyGoToNotifications = () => {
    this.context.router.history.push('/notifications');
  }

  handleHotkeyGoToLocal = () => {
    this.context.router.history.push('/timelines/public/local');
  }

  handleHotkeyGoToFederated = () => {
    this.context.router.history.push('/timelines/public');
  }

  handleHotkeyGoToDirect = () => {
    this.context.router.history.push('/timelines/direct');
  }

  handleHotkeyGoToStart = () => {
    this.context.router.history.push('/getting-started');
  }

  handleHotkeyGoToFavourites = () => {
    this.context.router.history.push('/favourites');
  }

  handleHotkeyGoToPinned = () => {
    this.context.router.history.push('/pinned');
  }

  handleHotkeyGoToProfile = () => {
    this.context.router.history.push(`/accounts/${me}`);
  }

  handleHotkeyGoToBlocked = () => {
    this.context.router.history.push('/blocks');
  }

  handleHotkeyGoToMuted = () => {
    this.context.router.history.push('/mutes');
  }

  render () {
    const { width, draggingOver } = this.state;
    const { children, layout, isWide, navbarUnder } = this.props;

    const columnsClass = layout => {
      switch (layout) {
      case 'single':
        return 'single-column';
      case 'multiple':
        return 'multi-columns';
      default:
        return 'auto-columns';
      }
    };

    const className = classNames('ui', columnsClass(layout), {
      'wide': isWide,
      'system-font': this.props.systemFontUi,
      'navbar-under': navbarUnder,
    });

    const handlers = {
      new: this.handleHotkeyNew,
      search: this.handleHotkeySearch,
      forceNew: this.handleHotkeyForceNew,
      focusColumn: this.handleHotkeyFocusColumn,
      back: this.handleHotkeyBack,
      goToHome: this.handleHotkeyGoToHome,
      goToNotifications: this.handleHotkeyGoToNotifications,
      goToLocal: this.handleHotkeyGoToLocal,
      goToFederated: this.handleHotkeyGoToFederated,
      goToDirect: this.handleHotkeyGoToDirect,
      goToStart: this.handleHotkeyGoToStart,
      goToFavourites: this.handleHotkeyGoToFavourites,
      goToPinned: this.handleHotkeyGoToPinned,
      goToProfile: this.handleHotkeyGoToProfile,
      goToBlocked: this.handleHotkeyGoToBlocked,
      goToMuted: this.handleHotkeyGoToMuted,
    };

    return (
      <HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef}>
        <div className={className} ref={this.setRef}>
          {navbarUnder ? null : (<TabsBar />)}

          <ColumnsAreaContainer ref={this.setColumnsAreaRef} singleColumn={isMobile(width, layout)}>
            <WrappedSwitch>
              <Redirect from='/' to='/getting-started' exact />
              <WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
              <WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
              <WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
              <WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
              <WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} />
              <WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />

              <WrappedRoute path='/notifications' component={Notifications} content={children} />
              <WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
              <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />

              <WrappedRoute path='/statuses/new' component={Compose} content={children} />
              <WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
              <WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
              <WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />

              <WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
              <WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
              <WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
              <WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />

              <WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
              <WrappedRoute path='/blocks' component={Blocks} content={children} />
              <WrappedRoute path='/mutes' component={Mutes} content={children} />

              <WrappedRoute component={GenericNotFound} content={children} />
            </WrappedSwitch>
          </ColumnsAreaContainer>

          <NotificationsContainer />
          {navbarUnder ? (<TabsBar />) : null}
          <LoadingBarContainer className='loading-bar' />
          <ModalContainer />
          <UploadArea active={draggingOver} onClose={this.closeUploadModal} />
        </div>
      </HotKeys>
    );
  }

}
<|endoftext|>


<commit_before>'use strict';

angular.module('acceptanceTests').controller('IndexCtrl', function($scope, $http, $q, $window, $state, $stateParams, $location, droitsDescription, acceptanceTests) {
    if ($stateParams.testId) {
        $location.hash('test-' + $stateParams.testId);
        _.find(acceptanceTests, {'_id': $stateParams.testId}).open = true;
    }

    $scope.tests = acceptanceTests;
    $scope.categories = [];
    var categoriesMap = {};
    var unknownCategory = {name: 'Non catégorisés', tests: []};
    acceptanceTests.forEach(function(acceptanceTest) {
        var index = acceptanceTest.name.indexOf(']');
        if (-1 !== index) {
            var categoryName = acceptanceTest.name.substring(1, index);
            acceptanceTest.name = acceptanceTest.name.substring(index + 2, acceptanceTest.name.length);
            var category = categoriesMap[categoryName];
            if (!category) {
                category = categoriesMap[categoryName] = {name: categoryName, tests: []};
                $scope.categories.push(category);
            }
            category.tests.push(acceptanceTest);
            acceptanceTest.category = category;
        } else {
            unknownCategory.tests.push(acceptanceTest);
            acceptanceTest.category = unknownCategory;
        }
    });

    if (unknownCategory.tests.length) {
        $scope.categories.push(unknownCategory);
    }

    $scope.categories.forEach(function(category) {
        category.tests = _.sortBy(category.tests, 'name');
    });

    $scope.droits = _.indexBy(droitsDescription, 'id');
    $scope.pendingTests = 0;

    $scope.displayDroitValue = function(value) {
        if (_.isBoolean(value)) {
            return value ? 'Oui' : 'Non';
        }

        if (_.isNumber(value)) {
            return '' + value + ' €';
        }

        return '';
    };

    $scope.newLineToBr = function(text) {
        text = text
                .replace(/&/g, '&')
                .replace(/>/g, '>')
                .replace(/</g, '<');

        return text.replace(/\n/g, '
');
    };

    $scope.launchSingle = function(test) {
        delete test.status;
        test.running = true;
        test.droitsAttendus.forEach(function(droit) {
            delete droit.status;
            delete droit.actualValue;
        });

        var deferred = $q.defer();

        var promise = $http.get('/api/situations/' + test.situation + '/simulation');
        promise.then(function(result) {
            var droits = result.data;
            test.status = 'ok';
            test.droitsAttendus.forEach(function(droit) {
                var actualValue = droits[droit.id];
                if (angular.isDefined(actualValue)) {
                    delete droits[droit.id];
                    droit.actualValue = actualValue;
                    if (_.isUndefined(droit.expectedValue)) {
                        droit.status = 'unknown';
                    } else if (droit.actualValue === droit.expectedValue) {
                        droit.status = 'ok';
                    } else if ((Math.abs(droit.actualValue - droit.expectedValue) / droit.expectedValue) < 0.02) {
                        droit.status = 'near';
                        if (test.status !== 'ko') test.status = 'near';
                    } else {
                        droit.status = 'ko';
                        test.status = 'ko';
                    }
                }
            });
            _.forEach(droits, function(value, id) {
                if (value) {
                    test.droitsAttendus.push({ id: id, expectedValue: undefined, actualValue: value, status: 'unknown' });
                }
            });
            _.where(test.droitsAttendus, { status: undefined }).forEach(function(droit) {
                droit.status = 'ko';
                test.status = 'ko';
                droit.actualValue = false;
            });
            if (test.status === 'ko') {
                deferred.reject();
            } else {
                deferred.resolve();
            }
        }, function() {
            test.status = 'ko';
            test.droitsAttendus.forEach(function(droit) {
                droit.status = 'ko';
            });
            deferred.reject();
        });

        deferred.promise.finally(function() {
            test.running = false;
        });

        return deferred.promise;
    };

    $scope.launchAll = function() {
        $scope.pendingTests = $scope.tests.length;
        $scope.errors = 0;
        $scope.categories.forEach(function(category) {
            category.errors = 0;
        });
        $scope.tests.forEach(function(test) {
            $scope.launchSingle(test).then(function() {}, function() {
                $scope.errors++;
                test.category.errors++;
            }).finally(function() {
                $scope.pendingTests--;
            });
        });
    };

    $scope.showErrors = function() {
        return angular.isDefined($scope.errors);
    };

    $scope.deleteTest = function(test) {
        if ($window.confirm('Êtes-vous sûr de vouloir supprimer ce test ?')) {
            $http.delete('/api/acceptance-tests/' + test._id).then(function() {
                $state.go($state.current, {}, {reload: true});
            });
        }
    };

    $scope.toggleSituation = function(test) {
        if (!test.situationObject) {
            $http.get('/api/situations/' + test.situation).then(function(result) {
                test.situationObject = result.data;
            });
        }
        test.showSituation = !test.showSituation;
    };
});
<commit_msg>lancement des tests d'acceptance dans l'ordre<commit_after>'use strict';

angular.module('acceptanceTests').controller('IndexCtrl', function($scope, $http, $q, $window, $state, $stateParams, $location, droitsDescription, acceptanceTests) {
    if ($stateParams.testId) {
        $location.hash('test-' + $stateParams.testId);
        _.find(acceptanceTests, {'_id': $stateParams.testId}).open = true;
    }

    $scope.tests = acceptanceTests;
    $scope.categories = [];
    var categoriesMap = {};
    var unknownCategory = {name: 'Non catégorisés', tests: []};
    acceptanceTests.forEach(function(acceptanceTest) {
        var index = acceptanceTest.name.indexOf(']');
        if (-1 !== index) {
            var categoryName = acceptanceTest.name.substring(1, index);
            acceptanceTest.name = acceptanceTest.name.substring(index + 2, acceptanceTest.name.length);
            var category = categoriesMap[categoryName];
            if (!category) {
                category = categoriesMap[categoryName] = {name: categoryName, tests: []};
                $scope.categories.push(category);
            }
            category.tests.push(acceptanceTest);
        } else {
            unknownCategory.tests.push(acceptanceTest);
        }
    });

    if (unknownCategory.tests.length) {
        $scope.categories.push(unknownCategory);
    }

    $scope.categories.forEach(function(category) {
        category.tests = _.sortBy(category.tests, 'name');
    });

    $scope.droits = _.indexBy(droitsDescription, 'id');
    $scope.pendingTests = 0;

    $scope.displayDroitValue = function(value) {
        if (_.isBoolean(value)) {
            return value ? 'Oui' : 'Non';
        }

        if (_.isNumber(value)) {
            return '' + value + ' €';
        }

        return '';
    };

    $scope.newLineToBr = function(text) {
        text = text
                .replace(/&/g, '&')
                .replace(/>/g, '>')
                .replace(/</g, '<');

        return text.replace(/\n/g, '
');
    };

    $scope.launchSingle = function(test) {
        delete test.status;
        test.running = true;
        test.droitsAttendus.forEach(function(droit) {
            delete droit.status;
            delete droit.actualValue;
        });

        var deferred = $q.defer();

        var promise = $http.get('/api/situations/' + test.situation + '/simulation');
        promise.then(function(result) {
            var droits = result.data;
            test.status = 'ok';
            test.droitsAttendus.forEach(function(droit) {
                var actualValue = droits[droit.id];
                if (angular.isDefined(actualValue)) {
                    delete droits[droit.id];
                    droit.actualValue = actualValue;
                    if (_.isUndefined(droit.expectedValue)) {
                        droit.status = 'unknown';
                    } else if (droit.actualValue === droit.expectedValue) {
                        droit.status = 'ok';
                    } else if ((Math.abs(droit.actualValue - droit.expectedValue) / droit.expectedValue) < 0.02) {
                        droit.status = 'near';
                        if (test.status !== 'ko') test.status = 'near';
                    } else {
                        droit.status = 'ko';
                        test.status = 'ko';
                    }
                }
            });
            _.forEach(droits, function(value, id) {
                if (value) {
                    test.droitsAttendus.push({ id: id, expectedValue: undefined, actualValue: value, status: 'unknown' });
                }
            });
            _.where(test.droitsAttendus, { status: undefined }).forEach(function(droit) {
                droit.status = 'ko';
                test.status = 'ko';
                droit.actualValue = false;
            });
            if (test.status === 'ko') {
                deferred.reject();
            } else {
                deferred.resolve();
            }
        }, function() {
            test.status = 'ko';
            test.droitsAttendus.forEach(function(droit) {
                droit.status = 'ko';
            });
            deferred.reject();
        });

        deferred.promise.finally(function() {
            test.running = false;
        });

        return deferred.promise;
    };

    $scope.launchAll = function() {
        $scope.pendingTests = $scope.tests.length;
        $scope.errors = 0;
        $scope.categories.forEach(function(category) {
            category.errors = 0;
            category.tests.forEach(function(test) {
                $scope.launchSingle(test)
                    .catch(function() {
                        $scope.errors++;
                        category.errors++;
                    }).finally(function() {
                        $scope.pendingTests--;
                    });
            });
        });
    };

    $scope.showErrors = function() {
        return angular.isDefined($scope.errors);
    };

    $scope.deleteTest = function(test) {
        if ($window.confirm('Êtes-vous sûr de vouloir supprimer ce test ?')) {
            $http.delete('/api/acceptance-tests/' + test._id).then(function() {
                $state.go($state.current, {}, {reload: true});
            });
        }
    };

    $scope.toggleSituation = function(test) {
        if (!test.situationObject) {
            $http.get('/api/situations/' + test.situation).then(function(result) {
                test.situationObject = result.data;
            });
        }
        test.showSituation = !test.showSituation;
    };
});
<|endoftext|>


<commit_before>'use strict';

/**
 * @ngdoc function
 * @name grafterizerApp.controller:TransformationnewCtrl
 * @description
 * # TransformationnewCtrl
 * Controller of the grafterizerApp
 */
angular.module('grafterizerApp')
    .controller('TransformationNewCtrl', function (
                $scope,
                 $stateParams,
                 ontotextAPI,
                 $rootScope,
                 $state,
                 $mdToast,
                 $mdDialog,
                 transformationDataModel,
                 generateClojure) {

    $scope.document = {
        title: "New transformation",
        description: "",
    };
    var integerColumn = new transformationDataModel.CustomFunctionDeclaration("integerColumn", "(defn integerColumn [s] (Integer/parseInt s))");
    var stringColumn = new transformationDataModel.CustomFunctionDeclaration("stringColumn", "(defn stringColumn [str] (s str))");

    var boolean = new transformationDataModel.CustomFunctionDeclaration("boolean", "");
    var count = new transformationDataModel.CustomFunctionDeclaration("count", "");
    var cast = new transformationDataModel.CustomFunctionDeclaration("cast", "");
    var dec = new transformationDataModel.CustomFunctionDeclaration("dec", "");
    var double = new transformationDataModel.CustomFunctionDeclaration("double", "");
    var first = new transformationDataModel.CustomFunctionDeclaration("first", "");
    var float = new transformationDataModel.CustomFunctionDeclaration("float", "");
    var inc = new transformationDataModel.CustomFunctionDeclaration("inc", "");
    var keyword = new transformationDataModel.CustomFunctionDeclaration("keyword", "");
    var last = new transformationDataModel.CustomFunctionDeclaration("last", "");
    var long = new transformationDataModel.CustomFunctionDeclaration("long", "");
    var name = new transformationDataModel.CustomFunctionDeclaration("name", "");
    var second = new transformationDataModel.CustomFunctionDeclaration("second", "");
    var short = new transformationDataModel.CustomFunctionDeclaration("short", "");
  /*String functions*/
      var capitalize = new transformationDataModel.CustomFunctionDeclaration("capitalize", "");
      var join = new transformationDataModel.CustomFunctionDeclaration("join", ' (defn join [& strings] (clojure.string/join " " strings))');
      var lowerCase = new transformationDataModel.CustomFunctionDeclaration("lower-case", "");
      var upperCase = new transformationDataModel.CustomFunctionDeclaration("upper-case", "");
      var reverse = new transformationDataModel.CustomFunctionDeclaration("reverse", "");
      var trim = new transformationDataModel.CustomFunctionDeclaration("trim", "");
      var trimNewline = new transformationDataModel.CustomFunctionDeclaration("trim-newline", "");
      var triml = new transformationDataModel.CustomFunctionDeclaration("triml", "");
      var trimr = new transformationDataModel.CustomFunctionDeclaration("trimr", "");

/*Numbers*/

      var rem = new transformationDataModel.CustomFunctionDeclaration("rem", "");
      var add = new transformationDataModel.CustomFunctionDeclaration("+", "");
      var subtr = new transformationDataModel.CustomFunctionDeclaration("-", "");
      var prod = new transformationDataModel.CustomFunctionDeclaration("*", "");
      var div = new transformationDataModel.CustomFunctionDeclaration("/", "");
    $scope.clojure = "";
    $scope.pipeline = new transformationDataModel.Pipeline([]);
    $scope.transformation = new transformationDataModel.Transformation([integerColumn,
        stringColumn, count, boolean, cast, dec, double, first, float, inc, keyword, last, long, name, rem, second, short,
        capitalize, join, lowerCase,upperCase, replace, replaceFirst, reverse, split, splitLines, trim, trimNewline, trimr, triml,
        add, subtr, prod, div], [], [$scope.pipeline], []);

    window.canard = $scope;

    $rootScope.actions = {
        save: function(){
            // var transformationJSON = JSON.stringify($scope.transformation);
            var clojure = generateClojure.fromTransformation($scope.transformation);
            ontotextAPI.newTransformation({
                '@context': ontotextAPI.getContextDeclaration(),
                '@type': 'dcat:Transformation',
                'dct:title': $scope.document.title,
                'dct:description': $scope.document.description,
                'dcat:public': $scope.document['dct:public'] ? 'true': 'false',
                'dct:modified': moment().format("YYYY-MM-DD"),
                'dcat:transformationType': 'pipe',
                'dcat:transformationCommand': 'my-pipe'
            }, clojure, $scope.transformation)
                .success(function(data){
                $mdToast.show(
                    $mdToast.simple()
                    .content('Transformation saved')
                    .position('bottom left')
                    .hideDelay(6000)
                );
                $state.go('transformations.transformation', {
                    id: data['@id']
                });
            });
            /*Transformation.create({
	        	uri: "about:blank",
	        	name: $scope.document.title,
	        	metadata: $scope.document.description,
	        	clojure: $scope.clojure
	        }, function(data) {
	        	$state.go('transformations.transformation', {
	        		id: data.id
	        	});
	        }, function(err) {
	  			$mdToast.show(
			      $mdToast.simple()
			        .content('An error occured: '+err.statusText)
			        .position('right top')
			        .hideDelay(6000)
		        );
	        });*/
        }
    };
    $scope.$watch('fileUpload', function(){
        if ($scope.fileUpload) {
            // TODO
            $mdToast.show(
                $mdToast.simple()
                .content('You need to save the transformation first')
                .position('bottom left')
                .hideDelay(6000)
            );
        }
    });

    $scope.editPrefixers = function () {
        $scope.originalPrefixers = [];
        angular.copy($scope.transformation.prefixers, $scope.originalPrefixers);
        $mdDialog.show({
            templateUrl: 'views/editprefixes.html',
            controller: 'EditprefixersCtrl',
            scope: $scope.$new(false, $scope)
        }).then(function() {
        }, function() {
            angular.copy($scope.originalPrefixers, $scope.transformation.prefixers);
        });
    };

    $scope.defineCustomFunctions = function () {
        $scope.originalCustomFunctionDeclarations = [];
        angular.copy($scope.transformation.customFunctionDeclarations, $scope.originalCustomFunctionDeclarations);
        $mdDialog.show({
            templateUrl: 'views/createcustomfunction.html',
            controller: 'CustomfunctionsdialogcontrollerCtrl',
            scope: $scope.$new(false, $scope)
        }).then(function() {
        }, function() {
            angular.copy($scope.originalCustomFunctionDeclarations, $scope.transformation.customFunctionDeclarations);
        });
    };
});
<commit_msg>fix to remove broken functions<commit_after>'use strict';

/**
 * @ngdoc function
 * @name grafterizerApp.controller:TransformationnewCtrl
 * @description
 * # TransformationnewCtrl
 * Controller of the grafterizerApp
 */
angular.module('grafterizerApp')
    .controller('TransformationNewCtrl', function (
                $scope,
                 $stateParams,
                 ontotextAPI,
                 $rootScope,
                 $state,
                 $mdToast,
                 $mdDialog,
                 transformationDataModel,
                 generateClojure) {

    $scope.document = {
        title: "New transformation",
        description: "",
    };
    var integerColumn = new transformationDataModel.CustomFunctionDeclaration("integerColumn", "(defn integerColumn [s] (Integer/parseInt s))");
    var stringColumn = new transformationDataModel.CustomFunctionDeclaration("stringColumn", "(defn stringColumn [str] (s str))");

    var boolean = new transformationDataModel.CustomFunctionDeclaration("boolean", "");
    var count = new transformationDataModel.CustomFunctionDeclaration("count", "");
    var cast = new transformationDataModel.CustomFunctionDeclaration("cast", "");
    var dec = new transformationDataModel.CustomFunctionDeclaration("dec", "");
    var double = new transformationDataModel.CustomFunctionDeclaration("double", "");
    var first = new transformationDataModel.CustomFunctionDeclaration("first", "");
    var float = new transformationDataModel.CustomFunctionDeclaration("float", "");
    var inc = new transformationDataModel.CustomFunctionDeclaration("inc", "");
    var keyword = new transformationDataModel.CustomFunctionDeclaration("keyword", "");
    var last = new transformationDataModel.CustomFunctionDeclaration("last", "");
    var long = new transformationDataModel.CustomFunctionDeclaration("long", "");
    var name = new transformationDataModel.CustomFunctionDeclaration("name", "");
    var second = new transformationDataModel.CustomFunctionDeclaration("second", "");
    var short = new transformationDataModel.CustomFunctionDeclaration("short", "");
  /*String functions*/
      var capitalize = new transformationDataModel.CustomFunctionDeclaration("capitalize", "");
      var join = new transformationDataModel.CustomFunctionDeclaration("join", ' (defn join [& strings] (clojure.string/join " " strings))');
      var lowerCase = new transformationDataModel.CustomFunctionDeclaration("lower-case", "");
      var upperCase = new transformationDataModel.CustomFunctionDeclaration("upper-case", "");
      var reverse = new transformationDataModel.CustomFunctionDeclaration("reverse", "");
      var trim = new transformationDataModel.CustomFunctionDeclaration("trim", "");
      var trimNewline = new transformationDataModel.CustomFunctionDeclaration("trim-newline", "");
      var triml = new transformationDataModel.CustomFunctionDeclaration("triml", "");
      var trimr = new transformationDataModel.CustomFunctionDeclaration("trimr", "");

/*Numbers*/

      var rem = new transformationDataModel.CustomFunctionDeclaration("rem", "");
      var add = new transformationDataModel.CustomFunctionDeclaration("+", "");
      var subtr = new transformationDataModel.CustomFunctionDeclaration("-", "");
      var prod = new transformationDataModel.CustomFunctionDeclaration("*", "");
      var div = new transformationDataModel.CustomFunctionDeclaration("/", "");
    $scope.clojure = "";
    $scope.pipeline = new transformationDataModel.Pipeline([]);
    $scope.transformation = new transformationDataModel.Transformation([integerColumn,
        stringColumn, count, boolean, cast, dec, double, first, float, inc, keyword, last, long, name, rem, second, short,
        capitalize, join, lowerCase,upperCase, reverse,  trim, trimNewline, trimr, triml,
        add, subtr, prod, div], [], [$scope.pipeline], []);

    window.canard = $scope;

    $rootScope.actions = {
        save: function(){
            // var transformationJSON = JSON.stringify($scope.transformation);
            var clojure = generateClojure.fromTransformation($scope.transformation);
            ontotextAPI.newTransformation({
                '@context': ontotextAPI.getContextDeclaration(),
                '@type': 'dcat:Transformation',
                'dct:title': $scope.document.title,
                'dct:description': $scope.document.description,
                'dcat:public': $scope.document['dct:public'] ? 'true': 'false',
                'dct:modified': moment().format("YYYY-MM-DD"),
                'dcat:transformationType': 'pipe',
                'dcat:transformationCommand': 'my-pipe'
            }, clojure, $scope.transformation)
                .success(function(data){
                $mdToast.show(
                    $mdToast.simple()
                    .content('Transformation saved')
                    .position('bottom left')
                    .hideDelay(6000)
                );
                $state.go('transformations.transformation', {
                    id: data['@id']
                });
            });
            /*Transformation.create({
	        	uri: "about:blank",
	        	name: $scope.document.title,
	        	metadata: $scope.document.description,
	        	clojure: $scope.clojure
	        }, function(data) {
	        	$state.go('transformations.transformation', {
	        		id: data.id
	        	});
	        }, function(err) {
	  			$mdToast.show(
			      $mdToast.simple()
			        .content('An error occured: '+err.statusText)
			        .position('right top')
			        .hideDelay(6000)
		        );
	        });*/
        }
    };
    $scope.$watch('fileUpload', function(){
        if ($scope.fileUpload) {
            // TODO
            $mdToast.show(
                $mdToast.simple()
                .content('You need to save the transformation first')
                .position('bottom left')
                .hideDelay(6000)
            );
        }
    });

    $scope.editPrefixers = function () {
        $scope.originalPrefixers = [];
        angular.copy($scope.transformation.prefixers, $scope.originalPrefixers);
        $mdDialog.show({
            templateUrl: 'views/editprefixes.html',
            controller: 'EditprefixersCtrl',
            scope: $scope.$new(false, $scope)
        }).then(function() {
        }, function() {
            angular.copy($scope.originalPrefixers, $scope.transformation.prefixers);
        });
    };

    $scope.defineCustomFunctions = function () {
        $scope.originalCustomFunctionDeclarations = [];
        angular.copy($scope.transformation.customFunctionDeclarations, $scope.originalCustomFunctionDeclarations);
        $mdDialog.show({
            templateUrl: 'views/createcustomfunction.html',
            controller: 'CustomfunctionsdialogcontrollerCtrl',
            scope: $scope.$new(false, $scope)
        }).then(function() {
        }, function() {
            angular.copy($scope.originalCustomFunctionDeclarations, $scope.transformation.customFunctionDeclarations);
        });
    };
});
<|endoftext|>


<commit_before>'use strict';

/**
 * @ngdoc directive
 * @name otaniemi3dApp.directive:floorplan
 * @description
 * # floorplan
 */
angular.module('otaniemi3dApp')
  .directive('heatMap', function(heatmapService, $q, apiService, $timeout) {

  return {
    restrict: 'E',
    scope: {
      sensorData: '=',
      floorplan: '=',
      selectedRoom: '=',
      sensorType: '='
    },
    link: function(scope, element) {

      var isFloorplanLoaded = false;
      //Later we store dragging and zooming behavior to this variable.
      var zoomListener;

      getFloorplan(scope.floorplan)
        .then(appendFloorplan)
        .then(fetchSensorData)
        .then(bindSensors)
        .then(updateRoomColors);

      /*
       * Download svg from the server and save it to floorplan.svg
       */
      function getFloorplan(floorplan){
        var deferred = $q.defer();

        if (floorplan.svg) {
          deferred.resolve(floorplan);
        } else {
          d3.xml(floorplan.url, 'image/svg+xml', function (xml) {
            if (xml) {
              floorplan.svg = xml.documentElement;
              deferred.resolve(floorplan);
            } else {
              deferred.reject('Error while fetching a floorplan');
            }
          });
        }

        return deferred.promise;
      }

     /*
      * Append floorplan to the html element and register
      * zoom and drag listener.
      */
      function appendFloorplan(floorplan) {
        var svg = element[0].appendChild(floorplan.svg);

        svg = d3.select(svg)
            .attr('width', '100%')
            .attr('height', '100%')
            .attr('pointer-events', 'all')
            .attr('id', 'floorplan');

        svg.selectAll('path').each(function() {
          var elem = d3.select(this);
          if (!elem.attr('data-room-id')) {
            elem.attr('pointer-events', 'none');
          }
        });

        svg.selectAll('text').attr('pointer-events', 'none');

        //Remove title elements so that the browser's built-in
        //tooltip doesn't show
        svg.selectAll('title').remove();

        //Configure dragging and zooming behavior.
        zoomListener = d3.behavior.zoom()
          .scaleExtent([0.5, 10])
          .scale(floorplan.scale)
          .translate(floorplan.translate)
          .on('zoom', function() {
            svg.select('g').attr('transform', [
                'translate(',d3.event.translate,')',
                'scale(',d3.event.scale,')'
              ].join(''));
            floorplan.scale = d3.event.scale;
            floorplan.translate = d3.event.translate;
          });

        svg.call(zoomListener);

        scope.$broadcast('floorplan-loaded');
        isFloorplanLoaded = true;

        return floorplan;
      }

      function fetchSensorData(floorplan) {
        var deferred = $q.defer();

        var sensorRequest = {
          'Objects': {
            'Object': {
              'id': {
                'keyValue': 'K1'
              },
              'Object': []
            }
          }
        };

        d3.select(floorplan.svg)
          .selectAll('[data-room-id]')
          .each(function () {
            sensorRequest.Objects.Object.Object.push({
              'id': {
                'keyValue': d3.select(this).attr('data-room-id')
              }
            });
          });

        apiService.get(sensorRequest, {}, 'sensordata-new', true)
          .then(function success (data) {
            floorplan.data = data;
            deferred.resolve(floorplan);
          }, function error () {
            floorplan.data = floorplan.data || [];
            deferred.resolve(floorplan);
          });

        return deferred.promise;
      }

      /*
      * Read rooms and their html elements from the floorplan svg
      * and save data to the Rooms service.
      */
      function bindSensors(floorplan) {
        d3.select(floorplan.svg)
          .selectAll('[data-room-id]')
          .datum(function () {

            var datum = d3.select(this).datum();

            if (datum && datum.sensors.length) {
              return datum;
            }

            var roomData = { sensors: [] };

            var id = d3.select(this).attr('data-room-id');
            var roomName;

            for (var i = 0; i < floorplan.data.length; i ++) {
              var sensor = floorplan.data[i];

              if (sensor.roomId === id) {
                roomData.sensors.push(sensor);
                roomName = sensor.room;
              }
            }

            roomData.room = roomName ? roomName : id;
            roomData.roomId = id;

            floorplan.rooms.push({
              name: roomData.room,
              id: roomData.roomId
            });

            return roomData;
          });

        return floorplan;
      }

      function updateRoomColors(floorplan) {
        d3.select(floorplan.svg)
          .selectAll('[data-room-id]')
          .each(function () {
            var data = d3.select(this).datum();

            for (var i = 0; i < data.sensors.length; i++) {
              if (data.sensors[i].type === scope.sensorType.name) {
                var sensor = data.sensors[i];
                var value = sensor.values[0].value;
                var color = heatmapService.getColor(sensor.type, value);
                d3.select(this)
                  .style('fill', color.rgb)
                  .style('fill-opacity', color.opacity);
              }
            }
          });
      }

      function centerCamera(room) {
        // body...
      }

      function zoomAndPan(scale, translate) {
        if (zoomListener) {
          scope.floorplan.translate = translate;
          scope.floorplan.scale = scale;

          zoomListener
            .scale(scale)
            .translate(translate)
            .event(d3.select(element[0]).select('#floorplan'));
        }
      }

      scope.$on('reset-zoom', function () {
        zoomAndPan(1, [0,0]);
      });

      scope.$on('room-selected', function (_, roomInfo) {
        d3.select(scope.floorplan.svg)
          .selectAll('[data-room-id]')
          .each(function () {
            var room = d3.select(this);

            //remove old room selection highlight
            room
              .style('stroke-width', null)
              .style('stroke', null)
              .style('stroke-opacity', null);

            if (room.datum().roomId === roomInfo.id) {
              room
                .style('stroke-width', '15')
                .style('stroke', '#ffcc00')
                .style('stroke-opacity', '0')
                .transition()
                .ease('cubic-in-out')
                .style('stroke-opacity', '1');

              //move room to the center of screen if zoomed in
              if (scope.floorplan.scale > 1.2) {
                centerCamera(room);
              }
            }
          });
      });

      scope.$watch('sensorType', function () {
        if (isFloorplanLoaded) {
          updateRoomColors(scope.floorplan);
        }
      });
    }
  };});
<commit_msg>Remove unnecessary title removal<commit_after>'use strict';

/**
 * @ngdoc directive
 * @name otaniemi3dApp.directive:floorplan
 * @description
 * # floorplan
 */
angular.module('otaniemi3dApp')
  .directive('heatMap', function(heatmapService, $q, apiService, $timeout) {

  return {
    restrict: 'E',
    scope: {
      sensorData: '=',
      floorplan: '=',
      selectedRoom: '=',
      sensorType: '='
    },
    link: function(scope, element) {

      var isFloorplanLoaded = false;
      //Later we store dragging and zooming behavior to this variable.
      var zoomListener;

      getFloorplan(scope.floorplan)
        .then(appendFloorplan)
        .then(fetchSensorData)
        .then(bindSensors)
        .then(updateRoomColors);

      /*
       * Download svg from the server and save it to floorplan.svg
       */
      function getFloorplan(floorplan){
        var deferred = $q.defer();

        if (floorplan.svg) {
          deferred.resolve(floorplan);
        } else {
          d3.xml(floorplan.url, 'image/svg+xml', function (xml) {
            if (xml) {
              floorplan.svg = xml.documentElement;
              deferred.resolve(floorplan);
            } else {
              deferred.reject('Error while fetching a floorplan');
            }
          });
        }

        return deferred.promise;
      }

     /*
      * Append floorplan to the html element and register
      * zoom and drag listener.
      */
      function appendFloorplan(floorplan) {
        var svg = element[0].appendChild(floorplan.svg);

        svg = d3.select(svg)
            .attr('width', '100%')
            .attr('height', '100%')
            .attr('pointer-events', 'all')
            .attr('id', 'floorplan');

        svg.selectAll('path').each(function() {
          var elem = d3.select(this);
          if (!elem.attr('data-room-id')) {
            elem.attr('pointer-events', 'none');
          }
        });

        svg.selectAll('text').attr('pointer-events', 'none');

        //Configure dragging and zooming behavior.
        zoomListener = d3.behavior.zoom()
          .scaleExtent([0.5, 10])
          .scale(floorplan.scale)
          .translate(floorplan.translate)
          .on('zoom', function() {
            svg.select('g').attr('transform', [
                'translate(',d3.event.translate,')',
                'scale(',d3.event.scale,')'
              ].join(''));
            floorplan.scale = d3.event.scale;
            floorplan.translate = d3.event.translate;
          });

        svg.call(zoomListener);

        scope.$broadcast('floorplan-loaded');
        isFloorplanLoaded = true;

        return floorplan;
      }

      function fetchSensorData(floorplan) {
        var deferred = $q.defer();

        var sensorRequest = {
          'Objects': {
            'Object': {
              'id': {
                'keyValue': 'K1'
              },
              'Object': []
            }
          }
        };

        d3.select(floorplan.svg)
          .selectAll('[data-room-id]')
          .each(function () {
            sensorRequest.Objects.Object.Object.push({
              'id': {
                'keyValue': d3.select(this).attr('data-room-id')
              }
            });
          });

        apiService.get(sensorRequest, {}, 'sensordata-new', true)
          .then(function success (data) {
            floorplan.data = data;
            deferred.resolve(floorplan);
          }, function error () {
            floorplan.data = floorplan.data || [];
            deferred.resolve(floorplan);
          });

        return deferred.promise;
      }

      /*
      * Read rooms and their html elements from the floorplan svg
      * and save data to the Rooms service.
      */
      function bindSensors(floorplan) {
        d3.select(floorplan.svg)
          .selectAll('[data-room-id]')
          .datum(function () {

            var datum = d3.select(this).datum();

            if (datum && datum.sensors.length) {
              return datum;
            }

            var roomData = { sensors: [] };

            var id = d3.select(this).attr('data-room-id');
            var roomName;

            for (var i = 0; i < floorplan.data.length; i ++) {
              var sensor = floorplan.data[i];

              if (sensor.roomId === id) {
                roomData.sensors.push(sensor);
                roomName = sensor.room;
              }
            }

            roomData.room = roomName ? roomName : id;
            roomData.roomId = id;

            floorplan.rooms.push({
              name: roomData.room,
              id: roomData.roomId
            });

            return roomData;
          });

        return floorplan;
      }

      function updateRoomColors(floorplan) {
        d3.select(floorplan.svg)
          .selectAll('[data-room-id]')
          .each(function () {
            var data = d3.select(this).datum();

            for (var i = 0; i < data.sensors.length; i++) {
              if (data.sensors[i].type === scope.sensorType.name) {
                var sensor = data.sensors[i];
                var value = sensor.values[0].value;
                var color = heatmapService.getColor(sensor.type, value);
                d3.select(this)
                  .style('fill', color.rgb)
                  .style('fill-opacity', color.opacity);
              }
            }
          });
      }

      function centerCamera(room) {
        // body...
      }

      function zoomAndPan(scale, translate) {
        if (zoomListener) {
          scope.floorplan.translate = translate;
          scope.floorplan.scale = scale;

          zoomListener
            .scale(scale)
            .translate(translate)
            .event(d3.select(element[0]).select('#floorplan'));
        }
      }

      scope.$on('reset-zoom', function () {
        zoomAndPan(1, [0,0]);
      });

      scope.$on('room-selected', function (_, roomInfo) {
        d3.select(scope.floorplan.svg)
          .selectAll('[data-room-id]')
          .each(function () {
            var room = d3.select(this);

            //remove old room selection highlight
            room
              .style('stroke-width', null)
              .style('stroke', null)
              .style('stroke-opacity', null);

            if (room.datum().roomId === roomInfo.id) {
              room
                .style('stroke-width', '15')
                .style('stroke', '#ffcc00')
                .style('stroke-opacity', '0')
                .transition()
                .ease('cubic-in-out')
                .style('stroke-opacity', '1');

              //move room to the center of screen if zoomed in
              if (scope.floorplan.scale > 1.2) {
                centerCamera(room);
              }
            }
          });
      });

      scope.$watch('sensorType', function () {
        if (isFloorplanLoaded) {
          updateRoomColors(scope.floorplan);
        }
      });
    }
  };});
<|endoftext|>


<commit_before>(function() {
  var out$ = typeof exports != 'undefined' && exports || this;

  var doctype = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">';

  function isExternal(url) {
    return url && url.lastIndexOf('http',0) == 0 && url.lastIndexOf(window.location.host) == -1;
  }

  function inlineImages(el, callback) {
    var images = el.querySelectorAll('image');
    var left = images.length;
    if (left == 0) {
      callback();
    }
    for (var i = 0; i < images.length; i++) {
      (function(image) {
        var href = image.getAttribute('xlink:href');
        if (href) {
          if (isExternal(href.value)) {
            console.warn("Cannot render embedded images linking to external hosts: "+href.value);
            return;
          }
        }
        var canvas = document.createElement('canvas');
        var ctx = canvas.getContext('2d');
        var img = new Image();
        href = href || image.getAttribute('href');
        if(href === null) {
          left--;
          if (left == 0) {
            callback();
          }
        } else {
          img.src = href;
        }

        img.onload = function() {
          canvas.width = img.width;
          canvas.height = img.height;
          ctx.drawImage(img, 0, 0);
          //image.setAttribute('xlink:href', canvas.toDataURL('image/png'));
          left--;
          if (left == 0) {
            callback();
          }
        }
        img.onerror = function() {
          console.log("Could not load "+href);
          left--;
          if (left == 0) {
            callback();
          }
        }
      })(images[i]);
    }
  }

  out$.svgAsDataUri = function(el, options, cb) {
    options = options || {};
    options.scale = options.scale || 1;
    var xmlns = "http://www.w3.org/2000/xmlns/";

    inlineImages(el, function() {
      var outer = document.createElement("div");
      var clone = el.cloneNode(true);
      var width, height;
      if(el.tagName == 'svg') {
        width = parseInt(clone.getAttribute('data-width') || clone.style.width || out$.getComputedStyle(el).getPropertyValue('width'));
        height = parseInt(clone.getAttribute('data-height') || clone.style.height || out$.getComputedStyle(el).getPropertyValue('height'));
      } else {
        var box = el.getBBox();
        width = box.x + box.width;
        height = box.y + box.height;
        clone.setAttribute('transform', clone.getAttribute('transform').replace(/translate\(.*?\)/, ''));

        var svg = document.createElementNS('http://www.w3.org/2000/svg','svg')
        svg.appendChild(clone)
        clone = svg;
      }

      clone.setAttribute("version", "1.1");
      clone.setAttributeNS(xmlns, "xmlns", "http://www.w3.org/2000/svg");
      clone.setAttributeNS(xmlns, "xmlns:xlink", "http://www.w3.org/1999/xlink");
      clone.setAttribute("width", width * options.scale);
      clone.setAttribute("height", height * options.scale);
      clone.setAttribute("viewBox", "0 0 " + width + " " + height);
      outer.appendChild(clone);

      var svg = doctype + outer.innerHTML;
      var uri = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(svg)));
      if (cb) {
        cb(uri);
      }
    });
  }

  out$.saveSvgAsPng = function(el, name, options) {
    options = options || {};
    out$.svgAsDataUri(el, options, function(uri) {
      var image = new Image();
      image.src = uri;
      image.onload = function() {
        var canvas = document.createElement('canvas');
        canvas.width = image.width;
        canvas.height = image.height;
        var context = canvas.getContext('2d');
        context.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height);

        var b64Data = canvas.toDataURL();
        var contentType = "image/png";

        var blob = b64toBlob(b64Data );
        var blobUrl = URL.createObjectURL(blob);

        var a = document.createElement("a");
        a.href = blobUrl;
        a.download = name;
        a.click();
        window.URL.revokeObjectURL(blobUrl);
      }
    });
  }

  function b64toBlob(b64Data, contentType, sliceSize) {
    contentType = contentType || '';
    sliceSize = sliceSize || 512;

    var byteCharacters = atob( b64Data.substring("data:image/png;base64,".length ) );
    // var byteCharacters = b64Data;
    var byteArrays = [];

    for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
      var slice = byteCharacters.slice(offset, offset + sliceSize);

      var byteNumbers = new Array(slice.length);
      for (var i = 0; i < slice.length; i++) {
        byteNumbers[i] = slice.charCodeAt(i);
      }

      var byteArray = new Uint8Array(byteNumbers);

      byteArrays.push(byteArray);
    }

    var blob = new Blob(byteArrays, {type: contentType});
    return blob;
  }
})();
<commit_msg>Regex to clean up dataURI instead of hardcode<commit_after>(function() {
  var out$ = typeof exports != 'undefined' && exports || this;

  var doctype = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">';

  function isExternal(url) {
    return url && url.lastIndexOf('http',0) == 0 && url.lastIndexOf(window.location.host) == -1;
  }

  function inlineImages(el, callback) {
    var images = el.querySelectorAll('image');
    var left = images.length;
    if (left == 0) {
      callback();
    }
    for (var i = 0; i < images.length; i++) {
      (function(image) {
        var href = image.getAttribute('xlink:href');
        if (href) {
          if (isExternal(href.value)) {
            console.warn("Cannot render embedded images linking to external hosts: "+href.value);
            return;
          }
        }
        var canvas = document.createElement('canvas');
        var ctx = canvas.getContext('2d');
        var img = new Image();
        href = href || image.getAttribute('href');
        if(href === null) {
          left--;
          if (left == 0) {
            callback();
          }
        } else {
          img.src = href;
        }

        img.onload = function() {
          canvas.width = img.width;
          canvas.height = img.height;
          ctx.drawImage(img, 0, 0);
          //image.setAttribute('xlink:href', canvas.toDataURL('image/png'));
          left--;
          if (left == 0) {
            callback();
          }
        }
        img.onerror = function() {
          console.log("Could not load "+href);
          left--;
          if (left == 0) {
            callback();
          }
        }
      })(images[i]);
    }
  }

  out$.svgAsDataUri = function(el, options, cb) {
    options = options || {};
    options.scale = options.scale || 1;
    var xmlns = "http://www.w3.org/2000/xmlns/";

    inlineImages(el, function() {
      var outer = document.createElement("div");
      var clone = el.cloneNode(true);
      var width, height;
      if(el.tagName == 'svg') {
        width = parseInt(clone.getAttribute('data-width') || clone.style.width || out$.getComputedStyle(el).getPropertyValue('width'));
        height = parseInt(clone.getAttribute('data-height') || clone.style.height || out$.getComputedStyle(el).getPropertyValue('height'));
      } else {
        var box = el.getBBox();
        width = box.x + box.width;
        height = box.y + box.height;
        clone.setAttribute('transform', clone.getAttribute('transform').replace(/translate\(.*?\)/, ''));

        var svg = document.createElementNS('http://www.w3.org/2000/svg','svg')
        svg.appendChild(clone)
        clone = svg;
      }

      clone.setAttribute("version", "1.1");
      clone.setAttributeNS(xmlns, "xmlns", "http://www.w3.org/2000/svg");
      clone.setAttributeNS(xmlns, "xmlns:xlink", "http://www.w3.org/1999/xlink");
      clone.setAttribute("width", width * options.scale);
      clone.setAttribute("height", height * options.scale);
      clone.setAttribute("viewBox", "0 0 " + width + " " + height);
      outer.appendChild(clone);

      var svg = doctype + outer.innerHTML;
      var uri = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(svg)));
      if (cb) {
        cb(uri);
      }
    });
  }

  out$.saveSvgAsPng = function(el, name, options) {
    options = options || {};
    out$.svgAsDataUri(el, options, function(uri) {
      var image = new Image();
      image.src = uri;
      image.onload = function() {
        var canvas = document.createElement('canvas');
        canvas.width = image.width;
        canvas.height = image.height;
        var context = canvas.getContext('2d');
        context.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height);

        var b64Data = canvas.toDataURL();
        var contentType = "image/png";

        var blob = b64toBlob(b64Data );
        var blobUrl = URL.createObjectURL(blob);

        var a = document.createElement("a");
        a.href = blobUrl;
        a.download = name;
        a.click();
        window.URL.revokeObjectURL(blobUrl);
      }
    });
  }

  function b64toBlob(b64Data, contentType, sliceSize) {
    contentType = contentType || '';
    sliceSize = sliceSize || 512;

    var byteCharacters = atob( b64Data.replace(/^.*?base64,/, '') );
    // var byteCharacters = b64Data;
    var byteArrays = [];

    for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
      var slice = byteCharacters.slice(offset, offset + sliceSize);

      var byteNumbers = new Array(slice.length);
      for (var i = 0; i < slice.length; i++) {
        byteNumbers[i] = slice.charCodeAt(i);
      }

      var byteArray = new Uint8Array(byteNumbers);

      byteArrays.push(byteArray);
    }

    var blob = new Blob(byteArrays, {type: contentType});
    return blob;
  }
})();
<|endoftext|>