fix view menu

fix view menu

<template>

        <router-link to="/" class="navbar-item" exact>ЕДДС</router-link>

          <router-link v-if="!user.authenticated" to="/login" class="navbar-item">Авторизация</router-link>
          <template v-else>
            <router-link to="/contacts" class="navbar-item">Контакты</router-link>
            <router-link to="/companies" class="navbar-item">Организации</router-link>
            <router-link to="/sirens" class="navbar-item">Сирены</router-link>

              Справочники

                <router-link to="/departments" class="navbar-item ">Отделы</router-link>
                <router-link to="/educations" class="navbar-item ">Обучение</router-link>
                <router-link to="/kinds" class="navbar-item ">Типы</router-link>
                <router-link to="/posts" class="navbar-item ">Должности</router-link>
                <router-link to="/practices" class="navbar-item ">Учения</router-link>
                <router-link to="/ranks" class="navbar-item ">Чины</router-link>
                <router-link to="/scopes" class="navbar-item ">Сферы</router-link>

                <router-link to="/sirentypes" class="navbar-item ">Типы сирен</router-link>

          </template>

          <vue-button v-if="user.authenticated" class="navbar-item" text="Выход" color="info" @click="logout"></vue-button>

</template>


header样式调整

header样式调整

<template>

        <template v-for="r in data">
<router-link :to="r.link" v-html="r.name"></router-link>
 </template>

          test

test11

test121212

          test2

</template>


style: 💄 Delete duplicate variable declaration

Issues: 0

<template>

      <slot v-if="!imagePath" name="image"></slot>

      <z-scroll v-if="scrollBar" :scrollVal.sync="scrollVal" style="overflow: visible;"/>
      <z-slider v-if="slider === true" :progress='progress'/>

          {{label}}

        <slot v-if="!imagePath" name="image"></slot>

          <slot></slot>

        <slot name="media" ></slot>

     <slot name="extension"></slot>

</template>


machine learning mockup

machine learning mockup

<template>

    <md-layout md-gutter md-align="center" style="margin-top: 20px;">
      Protein

      Carbohydrates

      Fat
    </md-layout>
    <md-table>
      <md-table-header>
        <md-table-row>
          <md-table-head>
            Product
          </md-table-head>
          <md-table-head md-numeric>
             Calories (g)
          </md-table-head>
        </md-table-row>
      </md-table-header>

      <md-table-body>
        <md-table-row>
          <md-table-cell>

Cauliflower

2.0g 2.5g

          </md-table-cell>
          <md-table-cell md-numeric>

20

0.0g

          </md-table-cell>
        </md-table-row>

        <md-table-row>
          <md-table-cell>

Spinach

2.8g 1.5g

          </md-table-cell>
          <md-table-cell md-numeric>

30

0.8g

          </md-table-cell>
        </md-table-row>

        <md-table-row>
          <md-table-cell>

Broccoli

2.82g 4.04g

          </md-table-cell>
          <md-table-cell md-numeric>

30

0.37g

          </md-table-cell>
        </md-table-row>

        <md-table-row>
          <md-table-cell>

Blueberries

0.6g 12.2g

          </md-table-cell>
          <md-table-cell md-numeric>

45

0.6g

          </md-table-cell>
        </md-table-row>

        <md-table-row>
          <md-table-cell>

Fish (Atlantic cod)

17.8g 0.0g

          </md-table-cell>
          <md-table-cell md-numeric>

82

0.0g

          </md-table-cell>
        </md-table-row>

        <md-table-row>
          <md-table-cell>

Sweet potatoes

1.0g 18.0g

          </md-table-cell>
          <md-table-cell md-numeric>

76

0.0g

          </md-table-cell>
        </md-table-row>

        <md-table-row>
          <md-table-cell>

Walnuts

16.0g 18.0g

          </md-table-cell>
          <md-table-cell md-numeric>

645

60.3g

          </md-table-cell>
        </md-table-row>

        <md-table-row>
          <md-table-cell>

Quinoa

4.0g 21.0g

          </md-table-cell>
          <md-table-cell md-numeric>

120

2.0g

          </md-table-cell>
        </md-table-row>

      </md-table-body>
    </md-table>

</template>

<template>

    <md-layout md-gutter md-align="center" style="margin-top: 20px;">
      Protein

      Carbohydrates

      Fat
    </md-layout>
    <md-table>
      <md-table-header>
        <md-table-row>
          <md-table-head>
            Product
          </md-table-head>
          <md-table-head md-numeric>
             Calories (g)
          </md-table-head>
        </md-table-row>
      </md-table-header>

      <md-table-body>
        <md-table-row v-for="(row, index) in 15" :key="index">
          <md-table-cell>

Mleko słonia

66g 17g

          </md-table-cell>
          <md-table-cell md-numeric>

10

12g

          </md-table-cell>
        </md-table-row>
      </md-table-body>
    </md-table>

</template>


remove unfinished code

remove unfinished code

<script lang="coffee">
import EventBus from '@/shared/services/event_bus'
import Records from '@/shared/services/records'
import Flash  from '@/shared/services/flash'
import RestfulClient from '@/shared/record_store/restful_client'
import { onError } from '@/shared/helpers/form'

export default
  props:
    close: Function
    group: Object
  data: ->
    idpMetadataUrl: ''
  methods:
    submit: ->
      remote = new RestfulClient('saml_providers')
      remote.create(idp_metadata_url: @idpMetadataUrl, group_id: @group.id)
      .then =>
        Flash.success 'configure_sso.success'
        @close()
      .catch =>
        alert("boo")
  computed:
    isProPlan: ->
      @group.subscriptionPlan == 'pp-pro-monthly' || @group.subscriptionPlan == 'pp-pro-annual'
</script>
<template lang="pug">
v-card.install-microsoft-modal
  v-card-title
    h1.headline(v-t="'configure_sso.title'")
    v-spacer
    dismiss-modal-button(:close="close")
  v-card-text
    p.helptext(v-html="$t('configure_sso.helptext')")
    p(v-if="!isProPlan" v-html="$t('configure_sso.pro_plan_only')")
    v-text-field(v-if="isProPlan" v-model='idpMetadataUrl' :label="$t('configure_sso.idp_metadata_url')" :placeholder="$t('configure_sso.idp_metadata_url_placeholder')")
  v-card-actions(v-if="isProPlan")
    v-spacer
    v-btn(color='primary' @click='submit()', v-t="'common.action.save'")
</template>

<script lang="coffee">
import EventBus from '@/shared/services/event_bus'
import Records from '@/shared/services/records'
import Flash  from '@/shared/services/flash'
import RestfulClient from '@/shared/record_store/restful_client'
import { onError } from '@/shared/helpers/form'

export default
  props:
    close: Function
    group: Object
  data: ->
    idpMetadataUrl: ''
    samlProviderId: null
  mounted: ->
    Records.samlProviders.fetchByGroupId(@$route.params.key)
    .then (res) =>
      @samlProviderId = res.saml_provider_id
  methods:
    submit: ->
      remote = new RestfulClient('saml_providers')
      remote.create(idp_metadata_url: @idpMetadataUrl, group_id: @group.id)
      .then =>
        Flash.success 'configure_sso.success'
        @close()
      .catch =>
        alert("boo")
  computed:
    isProPlan: ->
      @group.subscriptionPlan == 'pp-pro-monthly' || @group.subscriptionPlan == 'pp-pro-annual'
</script>
<template lang="pug">
v-card.install-microsoft-modal
  v-card-title
    h1.headline(v-t="'configure_sso.title'")
    v-spacer
    dismiss-modal-button(:close="close")
  v-card-text
    p.helptext(v-html="$t('configure_sso.helptext')")
    p(v-if="!isProPlan" v-html="$t('configure_sso.pro_plan_only')")
    v-text-field(v-if="isProPlan" v-model='idpMetadataUrl' :label="$t('configure_sso.idp_metadata_url')" :placeholder="$t('configure_sso.idp_metadata_url_placeholder')")
  v-card-actions(v-if="isProPlan")
    v-spacer
    v-btn(color='primary' @click='submit()', v-t="'common.action.save'")
</template>


stopped using useless variable

stopped using useless variable

<template>
<v-card v-if="value">
  <v-card-title>
    DNS
    <v-btn icon :title="'DNS configuration help'|translate" @click="showConfigHelp = true">
      <v-icon>mdi-information-outline</v-icon>
    </v-btn>
  </v-card-title>
  <v-card-text>
    <translate class="overline">MX records</translate>
    <template v-if="value.dns_global_status=='pending'">
      <v-row>
        <v-col>
          <v-chip color="info" small>
            <translate>Pending</translate>
          </v-chip>
        </v-col>
      </v-row>
    </template>
    <template v-else>
      <v-data-table
        :headers="mxRecordHeaders"
        :items="detail.mx_records"
        hide-default-footer
        >
        <template v-slot:item.updated="{ item }">
          {{ item.updated|date }}
        </template>
      </v-data-table>
    </template>
    <translate class="overline">Auto configuration</translate>
    <template v-if="value.dns_global_status=='pending'">
      <v-row>
        <v-col>
          <v-chip color="info" small>
            <translate>Pending</translate>
          </v-chip>
        </v-col>
      </v-row>
    </template>
    <template v-else>
      <v-row>
        <v-col>
          <v-chip
            v-if="detail.autoconfig_record"
            color="success"
            >
            <translate
              :translate-params="{ value: detail.autoconfig_record.value }"
              >
              autoconfig record (Mozilla): %{ value }
            </translate>
          </v-chip>
          <v-chip
            v-else
            color="warning"
            >
            <translate>autoconfig record (Mozilla) not found</translate>
          </v-chip>
        </v-col>
        <v-col>
          <v-chip
            v-if="detail.autodiscover_record"
            color="success"
            >
            <translate
              :translate-params="{ value: detail.autodiscover_record.value }"
              >
              autodiscover record (Microsoft): %{ value }
            </translate>
          </v-chip>
          <v-chip
            v-else
            color="warning"
            >
            <translate>autodiscover record (Microsoft) not found</translate>
          </v-chip>
        </v-col>
      </v-row>
      <translate class="overline">Authentication</translate>
      <v-row>
        <v-col>
          <v-chip
            v-if="detail.spf_record"
            color="success"
            >
            <translate>SPF record found</translate>
          </v-chip>
          <v-chip
            v-else
            color="error"
            >
            <translate>SPF record not found</translate>
          </v-chip>
        </v-col>
        <v-col>
          <v-chip
            v-if="detail.dkim_record"
            color="success"
            >
            <translate>DKIM record found</translate>
          </v-chip>
          <v-chip
            v-else
            color="error"
            >
            <translate>DKIM record not found</translate>
          </v-chip>
        </v-col>
        <v-col>
          <v-chip
            v-if="detail.dmarc_record"
            color="success"
            >
            <translate>DMARC record found</translate>
          </v-chip>
          <v-chip
            v-else
            color="error"
            >
            <translate>DMARC record not found</translate>
          </v-chip>
        </v-col>
      </v-row>
      <v-row v-if="value.enable_dkim">
        <v-col cols="3"><translate>DKIM key</translate></v-col>
        <v-col cols="9">

            <v-btn color="primary" small @click="showDKIMKey = true">
              <translate>Show key</translate>
            </v-btn>
            <v-btn icon :title="'Generate a new DKIM key'|translate" @click="generateNewKey">
              <v-icon>mdi-refresh</v-icon>
            </v-btn>

          <translate v-else>Not generated</translate>
        </v-col>
      </v-row>
    </template>
  </v-card-text>
  <v-dialog v-model="showConfigHelp"
            max-width="800px"
            persistent
            >
    <domain-dns-config :domain="value" @close="showConfigHelp = false" />
  </v-dialog>
  <v-dialog v-model="showDKIMKey"
            max-width="800px"
            persistent
            >
    <domain-dkim-key :domain="value" @close="showDKIMKey = false" />
  </v-dialog>
</v-card>
</template>

