mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
添加基础的图表元素
This commit is contained in:
parent
ae4d82e701
commit
f79712eef3
108
src/components/Chart.vue
Normal file
108
src/components/Chart.vue
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
<template>
|
||||||
|
<div class="chart">
|
||||||
|
<div
|
||||||
|
class="chart-content"
|
||||||
|
ref="chartRef"
|
||||||
|
:style="{
|
||||||
|
width: width + 'px',
|
||||||
|
height: height + 'px',
|
||||||
|
transform: `scale(${1 / slideScale})`,
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, inject, onMounted, PropType, ref, Ref, watch } from 'vue'
|
||||||
|
import upperFirst from 'lodash/upperFirst'
|
||||||
|
import Chartist, {
|
||||||
|
IChartistLineChart,
|
||||||
|
IChartistBarChart,
|
||||||
|
IChartistPieChart,
|
||||||
|
ILineChartOptions,
|
||||||
|
IBarChartOptions,
|
||||||
|
IPieChartOptions,
|
||||||
|
} from 'chartist'
|
||||||
|
import { ChartData, ChartType } from '@/types/slides'
|
||||||
|
|
||||||
|
import 'chartist/dist/scss/chartist.scss'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'chart',
|
||||||
|
props: {
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String as PropType<ChartType>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: Object as PropType<ChartData>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Object as PropType<ILineChartOptions & IBarChartOptions & IPieChartOptions>,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const chartRef = ref<HTMLElement | null>(null)
|
||||||
|
const slideScale: Ref<number> = inject('slideScale') || ref(1)
|
||||||
|
|
||||||
|
let chart: IChartistLineChart | IChartistBarChart | IChartistPieChart | undefined
|
||||||
|
|
||||||
|
const getDataAndOptions = () => {
|
||||||
|
const propsOptopns = props.options || {}
|
||||||
|
const options = {
|
||||||
|
...propsOptopns,
|
||||||
|
width: props.width * slideScale.value,
|
||||||
|
height: props.height * slideScale.value,
|
||||||
|
}
|
||||||
|
const data = props.type === 'pie' ? { ...props.data, series: props.data.series[0] } : props.data
|
||||||
|
return { data, options }
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderChart = () => {
|
||||||
|
if(!chartRef.value) return
|
||||||
|
|
||||||
|
const type = upperFirst(props.type)
|
||||||
|
const { data, options } = getDataAndOptions()
|
||||||
|
chart = new Chartist[type](chartRef.value, data, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateChart = () => {
|
||||||
|
if(!chart) {
|
||||||
|
renderChart()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const { data, options } = getDataAndOptions()
|
||||||
|
chart.update(data, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch([
|
||||||
|
() => props.width,
|
||||||
|
() => props.height,
|
||||||
|
() => props.data,
|
||||||
|
slideScale,
|
||||||
|
], updateChart)
|
||||||
|
|
||||||
|
onMounted(renderChart)
|
||||||
|
|
||||||
|
return {
|
||||||
|
slideScale,
|
||||||
|
chartRef,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.chart-content {
|
||||||
|
transform-origin: 0 0;
|
||||||
|
}
|
||||||
|
</style>
|
@ -49,6 +49,20 @@ export const DEFAULT_TABLE = {
|
|||||||
outline: {
|
outline: {
|
||||||
width: 2,
|
width: 2,
|
||||||
style: 'solid',
|
style: 'solid',
|
||||||
color: DEFAULT_COLOR
|
color: DEFAULT_COLOR,
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DEFAULT_FORMULA = {
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MIN_SIZE = {
|
||||||
|
text: 20,
|
||||||
|
image: 20,
|
||||||
|
shape: 15,
|
||||||
|
chart: 200,
|
||||||
|
table: 20,
|
||||||
|
formula: 20,
|
||||||
}
|
}
|
@ -1,6 +1,44 @@
|
|||||||
import { Slide } from '@/types/slides'
|
import { Slide } from '@/types/slides'
|
||||||
|
|
||||||
export const slides: Slide[] = [
|
export const slides: Slide[] = [
|
||||||
|
{
|
||||||
|
id: 'xsxa123',
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
id: 'sdasaxsxs',
|
||||||
|
type: 'chart',
|
||||||
|
left: 100,
|
||||||
|
top: 100,
|
||||||
|
width: 400,
|
||||||
|
height: 400,
|
||||||
|
chartType: 'pie',
|
||||||
|
data: {
|
||||||
|
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
|
||||||
|
series: [
|
||||||
|
[5, 2, 4, 2, 10],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
donut: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sdasaxs',
|
||||||
|
type: 'chart',
|
||||||
|
left: 600,
|
||||||
|
top: 100,
|
||||||
|
width: 300,
|
||||||
|
height: 300,
|
||||||
|
chartType: 'line',
|
||||||
|
data: {
|
||||||
|
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
|
||||||
|
series: [
|
||||||
|
[5, 2, 4, 2, 10],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'xxx1',
|
id: 'xxx1',
|
||||||
background: {
|
background: {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { IBarChartOptions, ILineChartOptions, IPieChartOptions } from 'chartist'
|
||||||
|
|
||||||
export interface PPTElementShadow {
|
export interface PPTElementShadow {
|
||||||
h: number;
|
h: number;
|
||||||
v: number;
|
v: number;
|
||||||
@ -105,7 +107,7 @@ export interface PPTLineElement {
|
|||||||
shadow?: PPTElementShadow;
|
shadow?: PPTElementShadow;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ChartType = 'bar' | 'horizontalBar' | 'line' | 'pie' | 'doughnut' | 'polarArea' | 'radar'
|
export type ChartType = 'bar' | 'line' | 'pie'
|
||||||
export interface ChartData {
|
export interface ChartData {
|
||||||
labels: string[];
|
labels: string[];
|
||||||
series: number[][];
|
series: number[][];
|
||||||
@ -121,6 +123,7 @@ export interface PPTChartElement {
|
|||||||
height: number;
|
height: number;
|
||||||
chartType: ChartType;
|
chartType: ChartType;
|
||||||
data: ChartData;
|
data: ChartData;
|
||||||
|
options?: ILineChartOptions & IBarChartOptions & IPieChartOptions;
|
||||||
outline?: PPTElementOutline;
|
outline?: PPTElementOutline;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,8 +148,20 @@ export interface PPTTableElement {
|
|||||||
colSizes: number[];
|
colSizes: number[];
|
||||||
data: TableElementCell[][];
|
data: TableElementCell[][];
|
||||||
}
|
}
|
||||||
|
export interface PPTFormulaElement {
|
||||||
|
type: 'formula';
|
||||||
|
id: string;
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
lock?: boolean;
|
||||||
|
groupId?: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
latex: string;
|
||||||
|
color?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type PPTElement = PPTTextElement | PPTImageElement | PPTShapeElement | PPTLineElement | PPTChartElement | PPTTableElement
|
export type PPTElement = PPTTextElement | PPTImageElement | PPTShapeElement | PPTLineElement | PPTChartElement | PPTTableElement | PPTFormulaElement
|
||||||
|
|
||||||
export interface PPTAnimation {
|
export interface PPTAnimation {
|
||||||
elId: string;
|
elId: string;
|
||||||
|
@ -4,6 +4,7 @@ import { State, MutationTypes } from '@/store'
|
|||||||
import { ElementTypes, PPTElement, PPTImageElement, PPTLineElement, PPTShapeElement } from '@/types/slides'
|
import { ElementTypes, PPTElement, PPTImageElement, PPTLineElement, PPTShapeElement } from '@/types/slides'
|
||||||
import { OperateResizeHandlers, AlignmentLineProps, MultiSelectRange } from '@/types/edit'
|
import { OperateResizeHandlers, AlignmentLineProps, MultiSelectRange } from '@/types/edit'
|
||||||
import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
|
import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
|
||||||
|
import { MIN_SIZE } from '@/configs/element'
|
||||||
import { AlignLine, uniqAlignLines } from '@/utils/element'
|
import { AlignLine, uniqAlignLines } from '@/utils/element'
|
||||||
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||||
|
|
||||||
@ -111,7 +112,7 @@ export default (
|
|||||||
const startPageX = e.pageX
|
const startPageX = e.pageX
|
||||||
const startPageY = e.pageY
|
const startPageY = e.pageY
|
||||||
|
|
||||||
const minSize = 15
|
const minSize = MIN_SIZE[element.type] || 20
|
||||||
const getSizeWithinRange = (size: number) => size < minSize ? minSize : size
|
const getSizeWithinRange = (size: number) => size < minSize ? minSize : size
|
||||||
|
|
||||||
let points: ReturnType<typeof getRotateElementPoints>
|
let points: ReturnType<typeof getRotateElementPoints>
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, Ref, ref, watch, watchEffect } from 'vue'
|
import { computed, defineComponent, provide, Ref, ref, watch, watchEffect } from 'vue'
|
||||||
import { useStore } from 'vuex'
|
import { useStore } from 'vuex'
|
||||||
import throttle from 'lodash/throttle'
|
import throttle from 'lodash/throttle'
|
||||||
import { State, MutationTypes } from '@/store'
|
import { State, MutationTypes } from '@/store'
|
||||||
@ -215,6 +215,8 @@ export default defineComponent({
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
provide('slideScale', canvasScale)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
elementList,
|
elementList,
|
||||||
activeElementIdList,
|
activeElementIdList,
|
||||||
|
@ -66,7 +66,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div style="flex: 3;">大小:</div>
|
<div style="flex: 3;">大小:</div>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
:min="15"
|
:min="minSize"
|
||||||
:max="1500"
|
:max="1500"
|
||||||
:step="5"
|
:step="5"
|
||||||
:value="width"
|
:value="width"
|
||||||
@ -83,7 +83,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<div style="flex: 1;" v-else></div>
|
<div style="flex: 1;" v-else></div>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
:min="15"
|
:min="minSize"
|
||||||
:max="800"
|
:max="800"
|
||||||
:step="5"
|
:step="5"
|
||||||
:disabled="handleElement.type === 'text'"
|
:disabled="handleElement.type === 'text'"
|
||||||
@ -138,6 +138,7 @@ import { useStore } from 'vuex'
|
|||||||
import round from 'lodash/round'
|
import round from 'lodash/round'
|
||||||
import { MutationTypes, State } from '@/store'
|
import { MutationTypes, State } from '@/store'
|
||||||
import { PPTElement } from '@/types/slides'
|
import { PPTElement } from '@/types/slides'
|
||||||
|
import { MIN_SIZE } from '@/configs/element'
|
||||||
import useOrderElement from '@/hooks/useOrderElement'
|
import useOrderElement from '@/hooks/useOrderElement'
|
||||||
import useAlignElementToCanvas from '@/hooks/useAlignElementToCanvas'
|
import useAlignElementToCanvas from '@/hooks/useAlignElementToCanvas'
|
||||||
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||||
@ -155,6 +156,11 @@ export default defineComponent({
|
|||||||
const rotate = ref(0)
|
const rotate = ref(0)
|
||||||
const fixedRatio = ref(false)
|
const fixedRatio = ref(false)
|
||||||
|
|
||||||
|
const minSize = computed(() => {
|
||||||
|
if(!handleElement.value) return 20
|
||||||
|
return MIN_SIZE[handleElement.value.type] || 20
|
||||||
|
})
|
||||||
|
|
||||||
watch(handleElement, () => {
|
watch(handleElement, () => {
|
||||||
if(!handleElement.value) return
|
if(!handleElement.value) return
|
||||||
|
|
||||||
@ -228,6 +234,7 @@ export default defineComponent({
|
|||||||
height,
|
height,
|
||||||
rotate,
|
rotate,
|
||||||
fixedRatio,
|
fixedRatio,
|
||||||
|
minSize,
|
||||||
updateLeft,
|
updateLeft,
|
||||||
updateTop,
|
updateTop,
|
||||||
updateWidth,
|
updateWidth,
|
||||||
|
@ -23,7 +23,7 @@ import BaseImageElement from '@/views/components/element/ImageElement/BaseImageE
|
|||||||
import BaseTextElement from '@/views/components/element/TextElement/BaseTextElement.vue'
|
import BaseTextElement from '@/views/components/element/TextElement/BaseTextElement.vue'
|
||||||
import BaseShapeElement from '@/views/components/element/ShapeElement/BaseShapeElement.vue'
|
import BaseShapeElement from '@/views/components/element/ShapeElement/BaseShapeElement.vue'
|
||||||
import BaseLineElement from '@/views/components/element/LineElement/BaseLineElement.vue'
|
import BaseLineElement from '@/views/components/element/LineElement/BaseLineElement.vue'
|
||||||
import BaseChartElement from '@/views/components/element/ChartElement/BaseChartElement.vue'
|
import ScreenChartElement from '@/views/components/element/ChartElement/ScreenChartElement.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'screen-element',
|
name: 'screen-element',
|
||||||
@ -48,7 +48,7 @@ export default defineComponent({
|
|||||||
[ElementTypes.TEXT]: BaseTextElement,
|
[ElementTypes.TEXT]: BaseTextElement,
|
||||||
[ElementTypes.SHAPE]: BaseShapeElement,
|
[ElementTypes.SHAPE]: BaseShapeElement,
|
||||||
[ElementTypes.LINE]: BaseLineElement,
|
[ElementTypes.LINE]: BaseLineElement,
|
||||||
[ElementTypes.CHART]: BaseChartElement,
|
[ElementTypes.CHART]: ScreenChartElement,
|
||||||
}
|
}
|
||||||
return elementTypeMap[props.elementInfo.type] || null
|
return elementTypeMap[props.elementInfo.type] || null
|
||||||
})
|
})
|
||||||
|
@ -59,7 +59,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, onMounted, onUnmounted, Ref, ref } from 'vue'
|
import { computed, defineComponent, onMounted, onUnmounted, provide, Ref, ref } from 'vue'
|
||||||
import { useStore } from 'vuex'
|
import { useStore } from 'vuex'
|
||||||
import throttle from 'lodash/throttle'
|
import throttle from 'lodash/throttle'
|
||||||
import { MutationTypes, State } from '@/store'
|
import { MutationTypes, State } from '@/store'
|
||||||
@ -216,6 +216,8 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
provide('slideScale', scale)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
slides,
|
slides,
|
||||||
|
@ -13,13 +13,15 @@
|
|||||||
:height="elementInfo.height"
|
:height="elementInfo.height"
|
||||||
:outline="elementInfo.outline"
|
:outline="elementInfo.outline"
|
||||||
/>
|
/>
|
||||||
Chart
|
<IconChartLine fill="#d70206" strokeWidth="2" :size="size" v-if="elementInfo.chartType === 'line'" />
|
||||||
|
<IconChartHistogram fill="#d70206" strokeWidth="2" :size="size" v-else-if="elementInfo.chartType === 'bar'" />
|
||||||
|
<IconChartProportion fill="#d70206" strokeWidth="2" :size="size" v-else-if="elementInfo.chartType === 'pie'" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from 'vue'
|
import { computed, defineComponent, PropType } from 'vue'
|
||||||
import { PPTChartElement } from '@/types/slides'
|
import { PPTChartElement } from '@/types/slides'
|
||||||
|
|
||||||
import ElementOutline from '@/views/components/element/ElementOutline.vue'
|
import ElementOutline from '@/views/components/element/ElementOutline.vue'
|
||||||
@ -35,6 +37,13 @@ export default defineComponent({
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
setup(props) {
|
||||||
|
const size = computed(() => Math.min(props.elementInfo.width, props.elementInfo.height))
|
||||||
|
|
||||||
|
return {
|
||||||
|
size,
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -46,5 +55,10 @@ export default defineComponent({
|
|||||||
.element-content {
|
.element-content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
opacity: .5;
|
||||||
|
background-color: rgba($color: #000, $alpha: .05);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
<template>
|
||||||
|
<div class="screen-element-chart"
|
||||||
|
:style="{
|
||||||
|
top: elementInfo.top + 'px',
|
||||||
|
left: elementInfo.left + 'px',
|
||||||
|
width: elementInfo.width + 'px',
|
||||||
|
height: elementInfo.height + 'px',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="element-content">
|
||||||
|
<ElementOutline
|
||||||
|
:width="elementInfo.width"
|
||||||
|
:height="elementInfo.height"
|
||||||
|
:outline="elementInfo.outline"
|
||||||
|
/>
|
||||||
|
<Chart
|
||||||
|
:width="elementInfo.width"
|
||||||
|
:height="elementInfo.height"
|
||||||
|
:type="elementInfo.chartType"
|
||||||
|
:data="elementInfo.data"
|
||||||
|
:options="elementInfo.options"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, PropType } from 'vue'
|
||||||
|
import { PPTChartElement } from '@/types/slides'
|
||||||
|
|
||||||
|
import ElementOutline from '@/views/components/element/ElementOutline.vue'
|
||||||
|
import Chart from '@/components/Chart.vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'screen-element-chart',
|
||||||
|
components: {
|
||||||
|
ElementOutline,
|
||||||
|
Chart,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
elementInfo: {
|
||||||
|
type: Object as PropType<PPTChartElement>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.screen-element-chart {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.element-content {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
@ -18,7 +18,13 @@
|
|||||||
:height="elementInfo.height"
|
:height="elementInfo.height"
|
||||||
:outline="elementInfo.outline"
|
:outline="elementInfo.outline"
|
||||||
/>
|
/>
|
||||||
Chart
|
<Chart
|
||||||
|
:width="elementInfo.width"
|
||||||
|
:height="elementInfo.height"
|
||||||
|
:type="elementInfo.chartType"
|
||||||
|
:data="elementInfo.data"
|
||||||
|
:options="elementInfo.options"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -29,11 +35,13 @@ import { PPTChartElement } from '@/types/slides'
|
|||||||
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
||||||
|
|
||||||
import ElementOutline from '@/views/components/element/ElementOutline.vue'
|
import ElementOutline from '@/views/components/element/ElementOutline.vue'
|
||||||
|
import Chart from '@/components/Chart.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'editable-element-chart',
|
name: 'editable-element-chart',
|
||||||
components: {
|
components: {
|
||||||
ElementOutline,
|
ElementOutline,
|
||||||
|
Chart,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
elementInfo: {
|
elementInfo: {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user