Перейти к содержимому
GitHubXDiscord

Пайплайн уведомлений

Это Vue-версия флагманского сценария Triggery. Сначала прочитай рецепт на Reactфайл триггера идентичен, и проза там разбирает сценарий. Ниже — код компонентов специфично под Vue.

Открыть в StackBlitz Открыть пример на GitHub
  • README.md обзорное описание
  • index.html точка входа Vite
  • Директорияsrc/
    • App.vue продьюсеры + реакторы
    • main.ts точка входа
    • Директорияtriggers/
      • index.ts правило — события, условия, действия, обработчик
src/triggers/message.trigger.ts
import { createTrigger } from '@triggery/core';

type Settings = { sound: boolean; notifications: boolean; dnd: boolean };
type Message  = { id: string; author: string; authorId: string; text: string; channelId: string };

export const messageTrigger = createTrigger<{
  events:     { 'new-message': Message };
  conditions: { settings: Settings; activeChannelId: string | null; currentUserId: string };
  actions:    {
    showToast:      { title: string; body: string };
    playSound:      'beep' | 'mention';
    incrementBadge: string;
  };
}>({
  id: 'message-received',
  events: ['new-message'],
  required: ['settings', 'currentUserId'],
  handler({ event, conditions, actions, check }) {
    const msg = event.payload;
    if (msg.channelId === conditions.activeChannelId) return;
    if (msg.authorId === conditions.currentUserId) return;
    actions.incrementBadge?.(msg.channelId);
    if (check.is('settings', s => s.notifications))    actions.showToast?.({ title: msg.author, body: msg.text });
    if (check.is('settings', s => s.sound && !s.dnd))  actions.debounce(800).playSound?.('beep');
  },
});
src/features/settings/SettingsPanel.vue
<script setup lang="ts">
import { useCondition } from '@triggery/vue';
import { ref } from 'vue';
import { messageTrigger } from '../../triggers/message.trigger';

const settings = ref({ sound: true, notifications: true, dnd: false });
useCondition(messageTrigger, 'settings', () => settings.value);
</script>

<template>
  <fieldset>
    <legend>Notifications</legend>
    <label>
      <input type="checkbox" v-model="settings.notifications" />
      Show toasts
    </label>
    <label>
      <input type="checkbox" v-model="settings.sound" />
      Play sound
    </label>
    <label>
      <input type="checkbox" v-model="settings.dnd" />
      Do Not Disturb
    </label>
  </fieldset>
</template>
src/features/session/SessionProvider.vue
<script setup lang="ts">
import { useCondition } from '@triggery/vue';
import { messageTrigger } from '../../triggers/message.trigger';

const props = defineProps<{ userId: string }>();
useCondition(messageTrigger, 'currentUserId', () => props.userId);
</script>

<template>
  <slot />
</template>
src/features/chat/Chat.vue
<script setup lang="ts">
import { useEvent, useCondition } from '@triggery/vue';
import { messageTrigger } from '../../triggers/message.trigger';

const props = defineProps<{ channelId: string | null }>();
useCondition(messageTrigger, 'activeChannelId', () => props.channelId);

const fireMessage = useEvent(messageTrigger, 'new-message');

function simulate() {
  fireMessage({
    id:        crypto.randomUUID(),
    author:    'Alice',
    authorId:  'u-alice',
    text:      'hi',
    channelId: 'c-lunch',
  });
}
</script>

<template>
  <button @click="simulate()">simulate inbound</button>
</template>
src/features/notifications/NotificationLayer.vue
<script setup lang="ts">
import { useAction } from '@triggery/vue';
import { useBadgeStore } from '../../stores/badge';
import { messageTrigger } from '../../triggers/message.trigger';

const badge = useBadgeStore();

let audio: HTMLAudioElement | null = null;

useAction(messageTrigger, 'showToast', ({ title, body }) => {
  console.log('toast', title, body);
});
useAction(messageTrigger, 'playSound', () => {
  audio ??= new Audio('/beep.mp3');
  audio.play().catch(() => {});
});
useAction(messageTrigger, 'incrementBadge', channelId => {
  badge.increment(channelId);
});
</script>

<template>
  <span hidden />
</template>
src/main.ts
import { createRuntime } from '@triggery/core';
import { TriggerRuntimePlugin } from '@triggery/vue';
import { createApp } from 'vue';
import App from './App.vue';

const runtime = createRuntime();

createApp(App).use(TriggerRuntimePlugin, { runtime }).mount('#root');