<template>
<v-card v-if="value">
  <v-card-title>
    DNS
    <v-btn icon :title="'DNS configuration help'|translate" @click="showConfigHelp = true">
      <v-icon>mdi-information-outline</v-icon>
    </v-btn>
  </v-card-title>
  <v-card-text>
    <translate class="overline">MX records</translate>
    <template v-if="domain.dns_global_status=='pending'">
      <v-row>
        <v-col>
          <v-chip color="info" small>
            <translate>Pending</translate>
          </v-chip>
        </v-col>
      </v-row>
    </template>
    <template v-else>
      <v-data-table
        :headers="mxRecordHeaders"
        :items="detail.mx_records"
        hide-default-footer
        >
        <template v-slot:item.updated="{ item }">
          {{ item.updated|date }}
        </template>
      </v-data-table>
    </template>
    <translate class="overline">Auto configuration</translate>
    <template v-if="domain.dns_global_status=='pending'">
      <v-row>
        <v-col>
          <v-chip color="info" small>
            <translate>Pending</translate>
          </v-chip>
        </v-col>
      </v-row>
    </template>
    <template v-else>
      <v-row>
        <v-col>
          <v-chip
            v-if="detail.autoconfig_record"
            color="success"
            >
            <translate
              :translate-params="{ value: detail.autoconfig_record.value }"
              >
              autoconfig record (Mozilla): %{ value }
            </translate>
          </v-chip>
          <v-chip
            v-else
            color="warning"
            >
            <translate>autoconfig record (Mozilla) not found</translate>
          </v-chip>
        </v-col>
        <v-col>
          <v-chip
            v-if="detail.autodiscover_record"
            color="success"
            >
            <translate
              :translate-params="{ value: detail.autodiscover_record.value }"
              >
              autodiscover record (Microsoft): %{ value }
            </translate>
          </v-chip>
          <v-chip
            v-else
            color="warning"
            >
            <translate>autodiscover record (Microsoft) not found</translate>
          </v-chip>
        </v-col>
      </v-row>
      <translate class="overline">Authentication</translate>
      <v-row>
        <v-col>
          <v-chip
            v-if="detail.spf_record"
            color="success"
            >
            <translate>SPF record found</translate>
          </v-chip>
          <v-chip
            v-else
            color="error"
            >
            <translate>SPF record not found</translate>
          </v-chip>
        </v-col>
        <v-col>
          <v-chip
            v-if="detail.dkim_record"
            color="success"
            >
            <translate>DKIM record found</translate>
          </v-chip>
          <v-chip
            v-else
            color="error"
            >
            <translate>DKIM record not found</translate>
          </v-chip>
        </v-col>
        <v-col>
          <v-chip
            v-if="detail.dmarc_record"
            color="success"
            >
            <translate>DMARC record found</translate>
          </v-chip>
          <v-chip
            v-else
            color="error"
            >
            <translate>DMARC record not found</translate>
          </v-chip>
        </v-col>
      </v-row>
      <v-row v-if="value.enable_dkim">
        <v-col cols="3"><translate>DKIM key</translate></v-col>
        <v-col cols="9">

            <v-btn color="primary" small @click="showDKIMKey = true">
              <translate>Show key</translate>
            </v-btn>
            <v-btn icon :title="'Generate a new DKIM key'|translate" @click="generateNewKey">
              <v-icon>mdi-refresh</v-icon>
            </v-btn>

          <translate v-else>Not generated</translate>
        </v-col>
      </v-row>
    </template>
  </v-card-text>
  <v-dialog v-model="showConfigHelp"
            max-width="800px"
            persistent
            >
    <domain-dns-config :domain="value" @close="showConfigHelp = false" />
  </v-dialog>
  <v-dialog v-model="showDKIMKey"
            max-width="800px"
            persistent
            >
    <domain-dkim-key :domain="value" @close="showDKIMKey = false" />
  </v-dialog>
</v-card>
</template>


translate role.kind

translate role.kind

<template>

        All Users

       ( {{ visibleUsers.length }} )

      Filter User Type
      <select v-model="roleFilter" id="type-filter" name="type-filter">
        <option value="all"> All Users </option>
        <option value="admin"> Admins </option>
        <option value="learner"> Learners </option>
      </select>

        <svg class="icon" src="../icons/search.svg" aria-hidden="true"></svg>
        <input
          id="search-field"
          aria-label="Search for a user..."
          type="search"
          v-model="searchFilter"
          placeholder="Search for a user...">

        <icon-button @click="openCreateUserModal" class="create-user-button" text="Add New" :primary="true">
          <svg class="add-user" src="../icons/add_new_user.svg"></svg>
        </icon-button>

    <user-edit-modal
      v-if="editingUser"
      :userid="currentUserEdit.id"
      :username="currentUserEdit.username"
      :fullname="currentUserEdit.full_name"
      :roles="currentUserEdit.roles"
      @close="closeEditUserModal">
    </user-edit-modal>
    <user-create-modal
      v-if="creatingUser"
      @close="closeCreateUserModal">
    </user-create-modal>

Users

           Full Name

              Role

           Username
           Edit

              {{user.full_name}}

              {{role.kind === 'admin' ? $tr('admin') : $tr('coach')}}

            {{user.username}}

            <icon-button class="edit-user-button" @click="openEditUserModal(user)">
              Edit Account Info
              <svg src="../icons/pencil.svg"></svg>
            </icon-button>

{{ $tr('noUsersExist') }}

{{ $tr('allUsersFilteredOut') }}

</template>

<template>

        All Users

       ( {{ visibleUsers.length }} )

      Filter User Type
      <select v-model="roleFilter" id="type-filter" name="type-filter">
        <option value="all"> All Users </option>
        <option value="admin"> Admins </option>
        <option value="learner"> Learners </option>
      </select>

        <svg class="icon" src="../icons/search.svg" aria-hidden="true"></svg>
        <input
          id="search-field"
          aria-label="Search for a user..."
          type="search"
          v-model="searchFilter"
          placeholder="Search for a user...">

        <icon-button @click="openCreateUserModal" class="create-user-button" text="Add New" :primary="true">
          <svg class="add-user" src="../icons/add_new_user.svg"></svg>
        </icon-button>

    <user-edit-modal
      v-if="editingUser"
      :userid="currentUserEdit.id"
      :username="currentUserEdit.username"
      :fullname="currentUserEdit.full_name"
      :roles="currentUserEdit.roles"
      @close="closeEditUserModal">
    </user-edit-modal>
    <user-create-modal
      v-if="creatingUser"
      @close="closeCreateUserModal">
    </user-create-modal>

Users

           Full Name

              Role

           Username
           Edit

              {{user.full_name}}

              {{role.kind}}

            {{user.username}}

            <icon-button class="edit-user-button" @click="openEditUserModal(user)">
              Edit Account Info
              <svg src="../icons/pencil.svg"></svg>
            </icon-button>

{{ $tr('noUsersExist') }}

{{ $tr('allUsersFilteredOut') }}

</template>


Frequently used emojis

Frequently used emojis

Signed-off-by: Marco <3397c7e210a9d1588196cfebb12b2c6b133f6934@icloud.com>

<template>

		<template v-if="!isReactionsMenuOpen">
			<NcButton v-if="canReact"
				type="tertiary"
				:aria-label="t('spreed', 'Add a reaction to this message')"
				@click="openReactionsMenu">
				<template #icon>
					<EmoticonOutline :size="20" />
				</template>
			</NcButton>
			<NcActions v-show="isReplyable">
				<NcActionButton @click.stop="handleReply">
					<template #icon>
						<Reply :size="16" />
					</template>
					{{ t('spreed', 'Reply') }}
				</NcActionButton>
			</NcActions>
			<NcActions :force-menu="true"
				:container="`#message_${id}`"
				placement="bottom-end"
				:boundaries-element="containerElement"
				@open="onMenuOpen"
				@close="onMenuClose">
				<NcActionButton v-if="isPrivateReplyable"
					icon="icon-user"
					:close-after-click="true"
					@click.stop="handlePrivateReply">
					{{ t('spreed', 'Reply privately') }}
				</NcActionButton>
				<NcActionButton icon="icon-external"
					:close-after-click="true"
					@click.stop.prevent="handleCopyMessageLink">
					{{ t('spreed', 'Copy message link') }}
				</NcActionButton>
				<NcActionButton :close-after-click="true"
					@click.stop="handleMarkAsUnread">
					<template #icon>
						<EyeOffOutline :size="16" />
					</template>
					{{ t('spreed', 'Mark as unread') }}
				</NcActionButton>
				<NcActionLink v-if="linkToFile"
					:href="linkToFile">
					<File slot="icon"
						:size="20" />
					{{ t('spreed', 'Go to file') }}
				</NcActionLink>
				<NcActionButton v-if="canForwardMessage"
					:close-after-click="true"
					@click.stop="openForwarder">
					<template #icon>
						<Share :size="16" />
					</template>
					{{ t('spreed', 'Forward message') }}
				</NcActionButton>
				<NcActionSeparator v-if="messageActions.length > 0" />
				<template v-for="action in messageActions">
					<NcActionButton :key="action.label"
						:icon="action.icon"
						:close-after-click="true"
						@click="action.callback(messageApiData)">
						{{ action.label }}
					</NcActionButton>
				</template>
				<template v-if="isDeleteable">
					<NcActionSeparator />
					<NcActionButton icon="icon-delete"
						:close-after-click="true"
						@click.stop="handleDelete">
						{{ t('spreed', 'Delete') }}
					</NcActionButton>
				</template>
			</NcActions>
		</template>

		<template v-if="isReactionsMenuOpen">
			<NcButton type="tertiary"
				:aria-label="t('spreed', 'Close reactions menu')"
				@click="closeReactionsMenu">
				<template #icon>
					<ArrowLeft :size="20" />
				</template>
			</NcButton>
			<NcButton v-for="emoji in frequentlyUsedEmojis"
				:key="emoji"
				type="tertiary"
				:aria-label="t('spreed', 'React with {emoji}', { emoji })"
				@click="handleReactionClick(emoji)">
				<template #icon>
					{{ emoji }}
				</template>
			</NcButton>

			<NcEmojiPicker :container="`#message_${id} .message-buttons-bar`"
				:boundary="containerElement"
				placement="auto"
				@select="handleReactionClick"
				@after-show="onEmojiPickerOpen"
				@after-hide="onEmojiPickerClose">
				<NcButton type="tertiary"
					:aria-label="t('spreed', 'React with another emoji')">
					<template #icon>
						<Plus :size="20" />
					</template>
				</NcButton>
			</NcEmojiPicker>
		</template>
		<Forwarder v-if="isForwarderOpen"
			:message-object="messageObject"
			@close="closeForwarder" />

</template>

