refactor: Widgetのcomposition api移行 (#8125)
* wip * wip * wip * wip * wip * wip * fix
This commit is contained in:
parent
faef125b74
commit
0bbde336b3
23 changed files with 1389 additions and 1221 deletions
|
@ -10,7 +10,7 @@
|
||||||
<MkButton inline @click="$emit('exit')">{{ $ts.close }}</MkButton>
|
<MkButton inline @click="$emit('exit')">{{ $ts.close }}</MkButton>
|
||||||
</header>
|
</header>
|
||||||
<XDraggable
|
<XDraggable
|
||||||
v-model="_widgets"
|
v-model="widgets_"
|
||||||
item-key="id"
|
item-key="id"
|
||||||
animation="150"
|
animation="150"
|
||||||
>
|
>
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
<div class="customize-container">
|
<div class="customize-container">
|
||||||
<button class="config _button" @click.prevent.stop="configWidget(element.id)"><i class="fas fa-cog"></i></button>
|
<button class="config _button" @click.prevent.stop="configWidget(element.id)"><i class="fas fa-cog"></i></button>
|
||||||
<button class="remove _button" @click.prevent.stop="removeWidget(element)"><i class="fas fa-times"></i></button>
|
<button class="remove _button" @click.prevent.stop="removeWidget(element)"><i class="fas fa-times"></i></button>
|
||||||
<component :is="`mkw-${element.name}`" :widget="element" :setting-callback="setting => settings[element.id] = setting" @updateProps="updateWidget(element.id, $event)"/>
|
<component :ref="el => widgetRefs[element.id] = el" :is="`mkw-${element.name}`" :widget="element" @updateProps="updateWidget(element.id, $event)"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</XDraggable>
|
</XDraggable>
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, defineAsyncComponent } from 'vue';
|
import { defineComponent, defineAsyncComponent, reactive, ref, computed } from 'vue';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import MkSelect from '@/components/form/select.vue';
|
import MkSelect from '@/components/form/select.vue';
|
||||||
import MkButton from '@/components/ui/button.vue';
|
import MkButton from '@/components/ui/button.vue';
|
||||||
|
@ -54,50 +54,47 @@ export default defineComponent({
|
||||||
|
|
||||||
emits: ['updateWidgets', 'addWidget', 'removeWidget', 'updateWidget', 'exit'],
|
emits: ['updateWidgets', 'addWidget', 'removeWidget', 'updateWidget', 'exit'],
|
||||||
|
|
||||||
data() {
|
setup(props, context) {
|
||||||
return {
|
const widgetRefs = reactive({});
|
||||||
widgetAdderSelected: null,
|
const configWidget = (id: string) => {
|
||||||
widgetDefs,
|
widgetRefs[id].configure();
|
||||||
settings: {},
|
|
||||||
};
|
};
|
||||||
},
|
const widgetAdderSelected = ref(null);
|
||||||
|
const addWidget = () => {
|
||||||
|
if (widgetAdderSelected.value == null) return;
|
||||||
|
|
||||||
computed: {
|
context.emit('addWidget', {
|
||||||
_widgets: {
|
name: widgetAdderSelected.value,
|
||||||
get() {
|
|
||||||
return this.widgets;
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
this.$emit('updateWidgets', value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
configWidget(id) {
|
|
||||||
this.settings[id]();
|
|
||||||
},
|
|
||||||
|
|
||||||
addWidget() {
|
|
||||||
if (this.widgetAdderSelected == null) return;
|
|
||||||
|
|
||||||
this.$emit('addWidget', {
|
|
||||||
name: this.widgetAdderSelected,
|
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
data: {}
|
data: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.widgetAdderSelected = null;
|
widgetAdderSelected.value = null;
|
||||||
},
|
};
|
||||||
|
const removeWidget = (widget) => {
|
||||||
|
context.emit('removeWidget', widget);
|
||||||
|
};
|
||||||
|
const updateWidget = (id, data) => {
|
||||||
|
context.emit('updateWidget', { id, data });
|
||||||
|
};
|
||||||
|
const widgets_ = computed({
|
||||||
|
get: () => props.widgets,
|
||||||
|
set: (value) => {
|
||||||
|
context.emit('updateWidgets', value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
removeWidget(widget) {
|
return {
|
||||||
this.$emit('removeWidget', widget);
|
widgetRefs,
|
||||||
},
|
configWidget,
|
||||||
|
widgetAdderSelected,
|
||||||
updateWidget(id, data) {
|
widgetDefs,
|
||||||
this.$emit('updateWidget', { id, data });
|
addWidget,
|
||||||
},
|
removeWidget,
|
||||||
}
|
updateWidget,
|
||||||
|
widgets_,
|
||||||
|
};
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -21,11 +21,39 @@ export type FormItem = {
|
||||||
default: string | null;
|
default: string | null;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
enum: string[];
|
enum: string[];
|
||||||
|
} | {
|
||||||
|
label?: string;
|
||||||
|
type: 'radio';
|
||||||
|
default: unknown | null;
|
||||||
|
hidden?: boolean;
|
||||||
|
options: {
|
||||||
|
label: string;
|
||||||
|
value: unknown;
|
||||||
|
}[];
|
||||||
|
} | {
|
||||||
|
label?: string;
|
||||||
|
type: 'object';
|
||||||
|
default: Record<string, unknown> | null;
|
||||||
|
hidden: true;
|
||||||
} | {
|
} | {
|
||||||
label?: string;
|
label?: string;
|
||||||
type: 'array';
|
type: 'array';
|
||||||
default: unknown[] | null;
|
default: unknown[] | null;
|
||||||
hidden?: boolean;
|
hidden: true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Form = Record<string, FormItem>;
|
export type Form = Record<string, FormItem>;
|
||||||
|
|
||||||
|
type GetItemType<Item extends FormItem> =
|
||||||
|
Item['type'] extends 'string' ? string :
|
||||||
|
Item['type'] extends 'number' ? number :
|
||||||
|
Item['type'] extends 'boolean' ? boolean :
|
||||||
|
Item['type'] extends 'radio' ? unknown :
|
||||||
|
Item['type'] extends 'enum' ? string :
|
||||||
|
Item['type'] extends 'array' ? unknown[] :
|
||||||
|
Item['type'] extends 'object' ? Record<string, unknown>
|
||||||
|
: never;
|
||||||
|
|
||||||
|
export type GetFormResultType<F extends Form> = {
|
||||||
|
[P in keyof F]: GetItemType<F[P]>;
|
||||||
|
};
|
||||||
|
|
|
@ -1,82 +1,89 @@
|
||||||
<template>
|
<template>
|
||||||
<MkContainer :show-header="props.showHeader" :naked="props.transparent">
|
<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent">
|
||||||
<template #header><i class="fas fa-chart-bar"></i>{{ $ts._widgets.activity }}</template>
|
<template #header><i class="fas fa-chart-bar"></i>{{ $ts._widgets.activity }}</template>
|
||||||
<template #func><button class="_button" @click="toggleView()"><i class="fas fa-sort"></i></button></template>
|
<template #func><button class="_button" @click="toggleView()"><i class="fas fa-sort"></i></button></template>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<MkLoading v-if="fetching"/>
|
<MkLoading v-if="fetching"/>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<XCalendar v-show="props.view === 0" :data="[].concat(activity)"/>
|
<XCalendar v-show="widgetProps.view === 0" :data="[].concat(activity)"/>
|
||||||
<XChart v-show="props.view === 1" :data="[].concat(activity)"/>
|
<XChart v-show="widgetProps.view === 1" :data="[].concat(activity)"/>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</MkContainer>
|
</MkContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue';
|
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
||||||
|
import { GetFormResultType } from '@/scripts/form';
|
||||||
|
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
|
||||||
|
import * as os from '@/os';
|
||||||
import MkContainer from '@/components/ui/container.vue';
|
import MkContainer from '@/components/ui/container.vue';
|
||||||
import define from './define';
|
|
||||||
import XCalendar from './activity.calendar.vue';
|
import XCalendar from './activity.calendar.vue';
|
||||||
import XChart from './activity.chart.vue';
|
import XChart from './activity.chart.vue';
|
||||||
import * as os from '@/os';
|
import { $i } from '@/account';
|
||||||
|
|
||||||
const widget = define({
|
const name = 'activity';
|
||||||
name: 'activity',
|
|
||||||
props: () => ({
|
const widgetPropsDef = {
|
||||||
showHeader: {
|
showHeader: {
|
||||||
type: 'boolean',
|
type: 'boolean' as const,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
transparent: {
|
transparent: {
|
||||||
type: 'boolean',
|
type: 'boolean' as const,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
view: {
|
view: {
|
||||||
type: 'number',
|
type: 'number' as const,
|
||||||
default: 0,
|
default: 0,
|
||||||
hidden: true,
|
hidden: true,
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
|
|
||||||
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
|
||||||
|
//const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
||||||
|
//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
||||||
|
const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
|
||||||
|
const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
|
||||||
|
|
||||||
|
const { widgetProps, configure, save } = useWidgetPropsManager(name,
|
||||||
|
widgetPropsDef,
|
||||||
|
props,
|
||||||
|
emit,
|
||||||
|
);
|
||||||
|
|
||||||
|
const activity = ref(null);
|
||||||
|
const fetching = ref(true);
|
||||||
|
|
||||||
|
const toggleView = () => {
|
||||||
|
if (widgetProps.view === 1) {
|
||||||
|
widgetProps.view = 0;
|
||||||
|
} else {
|
||||||
|
widgetProps.view++;
|
||||||
|
}
|
||||||
|
save();
|
||||||
|
};
|
||||||
|
|
||||||
|
os.api('charts/user/notes', {
|
||||||
|
userId: $i.id,
|
||||||
|
span: 'day',
|
||||||
|
limit: 7 * 21,
|
||||||
|
}).then(res => {
|
||||||
|
activity.value = res.diffs.normal.map((_, i) => ({
|
||||||
|
total: res.diffs.normal[i] + res.diffs.reply[i] + res.diffs.renote[i],
|
||||||
|
notes: res.diffs.normal[i],
|
||||||
|
replies: res.diffs.reply[i],
|
||||||
|
renotes: res.diffs.renote[i]
|
||||||
|
}));
|
||||||
|
fetching.value = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
export default defineComponent({
|
defineExpose<WidgetComponentExpose>({
|
||||||
components: {
|
name,
|
||||||
MkContainer,
|
configure,
|
||||||
XCalendar,
|
id: props.widget ? props.widget.id : null,
|
||||||
XChart,
|
|
||||||
},
|
|
||||||
extends: widget,
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
fetching: true,
|
|
||||||
activity: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
os.api('charts/user/notes', {
|
|
||||||
userId: this.$i.id,
|
|
||||||
span: 'day',
|
|
||||||
limit: 7 * 21
|
|
||||||
}).then(activity => {
|
|
||||||
this.activity = activity.diffs.normal.map((_, i) => ({
|
|
||||||
total: activity.diffs.normal[i] + activity.diffs.reply[i] + activity.diffs.renote[i],
|
|
||||||
notes: activity.diffs.normal[i],
|
|
||||||
replies: activity.diffs.reply[i],
|
|
||||||
renotes: activity.diffs.renote[i]
|
|
||||||
}));
|
|
||||||
this.fetching = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
toggleView() {
|
|
||||||
if (this.props.view === 1) {
|
|
||||||
this.props.view = 0;
|
|
||||||
} else {
|
|
||||||
this.props.view++;
|
|
||||||
}
|
|
||||||
this.save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,51 +1,65 @@
|
||||||
<template>
|
<template>
|
||||||
<MkContainer :naked="props.transparent" :show-header="false">
|
<MkContainer :naked="widgetProps.transparent" :show-header="false">
|
||||||
<iframe ref="live2d" class="dedjhjmo" src="https://misskey-dev.github.io/mascot-web/?scale=1.5&y=1.1&eyeY=100" @click="touched"></iframe>
|
<iframe ref="live2d" class="dedjhjmo" src="https://misskey-dev.github.io/mascot-web/?scale=1.5&y=1.1&eyeY=100" @click="touched"></iframe>
|
||||||
</MkContainer>
|
</MkContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, markRaw } from 'vue';
|
import { onMounted, onUnmounted, reactive, ref } from 'vue';
|
||||||
import define from './define';
|
import { GetFormResultType } from '@/scripts/form';
|
||||||
import MkContainer from '@/components/ui/container.vue';
|
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
|
||||||
import * as os from '@/os';
|
|
||||||
|
|
||||||
const widget = define({
|
const name = 'ai';
|
||||||
name: 'ai',
|
|
||||||
props: () => ({
|
const widgetPropsDef = {
|
||||||
transparent: {
|
transparent: {
|
||||||
type: 'boolean',
|
type: 'boolean' as const,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
|
|
||||||
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
|
||||||
|
//const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
||||||
|
//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
||||||
|
const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
|
||||||
|
const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
|
||||||
|
|
||||||
|
const { widgetProps, configure } = useWidgetPropsManager(name,
|
||||||
|
widgetPropsDef,
|
||||||
|
props,
|
||||||
|
emit,
|
||||||
|
);
|
||||||
|
|
||||||
|
const live2d = ref<HTMLIFrameElement>();
|
||||||
|
|
||||||
|
const touched = () => {
|
||||||
|
//if (this.live2d) this.live2d.changeExpression('gurugurume');
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const onMousemove = (ev: MouseEvent) => {
|
||||||
|
const iframeRect = live2d.value.getBoundingClientRect();
|
||||||
|
live2d.value.contentWindow.postMessage({
|
||||||
|
type: 'moveCursor',
|
||||||
|
body: {
|
||||||
|
x: ev.clientX - iframeRect.left,
|
||||||
|
y: ev.clientY - iframeRect.top,
|
||||||
|
}
|
||||||
|
}, '*');
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('mousemove', onMousemove, { passive: true });
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('mousemove', onMousemove);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export default defineComponent({
|
defineExpose<WidgetComponentExpose>({
|
||||||
components: {
|
name,
|
||||||
MkContainer,
|
configure,
|
||||||
},
|
id: props.widget ? props.widget.id : null,
|
||||||
extends: widget,
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
window.addEventListener('mousemove', ev => {
|
|
||||||
const iframeRect = this.$refs.live2d.getBoundingClientRect();
|
|
||||||
this.$refs.live2d.contentWindow.postMessage({
|
|
||||||
type: 'moveCursor',
|
|
||||||
body: {
|
|
||||||
x: ev.clientX - iframeRect.left,
|
|
||||||
y: ev.clientY - iframeRect.top,
|
|
||||||
}
|
|
||||||
}, '*');
|
|
||||||
}, { passive: true });
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
touched() {
|
|
||||||
//if (this.live2d) this.live2d.changeExpression('gurugurume');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<MkContainer :show-header="props.showHeader">
|
<MkContainer :show-header="widgetProps.showHeader">
|
||||||
<template #header><i class="fas fa-terminal"></i>{{ $ts._widgets.aiscript }}</template>
|
<template #header><i class="fas fa-terminal"></i>{{ $ts._widgets.aiscript }}</template>
|
||||||
|
|
||||||
<div class="uylguesu _monospace">
|
<div class="uylguesu _monospace">
|
||||||
<textarea v-model="props.script" placeholder="(1 + 1)"></textarea>
|
<textarea v-model="widgetProps.script" placeholder="(1 + 1)"></textarea>
|
||||||
<button class="_buttonPrimary" @click="run">RUN</button>
|
<button class="_buttonPrimary" @click="run">RUN</button>
|
||||||
<div class="logs">
|
<div class="logs">
|
||||||
<div v-for="log in logs" :key="log.id" class="log" :class="{ print: log.print }">{{ log.text }}</div>
|
<div v-for="log in logs" :key="log.id" class="log" :class="{ print: log.print }">{{ log.text }}</div>
|
||||||
|
@ -12,97 +12,109 @@
|
||||||
</MkContainer>
|
</MkContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue';
|
import { onMounted, onUnmounted, ref, watch } from 'vue';
|
||||||
import MkContainer from '@/components/ui/container.vue';
|
import { GetFormResultType } from '@/scripts/form';
|
||||||
import define from './define';
|
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
|
||||||
import * as os from '@/os';
|
import * as os from '@/os';
|
||||||
|
import MkContainer from '@/components/ui/container.vue';
|
||||||
import { AiScript, parse, utils } from '@syuilo/aiscript';
|
import { AiScript, parse, utils } from '@syuilo/aiscript';
|
||||||
import { createAiScriptEnv } from '@/scripts/aiscript/api';
|
import { createAiScriptEnv } from '@/scripts/aiscript/api';
|
||||||
|
import { $i } from '@/account';
|
||||||
|
|
||||||
const widget = define({
|
const name = 'aiscript';
|
||||||
name: 'aiscript',
|
|
||||||
props: () => ({
|
|
||||||
showHeader: {
|
|
||||||
type: 'boolean',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
script: {
|
|
||||||
type: 'string',
|
|
||||||
multiline: true,
|
|
||||||
default: '(1 + 1)',
|
|
||||||
hidden: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
export default defineComponent({
|
const widgetPropsDef = {
|
||||||
components: {
|
showHeader: {
|
||||||
MkContainer
|
type: 'boolean' as const,
|
||||||
|
default: true,
|
||||||
},
|
},
|
||||||
extends: widget,
|
script: {
|
||||||
|
type: 'string' as const,
|
||||||
data() {
|
multiline: true,
|
||||||
return {
|
default: '(1 + 1)',
|
||||||
logs: [],
|
hidden: true,
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
methods: {
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
async run() {
|
|
||||||
this.logs = [];
|
// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
|
||||||
const aiscript = new AiScript(createAiScriptEnv({
|
//const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
||||||
storageKey: 'widget',
|
//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
||||||
token: this.$i?.token,
|
const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
|
||||||
}), {
|
const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
|
||||||
in: (q) => {
|
|
||||||
return new Promise(ok => {
|
const { widgetProps, configure } = useWidgetPropsManager(name,
|
||||||
os.inputText({
|
widgetPropsDef,
|
||||||
title: q,
|
props,
|
||||||
}).then(({ canceled, result: a }) => {
|
emit,
|
||||||
ok(a);
|
);
|
||||||
});
|
|
||||||
});
|
const logs = ref<{
|
||||||
},
|
id: string;
|
||||||
out: (value) => {
|
text: string;
|
||||||
this.logs.push({
|
print: boolean;
|
||||||
id: Math.random(),
|
}[]>([]);
|
||||||
text: value.type === 'str' ? value.value : utils.valToString(value),
|
|
||||||
print: true
|
const run = async () => {
|
||||||
});
|
logs.value = [];
|
||||||
},
|
const aiscript = new AiScript(createAiScriptEnv({
|
||||||
log: (type, params) => {
|
storageKey: 'widget',
|
||||||
switch (type) {
|
token: $i?.token,
|
||||||
case 'end': this.logs.push({
|
}), {
|
||||||
id: Math.random(),
|
in: (q) => {
|
||||||
text: utils.valToString(params.val, true),
|
return new Promise(ok => {
|
||||||
print: false
|
os.inputText({
|
||||||
}); break;
|
title: q,
|
||||||
default: break;
|
}).then(({ canceled, result: a }) => {
|
||||||
}
|
ok(a);
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let ast;
|
|
||||||
try {
|
|
||||||
ast = parse(this.props.script);
|
|
||||||
} catch (e) {
|
|
||||||
os.alert({
|
|
||||||
type: 'error',
|
|
||||||
text: 'Syntax error :('
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await aiscript.exec(ast);
|
|
||||||
} catch (e) {
|
|
||||||
os.alert({
|
|
||||||
type: 'error',
|
|
||||||
text: e
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
out: (value) => {
|
||||||
|
logs.value.push({
|
||||||
|
id: Math.random().toString(),
|
||||||
|
text: value.type === 'str' ? value.value : utils.valToString(value),
|
||||||
|
print: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
log: (type, params) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'end': logs.value.push({
|
||||||
|
id: Math.random().toString(),
|
||||||
|
text: utils.valToString(params.val, true),
|
||||||
|
print: false,
|
||||||
|
}); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let ast;
|
||||||
|
try {
|
||||||
|
ast = parse(widgetProps.script);
|
||||||
|
} catch (e) {
|
||||||
|
os.alert({
|
||||||
|
type: 'error',
|
||||||
|
text: 'Syntax error :(',
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
await aiscript.exec(ast);
|
||||||
|
} catch (e) {
|
||||||
|
os.alert({
|
||||||
|
type: 'error',
|
||||||
|
text: e,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose<WidgetComponentExpose>({
|
||||||
|
name,
|
||||||
|
configure,
|
||||||
|
id: props.widget ? props.widget.id : null,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,90 +1,99 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mkw-button">
|
<div class="mkw-button">
|
||||||
<MkButton :primary="props.colored" full @click="run">
|
<MkButton :primary="widgetProps.colored" full @click="run">
|
||||||
{{ props.label }}
|
{{ widgetProps.label }}
|
||||||
</MkButton>
|
</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue';
|
import { onMounted, onUnmounted, ref, watch } from 'vue';
|
||||||
import define from './define';
|
import { GetFormResultType } from '@/scripts/form';
|
||||||
import MkButton from '@/components/ui/button.vue';
|
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
|
||||||
import * as os from '@/os';
|
import * as os from '@/os';
|
||||||
import { AiScript, parse, utils } from '@syuilo/aiscript';
|
import { AiScript, parse, utils } from '@syuilo/aiscript';
|
||||||
import { createAiScriptEnv } from '@/scripts/aiscript/api';
|
import { createAiScriptEnv } from '@/scripts/aiscript/api';
|
||||||
|
import { $i } from '@/account';
|
||||||
|
import MkButton from '@/components/ui/button.vue';
|
||||||
|
|
||||||
const widget = define({
|
const name = 'button';
|
||||||
name: 'button',
|
|
||||||
props: () => ({
|
|
||||||
label: {
|
|
||||||
type: 'string',
|
|
||||||
default: 'BUTTON',
|
|
||||||
},
|
|
||||||
colored: {
|
|
||||||
type: 'boolean',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
script: {
|
|
||||||
type: 'string',
|
|
||||||
multiline: true,
|
|
||||||
default: 'Mk:dialog("hello" "world")',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
export default defineComponent({
|
const widgetPropsDef = {
|
||||||
components: {
|
label: {
|
||||||
MkButton
|
type: 'string' as const,
|
||||||
|
default: 'BUTTON',
|
||||||
},
|
},
|
||||||
extends: widget,
|
colored: {
|
||||||
data() {
|
type: 'boolean' as const,
|
||||||
return {
|
default: true,
|
||||||
};
|
|
||||||
},
|
},
|
||||||
methods: {
|
script: {
|
||||||
async run() {
|
type: 'string' as const,
|
||||||
const aiscript = new AiScript(createAiScriptEnv({
|
multiline: true,
|
||||||
storageKey: 'widget',
|
default: 'Mk:dialog("hello" "world")',
|
||||||
token: this.$i?.token,
|
},
|
||||||
}), {
|
};
|
||||||
in: (q) => {
|
|
||||||
return new Promise(ok => {
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
os.inputText({
|
|
||||||
title: q,
|
// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
|
||||||
}).then(({ canceled, result: a }) => {
|
//const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
||||||
ok(a);
|
//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
||||||
});
|
const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
|
||||||
});
|
const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
|
||||||
},
|
|
||||||
out: (value) => {
|
const { widgetProps, configure } = useWidgetPropsManager(name,
|
||||||
// nop
|
widgetPropsDef,
|
||||||
},
|
props,
|
||||||
log: (type, params) => {
|
emit,
|
||||||
// nop
|
);
|
||||||
}
|
|
||||||
|
const run = async () => {
|
||||||
|
const aiscript = new AiScript(createAiScriptEnv({
|
||||||
|
storageKey: 'widget',
|
||||||
|
token: $i?.token,
|
||||||
|
}), {
|
||||||
|
in: (q) => {
|
||||||
|
return new Promise(ok => {
|
||||||
|
os.inputText({
|
||||||
|
title: q,
|
||||||
|
}).then(({ canceled, result: a }) => {
|
||||||
|
ok(a);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let ast;
|
|
||||||
try {
|
|
||||||
ast = parse(this.props.script);
|
|
||||||
} catch (e) {
|
|
||||||
os.alert({
|
|
||||||
type: 'error',
|
|
||||||
text: 'Syntax error :('
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await aiscript.exec(ast);
|
|
||||||
} catch (e) {
|
|
||||||
os.alert({
|
|
||||||
type: 'error',
|
|
||||||
text: e
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
out: (value) => {
|
||||||
|
// nop
|
||||||
|
},
|
||||||
|
log: (type, params) => {
|
||||||
|
// nop
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let ast;
|
||||||
|
try {
|
||||||
|
ast = parse(widgetProps.script);
|
||||||
|
} catch (e) {
|
||||||
|
os.alert({
|
||||||
|
type: 'error',
|
||||||
|
text: 'Syntax error :(',
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
await aiscript.exec(ast);
|
||||||
|
} catch (e) {
|
||||||
|
os.alert({
|
||||||
|
type: 'error',
|
||||||
|
text: e,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose<WidgetComponentExpose>({
|
||||||
|
name,
|
||||||
|
configure,
|
||||||
|
id: props.widget ? props.widget.id : null,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mkw-calendar" :class="{ _panel: !props.transparent }">
|
<div class="mkw-calendar" :class="{ _panel: !widgetProps.transparent }">
|
||||||
<div class="calendar" :class="{ isHoliday }">
|
<div class="calendar" :class="{ isHoliday }">
|
||||||
<p class="month-and-year">
|
<p class="month-and-year">
|
||||||
<span class="year">{{ $t('yearX', { year }) }}</span>
|
<span class="year">{{ $t('yearX', { year }) }}</span>
|
||||||
|
@ -32,77 +32,87 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue';
|
import { onUnmounted, ref } from 'vue';
|
||||||
import define from './define';
|
import { GetFormResultType } from '@/scripts/form';
|
||||||
|
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
|
||||||
|
import { i18n } from '@/i18n';
|
||||||
|
|
||||||
const widget = define({
|
const name = 'calendar';
|
||||||
name: 'calendar',
|
|
||||||
props: () => ({
|
const widgetPropsDef = {
|
||||||
transparent: {
|
transparent: {
|
||||||
type: 'boolean',
|
type: 'boolean' as const,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
|
|
||||||
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
|
||||||
|
//const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
||||||
|
//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
||||||
|
const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
|
||||||
|
const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
|
||||||
|
|
||||||
|
const { widgetProps, configure } = useWidgetPropsManager(name,
|
||||||
|
widgetPropsDef,
|
||||||
|
props,
|
||||||
|
emit,
|
||||||
|
);
|
||||||
|
|
||||||
|
const year = ref(0);
|
||||||
|
const month = ref(0);
|
||||||
|
const day = ref(0);
|
||||||
|
const weekDay = ref('');
|
||||||
|
const yearP = ref(0);
|
||||||
|
const monthP = ref(0);
|
||||||
|
const dayP = ref(0);
|
||||||
|
const isHoliday = ref(false);
|
||||||
|
const tick = () => {
|
||||||
|
const now = new Date();
|
||||||
|
const nd = now.getDate();
|
||||||
|
const nm = now.getMonth();
|
||||||
|
const ny = now.getFullYear();
|
||||||
|
|
||||||
|
year.value = ny;
|
||||||
|
month.value = nm + 1;
|
||||||
|
day.value = nd;
|
||||||
|
weekDay.value = [
|
||||||
|
i18n.locale._weekday.sunday,
|
||||||
|
i18n.locale._weekday.monday,
|
||||||
|
i18n.locale._weekday.tuesday,
|
||||||
|
i18n.locale._weekday.wednesday,
|
||||||
|
i18n.locale._weekday.thursday,
|
||||||
|
i18n.locale._weekday.friday,
|
||||||
|
i18n.locale._weekday.saturday
|
||||||
|
][now.getDay()];
|
||||||
|
|
||||||
|
const dayNumer = now.getTime() - new Date(ny, nm, nd).getTime();
|
||||||
|
const dayDenom = 1000/*ms*/ * 60/*s*/ * 60/*m*/ * 24/*h*/;
|
||||||
|
const monthNumer = now.getTime() - new Date(ny, nm, 1).getTime();
|
||||||
|
const monthDenom = new Date(ny, nm + 1, 1).getTime() - new Date(ny, nm, 1).getTime();
|
||||||
|
const yearNumer = now.getTime() - new Date(ny, 0, 1).getTime();
|
||||||
|
const yearDenom = new Date(ny + 1, 0, 1).getTime() - new Date(ny, 0, 1).getTime();
|
||||||
|
|
||||||
|
dayP.value = dayNumer / dayDenom * 100;
|
||||||
|
monthP.value = monthNumer / monthDenom * 100;
|
||||||
|
yearP.value = yearNumer / yearDenom * 100;
|
||||||
|
|
||||||
|
isHoliday.value = now.getDay() === 0 || now.getDay() === 6;
|
||||||
|
};
|
||||||
|
|
||||||
|
tick();
|
||||||
|
|
||||||
|
const intervalId = setInterval(tick, 1000);
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearInterval(intervalId);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default defineComponent({
|
defineExpose<WidgetComponentExpose>({
|
||||||
extends: widget,
|
name,
|
||||||
data() {
|
configure,
|
||||||
return {
|
id: props.widget ? props.widget.id : null,
|
||||||
now: new Date(),
|
|
||||||
year: null,
|
|
||||||
month: null,
|
|
||||||
day: null,
|
|
||||||
weekDay: null,
|
|
||||||
yearP: null,
|
|
||||||
dayP: null,
|
|
||||||
monthP: null,
|
|
||||||
isHoliday: null,
|
|
||||||
clock: null
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.tick();
|
|
||||||
this.clock = setInterval(this.tick, 1000);
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
clearInterval(this.clock);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
tick() {
|
|
||||||
const now = new Date();
|
|
||||||
const nd = now.getDate();
|
|
||||||
const nm = now.getMonth();
|
|
||||||
const ny = now.getFullYear();
|
|
||||||
|
|
||||||
this.year = ny;
|
|
||||||
this.month = nm + 1;
|
|
||||||
this.day = nd;
|
|
||||||
this.weekDay = [
|
|
||||||
this.$ts._weekday.sunday,
|
|
||||||
this.$ts._weekday.monday,
|
|
||||||
this.$ts._weekday.tuesday,
|
|
||||||
this.$ts._weekday.wednesday,
|
|
||||||
this.$ts._weekday.thursday,
|
|
||||||
this.$ts._weekday.friday,
|
|
||||||
this.$ts._weekday.saturday
|
|
||||||
][now.getDay()];
|
|
||||||
|
|
||||||
const dayNumer = now.getTime() - new Date(ny, nm, nd).getTime();
|
|
||||||
const dayDenom = 1000/*ms*/ * 60/*s*/ * 60/*m*/ * 24/*h*/;
|
|
||||||
const monthNumer = now.getTime() - new Date(ny, nm, 1).getTime();
|
|
||||||
const monthDenom = new Date(ny, nm + 1, 1).getTime() - new Date(ny, nm, 1).getTime();
|
|
||||||
const yearNumer = now.getTime() - new Date(ny, 0, 1).getTime();
|
|
||||||
const yearDenom = new Date(ny + 1, 0, 1).getTime() - new Date(ny, 0, 1).getTime();
|
|
||||||
|
|
||||||
this.dayP = dayNumer / dayDenom * 100;
|
|
||||||
this.monthP = monthNumer / monthDenom * 100;
|
|
||||||
this.yearP = yearNumer / yearDenom * 100;
|
|
||||||
|
|
||||||
this.isHoliday = now.getDay() === 0 || now.getDay() === 6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,45 +1,56 @@
|
||||||
<template>
|
<template>
|
||||||
<MkContainer :naked="props.transparent" :show-header="false">
|
<MkContainer :naked="widgetProps.transparent" :show-header="false">
|
||||||
<div class="vubelbmv">
|
<div class="vubelbmv">
|
||||||
<MkAnalogClock class="clock" :thickness="props.thickness"/>
|
<MkAnalogClock class="clock" :thickness="widgetProps.thickness"/>
|
||||||
</div>
|
</div>
|
||||||
</MkContainer>
|
</MkContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue';
|
import { } from 'vue';
|
||||||
import define from './define';
|
import { GetFormResultType } from '@/scripts/form';
|
||||||
|
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
|
||||||
import MkContainer from '@/components/ui/container.vue';
|
import MkContainer from '@/components/ui/container.vue';
|
||||||
import MkAnalogClock from '@/components/analog-clock.vue';
|
import MkAnalogClock from '@/components/analog-clock.vue';
|
||||||
import * as os from '@/os';
|
|
||||||
|
|
||||||
const widget = define({
|
const name = 'clock';
|
||||||
name: 'clock',
|
|
||||||
props: () => ({
|
|
||||||
transparent: {
|
|
||||||
type: 'boolean',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
thickness: {
|
|
||||||
type: 'radio',
|
|
||||||
default: 0.1,
|
|
||||||
options: [{
|
|
||||||
value: 0.1, label: 'thin'
|
|
||||||
}, {
|
|
||||||
value: 0.2, label: 'medium'
|
|
||||||
}, {
|
|
||||||
value: 0.3, label: 'thick'
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
export default defineComponent({
|
const widgetPropsDef = {
|
||||||
components: {
|
transparent: {
|
||||||
MkContainer,
|
type: 'boolean' as const,
|
||||||
MkAnalogClock
|
default: false,
|
||||||
},
|
},
|
||||||
extends: widget,
|
thickness: {
|
||||||
|
type: 'radio' as const,
|
||||||
|
default: 0.1,
|
||||||
|
options: [{
|
||||||
|
value: 0.1, label: 'thin'
|
||||||
|
}, {
|
||||||
|
value: 0.2, label: 'medium'
|
||||||
|
}, {
|
||||||
|
value: 0.3, label: 'thick'
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
|
||||||
|
//const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
||||||
|
//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
||||||
|
const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
|
||||||
|
const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
|
||||||
|
|
||||||
|
const { widgetProps, configure } = useWidgetPropsManager(name,
|
||||||
|
widgetPropsDef,
|
||||||
|
props,
|
||||||
|
emit,
|
||||||
|
);
|
||||||
|
|
||||||
|
defineExpose<WidgetComponentExpose>({
|
||||||
|
name,
|
||||||
|
configure,
|
||||||
|
id: props.widget ? props.widget.id : null,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import { throttle } from 'throttle-debounce';
|
|
||||||
import { Form } from '@/scripts/form';
|
|
||||||
import * as os from '@/os';
|
|
||||||
|
|
||||||
export default function <T extends Form>(data: {
|
|
||||||
name: string;
|
|
||||||
props?: () => T;
|
|
||||||
}) {
|
|
||||||
return defineComponent({
|
|
||||||
props: {
|
|
||||||
widget: {
|
|
||||||
type: Object,
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
settingCallback: {
|
|
||||||
required: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
emits: ['updateProps'],
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
props: this.widget ? JSON.parse(JSON.stringify(this.widget.data)) : {},
|
|
||||||
save: throttle(3000, () => {
|
|
||||||
this.$emit('updateProps', this.props);
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
id(): string {
|
|
||||||
return this.widget ? this.widget.id : null;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
created() {
|
|
||||||
this.mergeProps();
|
|
||||||
|
|
||||||
this.$watch('props', () => {
|
|
||||||
this.mergeProps();
|
|
||||||
}, { deep: true });
|
|
||||||
|
|
||||||
if (this.settingCallback) this.settingCallback(this.setting);
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
mergeProps() {
|
|
||||||
if (data.props) {
|
|
||||||
const defaultProps = data.props();
|
|
||||||
for (const prop of Object.keys(defaultProps)) {
|
|
||||||
if (this.props.hasOwnProperty(prop)) continue;
|
|
||||||
this.props[prop] = defaultProps[prop].default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async setting() {
|
|
||||||
const form = data.props();
|
|
||||||
for (const item of Object.keys(form)) {
|
|
||||||
form[item].default = this.props[item];
|
|
||||||
}
|
|
||||||
const { canceled, result } = await os.form(data.name, form);
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
for (const key of Object.keys(result)) {
|
|
||||||
this.props[key] = result[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.save();
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,73 +1,84 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mkw-digitalClock _monospace" :class="{ _panel: !props.transparent }" :style="{ fontSize: `${props.fontSize}em` }">
|
<div class="mkw-digitalClock _monospace" :class="{ _panel: !widgetProps.transparent }" :style="{ fontSize: `${widgetProps.fontSize}em` }">
|
||||||
<span>
|
<span>
|
||||||
<span v-text="hh"></span>
|
<span v-text="hh"></span>
|
||||||
<span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span>
|
<span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span>
|
||||||
<span v-text="mm"></span>
|
<span v-text="mm"></span>
|
||||||
<span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span>
|
<span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span>
|
||||||
<span v-text="ss"></span>
|
<span v-text="ss"></span>
|
||||||
<span v-if="props.showMs" :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span>
|
<span v-if="widgetProps.showMs" :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span>
|
||||||
<span v-if="props.showMs" v-text="ms"></span>
|
<span v-if="widgetProps.showMs" v-text="ms"></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue';
|
import { onUnmounted, ref, watch } from 'vue';
|
||||||
import define from './define';
|
import { GetFormResultType } from '@/scripts/form';
|
||||||
import * as os from '@/os';
|
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
|
||||||
|
|
||||||
const widget = define({
|
const name = 'digitalClock';
|
||||||
name: 'digitalClock',
|
|
||||||
props: () => ({
|
const widgetPropsDef = {
|
||||||
transparent: {
|
transparent: {
|
||||||
type: 'boolean',
|
type: 'boolean' as const,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
fontSize: {
|
fontSize: {
|
||||||
type: 'number',
|
type: 'number' as const,
|
||||||
default: 1.5,
|
default: 1.5,
|
||||||
step: 0.1,
|
step: 0.1,
|
||||||
},
|
},
|
||||||
showMs: {
|
showMs: {
|
||||||
type: 'boolean',
|
type: 'boolean' as const,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
|
|
||||||
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
|
||||||
|
//const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
||||||
|
//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
||||||
|
const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
|
||||||
|
const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
|
||||||
|
|
||||||
|
const { widgetProps, configure } = useWidgetPropsManager(name,
|
||||||
|
widgetPropsDef,
|
||||||
|
props,
|
||||||
|
emit,
|
||||||
|
);
|
||||||
|
|
||||||
|
let intervalId;
|
||||||
|
const hh = ref('');
|
||||||
|
const mm = ref('');
|
||||||
|
const ss = ref('');
|
||||||
|
const ms = ref('');
|
||||||
|
const showColon = ref(true);
|
||||||
|
const tick = () => {
|
||||||
|
const now = new Date();
|
||||||
|
hh.value = now.getHours().toString().padStart(2, '0');
|
||||||
|
mm.value = now.getMinutes().toString().padStart(2, '0');
|
||||||
|
ss.value = now.getSeconds().toString().padStart(2, '0');
|
||||||
|
ms.value = Math.floor(now.getMilliseconds() / 10).toString().padStart(2, '0');
|
||||||
|
showColon.value = now.getSeconds() % 2 === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
tick();
|
||||||
|
|
||||||
|
watch(() => widgetProps.showMs, () => {
|
||||||
|
if (intervalId) clearInterval(intervalId);
|
||||||
|
intervalId = setInterval(tick, widgetProps.showMs ? 10 : 1000);
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearInterval(intervalId);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default defineComponent({
|
defineExpose<WidgetComponentExpose>({
|
||||||
extends: widget,
|
name,
|
||||||
data() {
|
configure,
|
||||||
return {
|
id: props.widget ? props.widget.id : null,
|
||||||
clock: null,
|
|
||||||
hh: null,
|
|
||||||
mm: null,
|
|
||||||
ss: null,
|
|
||||||
ms: null,
|
|
||||||
showColon: true,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.tick();
|
|
||||||
this.$watch(() => this.props.showMs, () => {
|
|
||||||
if (this.clock) clearInterval(this.clock);
|
|
||||||
this.clock = setInterval(this.tick, this.props.showMs ? 10 : 1000);
|
|
||||||
}, { immediate: true });
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
clearInterval(this.clock);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
tick() {
|
|
||||||
const now = new Date();
|
|
||||||
this.hh = now.getHours().toString().padStart(2, '0');
|
|
||||||
this.mm = now.getMinutes().toString().padStart(2, '0');
|
|
||||||
this.ss = now.getSeconds().toString().padStart(2, '0');
|
|
||||||
this.ms = Math.floor(now.getMilliseconds() / 10).toString().padStart(2, '0');
|
|
||||||
this.showColon = now.getSeconds() % 2 === 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<MkContainer :show-header="props.showHeader" :foldable="foldable" :scrollable="scrollable">
|
<MkContainer :show-header="widgetProps.showHeader" :foldable="foldable" :scrollable="scrollable">
|
||||||
<template #header><i class="fas fa-globe"></i>{{ $ts._widgets.federation }}</template>
|
<template #header><i class="fas fa-globe"></i>{{ $ts._widgets.federation }}</template>
|
||||||
|
|
||||||
<div class="wbrkwalb">
|
<div class="wbrkwalb">
|
||||||
|
@ -18,66 +18,64 @@
|
||||||
</MkContainer>
|
</MkContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue';
|
import { onMounted, onUnmounted, ref } from 'vue';
|
||||||
|
import { GetFormResultType } from '@/scripts/form';
|
||||||
|
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
|
||||||
import MkContainer from '@/components/ui/container.vue';
|
import MkContainer from '@/components/ui/container.vue';
|
||||||
import define from './define';
|
|
||||||
import MkMiniChart from '@/components/mini-chart.vue';
|
import MkMiniChart from '@/components/mini-chart.vue';
|
||||||
import * as os from '@/os';
|
import * as os from '@/os';
|
||||||
|
|
||||||
const widget = define({
|
const name = 'federation';
|
||||||
name: 'federation',
|
|
||||||
props: () => ({
|
const widgetPropsDef = {
|
||||||
showHeader: {
|
showHeader: {
|
||||||
type: 'boolean',
|
type: 'boolean' as const,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
|
|
||||||
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
|
||||||
|
//const props = defineProps<WidgetComponentProps<WidgetProps> & { foldable?: boolean; scrollable?: boolean; }>();
|
||||||
|
//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
||||||
|
const props = defineProps<{ widget?: Widget<WidgetProps>; foldable?: boolean; scrollable?: boolean; }>();
|
||||||
|
const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
|
||||||
|
|
||||||
|
const { widgetProps, configure } = useWidgetPropsManager(name,
|
||||||
|
widgetPropsDef,
|
||||||
|
props,
|
||||||
|
emit,
|
||||||
|
);
|
||||||
|
|
||||||
|
const instances = ref([]);
|
||||||
|
const charts = ref([]);
|
||||||
|
const fetching = ref(true);
|
||||||
|
|
||||||
|
const fetch = async () => {
|
||||||
|
const instances = await os.api('federation/instances', {
|
||||||
|
sort: '+lastCommunicatedAt',
|
||||||
|
limit: 5
|
||||||
|
});
|
||||||
|
const charts = await Promise.all(instances.map(i => os.api('charts/instance', { host: i.host, limit: 16, span: 'hour' })));
|
||||||
|
instances.value = instances;
|
||||||
|
charts.value = charts;
|
||||||
|
fetching.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetch();
|
||||||
|
const intervalId = setInterval(fetch, 1000 * 60);
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export default defineComponent({
|
defineExpose<WidgetComponentExpose>({
|
||||||
components: {
|
name,
|
||||||
MkContainer, MkMiniChart
|
configure,
|
||||||
},
|
id: props.widget ? props.widget.id : null,
|
||||||
extends: widget,
|
|
||||||
props: {
|
|
||||||
foldable: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
scrollable: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
instances: [],
|
|
||||||
charts: [],
|
|
||||||
fetching: true,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.fetch();
|
|
||||||
this.clock = setInterval(this.fetch, 1000 * 60);
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
clearInterval(this.clock);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async fetch() {
|
|
||||||
const instances = await os.api('federation/instances', {
|
|
||||||
sort: '+lastCommunicatedAt',
|
|
||||||
limit: 5
|
|
||||||
});
|
|
||||||
const charts = await Promise.all(instances.map(i => os.api('charts/instance', { host: i.host, limit: 16, span: 'hour' })));
|
|
||||||
this.instances = instances;
|
|
||||||
this.charts = charts;
|
|
||||||
this.fetching = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,134 +1,146 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mkw-jobQueue _monospace" :class="{ _panel: !props.transparent }">
|
<div class="mkw-jobQueue _monospace" :class="{ _panel: !widgetProps.transparent }">
|
||||||
<div class="inbox">
|
<div class="inbox">
|
||||||
<div class="label">Inbox queue<i v-if="inbox.waiting > 0" class="fas fa-exclamation-triangle icon"></i></div>
|
<div class="label">Inbox queue<i v-if="current.inbox.waiting > 0" class="fas fa-exclamation-triangle icon"></i></div>
|
||||||
<div class="values">
|
<div class="values">
|
||||||
<div>
|
<div>
|
||||||
<div>Process</div>
|
<div>Process</div>
|
||||||
<div :class="{ inc: inbox.activeSincePrevTick > prev.inbox.activeSincePrevTick, dec: inbox.activeSincePrevTick < prev.inbox.activeSincePrevTick }">{{ number(inbox.activeSincePrevTick) }}</div>
|
<div :class="{ inc: current.inbox.activeSincePrevTick > prev.inbox.activeSincePrevTick, dec: current.inbox.activeSincePrevTick < prev.inbox.activeSincePrevTick }">{{ number(current.inbox.activeSincePrevTick) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>Active</div>
|
<div>Active</div>
|
||||||
<div :class="{ inc: inbox.active > prev.inbox.active, dec: inbox.active < prev.inbox.active }">{{ number(inbox.active) }}</div>
|
<div :class="{ inc: current.inbox.active > prev.inbox.active, dec: current.inbox.active < prev.inbox.active }">{{ number(current.inbox.active) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>Delayed</div>
|
<div>Delayed</div>
|
||||||
<div :class="{ inc: inbox.delayed > prev.inbox.delayed, dec: inbox.delayed < prev.inbox.delayed }">{{ number(inbox.delayed) }}</div>
|
<div :class="{ inc: current.inbox.delayed > prev.inbox.delayed, dec: current.inbox.delayed < prev.inbox.delayed }">{{ number(current.inbox.delayed) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>Waiting</div>
|
<div>Waiting</div>
|
||||||
<div :class="{ inc: inbox.waiting > prev.inbox.waiting, dec: inbox.waiting < prev.inbox.waiting }">{{ number(inbox.waiting) }}</div>
|
<div :class="{ inc: current.inbox.waiting > prev.inbox.waiting, dec: current.inbox.waiting < prev.inbox.waiting }">{{ number(current.inbox.waiting) }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="deliver">
|
<div class="deliver">
|
||||||
<div class="label">Deliver queue<i v-if="deliver.waiting > 0" class="fas fa-exclamation-triangle icon"></i></div>
|
<div class="label">Deliver queue<i v-if="current.deliver.waiting > 0" class="fas fa-exclamation-triangle icon"></i></div>
|
||||||
<div class="values">
|
<div class="values">
|
||||||
<div>
|
<div>
|
||||||
<div>Process</div>
|
<div>Process</div>
|
||||||
<div :class="{ inc: deliver.activeSincePrevTick > prev.deliver.activeSincePrevTick, dec: deliver.activeSincePrevTick < prev.deliver.activeSincePrevTick }">{{ number(deliver.activeSincePrevTick) }}</div>
|
<div :class="{ inc: current.deliver.activeSincePrevTick > prev.deliver.activeSincePrevTick, dec: current.deliver.activeSincePrevTick < prev.deliver.activeSincePrevTick }">{{ number(current.deliver.activeSincePrevTick) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>Active</div>
|
<div>Active</div>
|
||||||
<div :class="{ inc: deliver.active > prev.deliver.active, dec: deliver.active < prev.deliver.active }">{{ number(deliver.active) }}</div>
|
<div :class="{ inc: current.deliver.active > prev.deliver.active, dec: current.deliver.active < prev.deliver.active }">{{ number(current.deliver.active) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>Delayed</div>
|
<div>Delayed</div>
|
||||||
<div :class="{ inc: deliver.delayed > prev.deliver.delayed, dec: deliver.delayed < prev.deliver.delayed }">{{ number(deliver.delayed) }}</div>
|
<div :class="{ inc: current.deliver.delayed > prev.deliver.delayed, dec: current.deliver.delayed < prev.deliver.delayed }">{{ number(current.deliver.delayed) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>Waiting</div>
|
<div>Waiting</div>
|
||||||
<div :class="{ inc: deliver.waiting > prev.deliver.waiting, dec: deliver.waiting < prev.deliver.waiting }">{{ number(deliver.waiting) }}</div>
|
<div :class="{ inc: current.deliver.waiting > prev.deliver.waiting, dec: current.deliver.waiting < prev.deliver.waiting }">{{ number(current.deliver.waiting) }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, markRaw } from 'vue';
|
import { onMounted, onUnmounted, reactive, ref } from 'vue';
|
||||||
import define from './define';
|
import { GetFormResultType } from '@/scripts/form';
|
||||||
import * as os from '@/os';
|
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
|
||||||
import { stream } from '@/stream';
|
import { stream } from '@/stream';
|
||||||
import number from '@/filters/number';
|
import number from '@/filters/number';
|
||||||
import * as sound from '@/scripts/sound';
|
import * as sound from '@/scripts/sound';
|
||||||
|
import * as os from '@/os';
|
||||||
|
|
||||||
const widget = define({
|
const name = 'jobQueue';
|
||||||
name: 'jobQueue',
|
|
||||||
props: () => ({
|
const widgetPropsDef = {
|
||||||
transparent: {
|
transparent: {
|
||||||
type: 'boolean',
|
type: 'boolean' as const,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
sound: {
|
sound: {
|
||||||
type: 'boolean',
|
type: 'boolean' as const,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
|
|
||||||
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
|
||||||
|
//const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
||||||
|
//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
||||||
|
const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
|
||||||
|
const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
|
||||||
|
|
||||||
|
const { widgetProps, configure } = useWidgetPropsManager(name,
|
||||||
|
widgetPropsDef,
|
||||||
|
props,
|
||||||
|
emit,
|
||||||
|
);
|
||||||
|
|
||||||
|
const connection = stream.useChannel('queueStats');
|
||||||
|
const current = reactive({
|
||||||
|
inbox: {
|
||||||
|
activeSincePrevTick: 0,
|
||||||
|
active: 0,
|
||||||
|
waiting: 0,
|
||||||
|
delayed: 0,
|
||||||
|
},
|
||||||
|
deliver: {
|
||||||
|
activeSincePrevTick: 0,
|
||||||
|
active: 0,
|
||||||
|
waiting: 0,
|
||||||
|
delayed: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const prev = reactive({} as typeof current);
|
||||||
|
const jammedSound = sound.setVolume(sound.getAudio('syuilo/queue-jammed'), 1);
|
||||||
|
|
||||||
|
for (const domain of ['inbox', 'deliver']) {
|
||||||
|
prev[domain] = JSON.parse(JSON.stringify(current[domain]));
|
||||||
|
}
|
||||||
|
|
||||||
|
const onStats = (stats) => {
|
||||||
|
for (const domain of ['inbox', 'deliver']) {
|
||||||
|
prev[domain] = JSON.parse(JSON.stringify(current[domain]));
|
||||||
|
current[domain].activeSincePrevTick = stats[domain].activeSincePrevTick;
|
||||||
|
current[domain].active = stats[domain].active;
|
||||||
|
current[domain].waiting = stats[domain].waiting;
|
||||||
|
current[domain].delayed = stats[domain].delayed;
|
||||||
|
|
||||||
|
if (current[domain].waiting > 0 && widgetProps.sound && jammedSound.paused) {
|
||||||
|
jammedSound.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onStatsLog = (statsLog) => {
|
||||||
|
for (const stats of [...statsLog].reverse()) {
|
||||||
|
onStats(stats);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
connection.on('stats', onStats);
|
||||||
|
connection.on('statsLog', onStatsLog);
|
||||||
|
|
||||||
|
connection.send('requestLog', {
|
||||||
|
id: Math.random().toString().substr(2, 8),
|
||||||
|
length: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default defineComponent({
|
onUnmounted(() => {
|
||||||
extends: widget,
|
connection.off('stats', onStats);
|
||||||
data() {
|
connection.off('statsLog', onStatsLog);
|
||||||
return {
|
connection.dispose();
|
||||||
connection: markRaw(stream.useChannel('queueStats')),
|
});
|
||||||
inbox: {
|
|
||||||
activeSincePrevTick: 0,
|
|
||||||
active: 0,
|
|
||||||
waiting: 0,
|
|
||||||
delayed: 0,
|
|
||||||
},
|
|
||||||
deliver: {
|
|
||||||
activeSincePrevTick: 0,
|
|
||||||
active: 0,
|
|
||||||
waiting: 0,
|
|
||||||
delayed: 0,
|
|
||||||
},
|
|
||||||
prev: {},
|
|
||||||
sound: sound.setVolume(sound.getAudio('syuilo/queue-jammed'), 1)
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
for (const domain of ['inbox', 'deliver']) {
|
|
||||||
this.prev[domain] = JSON.parse(JSON.stringify(this[domain]));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.connection.on('stats', this.onStats);
|
|
||||||
this.connection.on('statsLog', this.onStatsLog);
|
|
||||||
|
|
||||||
this.connection.send('requestLog', {
|
defineExpose<WidgetComponentExpose>({
|
||||||
id: Math.random().toString().substr(2, 8),
|
name,
|
||||||
length: 1
|
configure,
|
||||||
});
|
id: props.widget ? props.widget.id : null,
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
this.connection.off('stats', this.onStats);
|
|
||||||
this.connection.off('statsLog', this.onStatsLog);
|
|
||||||
this.connection.dispose();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onStats(stats) {
|
|
||||||
for (const domain of ['inbox', 'deliver']) {
|
|
||||||
this.prev[domain] = JSON.parse(JSON.stringify(this[domain]));
|
|
||||||
this[domain].activeSincePrevTick = stats[domain].activeSincePrevTick;
|
|
||||||
this[domain].active = stats[domain].active;
|
|
||||||
this[domain].waiting = stats[domain].waiting;
|
|
||||||
this[domain].delayed = stats[domain].delayed;
|
|
||||||
|
|
||||||
if (this[domain].waiting > 0 && this.props.sound && this.sound.paused) {
|
|
||||||
this.sound.play();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onStatsLog(statsLog) {
|
|
||||||
for (const stats of [...statsLog].reverse()) {
|
|
||||||
this.onStats(stats);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
number
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<MkContainer :show-header="props.showHeader">
|
<MkContainer :show-header="widgetProps.showHeader">
|
||||||
<template #header><i class="fas fa-sticky-note"></i>{{ $ts._widgets.memo }}</template>
|
<template #header><i class="fas fa-sticky-note"></i>{{ $ts._widgets.memo }}</template>
|
||||||
|
|
||||||
<div class="otgbylcu">
|
<div class="otgbylcu">
|
||||||
|
@ -9,56 +9,60 @@
|
||||||
</MkContainer>
|
</MkContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue';
|
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
||||||
import MkContainer from '@/components/ui/container.vue';
|
import { GetFormResultType } from '@/scripts/form';
|
||||||
import define from './define';
|
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
|
||||||
import * as os from '@/os';
|
import * as os from '@/os';
|
||||||
|
import MkContainer from '@/components/ui/container.vue';
|
||||||
|
import { defaultStore } from '@/store';
|
||||||
|
|
||||||
const widget = define({
|
const name = 'memo';
|
||||||
name: 'memo',
|
|
||||||
props: () => ({
|
const widgetPropsDef = {
|
||||||
showHeader: {
|
showHeader: {
|
||||||
type: 'boolean',
|
type: 'boolean' as const,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
|
|
||||||
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
|
||||||
|
//const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
||||||
|
//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
||||||
|
const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
|
||||||
|
const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
|
||||||
|
|
||||||
|
const { widgetProps, configure } = useWidgetPropsManager(name,
|
||||||
|
widgetPropsDef,
|
||||||
|
props,
|
||||||
|
emit,
|
||||||
|
);
|
||||||
|
|
||||||
|
const text = ref<string | null>(defaultStore.state.memo);
|
||||||
|
const changed = ref(false);
|
||||||
|
let timeoutId;
|
||||||
|
|
||||||
|
const saveMemo = () => {
|
||||||
|
defaultStore.set('memo', text.value);
|
||||||
|
changed.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChange = () => {
|
||||||
|
changed.value = true;
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
timeoutId = setTimeout(saveMemo, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(() => defaultStore.reactiveState.memo, newText => {
|
||||||
|
text.value = newText.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
export default defineComponent({
|
defineExpose<WidgetComponentExpose>({
|
||||||
components: {
|
name,
|
||||||
MkContainer
|
configure,
|
||||||
},
|
id: props.widget ? props.widget.id : null,
|
||||||
extends: widget,
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
text: null,
|
|
||||||
changed: false,
|
|
||||||
timeoutId: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
created() {
|
|
||||||
this.text = this.$store.state.memo;
|
|
||||||
|
|
||||||
this.$watch(() => this.$store.reactiveState.memo, text => {
|
|
||||||
this.text = text;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
onChange() {
|
|
||||||
this.changed = true;
|
|
||||||
clearTimeout(this.timeoutId);
|
|
||||||
this.timeoutId = setTimeout(this.saveMemo, 1000);
|
|
||||||
},
|
|
||||||
|
|
||||||
saveMemo() {
|
|
||||||
this.$store.set('memo', this.text);
|
|
||||||
this.changed = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,65 +1,68 @@
|
||||||
<template>
|
<template>
|
||||||
<MkContainer :style="`height: ${props.height}px;`" :show-header="props.showHeader" :scrollable="true">
|
<MkContainer :style="`height: ${widgetProps.height}px;`" :show-header="widgetProps.showHeader" :scrollable="true">
|
||||||
<template #header><i class="fas fa-bell"></i>{{ $ts.notifications }}</template>
|
<template #header><i class="fas fa-bell"></i>{{ $ts.notifications }}</template>
|
||||||
<template #func><button class="_button" @click="configure()"><i class="fas fa-cog"></i></button></template>
|
<template #func><button class="_button" @click="configureNotification()"><i class="fas fa-cog"></i></button></template>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<XNotifications :include-types="props.includingTypes"/>
|
<XNotifications :include-types="widgetProps.includingTypes"/>
|
||||||
</div>
|
</div>
|
||||||
</MkContainer>
|
</MkContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue';
|
import { GetFormResultType } from '@/scripts/form';
|
||||||
|
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
|
||||||
import MkContainer from '@/components/ui/container.vue';
|
import MkContainer from '@/components/ui/container.vue';
|
||||||
import XNotifications from '@/components/notifications.vue';
|
import XNotifications from '@/components/notifications.vue';
|
||||||
import define from './define';
|
|
||||||
import * as os from '@/os';
|
import * as os from '@/os';
|
||||||
|
|
||||||
const widget = define({
|
const name = 'notifications';
|
||||||
name: 'notifications',
|
|
||||||
props: () => ({
|
|
||||||
showHeader: {
|
|
||||||
type: 'boolean',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
height: {
|
|
||||||
type: 'number',
|
|
||||||
default: 300,
|
|
||||||
},
|
|
||||||
includingTypes: {
|
|
||||||
type: 'array',
|
|
||||||
hidden: true,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
export default defineComponent({
|
const widgetPropsDef = {
|
||||||
|
showHeader: {
|
||||||
components: {
|
type: 'boolean' as const,
|
||||||
MkContainer,
|
default: true,
|
||||||
XNotifications,
|
|
||||||
},
|
},
|
||||||
extends: widget,
|
height: {
|
||||||
|
type: 'number' as const,
|
||||||
data() {
|
default: 300,
|
||||||
return {
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
includingTypes: {
|
||||||
|
type: 'array' as const,
|
||||||
|
hidden: true,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
methods: {
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
configure() {
|
|
||||||
os.popup(import('@/components/notification-setting-window.vue'), {
|
// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
|
||||||
includingTypes: this.props.includingTypes,
|
//const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
||||||
}, {
|
//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
||||||
done: async (res) => {
|
const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
|
||||||
const { includingTypes } = res;
|
const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
|
||||||
this.props.includingTypes = includingTypes;
|
|
||||||
this.save();
|
const { widgetProps, configure, save } = useWidgetPropsManager(name,
|
||||||
}
|
widgetPropsDef,
|
||||||
}, 'closed');
|
props,
|
||||||
|
emit,
|
||||||
|
);
|
||||||
|
|
||||||
|
const configureNotification = () => {
|
||||||
|
os.popup(import('@/components/notification-setting-window.vue'), {
|
||||||
|
includingTypes: widgetProps.includingTypes,
|
||||||
|
}, {
|
||||||
|
done: async (res) => {
|
||||||
|
const { includingTypes } = res;
|
||||||
|
widgetProps.includingTypes = includingTypes;
|
||||||
|
save();
|
||||||
}
|
}
|
||||||
}
|
}, 'closed');
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose<WidgetComponentExpose>({
|
||||||
|
name,
|
||||||
|
configure,
|
||||||
|
id: props.widget ? props.widget.id : null,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,48 +1,60 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mkw-onlineUsers" :class="{ _panel: !props.transparent, pad: !props.transparent }">
|
<div class="mkw-onlineUsers" :class="{ _panel: !widgetProps.transparent, pad: !widgetProps.transparent }">
|
||||||
<I18n v-if="onlineUsersCount" :src="$ts.onlineUsersCount" text-tag="span" class="text">
|
<I18n v-if="onlineUsersCount" :src="$ts.onlineUsersCount" text-tag="span" class="text">
|
||||||
<template #n><b>{{ onlineUsersCount }}</b></template>
|
<template #n><b>{{ onlineUsersCount }}</b></template>
|
||||||
</I18n>
|
</I18n>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue';
|
import { onMounted, onUnmounted, ref } from 'vue';
|
||||||
import define from './define';
|
import { GetFormResultType } from '@/scripts/form';
|
||||||
|
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
|
||||||
import * as os from '@/os';
|
import * as os from '@/os';
|
||||||
|
|
||||||
const widget = define({
|
const name = 'onlineUsers';
|
||||||
name: 'onlineUsers',
|
|
||||||
props: () => ({
|
const widgetPropsDef = {
|
||||||
transparent: {
|
transparent: {
|
||||||
type: 'boolean',
|
type: 'boolean' as const,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
|
|
||||||
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
|
||||||
|
//const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
||||||
|
//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
||||||
|
const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
|
||||||
|
const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
|
||||||
|
|
||||||
|
const { widgetProps, configure } = useWidgetPropsManager(name,
|
||||||
|
widgetPropsDef,
|
||||||
|
props,
|
||||||
|
emit,
|
||||||
|
);
|
||||||
|
|
||||||
|
const onlineUsersCount = ref(0);
|
||||||
|
|
||||||
|
const tick = () => {
|
||||||
|
os.api('get-online-users-count').then(res => {
|
||||||
|
onlineUsersCount.value = res.count;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
tick();
|
||||||
|
const intervalId = setInterval(tick, 1000 * 15);
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export default defineComponent({
|
defineExpose<WidgetComponentExpose>({
|
||||||
extends: widget,
|
name,
|
||||||
data() {
|
configure,
|
||||||
return {
|
id: props.widget ? props.widget.id : null,
|
||||||
onlineUsersCount: null,
|
|
||||||
clock: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.tick();
|
|
||||||
this.clock = setInterval(this.tick, 1000 * 15);
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
clearInterval(this.clock);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
tick() {
|
|
||||||
os.api('get-online-users-count').then(res => {
|
|
||||||
this.onlineUsersCount = res.count;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<MkContainer :show-header="props.showHeader" :naked="props.transparent" :class="$style.root" :data-transparent="props.transparent ? true : null">
|
<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent" :class="$style.root" :data-transparent="widgetProps.transparent ? true : null">
|
||||||
<template #header><i class="fas fa-camera"></i>{{ $ts._widgets.photos }}</template>
|
<template #header><i class="fas fa-camera"></i>{{ $ts._widgets.photos }}</template>
|
||||||
|
|
||||||
<div class="">
|
<div class="">
|
||||||
|
@ -14,70 +14,77 @@
|
||||||
</MkContainer>
|
</MkContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, markRaw } from 'vue';
|
import { onMounted, onUnmounted, reactive, ref } from 'vue';
|
||||||
import MkContainer from '@/components/ui/container.vue';
|
import { GetFormResultType } from '@/scripts/form';
|
||||||
import define from './define';
|
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
|
||||||
|
import { stream } from '@/stream';
|
||||||
import { getStaticImageUrl } from '@/scripts/get-static-image-url';
|
import { getStaticImageUrl } from '@/scripts/get-static-image-url';
|
||||||
import * as os from '@/os';
|
import * as os from '@/os';
|
||||||
import { stream } from '@/stream';
|
import MkContainer from '@/components/ui/container.vue';
|
||||||
|
import { defaultStore } from '@/store';
|
||||||
|
|
||||||
const widget = define({
|
const name = 'photos';
|
||||||
name: 'photos',
|
|
||||||
props: () => ({
|
const widgetPropsDef = {
|
||||||
showHeader: {
|
showHeader: {
|
||||||
type: 'boolean',
|
type: 'boolean' as const,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
transparent: {
|
transparent: {
|
||||||
type: 'boolean',
|
type: 'boolean' as const,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
|
|
||||||
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
|
||||||
|
//const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
||||||
|
//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
||||||
|
const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
|
||||||
|
const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
|
||||||
|
|
||||||
|
const { widgetProps, configure } = useWidgetPropsManager(name,
|
||||||
|
widgetPropsDef,
|
||||||
|
props,
|
||||||
|
emit,
|
||||||
|
);
|
||||||
|
|
||||||
|
const connection = stream.useChannel('main');
|
||||||
|
const images = ref([]);
|
||||||
|
const fetching = ref(true);
|
||||||
|
|
||||||
|
const onDriveFileCreated = (file) => {
|
||||||
|
if (/^image\/.+$/.test(file.type)) {
|
||||||
|
images.value.unshift(file);
|
||||||
|
if (images.value.length > 9) images.value.pop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const thumbnail = (image: any): string => {
|
||||||
|
return defaultStore.state.disableShowingAnimatedImages
|
||||||
|
? getStaticImageUrl(image.thumbnailUrl)
|
||||||
|
: image.thumbnailUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
os.api('drive/stream', {
|
||||||
|
type: 'image/*',
|
||||||
|
limit: 9
|
||||||
|
}).then(res => {
|
||||||
|
images.value = res;
|
||||||
|
fetching.value = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
export default defineComponent({
|
connection.on('driveFileCreated', onDriveFileCreated);
|
||||||
components: {
|
onUnmounted(() => {
|
||||||
MkContainer,
|
connection.dispose();
|
||||||
},
|
});
|
||||||
extends: widget,
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
images: [],
|
|
||||||
fetching: true,
|
|
||||||
connection: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.connection = markRaw(stream.useChannel('main'));
|
|
||||||
|
|
||||||
this.connection.on('driveFileCreated', this.onDriveFileCreated);
|
defineExpose<WidgetComponentExpose>({
|
||||||
|
name,
|
||||||
os.api('drive/stream', {
|
configure,
|
||||||
type: 'image/*',
|
id: props.widget ? props.widget.id : null,
|
||||||
limit: 9
|
|
||||||
}).then(images => {
|
|
||||||
this.images = images;
|
|
||||||
this.fetching = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
this.connection.dispose();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onDriveFileCreated(file) {
|
|
||||||
if (/^image\/.+$/.test(file.type)) {
|
|
||||||
this.images.unshift(file);
|
|
||||||
if (this.images.length > 9) this.images.pop();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
thumbnail(image: any): string {
|
|
||||||
return this.$store.state.disableShowingAnimatedImages
|
|
||||||
? getStaticImageUrl(image.thumbnailUrl)
|
|
||||||
: image.thumbnailUrl;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -2,22 +2,34 @@
|
||||||
<XPostForm class="_panel" :fixed="true" :autofocus="false"/>
|
<XPostForm class="_panel" :fixed="true" :autofocus="false"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue';
|
import { } from 'vue';
|
||||||
|
import { GetFormResultType } from '@/scripts/form';
|
||||||
|
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
|
||||||
import XPostForm from '@/components/post-form.vue';
|
import XPostForm from '@/components/post-form.vue';
|
||||||
import define from './define';
|
|
||||||
|
|
||||||
const widget = define({
|
const name = 'postForm';
|
||||||
name: 'postForm',
|
|
||||||
props: () => ({
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
export default defineComponent({
|
const widgetPropsDef = {
|
||||||
|
};
|
||||||
|
|
||||||
components: {
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
XPostForm,
|
|
||||||
},
|
// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
|
||||||
extends: widget,
|
//const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
||||||
|
//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
||||||
|
const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
|
||||||
|
const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
|
||||||
|
|
||||||
|
const { widgetProps, configure } = useWidgetPropsManager(name,
|
||||||
|
widgetPropsDef,
|
||||||
|
props,
|
||||||
|
emit,
|
||||||
|
);
|
||||||
|
|
||||||
|
defineExpose<WidgetComponentExpose>({
|
||||||
|
name,
|
||||||
|
configure,
|
||||||
|
id: props.widget ? props.widget.id : null,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<MkContainer :show-header="props.showHeader">
|
<MkContainer :show-header="widgetProps.showHeader">
|
||||||
<template #header><i class="fas fa-rss-square"></i>RSS</template>
|
<template #header><i class="fas fa-rss-square"></i>RSS</template>
|
||||||
<template #func><button class="_button" @click="setting"><i class="fas fa-cog"></i></button></template>
|
<template #func><button class="_button" @click="configure"><i class="fas fa-cog"></i></button></template>
|
||||||
|
|
||||||
<div class="ekmkgxbj">
|
<div class="ekmkgxbj">
|
||||||
<MkLoading v-if="fetching"/>
|
<MkLoading v-if="fetching"/>
|
||||||
|
@ -12,57 +12,66 @@
|
||||||
</MkContainer>
|
</MkContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue';
|
import { onMounted, onUnmounted, ref, watch } from 'vue';
|
||||||
import MkContainer from '@/components/ui/container.vue';
|
import { GetFormResultType } from '@/scripts/form';
|
||||||
import define from './define';
|
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
|
||||||
import * as os from '@/os';
|
import * as os from '@/os';
|
||||||
|
import MkContainer from '@/components/ui/container.vue';
|
||||||
|
|
||||||
const widget = define({
|
const name = 'rss';
|
||||||
name: 'rss',
|
|
||||||
props: () => ({
|
const widgetPropsDef = {
|
||||||
showHeader: {
|
showHeader: {
|
||||||
type: 'boolean',
|
type: 'boolean' as const,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
url: {
|
url: {
|
||||||
type: 'string',
|
type: 'string' as const,
|
||||||
default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews',
|
default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews',
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
|
|
||||||
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
|
||||||
|
//const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
||||||
|
//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
||||||
|
const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
|
||||||
|
const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
|
||||||
|
|
||||||
|
const { widgetProps, configure } = useWidgetPropsManager(name,
|
||||||
|
widgetPropsDef,
|
||||||
|
props,
|
||||||
|
emit,
|
||||||
|
);
|
||||||
|
|
||||||
|
const items = ref([]);
|
||||||
|
const fetching = ref(true);
|
||||||
|
|
||||||
|
const tick = () => {
|
||||||
|
fetch(`https://api.rss2json.com/v1/api.json?rss_url=${this.props.url}`, {}).then(res => {
|
||||||
|
res.json().then(feed => {
|
||||||
|
items.value = feed.items;
|
||||||
|
fetching.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(() => widgetProps.url, tick);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
tick();
|
||||||
|
const intervalId = setInterval(tick, 60000);
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export default defineComponent({
|
defineExpose<WidgetComponentExpose>({
|
||||||
components: {
|
name,
|
||||||
MkContainer
|
configure,
|
||||||
},
|
id: props.widget ? props.widget.id : null,
|
||||||
extends: widget,
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
items: [],
|
|
||||||
fetching: true,
|
|
||||||
clock: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.fetch();
|
|
||||||
this.clock = setInterval(this.fetch, 60000);
|
|
||||||
this.$watch(() => this.props.url, this.fetch);
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
clearInterval(this.clock);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetch() {
|
|
||||||
fetch(`https://api.rss2json.com/v1/api.json?rss_url=${this.props.url}`, {
|
|
||||||
}).then(res => {
|
|
||||||
res.json().then(feed => {
|
|
||||||
this.items = feed.items;
|
|
||||||
this.fetching = false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
<template>
|
<template>
|
||||||
<MkContainer :show-header="props.showHeader" :naked="props.transparent">
|
<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent">
|
||||||
<template #header><i class="fas fa-server"></i>{{ $ts._widgets.serverMetric }}</template>
|
<template #header><i class="fas fa-server"></i>{{ $ts._widgets.serverMetric }}</template>
|
||||||
<template #func><button class="_button" @click="toggleView()"><i class="fas fa-sort"></i></button></template>
|
<template #func><button class="_button" @click="toggleView()"><i class="fas fa-sort"></i></button></template>
|
||||||
|
|
||||||
<div v-if="meta" class="mkw-serverMetric">
|
<div v-if="meta" class="mkw-serverMetric">
|
||||||
<XCpuMemory v-if="props.view === 0" :connection="connection" :meta="meta"/>
|
<XCpuMemory v-if="widgetProps.view === 0" :connection="connection" :meta="meta"/>
|
||||||
<XNet v-if="props.view === 1" :connection="connection" :meta="meta"/>
|
<XNet v-else-if="widgetProps.view === 1" :connection="connection" :meta="meta"/>
|
||||||
<XCpu v-if="props.view === 2" :connection="connection" :meta="meta"/>
|
<XCpu v-else-if="widgetProps.view === 2" :connection="connection" :meta="meta"/>
|
||||||
<XMemory v-if="props.view === 3" :connection="connection" :meta="meta"/>
|
<XMemory v-else-if="widgetProps.view === 3" :connection="connection" :meta="meta"/>
|
||||||
<XDisk v-if="props.view === 4" :connection="connection" :meta="meta"/>
|
<XDisk v-else-if="widgetProps.view === 4" :connection="connection" :meta="meta"/>
|
||||||
</div>
|
</div>
|
||||||
</MkContainer>
|
</MkContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, markRaw } from 'vue';
|
import { onMounted, onUnmounted, ref } from 'vue';
|
||||||
import define from '../define';
|
import { GetFormResultType } from '@/scripts/form';
|
||||||
|
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from '../widget';
|
||||||
import MkContainer from '@/components/ui/container.vue';
|
import MkContainer from '@/components/ui/container.vue';
|
||||||
import XCpuMemory from './cpu-mem.vue';
|
import XCpuMemory from './cpu-mem.vue';
|
||||||
import XNet from './net.vue';
|
import XNet from './net.vue';
|
||||||
|
@ -25,59 +26,61 @@ import XDisk from './disk.vue';
|
||||||
import * as os from '@/os';
|
import * as os from '@/os';
|
||||||
import { stream } from '@/stream';
|
import { stream } from '@/stream';
|
||||||
|
|
||||||
const widget = define({
|
const name = 'serverMetric';
|
||||||
name: 'serverMetric',
|
|
||||||
props: () => ({
|
const widgetPropsDef = {
|
||||||
showHeader: {
|
showHeader: {
|
||||||
type: 'boolean',
|
type: 'boolean' as const,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
transparent: {
|
transparent: {
|
||||||
type: 'boolean',
|
type: 'boolean' as const,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
view: {
|
view: {
|
||||||
type: 'number',
|
type: 'number' as const,
|
||||||
default: 0,
|
default: 0,
|
||||||
hidden: true,
|
hidden: true,
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
|
|
||||||
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
|
||||||
|
//const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
||||||
|
//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
||||||
|
const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
|
||||||
|
const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
|
||||||
|
|
||||||
|
const { widgetProps, configure, save } = useWidgetPropsManager(name,
|
||||||
|
widgetPropsDef,
|
||||||
|
props,
|
||||||
|
emit,
|
||||||
|
);
|
||||||
|
|
||||||
|
const meta = ref(null);
|
||||||
|
|
||||||
|
os.api('server-info', {}).then(res => {
|
||||||
|
meta.value = res;
|
||||||
});
|
});
|
||||||
|
|
||||||
export default defineComponent({
|
const toggleView = () => {
|
||||||
components: {
|
if (widgetProps.view == 4) {
|
||||||
MkContainer,
|
widgetProps.view = 0;
|
||||||
XCpuMemory,
|
} else {
|
||||||
XNet,
|
widgetProps.view++;
|
||||||
XCpu,
|
|
||||||
XMemory,
|
|
||||||
XDisk,
|
|
||||||
},
|
|
||||||
extends: widget,
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
meta: null,
|
|
||||||
connection: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
os.api('server-info', {}).then(res => {
|
|
||||||
this.meta = res;
|
|
||||||
});
|
|
||||||
this.connection = markRaw(stream.useChannel('serverStats'));
|
|
||||||
},
|
|
||||||
unmounted() {
|
|
||||||
this.connection.dispose();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
toggleView() {
|
|
||||||
if (this.props.view == 4) {
|
|
||||||
this.props.view = 0;
|
|
||||||
} else {
|
|
||||||
this.props.view++;
|
|
||||||
}
|
|
||||||
this.save();
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
save();
|
||||||
|
};
|
||||||
|
|
||||||
|
const connection = stream.useChannel('serverStats');
|
||||||
|
onUnmounted(() => {
|
||||||
|
connection.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
defineExpose<WidgetComponentExpose>({
|
||||||
|
name,
|
||||||
|
configure,
|
||||||
|
id: props.widget ? props.widget.id : null,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,126 +1,116 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="kvausudm _panel">
|
<div class="kvausudm _panel" :style="{ height: widgetProps.height + 'px' }">
|
||||||
<div @click="choose">
|
<div @click="choose">
|
||||||
<p v-if="props.folderId == null">
|
<p v-if="widgetProps.folderId == null">
|
||||||
<template v-if="isCustomizeMode">{{ $t('folder-customize-mode') }}</template>
|
{{ $ts.folder }}
|
||||||
<template v-else>{{ $ts.folder }}</template>
|
|
||||||
</p>
|
</p>
|
||||||
<p v-if="props.folderId != null && images.length === 0 && !fetching">{{ $t('no-image') }}</p>
|
<p v-if="widgetProps.folderId != null && images.length === 0 && !fetching">{{ $t('no-image') }}</p>
|
||||||
<div ref="slideA" class="slide a"></div>
|
<div ref="slideA" class="slide a"></div>
|
||||||
<div ref="slideB" class="slide b"></div>
|
<div ref="slideB" class="slide b"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue';
|
import { nextTick, onMounted, onUnmounted, reactive, ref } from 'vue';
|
||||||
import define from './define';
|
import { GetFormResultType } from '@/scripts/form';
|
||||||
|
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
|
||||||
import * as os from '@/os';
|
import * as os from '@/os';
|
||||||
|
|
||||||
const widget = define({
|
const name = 'slideshow';
|
||||||
name: 'slideshow',
|
|
||||||
props: () => ({
|
const widgetPropsDef = {
|
||||||
height: {
|
height: {
|
||||||
type: 'number',
|
type: 'number' as const,
|
||||||
default: 300,
|
default: 300,
|
||||||
},
|
},
|
||||||
folderId: {
|
folderId: {
|
||||||
type: 'string',
|
type: 'string' as const,
|
||||||
default: null,
|
default: null,
|
||||||
hidden: true,
|
hidden: true,
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
|
|
||||||
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
|
||||||
|
//const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
||||||
|
//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
||||||
|
const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
|
||||||
|
const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
|
||||||
|
|
||||||
|
const { widgetProps, configure, save } = useWidgetPropsManager(name,
|
||||||
|
widgetPropsDef,
|
||||||
|
props,
|
||||||
|
emit,
|
||||||
|
);
|
||||||
|
|
||||||
|
const images = ref([]);
|
||||||
|
const fetching = ref(true);
|
||||||
|
const slideA = ref<HTMLElement>();
|
||||||
|
const slideB = ref<HTMLElement>();
|
||||||
|
|
||||||
|
const change = () => {
|
||||||
|
if (images.value.length == 0) return;
|
||||||
|
|
||||||
|
const index = Math.floor(Math.random() * images.value.length);
|
||||||
|
const img = `url(${ images.value[index].url })`;
|
||||||
|
|
||||||
|
slideB.value.style.backgroundImage = img;
|
||||||
|
|
||||||
|
slideB.value.classList.add('anime');
|
||||||
|
setTimeout(() => {
|
||||||
|
// 既にこのウィジェットがunmountされていたら要素がない
|
||||||
|
if (slideA.value == null) return;
|
||||||
|
|
||||||
|
slideA.value.style.backgroundImage = img;
|
||||||
|
|
||||||
|
slideB.value.classList.remove('anime');
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetch = () => {
|
||||||
|
fetching.value = true;
|
||||||
|
|
||||||
|
os.api('drive/files', {
|
||||||
|
folderId: widgetProps.folderId,
|
||||||
|
type: 'image/*',
|
||||||
|
limit: 100
|
||||||
|
}).then(res => {
|
||||||
|
images.value = res;
|
||||||
|
fetching.value = false;
|
||||||
|
slideA.value.style.backgroundImage = '';
|
||||||
|
slideB.value.style.backgroundImage = '';
|
||||||
|
change();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const choose = () => {
|
||||||
|
os.selectDriveFolder(false).then(folder => {
|
||||||
|
if (folder == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
widgetProps.folderId = folder.id;
|
||||||
|
save();
|
||||||
|
fetch();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (widgetProps.folderId != null) {
|
||||||
|
fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
const intervalId = setInterval(change, 10000);
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export default defineComponent({
|
defineExpose<WidgetComponentExpose>({
|
||||||
extends: widget,
|
name,
|
||||||
data() {
|
configure,
|
||||||
return {
|
id: props.widget ? props.widget.id : null,
|
||||||
images: [],
|
|
||||||
fetching: true,
|
|
||||||
clock: null
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.applySize();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.props.folderId != null) {
|
|
||||||
this.fetch();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.clock = setInterval(this.change, 10000);
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
clearInterval(this.clock);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
applySize() {
|
|
||||||
let h;
|
|
||||||
|
|
||||||
if (this.props.size == 1) {
|
|
||||||
h = 250;
|
|
||||||
} else {
|
|
||||||
h = 170;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$el.style.height = `${h}px`;
|
|
||||||
},
|
|
||||||
resize() {
|
|
||||||
if (this.props.size == 1) {
|
|
||||||
this.props.size = 0;
|
|
||||||
} else {
|
|
||||||
this.props.size++;
|
|
||||||
}
|
|
||||||
this.save();
|
|
||||||
|
|
||||||
this.applySize();
|
|
||||||
},
|
|
||||||
change() {
|
|
||||||
if (this.images.length == 0) return;
|
|
||||||
|
|
||||||
const index = Math.floor(Math.random() * this.images.length);
|
|
||||||
const img = `url(${ this.images[index].url })`;
|
|
||||||
|
|
||||||
(this.$refs.slideB as any).style.backgroundImage = img;
|
|
||||||
|
|
||||||
this.$refs.slideB.classList.add('anime');
|
|
||||||
setTimeout(() => {
|
|
||||||
// 既にこのウィジェットがunmountされていたら要素がない
|
|
||||||
if ((this.$refs.slideA as any) == null) return;
|
|
||||||
|
|
||||||
(this.$refs.slideA as any).style.backgroundImage = img;
|
|
||||||
|
|
||||||
this.$refs.slideB.classList.remove('anime');
|
|
||||||
}, 1000);
|
|
||||||
},
|
|
||||||
fetch() {
|
|
||||||
this.fetching = true;
|
|
||||||
|
|
||||||
os.api('drive/files', {
|
|
||||||
folderId: this.props.folderId,
|
|
||||||
type: 'image/*',
|
|
||||||
limit: 100
|
|
||||||
}).then(images => {
|
|
||||||
this.images = images;
|
|
||||||
this.fetching = false;
|
|
||||||
(this.$refs.slideA as any).style.backgroundImage = '';
|
|
||||||
(this.$refs.slideB as any).style.backgroundImage = '';
|
|
||||||
this.change();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
choose() {
|
|
||||||
os.selectDriveFolder(false).then(folder => {
|
|
||||||
if (folder == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.props.folderId = folder.id;
|
|
||||||
this.save();
|
|
||||||
this.fetch();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,116 +1,129 @@
|
||||||
<template>
|
<template>
|
||||||
<MkContainer :show-header="props.showHeader" :style="`height: ${props.height}px;`" :scrollable="true">
|
<MkContainer :show-header="widgetProps.showHeader" :style="`height: ${widgetProps.height}px;`" :scrollable="true">
|
||||||
<template #header>
|
<template #header>
|
||||||
<button class="_button" @click="choose">
|
<button class="_button" @click="choose">
|
||||||
<i v-if="props.src === 'home'" class="fas fa-home"></i>
|
<i v-if="widgetProps.src === 'home'" class="fas fa-home"></i>
|
||||||
<i v-else-if="props.src === 'local'" class="fas fa-comments"></i>
|
<i v-else-if="widgetProps.src === 'local'" class="fas fa-comments"></i>
|
||||||
<i v-else-if="props.src === 'social'" class="fas fa-share-alt"></i>
|
<i v-else-if="widgetProps.src === 'social'" class="fas fa-share-alt"></i>
|
||||||
<i v-else-if="props.src === 'global'" class="fas fa-globe"></i>
|
<i v-else-if="widgetProps.src === 'global'" class="fas fa-globe"></i>
|
||||||
<i v-else-if="props.src === 'list'" class="fas fa-list-ul"></i>
|
<i v-else-if="widgetProps.src === 'list'" class="fas fa-list-ul"></i>
|
||||||
<i v-else-if="props.src === 'antenna'" class="fas fa-satellite"></i>
|
<i v-else-if="widgetProps.src === 'antenna'" class="fas fa-satellite"></i>
|
||||||
<span style="margin-left: 8px;">{{ props.src === 'list' ? props.list.name : props.src === 'antenna' ? props.antenna.name : $t('_timelines.' + props.src) }}</span>
|
<span style="margin-left: 8px;">{{ widgetProps.src === 'list' ? widgetProps.list.name : widgetProps.src === 'antenna' ? widgetProps.antenna.name : $t('_timelines.' + widgetProps.src) }}</span>
|
||||||
<i :class="menuOpened ? 'fas fa-angle-up' : 'fas fa-angle-down'" style="margin-left: 8px;"></i>
|
<i :class="menuOpened ? 'fas fa-angle-up' : 'fas fa-angle-down'" style="margin-left: 8px;"></i>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<XTimeline :key="props.src === 'list' ? `list:${props.list.id}` : props.src === 'antenna' ? `antenna:${props.antenna.id}` : props.src" :src="props.src" :list="props.list ? props.list.id : null" :antenna="props.antenna ? props.antenna.id : null"/>
|
<XTimeline :key="widgetProps.src === 'list' ? `list:${widgetProps.list.id}` : widgetProps.src === 'antenna' ? `antenna:${widgetProps.antenna.id}` : widgetProps.src" :src="widgetProps.src" :list="widgetProps.list ? widgetProps.list.id : null" :antenna="widgetProps.antenna ? widgetProps.antenna.id : null"/>
|
||||||
</div>
|
</div>
|
||||||
</MkContainer>
|
</MkContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue';
|
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
||||||
|
import { GetFormResultType } from '@/scripts/form';
|
||||||
|
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
|
||||||
|
import * as os from '@/os';
|
||||||
import MkContainer from '@/components/ui/container.vue';
|
import MkContainer from '@/components/ui/container.vue';
|
||||||
import XTimeline from '@/components/timeline.vue';
|
import XTimeline from '@/components/timeline.vue';
|
||||||
import define from './define';
|
import { $i } from '@/account';
|
||||||
import * as os from '@/os';
|
import { i18n } from '@/i18n';
|
||||||
|
|
||||||
const widget = define({
|
const name = 'timeline';
|
||||||
name: 'timeline',
|
|
||||||
props: () => ({
|
|
||||||
showHeader: {
|
|
||||||
type: 'boolean',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
height: {
|
|
||||||
type: 'number',
|
|
||||||
default: 300,
|
|
||||||
},
|
|
||||||
src: {
|
|
||||||
type: 'string',
|
|
||||||
default: 'home',
|
|
||||||
hidden: true,
|
|
||||||
},
|
|
||||||
list: {
|
|
||||||
type: 'object',
|
|
||||||
default: null,
|
|
||||||
hidden: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
export default defineComponent({
|
const widgetPropsDef = {
|
||||||
components: {
|
showHeader: {
|
||||||
MkContainer,
|
type: 'boolean' as const,
|
||||||
XTimeline,
|
default: true,
|
||||||
},
|
},
|
||||||
extends: widget,
|
height: {
|
||||||
|
type: 'number' as const,
|
||||||
data() {
|
default: 300,
|
||||||
return {
|
|
||||||
menuOpened: false,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
src: {
|
||||||
|
type: 'string' as const,
|
||||||
|
default: 'home',
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
antenna: {
|
||||||
|
type: 'object' as const,
|
||||||
|
default: null,
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
type: 'object' as const,
|
||||||
|
default: null,
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
methods: {
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
async choose(ev) {
|
|
||||||
this.menuOpened = true;
|
|
||||||
const [antennas, lists] = await Promise.all([
|
|
||||||
os.api('antennas/list'),
|
|
||||||
os.api('users/lists/list')
|
|
||||||
]);
|
|
||||||
const antennaItems = antennas.map(antenna => ({
|
|
||||||
text: antenna.name,
|
|
||||||
icon: 'fas fa-satellite',
|
|
||||||
action: () => {
|
|
||||||
this.props.antenna = antenna;
|
|
||||||
this.setSrc('antenna');
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
const listItems = lists.map(list => ({
|
|
||||||
text: list.name,
|
|
||||||
icon: 'fas fa-list-ul',
|
|
||||||
action: () => {
|
|
||||||
this.props.list = list;
|
|
||||||
this.setSrc('list');
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
os.popupMenu([{
|
|
||||||
text: this.$ts._timelines.home,
|
|
||||||
icon: 'fas fa-home',
|
|
||||||
action: () => { this.setSrc('home') }
|
|
||||||
}, {
|
|
||||||
text: this.$ts._timelines.local,
|
|
||||||
icon: 'fas fa-comments',
|
|
||||||
action: () => { this.setSrc('local') }
|
|
||||||
}, {
|
|
||||||
text: this.$ts._timelines.social,
|
|
||||||
icon: 'fas fa-share-alt',
|
|
||||||
action: () => { this.setSrc('social') }
|
|
||||||
}, {
|
|
||||||
text: this.$ts._timelines.global,
|
|
||||||
icon: 'fas fa-globe',
|
|
||||||
action: () => { this.setSrc('global') }
|
|
||||||
}, antennaItems.length > 0 ? null : undefined, ...antennaItems, listItems.length > 0 ? null : undefined, ...listItems], ev.currentTarget || ev.target).then(() => {
|
|
||||||
this.menuOpened = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
setSrc(src) {
|
// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
|
||||||
this.props.src = src;
|
//const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
||||||
this.save();
|
//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
||||||
},
|
const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
|
||||||
}
|
const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
|
||||||
|
|
||||||
|
const { widgetProps, configure, save } = useWidgetPropsManager(name,
|
||||||
|
widgetPropsDef,
|
||||||
|
props,
|
||||||
|
emit,
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuOpened = ref(false);
|
||||||
|
|
||||||
|
const setSrc = (src) => {
|
||||||
|
widgetProps.src = src;
|
||||||
|
save();
|
||||||
|
};
|
||||||
|
|
||||||
|
const choose = async (ev) => {
|
||||||
|
menuOpened.value = true;
|
||||||
|
const [antennas, lists] = await Promise.all([
|
||||||
|
os.api('antennas/list'),
|
||||||
|
os.api('users/lists/list')
|
||||||
|
]);
|
||||||
|
const antennaItems = antennas.map(antenna => ({
|
||||||
|
text: antenna.name,
|
||||||
|
icon: 'fas fa-satellite',
|
||||||
|
action: () => {
|
||||||
|
widgetProps.antenna = antenna;
|
||||||
|
setSrc('antenna');
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
const listItems = lists.map(list => ({
|
||||||
|
text: list.name,
|
||||||
|
icon: 'fas fa-list-ul',
|
||||||
|
action: () => {
|
||||||
|
widgetProps.list = list;
|
||||||
|
setSrc('list');
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
os.popupMenu([{
|
||||||
|
text: i18n.locale._timelines.home,
|
||||||
|
icon: 'fas fa-home',
|
||||||
|
action: () => { setSrc('home') }
|
||||||
|
}, {
|
||||||
|
text: i18n.locale._timelines.local,
|
||||||
|
icon: 'fas fa-comments',
|
||||||
|
action: () => { setSrc('local') }
|
||||||
|
}, {
|
||||||
|
text: i18n.locale._timelines.social,
|
||||||
|
icon: 'fas fa-share-alt',
|
||||||
|
action: () => { setSrc('social') }
|
||||||
|
}, {
|
||||||
|
text: i18n.locale._timelines.global,
|
||||||
|
icon: 'fas fa-globe',
|
||||||
|
action: () => { setSrc('global') }
|
||||||
|
}, antennaItems.length > 0 ? null : undefined, ...antennaItems, listItems.length > 0 ? null : undefined, ...listItems], ev.currentTarget || ev.target).then(() => {
|
||||||
|
menuOpened.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose<WidgetComponentExpose>({
|
||||||
|
name,
|
||||||
|
configure,
|
||||||
|
id: props.widget ? props.widget.id : null,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<MkContainer :show-header="props.showHeader">
|
<MkContainer :show-header="widgetProps.showHeader">
|
||||||
<template #header><i class="fas fa-hashtag"></i>{{ $ts._widgets.trends }}</template>
|
<template #header><i class="fas fa-hashtag"></i>{{ $ts._widgets.trends }}</template>
|
||||||
|
|
||||||
<div class="wbrkwala">
|
<div class="wbrkwala">
|
||||||
|
@ -17,49 +17,59 @@
|
||||||
</MkContainer>
|
</MkContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue';
|
import { onMounted, onUnmounted, ref } from 'vue';
|
||||||
|
import { GetFormResultType } from '@/scripts/form';
|
||||||
|
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
|
||||||
import MkContainer from '@/components/ui/container.vue';
|
import MkContainer from '@/components/ui/container.vue';
|
||||||
import define from './define';
|
|
||||||
import MkMiniChart from '@/components/mini-chart.vue';
|
import MkMiniChart from '@/components/mini-chart.vue';
|
||||||
import * as os from '@/os';
|
import * as os from '@/os';
|
||||||
|
|
||||||
const widget = define({
|
const name = 'hashtags';
|
||||||
name: 'hashtags',
|
|
||||||
props: () => ({
|
const widgetPropsDef = {
|
||||||
showHeader: {
|
showHeader: {
|
||||||
type: 'boolean',
|
type: 'boolean' as const,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
})
|
};
|
||||||
|
|
||||||
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
||||||
|
|
||||||
|
// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
|
||||||
|
//const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
||||||
|
//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
||||||
|
const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
|
||||||
|
const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
|
||||||
|
|
||||||
|
const { widgetProps, configure } = useWidgetPropsManager(name,
|
||||||
|
widgetPropsDef,
|
||||||
|
props,
|
||||||
|
emit,
|
||||||
|
);
|
||||||
|
|
||||||
|
const stats = ref([]);
|
||||||
|
const fetching = ref(true);
|
||||||
|
|
||||||
|
const fetch = () => {
|
||||||
|
os.api('hashtags/trend').then(stats => {
|
||||||
|
stats.value = stats;
|
||||||
|
fetching.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetch();
|
||||||
|
const intervalId = setInterval(fetch, 1000 * 60);
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export default defineComponent({
|
defineExpose<WidgetComponentExpose>({
|
||||||
components: {
|
name,
|
||||||
MkContainer, MkMiniChart
|
configure,
|
||||||
},
|
id: props.widget ? props.widget.id : null,
|
||||||
extends: widget,
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
stats: [],
|
|
||||||
fetching: true,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.fetch();
|
|
||||||
this.clock = setInterval(this.fetch, 1000 * 60);
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
clearInterval(this.clock);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetch() {
|
|
||||||
os.api('hashtags/trend').then(stats => {
|
|
||||||
this.stats = stats;
|
|
||||||
this.fetching = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
71
packages/client/src/widgets/widget.ts
Normal file
71
packages/client/src/widgets/widget.ts
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import { reactive, watch } from 'vue';
|
||||||
|
import { throttle } from 'throttle-debounce';
|
||||||
|
import { Form, GetFormResultType } from '@/scripts/form';
|
||||||
|
import * as os from '@/os';
|
||||||
|
|
||||||
|
export type Widget<P extends Record<string, unknown>> = {
|
||||||
|
id: string;
|
||||||
|
data: Partial<P>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WidgetComponentProps<P extends Record<string, unknown>> = {
|
||||||
|
widget?: Widget<P>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WidgetComponentEmits<P extends Record<string, unknown>> = {
|
||||||
|
(e: 'updateProps', props: P);
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WidgetComponentExpose = {
|
||||||
|
name: string;
|
||||||
|
id: string | null;
|
||||||
|
configure: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useWidgetPropsManager = <F extends Form & Record<string, { default: any; }>>(
|
||||||
|
name: string,
|
||||||
|
propsDef: F,
|
||||||
|
props: Readonly<WidgetComponentProps<GetFormResultType<F>>>,
|
||||||
|
emit: WidgetComponentEmits<GetFormResultType<F>>,
|
||||||
|
): {
|
||||||
|
widgetProps: GetFormResultType<F>;
|
||||||
|
save: () => void;
|
||||||
|
configure: () => void;
|
||||||
|
} => {
|
||||||
|
const widgetProps = reactive(props.widget ? JSON.parse(JSON.stringify(props.widget.data)) : {});
|
||||||
|
|
||||||
|
const mergeProps = () => {
|
||||||
|
for (const prop of Object.keys(propsDef)) {
|
||||||
|
if (widgetProps.hasOwnProperty(prop)) continue;
|
||||||
|
widgetProps[prop] = propsDef[prop].default;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
watch(widgetProps, () => {
|
||||||
|
mergeProps();
|
||||||
|
}, { deep: true, immediate: true, });
|
||||||
|
|
||||||
|
const save = throttle(3000, () => {
|
||||||
|
emit('updateProps', widgetProps)
|
||||||
|
});
|
||||||
|
|
||||||
|
const configure = async () => {
|
||||||
|
const form = JSON.parse(JSON.stringify(propsDef));
|
||||||
|
for (const item of Object.keys(form)) {
|
||||||
|
form[item].default = widgetProps[item];
|
||||||
|
}
|
||||||
|
const { canceled, result } = await os.form(name, form);
|
||||||
|
if (canceled) return;
|
||||||
|
|
||||||
|
for (const key of Object.keys(result)) {
|
||||||
|
widgetProps[key] = result[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
save();
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
widgetProps,
|
||||||
|
save,
|
||||||
|
configure,
|
||||||
|
};
|
||||||
|
};
|
Loading…
Reference in a new issue