Sharkey/packages/frontend/src/pages/admin/ads.vue

276 lines
7.6 KiB
Vue
Raw Normal View History

<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<MkStickyContainer>
<template #header>
2023-10-29 08:09:20 +01:00
<XHeader :actions="headerActions" :tabs="headerTabs"/>
</template>
2023-05-19 13:52:15 +02:00
<MkSpacer :contentMax="900">
<MkSelect v-model="filterType" :class="$style.input" @update:modelValue="filterItems">
<template #label>{{ i18n.ts.state }}</template>
<option value="all">{{ i18n.ts.all }}</option>
<option value="publishing">{{ i18n.ts.publishing }}</option>
<option value="expired">{{ i18n.ts.expired }}</option>
</MkSelect>
2023-05-19 13:52:15 +02:00
<div>
<div v-for="ad in ads" class="_panel _gaps_m" :class="$style.ad">
<MkAd v-if="ad.url" :key="ad.id" :specify="ad"/>
2023-01-05 13:04:56 +01:00
<MkInput v-model="ad.url" type="url">
<template #label>URL</template>
</MkInput>
<MkInput v-model="ad.imageUrl" type="url">
<template #label>{{ i18n.ts.imageUrl }}</template>
</MkInput>
2023-01-07 07:09:46 +01:00
<MkRadios v-model="ad.place">
<template #label>Form</template>
<option value="square">square</option>
<option value="horizontal">horizontal</option>
<option value="horizontal-big">horizontal-big</option>
2023-01-07 07:09:46 +01:00
</MkRadios>
<!--
2021-10-23 21:03:07 +02:00
<div style="margin: 32px 0;">
{{ i18n.ts.priority }}
<MkRadio v-model="ad.priority" value="high">{{ i18n.ts.high }}</MkRadio>
<MkRadio v-model="ad.priority" value="middle">{{ i18n.ts.middle }}</MkRadio>
<MkRadio v-model="ad.priority" value="low">{{ i18n.ts.low }}</MkRadio>
2021-10-23 21:03:07 +02:00
</div>
-->
<FormSplit>
<MkInput v-model="ad.ratio" type="number">
<template #label>{{ i18n.ts.ratio }}</template>
</MkInput>
2023-02-15 06:31:59 +01:00
<MkInput v-model="ad.startsAt" type="datetime-local">
<template #label>{{ i18n.ts.startingperiod }}</template>
</MkInput>
<MkInput v-model="ad.expiresAt" type="datetime-local">
<template #label>{{ i18n.ts.expiration }}</template>
</MkInput>
</FormSplit>
<MkFolder>
<template #label>{{ i18n.ts.advancedSettings }}</template>
<span>
{{ i18n.ts._ad.timezoneinfo }}
<div v-for="(day, index) in daysOfWeek" :key="index">
2023-10-29 08:09:20 +01:00
<input
:id="`ad${ad.id}-${index}`" type="checkbox" :checked="(ad.dayOfWeek & (1 << index)) !== 0"
@change="toggleDayOfWeek(ad, index)"
>
<label :for="`ad${ad.id}-${index}`">{{ day }}</label>
</div>
</span>
</MkFolder>
2023-01-05 13:04:56 +01:00
<MkTextarea v-model="ad.memo">
<template #label>{{ i18n.ts.memo }}</template>
</MkTextarea>
2023-01-05 13:04:56 +01:00
<div class="buttons">
2023-10-29 08:09:20 +01:00
<MkButton class="button" inline primary style="margin-right: 12px;" @click="save(ad)">
<i
2024-06-21 14:13:02 +02:00
class="ti ti-device-floppy"
2023-10-29 08:09:20 +01:00
></i> {{ i18n.ts.save }}
</MkButton>
<MkButton class="button" inline danger @click="remove(ad)">
2024-06-21 18:15:52 +02:00
<i class="ti ti-trash"></i> {{ i18n.ts.remove }}
</MkButton>
</div>
2021-10-23 21:03:07 +02:00
</div>
<MkButton class="button" @click="more()">
2024-06-21 17:00:35 +02:00
<i class="ti ti-reload"></i>{{ i18n.ts.more }}
</MkButton>
2023-02-21 02:41:09 +01:00
</div>
</MkSpacer>
</MkStickyContainer>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
import * as Misskey from 'misskey-js';
import XHeader from './_header_.vue';
import MkButton from '@/components/MkButton.vue';
2023-01-07 07:09:46 +01:00
import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import MkRadios from '@/components/MkRadios.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkSelect from '@/components/MkSelect.vue';
2021-12-30 13:47:48 +01:00
import FormSplit from '@/components/form/split.vue';
2023-09-19 09:37:43 +02:00
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
2023-09-19 09:37:43 +02:00
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
const ads = ref<Misskey.entities.Ad[]>([]);
// ISO形式はTZがUTCになってしまうので、TZ分ずらして時間を初期化
const localTime = new Date();
const localTimeDiff = localTime.getTimezoneOffset() * 60 * 1000;
const daysOfWeek: string[] = [i18n.ts._weekday.sunday, i18n.ts._weekday.monday, i18n.ts._weekday.tuesday, i18n.ts._weekday.wednesday, i18n.ts._weekday.thursday, i18n.ts._weekday.friday, i18n.ts._weekday.saturday];
const filterType = ref('all');
let publishing: boolean | null = null;
misskeyApi('admin/ad/list', { publishing: publishing }).then(adsResponse => {
if (adsResponse != null) {
ads.value = adsResponse.map(r => {
const exdate = new Date(r.expiresAt);
const stdate = new Date(r.startsAt);
exdate.setMilliseconds(exdate.getMilliseconds() - localTimeDiff);
stdate.setMilliseconds(stdate.getMilliseconds() - localTimeDiff);
return {
...r,
expiresAt: exdate.toISOString().slice(0, 16),
startsAt: stdate.toISOString().slice(0, 16),
};
});
}
});
const filterItems = (v) => {
if (v === 'publishing') {
publishing = true;
} else if (v === 'expired') {
publishing = false;
} else {
publishing = null;
}
refresh();
};
2023-10-29 08:09:20 +01:00
// 選択された曜日(index)のビットフラグを操作する
function toggleDayOfWeek(ad, index) {
ad.dayOfWeek ^= 1 << index;
}
function add() {
ads.value.unshift({
id: null,
memo: '',
place: 'square',
priority: 'middle',
ratio: 1,
url: '',
imageUrl: null,
expiresAt: null,
2023-02-15 06:31:59 +01:00
startsAt: null,
dayOfWeek: 0,
});
}
function remove(ad) {
os.confirm({
type: 'warning',
text: i18n.tsx.removeAreYouSure({ x: ad.url }),
}).then(({ canceled }) => {
if (canceled) return;
ads.value = ads.value.filter(x => x !== ad);
if (ad.id == null) return;
os.apiWithDialog('admin/ad/delete', {
id: ad.id,
}).then(() => {
refresh();
});
});
}
function save(ad) {
if (ad.id == null) {
misskeyApi('admin/ad/create', {
...ad,
expiresAt: new Date(ad.expiresAt).getTime(),
2023-02-15 06:31:59 +01:00
startsAt: new Date(ad.startsAt).getTime(),
}).then(() => {
os.alert({
type: 'success',
text: i18n.ts.saved,
});
refresh();
}).catch(err => {
os.alert({
type: 'error',
text: err,
});
});
} else {
misskeyApi('admin/ad/update', {
...ad,
expiresAt: new Date(ad.expiresAt).getTime(),
2023-02-15 06:31:59 +01:00
startsAt: new Date(ad.startsAt).getTime(),
}).then(() => {
os.alert({
type: 'success',
text: i18n.ts.saved,
});
}).catch(err => {
os.alert({
type: 'error',
text: err,
});
});
}
}
2023-10-29 08:09:20 +01:00
function more() {
misskeyApi('admin/ad/list', { untilId: ads.value.reduce((acc, ad) => ad.id != null ? ad : acc).id, publishing: publishing }).then(adsResponse => {
if (adsResponse == null) return;
ads.value = ads.value.concat(adsResponse.map(r => {
const exdate = new Date(r.expiresAt);
const stdate = new Date(r.startsAt);
exdate.setMilliseconds(exdate.getMilliseconds() - localTimeDiff);
stdate.setMilliseconds(stdate.getMilliseconds() - localTimeDiff);
return {
...r,
expiresAt: exdate.toISOString().slice(0, 16),
startsAt: stdate.toISOString().slice(0, 16),
};
}));
});
}
function refresh() {
misskeyApi('admin/ad/list', { publishing: publishing }).then(adsResponse => {
if (adsResponse == null) return;
ads.value = adsResponse.map(r => {
const exdate = new Date(r.expiresAt);
const stdate = new Date(r.startsAt);
exdate.setMilliseconds(exdate.getMilliseconds() - localTimeDiff);
stdate.setMilliseconds(stdate.getMilliseconds() - localTimeDiff);
return {
...r,
expiresAt: exdate.toISOString().slice(0, 16),
startsAt: stdate.toISOString().slice(0, 16),
};
});
});
}
refresh();
const headerActions = computed(() => [{
asFullButton: true,
2024-06-21 13:42:33 +02:00
icon: 'ti ti-plus',
text: i18n.ts.add,
handler: add,
}]);
const headerTabs = computed(() => []);
definePageMetadata(() => ({
title: i18n.ts.ads,
2024-06-22 14:34:55 +02:00
icon: 'ti ti-ad',
}));
</script>
2023-05-19 13:52:15 +02:00
<style lang="scss" module>
.ad {
padding: 32px;
2021-12-10 07:33:01 +01:00
2023-05-19 13:52:15 +02:00
&:not(:last-child) {
margin-bottom: var(--margin);
2021-12-10 07:33:01 +01:00
}
}
.input {
margin-bottom: 32px;
}
</style>