<template>

		<template v-if="!isReactionsMenuOpen">
			<NcButton v-if="canReact"
				type="tertiary"
				:aria-label="t('spreed', 'Add a reaction to this message')"
				@click="openReactionsMenu">
				<template #icon>
					<EmoticonOutline :size="20" />
				</template>
			</NcButton>
			<NcActions v-show="isReplyable">
				<NcActionButton @click.stop="handleReply">
					<template #icon>
						<Reply :size="16" />
					</template>
					{{ t('spreed', 'Reply') }}
				</NcActionButton>
			</NcActions>
			<NcActions :force-menu="true"
				:container="`#message_${id}`"
				placement="bottom-end"
				:boundaries-element="containerElement"
				@open="onMenuOpen"
				@close="onMenuClose">
				<NcActionButton v-if="isPrivateReplyable"
					icon="icon-user"
					:close-after-click="true"
					@click.stop="handlePrivateReply">
					{{ t('spreed', 'Reply privately') }}
				</NcActionButton>
				<NcActionButton icon="icon-external"
					:close-after-click="true"
					@click.stop.prevent="handleCopyMessageLink">
					{{ t('spreed', 'Copy message link') }}
				</NcActionButton>
				<NcActionButton :close-after-click="true"
					@click.stop="handleMarkAsUnread">
					<template #icon>
						<EyeOffOutline :size="16" />
					</template>
					{{ t('spreed', 'Mark as unread') }}
				</NcActionButton>
				<NcActionLink v-if="linkToFile"
					:href="linkToFile">
					<File slot="icon"
						:size="20" />
					{{ t('spreed', 'Go to file') }}
				</NcActionLink>
				<NcActionButton v-if="canForwardMessage"
					:close-after-click="true"
					@click.stop="openForwarder">
					<template #icon>
						<Share :size="16" />
					</template>
					{{ t('spreed', 'Forward message') }}
				</NcActionButton>
				<NcActionSeparator v-if="messageActions.length > 0" />
				<template v-for="action in messageActions">
					<NcActionButton :key="action.label"
						:icon="action.icon"
						:close-after-click="true"
						@click="action.callback(messageApiData)">
						{{ action.label }}
					</NcActionButton>
				</template>
				<template v-if="isDeleteable">
					<NcActionSeparator />
					<NcActionButton icon="icon-delete"
						:close-after-click="true"
						@click.stop="handleDelete">
						{{ t('spreed', 'Delete') }}
					</NcActionButton>
				</template>
			</NcActions>
		</template>

		<template v-if="isReactionsMenuOpen">
			<NcButton type="tertiary"
				:aria-label="t('spreed', 'Close reactions menu')"
				@click="closeReactionsMenu">
				<template #icon>
					<ArrowLeft :size="20" />
				</template>
			</NcButton>
			<NcButton type="tertiary"
				:aria-label="t('spreed', 'React with {emoji}', { emoji: '👍' })"
				@click="handleReactionClick('👍')">
				<template #icon>
					👍
				</template>
			</NcButton>
			<NcButton type="tertiary"
				:aria-label="t('spreed', 'React with {emoji}', { emoji: '❤' })"
				@click="handleReactionClick('❤')">
				<template #icon>
					❤
				</template>
			</NcButton>
			<NcEmojiPicker :container="`#message_${id} .message-buttons-bar`"
				:boundary="containerElement"
				placement="auto"
				@select="handleReactionClick"
				@after-show="onEmojiPickerOpen"
				@after-hide="onEmojiPickerClose">
				<NcButton type="tertiary"
					:aria-label="t('spreed', 'React with another emoji')">
					<template #icon>
						<Plus :size="20" />
					</template>
				</NcButton>
			</NcEmojiPicker>
		</template>
		<Forwarder v-if="isForwarderOpen"
			:message-object="messageObject"
			@close="closeForwarder" />

</template>


Link to Github repository

Link to Github repository

<template>

      <select v-model="currentAnswerSet">
        <option :value="null">Load Answer Set…</option>
        <option v-for="answerSet in answerSets" :value="answerSet">{{ answerSet.name }}</option>
      </select>
      <button @click.stop.prevent="updateBoard" v-if="false">Reset and Send to Board</button>

        <control-window-tile v-for="n in 4" :answer-number="n + 1"
                             :answer.sync="this.currentAnswerSet.answers[n]"></control-window-tile>

        <control-window-tile v-for="n in 4" :answer-number="n + 5"
                             :answer.sync="this.currentAnswerSet.answers[n + 4]"></control-window-tile>

      <button class="StrikeButton" @click.stop.prevent="wrong(1)" title="Strike 1">☒</button>
      <button class="StrikeButton" @click.stop.prevent="wrong(2)" title="Strike 2">☒</button>
      <button class="StrikeButton" @click.stop.prevent="wrong(3)" title="Strike 3">☒</button>

        <button class="SaveAnswerSet" @click.stop.prevent="saveAnswerSet" title="Save as new…">💾
        </button>

        <button @click.prevent.stop="toggleMuted" class="MuteToggle">
          🔕
          🔔
        </button>

Feud Board on Github

</template>

<template>

      <select v-model="currentAnswerSet">
        <option :value="null">Load Answer Set…</option>
        <option v-for="answerSet in answerSets" :value="answerSet">{{ answerSet.name }}</option>
      </select>
      <button @click.stop.prevent="updateBoard" v-if="false">Reset and Send to Board</button>

        <control-window-tile v-for="n in 4" :answer-number="n + 1"
                             :answer.sync="this.currentAnswerSet.answers[n]"></control-window-tile>

        <control-window-tile v-for="n in 4" :answer-number="n + 5"
                             :answer.sync="this.currentAnswerSet.answers[n + 4]"></control-window-tile>

      <button class="StrikeButton" @click.stop.prevent="wrong(1)" title="Strike 1">☒</button>
      <button class="StrikeButton" @click.stop.prevent="wrong(2)" title="Strike 2">☒</button>
      <button class="StrikeButton" @click.stop.prevent="wrong(3)" title="Strike 3">☒</button>

        <button class="SaveAnswerSet" @click.stop.prevent="saveAnswerSet" title="Save as new…">💾
        </button>

        <button @click.prevent.stop="toggleMuted" class="MuteToggle">
          🔕
          🔔
        </button>

</template>


Add hover effect to boards list

Add hover effect to boards list

Signed-off-by: Simon Spannagel <385979ae470214a5c3123d1805a076c8e5e65bb4@kth.se>

<template>

		<Controls />

					Title

					Members

			<BoardItem v-for="board in filteredBoards" :key="board.id" :board="board" />

</template>


fix button.vue

fix button.vue

<script>
  export default {
    render: function (c) {
      var buttonEl, tagEl, self = this;

      buttonEl = c('button', {
        staticClass: 'btn',
        class: self.classObject,
        on: {
          click: self.onClick
        }
      }, [self.$slots.default]);

      if (self.tag) {
        tagEl = c(self.tag, { class: this.tagClass ? this.tagClass : '' }, [buttonEl]);
        return tagEl;
      }
      return buttonEl;
    },
    props: {
      tag: String,
      tagClass: String,
      primary: Boolean,
      success: Boolean,
      info: Boolean,
      danger: Boolean,
      warning: Boolean
    },
    computed: {
      classObject () {
        var self = this, co = {};
        ['primary', 'success', 'info', 'danger', 'warning'].forEach(function (prop, index) {
          if (self[prop]) co['btn-' + prop] = true;
        });
        return co;
      }
    },
    methods: {
      onClick (event) {
        this.$emit('click', event);
      }
    }
  }
</script>

<script>
  export default {
    render: function (c) {
      var buttonEl, self = this;
      buttonEl = c('button', {
        class: {
          'btn': true,
          'btn-primary': true
        },
        on: {
          click: self.onClick
        }
      }, [self.$slots.default]);

      return buttonEl;
    },
    methods: {
      onClick (event) {
        this.$emit('click', event);
      }
    }
  }
</script>


add transiton and blur

add transiton and blur

<template>

         {{seller.name}}

         {{seller.description}}／{{seller.deliveryTime}}分钟送达

         {{seller.supports[0].description}}

       {{seller.supports.length}}个

     {{seller.bulletin}}

{{seller.name}}

           <star :size="48" :score="seller.score"></star>

优惠信息

             {{seller.supports[$index].description}}

商家公告

{{seller.bulletin}}

</template>


layout interface patch

layout interface patch

<template>
    <div :is="type" :hidden-scroll="hiddenScroll" :body-clz="bodyClz">
        <!-- public -->
        <slot></slot>

        <!-- holy-grail -->
        <template slot="header">
            <slot name="header"></slot>
        </template>
        <template slot="left">
            <slot name="left"></slot>
        </template>
        <template slot="right">
            <slot name="right"></slot>
        </template>
        <template slot="footer">
            <slot name="footer"></slot>
        </template>

        <!-- add-on -->
        <template slot="pre">
            <slot name="pre"></slot>
        </template>
        <template slot="after">
            <slot name="after"></slot>
        </template>
    </div>
</template>
<script>
  import layoutTypes from './layout-types'
  const prefixClz = 'vvue-layout'
  export default {
    name: 'Layout',
    components: {
      'holy-grail': layoutTypes.HolyGrail,
      'add-on': layoutTypes.AddOn
    },
    props: {
      type: {
        type: String,
        default: 'holy-grail'
      },
      hiddenScroll: {
        type: Boolean,
        default: false
      },
      bodyClz: {
        type: String,
        default: ''
      }
    },
    data: () => ({
      prefixClz: prefixClz,
    }),
  }
</script>

<template>
    <div :is="type">
        <!-- public -->
        <slot></slot>

        <!-- holy-grail -->
        <template slot="header">
            <slot name="header"></slot>
        </template>
        <template slot="left">
            <slot name="left"></slot>
        </template>
        <template slot="right">
            <slot name="right"></slot>
        </template>
        <template slot="footer">
            <slot name="footer"></slot>
        </template>

        <!-- add-on -->
        <template slot="pre">
            <slot name="pre"></slot>
        </template>
        <template slot="after">
            <slot name="after"></slot>
        </template>
    </div>
</template>
<script>
  import layoutTypes from './layout-types'
  const prefixClz = 'vvue-layout'
  export default {
    name: 'Layout',
    components: {
      'holy-grail': layoutTypes.HolyGrail,
      'add-on': layoutTypes.AddOn
    },
    props: {
      type: {
        type: String,
        default: 'holy-grail'
      }
    },
    data: () => ({
      prefixClz: prefixClz,
    }),
  }
</script>
<style lang="scss">
</style>


暂时去掉一些项目

暂时去掉一些项目

<template>

        <el-form :model="proxy_form" :rules="rules" ref="proxy_form" label-width="200px">
          <el-form-item label="代理端口" prop="port">
            {{proxy_form.port}}
          </el-form-item>
          <el-form-item label="AnyProxy端口" prop="anyproxy_port">
            {{proxy_form.anyproxy_port}}
          </el-form-item>
          <el-form-item label="ws通信端口" prop="ws_port">
            {{proxy_form.ws_port}}
          </el-form-item>

          <el-form-item label="拦截url" prop="url">
            <el-input v-model="proxy_form.url"></el-input>
          </el-form-item>

          <el-form-item>
            <el-button type="primary" size="large"  @click="submitForm('proxy_form')" :disabled="loading">保存</el-button>
          </el-form-item>
        </el-form>

</template>

<template>

        <el-form :model="proxy_form" :rules="rules" ref="proxy_form" label-width="200px">
          <el-form-item label="代理端口" prop="port">
            {{proxy_form.port}}
          </el-form-item>
          <el-form-item label="AnyProxy端口" prop="anyproxy_port">
            {{proxy_form.anyproxy_port}}
          </el-form-item>
          <el-form-item label="ws通信端口" prop="ws_port">
            {{proxy_form.ws_port}}
          </el-form-item>

          <el-form-item label="拦截url" prop="url">
            <el-input v-model="proxy_form.url"></el-input>
          </el-form-item>
          <el-form-item label="限速值(默认不限速 kb/s)" prop="throttle">
            <el-input v-model.number="proxy_form.throttle" type="number"></el-input>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" size="large"  @click="submitForm('proxy_form')" :disabled="loading">保存</el-button>
          </el-form-item>
        </el-form>

</template>


Fix logo when no logo is set

Fix logo when no logo is set

<template>

        <router-link
           class="home-link"
           to="/"
           @click="toggleSidebar()"
        >

        </router-link>

{{ $t('main.user')}}

             <router-link :to="{name: 'todos'}">
               {{ $t("tasks.my_tasks") }}
             </router-link>

             <router-link :to="{name: 'open-productions'}">
               {{ $t("productions.open_productions") }}
             </router-link>

{{ $t('main.studio')}}

             <router-link :to="{name: 'productions'}">
             {{ $t("productions.title") }}
             </router-link>

             <router-link :to="{name: 'people'}">
             {{ $t("people.title") }}
             </router-link>

             <router-link :to="{name: 'timesheets'}">
             {{ $t("timesheets.title") }}
             </router-link>

{{ $t('main.admin')}}

             <router-link to="/task-types">
             {{ $t("task_types.title") }}
             </router-link>

             <router-link to="/task-status" class="task-status-link">
             {{ $t("task_status.title") }}
             </router-link>

             <router-link to="/asset-types">
             {{ $t("asset_types.title") }}
             </router-link>

             <router-link :to="{name: 'custom-actions'}">
             {{ $t("custom_actions.title") }}
             </router-link>

             <router-link :to="{name: 'settings'}">
               {{ $t("settings.title") }}
             </router-link>

</template>


Bugfix add undeclared prop "doneIcon" (#873)

<template>

    <step-tab
      v-if="__stepper.vertical"
      :vm="this"
    ></step-tab>

    <q-slide-transition>

          <slot></slot>

    </q-slide-transition>

</template>


验证失败错误提示未展示

验证失败错误提示未展示

<template>

      <inline-desc v-if="inlineDesc">{{inlineDesc}}</inline-desc>

      <input
      v-if="!type || type === 'text'"
      class="weui_input"
      :autocomplete="autocomplete"
      :autocapitalize="autocapitalize"
      :autocorrect="autocorrect"
      :spellcheck="spellcheck"
      :style="inputStyle"
      type="text"
      :name="name"
      :pattern="pattern"
      :placeholder="placeholder"
      :readonly="readonly"
      v-model="currentValue"
      @focus="focusHandler"
      @blur="blur"
      ref="input"/>
      <input
      v-if="type === 'number'"
      class="weui_input"
      :autocomplete="autocomplete"
      :autocapitalize="autocapitalize"
      :autocorrect="autocorrect"
      :spellcheck="spellcheck"
      :style="inputStyle"
      type="number"
      :name="name"
      :pattern="pattern"
      :placeholder="placeholder"
      :readonly="readonly"
      v-model="currentValue"
      @focus="focusHandler"
      @blur="blur"
      ref="input"/>
      <input
      v-if="type === 'email'"
      class="weui_input"
      :autocomplete="autocomplete"
      :autocapitalize="autocapitalize"
      :autocorrect="autocorrect"
      :spellcheck="spellcheck"
      :style="inputStyle"
      type="email"
      :name="name"
      :pattern="pattern"
      :placeholder="placeholder"
      :readonly="readonly"
      v-model="currentValue"
      @focus="focusHandler"
      @blur="blur"
      ref="input"/>
      <input
      v-if="type === 'password'"
      class="weui_input"
      :autocomplete="autocomplete"
      :autocapitalize="autocapitalize"
      :autocorrect="autocorrect"
      :spellcheck="spellcheck"
      :style="inputStyle"
      type="password"
      :name="name"
      :pattern="pattern"
      :placeholder="placeholder"
      :readonly="readonly"
      v-model="currentValue"
      @focus="focusHandler"
      @blur="blur"
      ref="input"/>

      <icon type="clear" v-show="!equalWith && showClear && currentValue && !readonly" @click.native="clear"></icon>

      <icon class="vux-input-icon" type="warn" :title="!valid ? firstError : ''" v-show="!novalidate && !equalWith && ((touched && !valid && firstError) || (forceShowError && !valid && firstError))"></icon>
      <icon class="vux-input-icon" type="warn" v-if="!novalidate && hasLengthEqual && dirty && equalWith && !valid"></icon>
      <icon type="success" v-show="!novalidate && equalWith && equalWith === currentValue && valid"></icon>

      <icon type="success" class="vux-input-icon" v-show="novalidate && iconType === 'success'"></icon>
      <icon type="warn" class="vux-input-icon" v-show="novalidate && iconType === 'error'"></icon>

      <slot name="right"></slot>

</template>


feat: improved menu scroll

feat: improved menu scroll

<template>
  <VueDropdown
    placement="bottom-start"
    trigger="manual"
    offset="0"
    :open.sync="isOpen"
    :open-group="`header-select-${_uid}`"
    :delay="{ show: 250, hide: 0 }"
    @apply-show="isShown = true"
    @apply-hide="isShown = false"
  >
    <template #trigger>

        <slot name="trigger">
          <VueButton
            class="flat"
            :icon-left="selectedItem.icon"
            :icon-right="orientation === 'landscape' ? 'arrow_drop_down' : null"
            :class="{
              'icon-button': orientation === 'portrait'
            }"
          >
            <template v-if="orientation === 'landscape'">
              {{ selectedItem.label }}
            </template>
          </VueButton>
        </slot>

    </template>

        <VueDropdownButton
          v-for="(item, index) of items"
          :key="index"
          :icon-left="item.icon || optionIcon"
          :class="{
            selected: selectedItem === item
          }"
          @click="select(item)"
        >
          <slot :item="item">
            {{ item.label }}
          </slot>
        </VueDropdownButton>

          Scroll to switch
          <VueIcon icon="mouse" />

          <VueButton
            class="flex-none icon-button flat invisible group-hover:visible"
            icon-left="close"
            @click="$shared.showMenuScrollTip = false"
          />

  </VueDropdown>
</template>


update scss url CA-406

update scss url CA-406

<template>

        <RightAside v-if="showRightAside" />

            <FrameworkEditorToolbar
                @changeProperties="changeProperties"
                @showExportModal="onOpenExportModal" />

                <Component
                    :is="dynamicThingComponent"
                    :id="'scroll-' + framework.shortId().split('/').pop()"
                    :obj="framework"
                    :repo="repo"
                    :parentNotEditable="queryParams.view==='true'"
                    @deleteObject="deleteObject"
                    :profile="conceptSchemeProfile"
                    @editNodeEvent="onEditNode()"
                    @doneEditingNodeEvent="onDoneEditingNode()"
                    :properties="properties">

                            Last modified {{ lastModified }}

                            Created {{ $moment(framework['schema:dateCreated']).fromNow() }}

                            Approved

                            Published

                </Component>
                <ConceptHierarchy
                    :container="framework"
                    containerType="ConceptScheme"
                    containerTypeGet="EcConceptScheme"
                    :viewOnly="queryParams.view === 'true'"
                    :repo="repo"
                    :exportOptions="conceptExportOptions"
                    :highlightList="highlightCompetency"
                    :profile="conceptProfile"
                    @deleteObject="deleteObject"
                    @exportObject="exportObject"
                    @editMultipleEvent="onEditMultiple"
                    @searchThings="handleSearch($event)"
                    @selectButtonClick="onSelectButtonClick"
                    :properties="properties"
                    @selectedArray="selectedArrayEvent" />

</template>


fix(SPA): 没有消息时不显示时间

fix(SPA): 没有消息时不显示时间

issue #655

<template>

    <JoLoadMore
      ref="loadmore"
      :show-bottom="false"
      @onRefresh="fetchMessage"
    >

        <RouterLink to="/message/system" tag="li">
          <svg class="m-style-svg m-svg-large"><use xlink:href="#icon-message-notice" /></svg>

{{ $t('message.system.name') }} {{ system.first.created_at | time2tips }}

              <template v-if="system.first.data.contents">{{ system.first.data.contents }}</template>
              <template v-else>{{ system.first.data | getNotificationDisplay }}</template>

              <BadgeIcon v-if="system.badge" :count="system.badge" />

暂无系统消息

        </RouterLink>

        <RouterLink to="/message/comments" tag="li">
          <svg class="m-style-svg m-svg-large"><use xlink:href="#icon-message-comment" /></svg>

{{ $t('message.comment.name') }} {{ comment.last_created_at | time2tips }}

{{ (comment.preview_users_names || []).join('、') }}评论了我 <BadgeIcon v-if="comment.badge" :count="comment.badge" />

还没有人评论了我

        </RouterLink>

        <RouterLink to="/message/likes" tag="li">
          <svg class="m-style-svg m-svg-large"><use xlink:href="#icon-message-like" /></svg>

{{ $t('message.like.name') }} {{ like.last_created_at | time2tips }}

{{ (like.preview_users_names || []).join('、') }}赞了我 <BadgeIcon v-if="like.badge" :count="like.badge" />

还没有人赞了我

        </RouterLink>

        <RouterLink to="/message/audits" tag="li">
          <svg class="m-style-svg m-svg-large"><use xlink:href="#icon-message-audit" /></svg>

审核通知

你有未审核的信息请及时处理 <BadgeIcon v-if="unreadAudits" :count="unreadAudits" />

        </RouterLink>

    </JoLoadMore>

</template>

<template>

    <JoLoadMore
      ref="loadmore"
      :show-bottom="false"
      @onRefresh="fetchMessage"
    >

        <RouterLink to="/message/system" tag="li">
          <svg class="m-style-svg m-svg-large"><use xlink:href="#icon-message-notice" /></svg>

系统消息 {{ system.first.created_at | time2tips }}

              <template v-if="system.first.data.contents">{{ system.first.data.contents }}</template>
              <template v-else>{{ system.first.data | getNotificationDisplay }}</template>

              <BadgeIcon v-if="system.badge" :count="system.badge" />

暂无系统消息

        </RouterLink>

        <RouterLink to="/message/comments" tag="li">
          <svg class="m-style-svg m-svg-large"><use xlink:href="#icon-message-comment" /></svg>

收到的评论 {{ comment.last_created_at | time2tips }}

{{ (comment.preview_users_names || []).join('、') }}评论了我 <BadgeIcon v-if="comment.badge" :count="comment.badge" />

还没有人评论了我

        </RouterLink>

        <RouterLink to="/message/likes" tag="li">
          <svg class="m-style-svg m-svg-large"><use xlink:href="#icon-message-like" /></svg>

收到的赞 {{ like.last_created_at | time2tips }}

{{ (like.preview_users_names || []).join('、') }}赞了我 <BadgeIcon v-if="like.badge" :count="like.badge" />

还没有人赞了我

        </RouterLink>

        <RouterLink to="/message/audits" tag="li">
          <svg class="m-style-svg m-svg-large"><use xlink:href="#icon-message-audit" /></svg>

审核通知

你有未审核的信息请及时处理 <BadgeIcon v-if="unreadAudits" :count="unreadAudits" />

        </RouterLink>

    </JoLoadMore>

</template>


Finish conversion

Finish conversion

<template>

          <select v-model="selectedLeft">
            <option
              v-for="(opt, idx) in options"
              :key="opt + idx"
              :value="opt">{{ opt | capitalize }}
            </option>
          </select>

          <input
            v-model="valueLeft"
            type="text"
            placeholder="Amount">

          <select v-model="selectedRight">
            <option
              v-for="(opt, idx) in options"
              :key="opt + idx"
              :value="opt">{{ opt | capitalize }}
            </option>
          </select>

          <input
            v-model="valueRight"
            type="text"
            placeholder="Amount">

</template>

<template>

          <select v-model="selectedLeft">
            <option
              v-for="(opt, idx) in options"
              :key="opt + idx"
              :value="opt">{{ opt | capitalize }}
            </option>
          </select>

          <input
            v-model="valueLeft"
            type="number"
            placeholder="Amount">

          <select v-model="selectedRight">
            <option
              v-for="(opt, idx) in options"
              :key="opt + idx"
              :value="opt">{{ opt | capitalize }}
            </option>
          </select>

          <input
            v-model="valueRight"
            type="number"
            placeholder="Amount">

</template>


Center loading spinner

Center loading spinner

<template>

Releases

      <ApolloQuery :query="require('../graphql/Releases.gql')" notifyOnNetworkStatusChange>
        <template slot-scope="{ result: { loading, error, data } }">

An error occurred: {{ error.message }}

                <Card :title="release.name" :description="release.groups[0].name" />

No results

        </template>
      </ApolloQuery>

</template>


fix(cover-view): app平台修复vue cover-view组件flex布局无效问题

<script>
import native from '../../mixins/native'
import cover from '../../mixins/cover'

export default {
  name: 'CoverView',
  mixins: [native, cover],
  props: {},
  data () {
    return {
      coverType: 'text',
      coverContent: ''
    }
  },
  render (createElement) {
    let coverContent = ''
    const $slots = this.$slots.default || []
    const _slots = $slots.filter(node => node.tag)
    if (!_slots.length) {
      $slots.forEach(node => {
        if (!node.tag) {
          coverContent += node.text || ''
        }
      })
      this.coverContent = coverContent
    } else {
      coverContent = _slots
    }
    return createElement('uni-cover-view', {
      on: {
        on: this.$listeners
      }
    }, [createElement('div', {
      ref: 'container',
      staticClass: 'uni-cover-view'
    }, [].concat(coverContent))])
  }
}
</script>
<style>
uni-cover-view {
  display: block;
  line-height: 1.2;
  overflow: hidden;
  white-space: nowrap;
  pointer-events: auto;
}

uni-cover-view[hidden] {
  display: none;
}

uni-cover-view .uni-cover-view {
  width: 100%;
  height: 100%;
  visibility: hidden;
  text-overflow: inherit;
  overflow: hidden;
  white-space: inherit;
  -webkit-align-items: inherit;
  align-items: inherit;
  -webkit-justify-content: inherit;
  justify-content: inherit;
  -webkit-flex-direction: inherit;
  flex-direction: inherit;
  -webkit-flex-wrap: inherit;
  flex-wrap: inherit;
  display: inherit;
  overflow: inherit;
}
</style>

<script>
import native from '../../mixins/native'
import cover from '../../mixins/cover'

export default {
  name: 'CoverView',
  mixins: [native, cover],
  props: {},
  data () {
    return {
      coverType: 'text',
      coverContent: ''
    }
  },
  render (createElement) {
    let coverContent = ''
    const $slots = this.$slots.default || []
    const _slots = $slots.filter(node => node.tag)
    if (!_slots.length) {
      $slots.forEach(node => {
        if (!node.tag) {
          coverContent += node.text || ''
        }
      })
      this.coverContent = coverContent
    } else {
      coverContent = _slots
    }
    return createElement('uni-cover-view', {
      on: {
        on: this.$listeners
      }
    }, [createElement('div', {
      ref: 'container',
      staticClass: 'uni-cover-view'
    }, [].concat(coverContent))])
  }
}
</script>
<style>
uni-cover-view {
  display: block;
  line-height: 1.2;
  overflow: hidden;
  white-space: nowrap;
  pointer-events: auto;
}

uni-cover-view[hidden] {
  display: none;
}

uni-cover-view .uni-cover-view {
  width: 100%;
  height: 100%;
  visibility: hidden;
}
</style>


ui: refine cursor pointer to dark list items

<template>

    <app-btn-menu @click.native="openMobileMenu" />

    <v-navigation-drawer
      v-model="isOpen"
      absolute
      temporary
      color="expandHeader"
    >
      <v-list-item class="pt-8 pb-8 pl-4 pr-1">
        <v-img
          class="mx-auto"
          src="@/assets/images/icons/logo-mew.png"
          max-height="36"
          max-width="130"
          @click="pushRoute({ name: 'Home' })"
        />

        <v-spacer></v-spacer>

        <v-btn x-large icon light @click="isOpen = false">
          <v-icon color="white" large>mdi-window-close</v-icon>
        </v-btn>
      </v-list-item>

      <v-list color="expandHeader" dark class="px-2">
        <template v-for="(item, index) in menu">
          <v-list-item v-if="!item.sub" :key="index" class="mb-3">
            <v-list-item-content v-if="item.to" @click="pushRoute(item.to)">

{{ item.label }}

            </v-list-item-content>

              <v-list-item-content class="white--text">

{{ item.label }}

              </v-list-item-content>

          </v-list-item>

          <v-list-group
            v-if="item.sub"
            :key="index"
            prepend-icon=""
            color="white"
            class="mb-3"
          >
            <template #activator>
              <v-list-item-content>

{{ item.label }}

              </v-list-item-content>
            </template>
            <v-list-item
              v-for="(child, ckey) in item.sub"
              :key="ckey"
              style="background-color: var(--v-expandHeader-base) !important"
              dense
              class="pl-4"
            >
              <v-list-item-content>
                <v-list-item-title
                  v-if="child.to"
                  class="pl-4 white--text font-weight-regular mew-body"
                  @click="pushRoute(child.to)"
                  v-text="child.label"
                ></v-list-item-title>

                  <v-list-item-title
                    class="pl-13 white--text font-weight-regular mew-body"
                    v-text="child.label"
                  ></v-list-item-title>

              </v-list-item-content>
            </v-list-item>
          </v-list-group>
        </template>
      </v-list>
    </v-navigation-drawer>

</template>


fix(timeline): hide canvas to prevent flashes of color

<script>
import * as PIXI from 'pixi.js'
import { install as installUnsafeEval } from '@pixi/unsafe-eval'
import { ref, onMounted, onUnmounted, watch, watchEffect, computed } from '@vue/composition-api'
import { useLayers, useTime, useSelectedEvent, onTimelineReset, onEventAdd, useCursor } from '.'
import Vue from 'vue'
import { useApps } from '../apps'
import { onKeyUp } from '@front/util/keyboard'
import { useDarkMode } from '@front/util/theme'
import SharedData from '@utils/shared-data'

const LAYER_SIZE = 16
const GROUP_SIZE = 6

installUnsafeEval(PIXI)

export default {
  setup () {
    const wrapper = ref(null)

    const { currentAppId } = useApps()
    const { startTime, endTime, minTime, maxTime } = useTime()
    const { darkMode } = useDarkMode()

    // Reset

    const resetCbs = []

    function onReset (cb) {
      resetCbs.push(cb)
    }

    function reset () {
      for (const cb of resetCbs) {
        cb()
      }
    }

    watch(currentAppId, () => reset())

    onTimelineReset(reset)

    // Pixi App

    /** @type {PIXI.Application} */
    let app

    /** @type {PIXI.Container} */
    let verticalScrollingContainer

    /** @type {PIXI.Container} */
    let horizontalScrollingContainer

    onMounted(() => {
      app = new PIXI.Application({
        resizeTo: wrapper.value,
        antialias: true,
        autoDensity: true
      })
      app.stage.interactive = true
      app.stage.hitArea = new PIXI.Rectangle(0, 0, 100000, 100000)
      updateBackground()
      wrapper.value.appendChild(app.view)
      app.view.style.opacity = 0
      app.renderer.on('postrender', () => {
        app.view.style.opacity = 1
      })

      verticalScrollingContainer = new PIXI.Container()
      app.stage.addChild(verticalScrollingContainer)

      horizontalScrollingContainer = new PIXI.Container()
      verticalScrollingContainer.addChild(horizontalScrollingContainer)
    })

    onUnmounted(() => {
      app.destroy()
    })

    function updateBackground () {
      if (darkMode.value) {
        app && (app.renderer.backgroundColor = 0x0b1015)
      } else {
        app && (app.renderer.backgroundColor = 0xffffff)
      }
    }

    watchEffect(() => {
      updateBackground()
    })

    // Layers

    const {
      layers,
      vScroll,
      hoverLayerId,
      selectedEventLayerId
    } = useLayers()

    /** @type {Container[]} */
    let layerContainers = []
    let layersMap = {}

    function initLayers () {
      let y = 0
      for (const layer of layers.value) {
        const container = new PIXI.Container()
        container.y = y
        y += (layer.height + 1) * LAYER_SIZE
        horizontalScrollingContainer.addChild(container)
        // allow z-index sorting
        container.sortableChildren = true
        layerContainers.push(container)
        layersMap[layer.id] = {
          layer,
          container
        }
      }
    }

    onMounted(() => {
      initLayers()
    })

    function resetLayers () {
      for (const container of layerContainers) {
        container.destroy()
      }
      layerContainers = []
      layersMap = {}
      initLayers()
      resetEvents()
    }

    onReset(() => {
      resetLayers()
    })

    watch(layers, () => resetLayers())

    // Layer hover

    /** @type {import('pixi.js').Graphics} */
    let layerHoverEffect

    onMounted(() => {
      layerHoverEffect = new PIXI.Graphics()
      layerHoverEffect.alpha = 0.1
      layerHoverEffect.visible = false
      verticalScrollingContainer.addChild(layerHoverEffect)
    })

    function getLayerY (layer) {
      return layers.value.slice(0, layers.value.indexOf(layer)).reduce((sum, layer) => sum + (layer.height + 1) * LAYER_SIZE, 0)
    }

    function drawLayerBackgroundEffects () {
      if (!layerHoverEffect) return

      const layerIds = [
        {
          id: hoverLayerId.value,
          alpha: 1
        },
        {
          id: hoverLayerId.value !== selectedEventLayerId.value ? selectedEventLayerId.value : null,
          alpha: 0.5
        }
      ].filter(({ id }) => id != null)

      if (layerIds.length) {
        layerHoverEffect.clear()
        layerIds.forEach(({ id, alpha }) => drawLayerBackground(id, alpha))
        layerHoverEffect.visible = true
      } else {
        layerHoverEffect.visible = false
      }
    }

    function drawLayerBackground (layerId, alpha = 1) {
      const { layer } = layersMap[layerId]
      layerHoverEffect.beginFill(layer.color, alpha)
      layerHoverEffect.drawRect(0, getLayerY(layer), app.view.width, (layer.height + 1) * LAYER_SIZE)
    }

    watch(hoverLayerId, () => {
      drawLayerBackgroundEffects()
    })

    watch(selectedEventLayerId, () => {
      drawLayerBackgroundEffects()
    })

    /**
     * @param {MouseEvent} event
     */
    function updateLayerHover (event) {
      let { offsetY } = event
      offsetY -= verticalScrollingContainer.y
      if (offsetY >= 0) {
        let y = 0
        // Find the hovering layer depending on each layer's height
        for (const layer of layers.value) {
          y += (layer.height + 1) * LAYER_SIZE
          if (offsetY <= y) {
            hoverLayerId.value = layer.id
            return
          }
        }
      }
      clearLayerHover()
    }

    function clearLayerHover () {
      hoverLayerId.value = null
    }

    // Events

    const { selectedEvent } = useSelectedEvent()

    let events = []

    function getEventPosition (event) {
      return (event.time - minTime.value) / (endTime.value - startTime.value) * app.view.width
    }

    function updateEventPosition (event) {
      event.container.x = getEventPosition(event)

      let y = 0
      if (event.group && event !== event.group.firstEvent) {
        // If the event is inside a group, just use the group position
        y = event.group.y
      } else {
        // Check for 'collision' with other event groups
        const l = event.layer.groups.length
        for (let i = 0; i < l; i++) {
          const group = event.layer.groups[i]
          if (group !== event.group && group.y === y && event.time >= group.firstEvent.time - 100 && event.time <= group.lastEvent.time + 100) {
            y++
            // We need to check all the layers again since we moved the event
            i = 0
          }
        }

        // If the event is the first in a group, update group position
        if (event.group) {
          event.group.y = y
        }

        // Might update the layer's height as well
        if (y + 1 > event.layer.height) {
          event.layer.height = y + 1
        }
      }
      event.container.y = (y + 1) * LAYER_SIZE
    }

    function addEvent (event, layerContainer) {
      // Container
      const eventContainer = new PIXI.Container()
      event.container = eventContainer
      updateEventPosition(event)
      layerContainer.addChild(eventContainer)

      // Graphics
      const g = new PIXI.Graphics()
      event.g = g
      refreshEventGraphics(event)
      eventContainer.addChild(g)

      // Group graphics
      if (event.group) {
        if (event.group.firstEvent === event) {
          const groupG = new PIXI.Graphics()
          event.groupG = groupG
          drawEventGroup(event)
          eventContainer.addChild(groupG)
        } else if (event.group.lastEvent === event) {
          drawEventGroup(event.group.firstEvent)
          // We need to check for collisions again
          queueEventsUpdate()
        }
      }

      events.push(event)

      return event
    }

    function initEvents () {
      for (const k in layersMap) {
        const { layer, container } = layersMap[k]
        for (const event of layer.displayedEvents) {
          addEvent(event, container)
        }
      }
    }

    onMounted(() => {
      initEvents()
    })

    function resetEvents () {
      for (const e of events) {
        e.g.destroy()
        e.g = null
      }
      events = []
      initEvents()
    }

    onEventAdd(event => {
      if (event.appId !== 'all' && event.appId !== currentAppId.value) return

      if (event.stackParent) {
        // The event graphics might grow
        refreshEventGraphics(event.stackParent)
        return
      }

      const layer = layersMap[event.layer.id]
      if (layer) {
        addEvent(event, layer.container)
      }
    })

    let eventsUpdateQueued = false

    function queueEventsUpdate () {
      if (eventsUpdateQueued) return
      eventsUpdateQueued = true
      Vue.nextTick(() => {
        updateEvents()
        eventsUpdateQueued = false
      })
    }

    function updateEvents () {
      for (const event of events) {
        updateEventPosition(event)

        if (event.groupG) {
          drawEventGroup(event)
        }
      }
    }

    watch(startTime, () => queueEventsUpdate())
    watch(endTime, () => queueEventsUpdate())
    watch(minTime, () => queueEventsUpdate())

    // Event selection

    onMounted(() => {
      app.stage.addListener('click', event => {
        // We find the nearest event from the mouse click position
        let choice
        let distance = Number.POSITIVE_INFINITY
        for (const e of events) {
          const globalPosition = e.g.getGlobalPosition()
          const d = Math.abs(globalPosition.x - event.data.global.x) + Math.abs(globalPosition.y - event.data.global.y)

          if (!choice || d < distance) {
            choice = e
            distance = d
          }
        }
        selectedEvent.value = choice
      })
    })

    function drawEvent (selected, event) {
      if (event) {
        let color = event.layer.color
        for (const subEvent of event.stackedEvents) {
          if (subEvent.logType === 'error') {
            color = 0xE53E3E
            break
          } else if (subEvent.logType === 'warning') {
            color = 0xECC94B
          }
        }

        if (event.g) {
          /** @type {PIXI.Graphics} */
          const g = event.g
          let size = event.stackedEvents.length > 1 ? 4 : 3
          g.clear()
          if (selected) {
            // Border-only style
            size -= 0.5
            g.lineStyle(1, color)
            g.beginFill(darkMode.value ? 0x0b1015 : 0xffffff)
            event.container.zIndex = 999999999
          } else {
            g.beginFill(color)
            event.container.zIndex = size
          }
          g.drawCircle(0, 0, size)
        }
      }
    }

    const drawSelectedEvent = drawEvent.bind(null, true)
    const drawUnselectedEvent = drawEvent.bind(null, false)

    function refreshEventGraphics (event) {
      if (selectedEvent.value === event) {
        drawSelectedEvent(event)
      } else {
        drawUnselectedEvent(event)
      }
    }

    watch(selectedEvent, (event, oldEvent) => {
      drawUnselectedEvent(oldEvent)
      drawSelectedEvent(event)
    })

    // Event selection with keyboard

    onKeyUp(event => {
      if (event.key === 'ArrowLeft') {
        let index
        if (selectedEvent.value) {
          index = events.indexOf(selectedEvent.value) - 1
          if (index < 0) {
            index = events.length - 1
          }
        } else {
          index = events.length - 1
        }
        selectedEvent.value = events[index]
      } else if (event.key === 'ArrowRight') {
        let index
        if (selectedEvent.value) {
          index = events.indexOf(selectedEvent.value) + 1
          if (index >= events.length) {
            index = 0
          }
        } else {
          index = 0
        }
        selectedEvent.value = events[index]
      }
    })

    // Event Groups

    function drawEventGroup (event) {
      if (event.groupG) {
        /** @type {PIXI.Graphics} */
        const g = event.groupG
        g.clear()
        g.lineStyle(1, event.layer.color, 0.5)
        g.beginFill(event.layer.color, 0.1)
        const size = getEventPosition(event.group.lastEvent) - getEventPosition(event.group.firstEvent)
        // Some adjustements were made on the vertical position and size to snap border pixels to the screen's grid (LoDPI)
        g.drawRoundedRect(-GROUP_SIZE, -GROUP_SIZE + 0.5, size + GROUP_SIZE * 2, GROUP_SIZE * 2 - 1, GROUP_SIZE)
      }
    }

    // Time cursor

    const { cursorTime } = useCursor()

    /** @type {PIXI.Graphics} */
    let timeCursor

    onMounted(() => {
      timeCursor = new PIXI.Graphics()
      timeCursor.visible = false
      drawTimeCursor()
      app.stage.addChild(timeCursor)
    })

    function drawTimeCursor () {
      timeCursor.clear()
      timeCursor.lineStyle(1, 0x888888, 0.2)
      timeCursor.moveTo(0.5, 0)
      timeCursor.lineTo(0.5, app.view.height)
    }

    /**
     * @param {MouseEvent} event
     */
    function updateCursorPosition (event) {
      const { offsetX } = event
      timeCursor.x = offsetX
      timeCursor.visible = true
      cursorTime.value = offsetX / app.view.width * (endTime.value - startTime.value) + startTime.value
    }

    function clearCursor () {
      timeCursor.visible = false
      cursorTime.value = null
    }

    // Time grid

    /** @type {PIXI.Graphics} */
    let timeGrid

    onMounted(() => {
      timeGrid = new PIXI.Graphics()
      timeGrid.visible = SharedData.timelineTimeGrid
      drawTimeGrid()
      app.stage.addChild(timeGrid)
    })

    function drawTimeGrid () {
      if (!timeGrid.visible) return

      const ratio = (endTime.value - startTime.value) / app.view.width
      let timeInterval = 10
      let width = timeInterval / ratio

      while (width < 20) {
        timeInterval *= 10
        width *= 10
      }

      const offset = startTime.value % timeInterval / ratio

      timeGrid.clear()
      timeGrid.lineStyle(1, 0x888888, 0.075)
      for (let x = -offset; x < app.view.width; x += width) {
        timeGrid.moveTo(x + 0.5, 0)
        timeGrid.lineTo(x + 0.5, app.view.height)
      }
    }

    watch(() => SharedData.timelineTimeGrid, value => {
      timeGrid.visible = value
      if (value) {
        drawTimeGrid()
      }
    })

    // Camera

    let cameraUpdateQueued = false

    function queueCameraUpdate () {
      if (cameraUpdateQueued) return
      cameraUpdateQueued = true
      Vue.nextTick(() => {
        updateCamera()
        cameraUpdateQueued = false
      })
    }

    function updateCamera () {
      horizontalScrollingContainer.x = -(startTime.value - minTime.value) / (endTime.value - startTime.value) * app.view.width
      drawLayerBackgroundEffects()
      drawTimeGrid()
    }

    watch(startTime, () => queueCameraUpdate())
    watch(endTime, () => queueCameraUpdate())

    /**
     * @param {MouseWheelEvent} event
     */
    function onMouseWheel (event) {
      const size = endTime.value - startTime.value
      const viewWidth = wrapper.value.offsetWidth

      if (event.ctrlKey || event.metaKey) {
        // Zoom
        event.preventDefault()

        const centerRatio = event.offsetX / viewWidth
        const center = size * centerRatio + startTime.value

        let newSize = size + event.deltaY / viewWidth * size * 2
        if (newSize < 100) {
          newSize = 100
        }

        let start = center - newSize * centerRatio
        let end = center + newSize * (1 - centerRatio)
        if (start < minTime.value) {
          start = minTime.value
        }
        if (end > maxTime.value) {
          end = maxTime.value
        }
        startTime.value = start
        endTime.value = end
      } else {
        let deltaX = event.deltaX

        if (deltaX === 0 && event.shiftKey && event.deltaY !== 0) {
          // Horitonzal scroll with vertical mouse wheel and shift key
          deltaX = event.deltaY
        }

        if (deltaX !== 0) {
          // Horizontal scroll
          const delta = deltaX / viewWidth * size
          let start = startTime.value += delta
          if (start < minTime.value) {
            start = minTime.value
          } else if (start + size >= maxTime.value) {
            start = maxTime.value - size
          }
          startTime.value = start
          endTime.value = start + size
        }
      }
    }

    // Vertical scroll

    function updateVScroll () {
      if (verticalScrollingContainer) {
        verticalScrollingContainer.y = -vScroll.value
      }
    }

    watch(vScroll, () => {
      updateVScroll()
    })

    onMounted(() => {
      updateVScroll()
    })

    // Resize

    function onResize () {
      app.view.style.opacity = 0
      app.queueResize()
      queueEventsUpdate()
      drawLayerBackgroundEffects()
      drawTimeCursor()
      drawTimeGrid()
    }

    // Events

    function onMouseMove (event) {
      updateLayerHover(event)
      updateCursorPosition(event)
    }

    function onMouseOut () {
      clearLayerHover()
      clearCursor()
    }

    return {
      wrapper,
      onMouseWheel,
      onMouseMove,
      onMouseOut,
      onResize
    }
  }
}
</script>

<template>
  <div
    ref="wrapper"
    class="relative overflow-hidden"
    @mousewheel="onMouseWheel"
    @mousemove="onMouseMove"
    @mouseout="onMouseOut"
  >
    <resize-observer @notify="onResize" />
  </div>
</template>

<script>
import * as PIXI from 'pixi.js'
import { install as installUnsafeEval } from '@pixi/unsafe-eval'
import { ref, onMounted, onUnmounted, watch, watchEffect, computed } from '@vue/composition-api'
import { useLayers, useTime, useSelectedEvent, onTimelineReset, onEventAdd, useCursor } from '.'
import Vue from 'vue'
import { useApps } from '../apps'
import { onKeyUp } from '@front/util/keyboard'
import { useDarkMode } from '@front/util/theme'
import SharedData from '@utils/shared-data'

const LAYER_SIZE = 16
const GROUP_SIZE = 6

installUnsafeEval(PIXI)

export default {
  setup () {
    const wrapper = ref(null)

    const { currentAppId } = useApps()
    const { startTime, endTime, minTime, maxTime } = useTime()
    const { darkMode } = useDarkMode()

    // Reset

    const resetCbs = []

    function onReset (cb) {
      resetCbs.push(cb)
    }

    function reset () {
      for (const cb of resetCbs) {
        cb()
      }
    }

    watch(currentAppId, () => reset())

    onTimelineReset(reset)

    // Pixi App

    /** @type {PIXI.Application} */
    let app

    /** @type {PIXI.Container} */
    let verticalScrollingContainer

    /** @type {PIXI.Container} */
    let horizontalScrollingContainer

    onMounted(() => {
      app = new PIXI.Application({
        resizeTo: wrapper.value,
        antialias: true,
        autoDensity: true
      })
      app.stage.interactive = true
      app.stage.hitArea = new PIXI.Rectangle(0, 0, 100000, 100000)
      updateBackground()
      wrapper.value.appendChild(app.view)

      verticalScrollingContainer = new PIXI.Container()
      app.stage.addChild(verticalScrollingContainer)

      horizontalScrollingContainer = new PIXI.Container()
      verticalScrollingContainer.addChild(horizontalScrollingContainer)
    })

    onUnmounted(() => {
      app.destroy()
    })

    function updateBackground () {
      if (darkMode.value) {
        app && (app.renderer.backgroundColor = 0x0b1015)
      } else {
        app && (app.renderer.backgroundColor = 0xffffff)
      }
    }

    watchEffect(() => {
      updateBackground()
    })

    // Layers

    const {
      layers,
      vScroll,
      hoverLayerId,
      selectedEventLayerId
    } = useLayers()

    /** @type {Container[]} */
    let layerContainers = []
    let layersMap = {}

    function initLayers () {
      let y = 0
      for (const layer of layers.value) {
        const container = new PIXI.Container()
        container.y = y
        y += (layer.height + 1) * LAYER_SIZE
        horizontalScrollingContainer.addChild(container)
        // allow z-index sorting
        container.sortableChildren = true
        layerContainers.push(container)
        layersMap[layer.id] = {
          layer,
          container
        }
      }
    }

    onMounted(() => {
      initLayers()
    })

    function resetLayers () {
      for (const container of layerContainers) {
        container.destroy()
      }
      layerContainers = []
      layersMap = {}
      initLayers()
      resetEvents()
    }

    onReset(() => {
      resetLayers()
    })

    watch(layers, () => resetLayers())

    // Layer hover

    /** @type {import('pixi.js').Graphics} */
    let layerHoverEffect

    onMounted(() => {
      layerHoverEffect = new PIXI.Graphics()
      layerHoverEffect.alpha = 0.1
      layerHoverEffect.visible = false
      verticalScrollingContainer.addChild(layerHoverEffect)
    })

    function getLayerY (layer) {
      return layers.value.slice(0, layers.value.indexOf(layer)).reduce((sum, layer) => sum + (layer.height + 1) * LAYER_SIZE, 0)
    }

    function drawLayerBackgroundEffects () {
      if (!layerHoverEffect) return

      const layerIds = [
        {
          id: hoverLayerId.value,
          alpha: 1
        },
        {
          id: hoverLayerId.value !== selectedEventLayerId.value ? selectedEventLayerId.value : null,
          alpha: 0.5
        }
      ].filter(({ id }) => id != null)

      if (layerIds.length) {
        layerHoverEffect.clear()
        layerIds.forEach(({ id, alpha }) => drawLayerBackground(id, alpha))
        layerHoverEffect.visible = true
      } else {
        layerHoverEffect.visible = false
      }
    }

    function drawLayerBackground (layerId, alpha = 1) {
      const { layer } = layersMap[layerId]
      layerHoverEffect.beginFill(layer.color, alpha)
      layerHoverEffect.drawRect(0, getLayerY(layer), app.view.width, (layer.height + 1) * LAYER_SIZE)
    }

    watch(hoverLayerId, () => {
      drawLayerBackgroundEffects()
    })

    watch(selectedEventLayerId, () => {
      drawLayerBackgroundEffects()
    })

    /**
     * @param {MouseEvent} event
     */
    function updateLayerHover (event) {
      let { offsetY } = event
      offsetY -= verticalScrollingContainer.y
      if (offsetY >= 0) {
        let y = 0
        // Find the hovering layer depending on each layer's height
        for (const layer of layers.value) {
          y += (layer.height + 1) * LAYER_SIZE
          if (offsetY <= y) {
            hoverLayerId.value = layer.id
            return
          }
        }
      }
      clearLayerHover()
    }

    function clearLayerHover () {
      hoverLayerId.value = null
    }

    // Events

    const { selectedEvent } = useSelectedEvent()

    let events = []

    function getEventPosition (event) {
      return (event.time - minTime.value) / (endTime.value - startTime.value) * app.view.width
    }

    function updateEventPosition (event) {
      event.container.x = getEventPosition(event)

      let y = 0
      if (event.group && event !== event.group.firstEvent) {
        // If the event is inside a group, just use the group position
        y = event.group.y
      } else {
        // Check for 'collision' with other event groups
        const l = event.layer.groups.length
        for (let i = 0; i < l; i++) {
          const group = event.layer.groups[i]
          if (group !== event.group && group.y === y && event.time >= group.firstEvent.time - 100 && event.time <= group.lastEvent.time + 100) {
            y++
            // We need to check all the layers again since we moved the event
            i = 0
          }
        }

        // If the event is the first in a group, update group position
        if (event.group) {
          event.group.y = y
        }

        // Might update the layer's height as well
        if (y + 1 > event.layer.height) {
          event.layer.height = y + 1
        }
      }
      event.container.y = (y + 1) * LAYER_SIZE
    }

    function addEvent (event, layerContainer) {
      // Container
      const eventContainer = new PIXI.Container()
      event.container = eventContainer
      updateEventPosition(event)
      layerContainer.addChild(eventContainer)

      // Graphics
      const g = new PIXI.Graphics()
      event.g = g
      refreshEventGraphics(event)
      eventContainer.addChild(g)

      // Group graphics
      if (event.group) {
        if (event.group.firstEvent === event) {
          const groupG = new PIXI.Graphics()
          event.groupG = groupG
          drawEventGroup(event)
          eventContainer.addChild(groupG)
        } else if (event.group.lastEvent === event) {
          drawEventGroup(event.group.firstEvent)
          // We need to check for collisions again
          queueEventsUpdate()
        }
      }

      events.push(event)

      return event
    }

    function initEvents () {
      for (const k in layersMap) {
        const { layer, container } = layersMap[k]
        for (const event of layer.displayedEvents) {
          addEvent(event, container)
        }
      }
    }

    onMounted(() => {
      initEvents()
    })

    function resetEvents () {
      for (const e of events) {
        e.g.destroy()
        e.g = null
      }
      events = []
      initEvents()
    }

    onEventAdd(event => {
      if (event.appId !== 'all' && event.appId !== currentAppId.value) return

      if (event.stackParent) {
        // The event graphics might grow
        refreshEventGraphics(event.stackParent)
        return
      }

      const layer = layersMap[event.layer.id]
      if (layer) {
        addEvent(event, layer.container)
      }
    })

    let eventsUpdateQueued = false

    function queueEventsUpdate () {
      if (eventsUpdateQueued) return
      eventsUpdateQueued = true
      Vue.nextTick(() => {
        updateEvents()
        eventsUpdateQueued = false
      })
    }

    function updateEvents () {
      for (const event of events) {
        updateEventPosition(event)

        if (event.groupG) {
          drawEventGroup(event)
        }
      }
    }

    watch(startTime, () => queueEventsUpdate())
    watch(endTime, () => queueEventsUpdate())
    watch(minTime, () => queueEventsUpdate())

    // Event selection

    onMounted(() => {
      app.stage.addListener('click', event => {
        // We find the nearest event from the mouse click position
        let choice
        let distance = Number.POSITIVE_INFINITY
        for (const e of events) {
          const globalPosition = e.g.getGlobalPosition()
          const d = Math.abs(globalPosition.x - event.data.global.x) + Math.abs(globalPosition.y - event.data.global.y)

          if (!choice || d < distance) {
            choice = e
            distance = d
          }
        }
        selectedEvent.value = choice
      })
    })

    function drawEvent (selected, event) {
      if (event) {
        let color = event.layer.color
        for (const subEvent of event.stackedEvents) {
          if (subEvent.logType === 'error') {
            color = 0xE53E3E
            break
          } else if (subEvent.logType === 'warning') {
            color = 0xECC94B
          }
        }

        if (event.g) {
          /** @type {PIXI.Graphics} */
          const g = event.g
          let size = event.stackedEvents.length > 1 ? 4 : 3
          g.clear()
          if (selected) {
            // Border-only style
            size -= 0.5
            g.lineStyle(1, color)
            g.beginFill(darkMode.value ? 0x0b1015 : 0xffffff)
            event.container.zIndex = 999999999
          } else {
            g.beginFill(color)
            event.container.zIndex = size
          }
          g.drawCircle(0, 0, size)
        }
      }
    }

    const drawSelectedEvent = drawEvent.bind(null, true)
    const drawUnselectedEvent = drawEvent.bind(null, false)

    function refreshEventGraphics (event) {
      if (selectedEvent.value === event) {
        drawSelectedEvent(event)
      } else {
        drawUnselectedEvent(event)
      }
    }

    watch(selectedEvent, (event, oldEvent) => {
      drawUnselectedEvent(oldEvent)
      drawSelectedEvent(event)
    })

    // Event selection with keyboard

    onKeyUp(event => {
      if (event.key === 'ArrowLeft') {
        let index
        if (selectedEvent.value) {
          index = events.indexOf(selectedEvent.value) - 1
          if (index < 0) {
            index = events.length - 1
          }
        } else {
          index = events.length - 1
        }
        selectedEvent.value = events[index]
      } else if (event.key === 'ArrowRight') {
        let index
        if (selectedEvent.value) {
          index = events.indexOf(selectedEvent.value) + 1
          if (index >= events.length) {
            index = 0
          }
        } else {
          index = 0
        }
        selectedEvent.value = events[index]
      }
    })

    // Event Groups

    function drawEventGroup (event) {
      if (event.groupG) {
        /** @type {PIXI.Graphics} */
        const g = event.groupG
        g.clear()
        g.lineStyle(1, event.layer.color, 0.5)
        g.beginFill(event.layer.color, 0.1)
        const size = getEventPosition(event.group.lastEvent) - getEventPosition(event.group.firstEvent)
        // Some adjustements were made on the vertical position and size to snap border pixels to the screen's grid (LoDPI)
        g.drawRoundedRect(-GROUP_SIZE, -GROUP_SIZE + 0.5, size + GROUP_SIZE * 2, GROUP_SIZE * 2 - 1, GROUP_SIZE)
      }
    }

    // Time cursor

    const { cursorTime } = useCursor()

    /** @type {PIXI.Graphics} */
    let timeCursor

    onMounted(() => {
      timeCursor = new PIXI.Graphics()
      timeCursor.visible = false
      drawTimeCursor()
      app.stage.addChild(timeCursor)
    })

    function drawTimeCursor () {
      timeCursor.clear()
      timeCursor.lineStyle(1, 0x888888, 0.2)
      timeCursor.moveTo(0.5, 0)
      timeCursor.lineTo(0.5, app.view.height)
    }

    /**
     * @param {MouseEvent} event
     */
    function updateCursorPosition (event) {
      const { offsetX } = event
      timeCursor.x = offsetX
      timeCursor.visible = true
      cursorTime.value = offsetX / app.view.width * (endTime.value - startTime.value) + startTime.value
    }

    function clearCursor () {
      timeCursor.visible = false
      cursorTime.value = null
    }

    // Time grid

    /** @type {PIXI.Graphics} */
    let timeGrid

    onMounted(() => {
      timeGrid = new PIXI.Graphics()
      timeGrid.visible = SharedData.timelineTimeGrid
      drawTimeGrid()
      app.stage.addChild(timeGrid)
    })

    function drawTimeGrid () {
      if (!timeGrid.visible) return

      const ratio = (endTime.value - startTime.value) / app.view.width
      let timeInterval = 10
      let width = timeInterval / ratio

      while (width < 20) {
        timeInterval *= 10
        width *= 10
      }

      const offset = startTime.value % timeInterval / ratio

      timeGrid.clear()
      timeGrid.lineStyle(1, 0x888888, 0.075)
      for (let x = -offset; x < app.view.width; x += width) {
        timeGrid.moveTo(x + 0.5, 0)
        timeGrid.lineTo(x + 0.5, app.view.height)
      }
    }

    watch(() => SharedData.timelineTimeGrid, value => {
      timeGrid.visible = value
      if (value) {
        drawTimeGrid()
      }
    })

    // Camera

    let cameraUpdateQueued = false

    function queueCameraUpdate () {
      if (cameraUpdateQueued) return
      cameraUpdateQueued = true
      Vue.nextTick(() => {
        updateCamera()
        cameraUpdateQueued = false
      })
    }

    function updateCamera () {
      horizontalScrollingContainer.x = -(startTime.value - minTime.value) / (endTime.value - startTime.value) * app.view.width
      drawLayerBackgroundEffects()
      drawTimeGrid()
    }

    watch(startTime, () => queueCameraUpdate())
    watch(endTime, () => queueCameraUpdate())

    /**
     * @param {MouseWheelEvent} event
     */
    function onMouseWheel (event) {
      const size = endTime.value - startTime.value
      const viewWidth = wrapper.value.offsetWidth

      if (event.ctrlKey || event.metaKey) {
        // Zoom
        event.preventDefault()

        const centerRatio = event.offsetX / viewWidth
        const center = size * centerRatio + startTime.value

        let newSize = size + event.deltaY / viewWidth * size * 2
        if (newSize < 100) {
          newSize = 100
        }

        let start = center - newSize * centerRatio
        let end = center + newSize * (1 - centerRatio)
        if (start < minTime.value) {
          start = minTime.value
        }
        if (end > maxTime.value) {
          end = maxTime.value
        }
        startTime.value = start
        endTime.value = end
      } else {
        let deltaX = event.deltaX

        if (deltaX === 0 && event.shiftKey && event.deltaY !== 0) {
          // Horitonzal scroll with vertical mouse wheel and shift key
          deltaX = event.deltaY
        }

        if (deltaX !== 0) {
          // Horizontal scroll
          const delta = deltaX / viewWidth * size
          let start = startTime.value += delta
          if (start < minTime.value) {
            start = minTime.value
          } else if (start + size >= maxTime.value) {
            start = maxTime.value - size
          }
          startTime.value = start
          endTime.value = start + size
        }
      }
    }

    // Vertical scroll

    function updateVScroll () {
      if (verticalScrollingContainer) {
        verticalScrollingContainer.y = -vScroll.value
      }
    }

    watch(vScroll, () => {
      updateVScroll()
    })

    onMounted(() => {
      updateVScroll()
    })

    // Resize

    function onResize () {
      app.queueResize()
      queueEventsUpdate()
      drawLayerBackgroundEffects()
      drawTimeCursor()
      drawTimeGrid()
    }

    // Events

    function onMouseMove (event) {
      updateLayerHover(event)
      updateCursorPosition(event)
    }

    function onMouseOut () {
      clearLayerHover()
      clearCursor()
    }

    return {
      wrapper,
      onMouseWheel,
      onMouseMove,
      onMouseOut,
      onResize
    }
  }
}
</script>

<template>
  <div
    ref="wrapper"
    class="relative overflow-hidden"
    @mousewheel="onMouseWheel"
    @mousemove="onMouseMove"
    @mouseout="onMouseOut"
  >
    <resize-observer @notify="onResize" />
  </div>
</template>


包含http路径时没有realpath，隐藏掉

包含http路径时没有realpath，隐藏掉

<template>

                数据库类型

                {{ data.attack_params.server }}

                执行的SQL语句

                {{ data.attack_params.query }}

                数据库类型

                {{ data.attack_params.server }}

                执行的SQL语句

                {{ data.attack_params.query }}

                读取的目录

                {{ data.attack_params.path }}

                读取的目录 - 真实路径

                {{ data.attack_params.realpath }}

                读取的文件

                {{ data.attack_params.path }}

                读取的文件 - 真实路径

                {{ data.attack_params.realpath }}

                写入的文件

                {{ data.attack_params.path }}

                写入的文件 - 真实路径

                {{ data.attack_params.realpath }}

                要包含的文件

                {{ data.attack_params.url }}

                要包含的文件 - 真实路径

                {{ data.attack_params.realpath }}

                源文件

                {{ data.attack_params.source }}

                目标文件

                {{ data.attack_params.dest }}

                Multipart 参数名称

                {{ data.attack_params.name }}

                上传的文件名

                {{ data.attack_params.filename }}

                上传的文件内容 - 前4KB

{{ data.attack_params.content }}

                源文件

                {{ data.attack_params.source }}

                目标文件

                {{ data.attack_params.dest }}

                要执行的命令

                {{ data.attack_params.command }}

                要加载的外部实体

                {{ data.attack_params.entity }}

                要执行的OGNL表达式

                {{ data.attack_params.expression }}

                要生成的Java类名

                {{ data.attack_params.clazz }}

                要访问的 URL

                {{ data.attack_params.url }}

                目标 IP

                {{ data.attack_params.ip.join(", ") }}

                XSS 参数名称

                {{ data.attack_params.name }}

                XSS 利用代码

                {{ data.attack_params.value }}

                菜刀要 eval 的内容

                {{ data.attack_params.eval }}

                WebShell 要执行的命令

                {{ data.attack_params.command }}

                WebShell 要写入的文件

                {{ data.attack_params.name }}

                WebShell 要写入的文件 - 真实路径

                {{ data.attack_params.realpath }}

                后门要执行的函数

                {{ data.attack_params.function }}()

</template>


PREF nodejs iptools vue+typescript+ivew ts动态填充，感觉还是js的写法

<template>

        <Layout>
            Header
            <Content>

            </Content>

                <Button type="success" long @click="created()">SUBMIT</Button>

        </Layout>

</template>


Remove unwanted mapState stuff

Remove unwanted mapState stuff

/**
PublishModal.Vue

Controls the display of a modal when the user tried to publish a page

There is a single modal, but different messages can be displayed in it.
Using a single modal rather than a series of separate ones makes the transition between different states visually more fluid.

The 3 states are:

1. The initial state. User is given the option to publish or cancel.
2. A successful publish. User is shown a link to the newly-published page.
3. An error message. Details of the error are given in a slide-down, in case they need to contact with the problem they're having.

An element loading spinner is shown after the user hits 'Publish'.

*/
<template>
<el-dialog
	title="Publish"
	v-model="publishModalVisible"
	:modal-append-to-body="true"
	v-loading="loading"
	element-loading-text="Publishing your page..."
	class="publish-modal"
	:before-close="handleClose"
	:close-on-press-escape="false"
	:close-on-click-modal="false"
>

You're about to publish the page {{ pageTitle }}

It will be published to the URL <el-tag type="gray">{{ renderedURL }}</el-tag>

				<el-button @click="cancelPublish">Cancel</el-button>
				<el-button type="danger" @click="publishPage">Publish now</el-button>

		<el-alert
			title="Your page was published"
			type="success"
			show-icon
			:closable=false
			>
		</el-alert>

View your new page now at {{ renderedURL }} (opens in a new tab)

				<el-button type="primary" @click="cancelPublish">Close</el-button>

		<el-alert
			title="Page not published"
			type="error"
			description="Sorry we had a problem publishing this page. It might be a connection problem, so try again in a bit."
			show-icon
			:closable=false
			>
		</el-alert>
		<el-collapse class="publish-modal__errors">
			<el-collapse-item title="Report errors" name="1">

If you're having persistent problems publishing your page, contact us and let us know the following error message:

				<el-tag type="gray">{{ error }}</el-tag>
			</el-collapse-item>
		</el-collapse>

				<el-button type="primary" @click="cancelPublish">Close</el-button>

</el-dialog>
</template>


Update DappDetailBodyContentCtas.vue

Update DappDetailBodyContentCtas.vue

<template>

      <transition name="fade">

<nuxt-link class="add-popover-button" :to="{ name: 'my-list' }">Go to my list</nuxt-link>

      </transition>
      <button v-if="!myList.includes(dapp.slug)" class="button -add" @click="addToMyList(dapp.slug)">
        <SvgPlus/> Add to my list
      </button>
      <button v-else class="button -add" @click="removeFromMyList(dapp.slug)">
        Remove from my list
      </button>

        Play game (NSFW)
        Launch ÐApp/website (NSFW)

        Play game (NSFW)
        Launch ÐApp (NSFW)

      Visit website (NSFW)

          <component :is="svgSocialComponent(social.platform)"></component>

</template>


fix template unique element in root template ...

<template lang='jade'>
  div
    dataset-picker.my2(v-on:dataset='updateDataset')
    .hr
    strat-picker.my2(v-on:stratConfig='updateStrat')
    .hr
    paper-trader(v-on:settings='updatePaperTrader')
    .hr
</template>

<script>

import datasetPicker from '../global/configbuilder/datasetpicker.vue'
import stratPicker from '../global/configbuilder/stratpicker.vue'
import paperTrader from '../global/configbuilder/papertrader.vue'
import _ from 'lodash'
import { get } from '../../tools/ajax'

export default {
  created: function() {
    get('configPart/performanceAnalyzer', (error, response) => {
      this.performanceAnalyzer = toml.parse(response.part);
      this.performanceAnalyzer.enabled = true;
    });
  },
  data: () => {
    return {
      dataset: {},
      strat: {},
      paperTrader: {},
      performanceAnalyzer: {}
    }
  },
  components: {
    stratPicker,
    datasetPicker,
    paperTrader
  },
  computed: {
    market: function() {
      if(!this.dataset.exchange)
        return {};

      return {
        exchange: this.dataset.exchange,
        currency: this.dataset.currency,
        asset: this.dataset.asset
      }
    },
    range: function() {
      if(!this.dataset.exchange)
        return {};

      return {
        from: this.dataset.from,
        to: this.dataset.to
      }
    },
    config: function() {
      let config = {};
      Object.assign(
        config,
        { watch: this.market },
        { paperTrader: this.paperTrader },
        this.strat,
        {
          backtest: {
            daterange: this.range
          }
        },
        { performanceAnalyzer: this.performanceAnalyzer }
      );

      config.valid = this.validConfig(config);

      return config;
    }
  },
  methods: {
    validConfig: function(config) {
      if(!config.backtest)
        return false;

      if(!config.backtest.daterange)
        return false;

      if(_.isEmpty(config.backtest.daterange))
        return false;

      if(!config.watch)
        return false;

      if(!config.tradingAdvisor)
        return false;

      let strat = config.tradingAdvisor.method;
      if(_.isEmpty(config[ strat ]))
        return false;

      if(config.tradingAdvisor) {
        if(_.isNaN(config.tradingAdvisor.candleSize))
          return false;
        else if(config.tradingAdvisor.candleSize == 0)
          return false;
      }

      return true;
    },
    updateDataset: function(set) {
      this.dataset = set;
      this.$emit('config', this.config);
    },
    updateStrat: function(sc) {
      this.strat = sc;
      this.$emit('config', this.config);
    },
    updatePaperTrader: function(pt) {
      this.paperTrader = pt;
      this.paperTrader.enabled = true;
      this.$emit('config', this.config);
    },
  }
}
</script>

<style>
</style>

<template lang='jade'>
  dataset-picker.my2(v-on:dataset='updateDataset')
  .hr
  strat-picker.my2(v-on:stratConfig='updateStrat')
  .hr
  paper-trader(v-on:settings='updatePaperTrader')
  .hr
</template>

<script>

import datasetPicker from '../global/configbuilder/datasetpicker.vue'
import stratPicker from '../global/configbuilder/stratpicker.vue'
import paperTrader from '../global/configbuilder/papertrader.vue'
import _ from 'lodash'
import { get } from '../../tools/ajax'

export default {
  created: function() {
    get('configPart/performanceAnalyzer', (error, response) => {
      this.performanceAnalyzer = toml.parse(response.part);
      this.performanceAnalyzer.enabled = true;
    });
  },
  data: () => {
    return {
      dataset: {},
      strat: {},
      paperTrader: {},
      performanceAnalyzer: {}
    }
  },
  components: {
    stratPicker,
    datasetPicker,
    paperTrader
  },
  computed: {
    market: function() {
      if(!this.dataset.exchange)
        return {};

      return {
        exchange: this.dataset.exchange,
        currency: this.dataset.currency,
        asset: this.dataset.asset
      }
    },
    range: function() {
      if(!this.dataset.exchange)
        return {};

      return {
        from: this.dataset.from,
        to: this.dataset.to
      }
    },
    config: function() {
      let config = {};
      Object.assign(
        config,
        { watch: this.market },
        { paperTrader: this.paperTrader },
        this.strat,
        {
          backtest: {
            daterange: this.range
          }
        },
        { performanceAnalyzer: this.performanceAnalyzer }
      );

      config.valid = this.validConfig(config);

      return config;
    }
  },
  methods: {
    validConfig: function(config) {
      if(!config.backtest)
        return false;

      if(!config.backtest.daterange)
        return false;

      if(_.isEmpty(config.backtest.daterange))
        return false;

      if(!config.watch)
        return false;

      if(!config.tradingAdvisor)
        return false;

      let strat = config.tradingAdvisor.method;
      if(_.isEmpty(config[ strat ]))
        return false;

      if(config.tradingAdvisor) {
        if(_.isNaN(config.tradingAdvisor.candleSize))
          return false;
        else if(config.tradingAdvisor.candleSize == 0)
          return false;
      }

      return true;
    },
    updateDataset: function(set) {
      this.dataset = set;
      this.$emit('config', this.config);
    },
    updateStrat: function(sc) {
      this.strat = sc;
      this.$emit('config', this.config);
    },
    updatePaperTrader: function(pt) {
      this.paperTrader = pt;
      this.paperTrader.enabled = true;
      this.$emit('config', this.config);
    },
  }
}
</script>

<style>
</style>