forked from ddrilling/asb_cloud_front
Отображение ROP добавлено
This commit is contained in:
parent
cae068e7b3
commit
46c8b1fe73
@ -8,8 +8,10 @@ import {
|
|||||||
PointElement,
|
PointElement,
|
||||||
LineElement,
|
LineElement,
|
||||||
ChartData,
|
ChartData,
|
||||||
ChartTypeRegistry,
|
ChartOptions,
|
||||||
ChartOptions} from 'chart.js'
|
ChartType,
|
||||||
|
ChartDataset
|
||||||
|
} from 'chart.js'
|
||||||
import 'chartjs-adapter-moment'
|
import 'chartjs-adapter-moment'
|
||||||
import ChartDataLabels from 'chartjs-plugin-datalabels'
|
import ChartDataLabels from 'chartjs-plugin-datalabels'
|
||||||
import zoomPlugin from 'chartjs-plugin-zoom'
|
import zoomPlugin from 'chartjs-plugin-zoom'
|
||||||
@ -25,16 +27,10 @@ Chart.register(
|
|||||||
zoomPlugin
|
zoomPlugin
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions: ChartOptions = {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
aspectRatio: 0.45,
|
aspectRatio: 0.45,
|
||||||
animation: false,
|
animation: false,
|
||||||
tooltips: {
|
|
||||||
enabled: true,
|
|
||||||
callbacks: {
|
|
||||||
label: (tooltipItem:any) => tooltipItem.yLabel
|
|
||||||
}
|
|
||||||
},
|
|
||||||
events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],
|
events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],
|
||||||
scales: {
|
scales: {
|
||||||
y: {
|
y: {
|
||||||
@ -97,99 +93,88 @@ const defaultOptions = {
|
|||||||
mode: 'x',
|
mode: 'x',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
tooltip: {
|
||||||
|
enabled: true,
|
||||||
|
callbacks: {
|
||||||
|
label: (tooltipItem: any) => tooltipItem.yLabel
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ChartTimeData = ChartData<keyof ChartTypeRegistry, {
|
export type ChartTimeDataPoint = {
|
||||||
x: number;
|
x: number
|
||||||
label: number;
|
label: number
|
||||||
y: Date;
|
y: Date
|
||||||
}[], unknown>
|
}
|
||||||
|
|
||||||
|
export type ChartTimeDataset = ChartDataset<ChartType, ChartTimeDataPoint[]>
|
||||||
|
export type ChartTimeData = ChartData<ChartType, ChartTimeDataPoint[], unknown>
|
||||||
|
|
||||||
export type ChartTimeDataParams = {
|
export type ChartTimeDataParams = {
|
||||||
data: ChartTimeData,
|
data: ChartTimeData
|
||||||
yStart?: Date,
|
yStart?: Date
|
||||||
yInterval?: number,
|
yInterval?: number
|
||||||
displayLabels?: Boolean,
|
displayLabels?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ChartTimeBaseProps = {
|
export type ChartTimeBaseProps = {
|
||||||
dataParams: ChartTimeDataParams,
|
dataParams: ChartTimeDataParams
|
||||||
// TODO: Create good type for options
|
options?: ChartOptions
|
||||||
options?: ChartOptions<keyof ChartTypeRegistry> | any,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TimeParams = {
|
export type TimeParams = {
|
||||||
unit: String
|
unit: string
|
||||||
stepSize: number
|
stepSize: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const linesPerInterval = 32
|
const linesPerInterval = 32
|
||||||
|
|
||||||
export const timeUnitByInterval = (intervalSec: number): String => {
|
const intervals = {
|
||||||
if(intervalSec <= 60)
|
millisecond: 0.001,
|
||||||
|
second: 1,
|
||||||
|
minute: 60,
|
||||||
|
hour: 60 * 60,
|
||||||
|
day: 60 * 60 * 24,
|
||||||
|
week: 60 * 60 * 24 * 7,
|
||||||
|
month: 60 * 60 * 24 * 30,
|
||||||
|
quarter: 60 * 60 * 24 * 91,
|
||||||
|
year: 60 * 60 * 24 * 365.25
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntervalType = keyof typeof intervals
|
||||||
|
|
||||||
|
export const timeUnitByInterval = (intervalSec: number): IntervalType => {
|
||||||
|
if(intervalSec <= intervals.minute)
|
||||||
return 'millisecond'
|
return 'millisecond'
|
||||||
|
|
||||||
if(intervalSec <= 32*60)
|
if(intervalSec <= 32 * intervals.minute)
|
||||||
return 'second'
|
return 'second'
|
||||||
|
|
||||||
if(intervalSec <= 32*60*60)
|
if(intervalSec <= 32 * intervals.hour)
|
||||||
return 'minute'
|
return 'minute'
|
||||||
|
|
||||||
if(intervalSec <= 32*12*60*60)
|
if(intervalSec <= 32 * intervals.day / 2)
|
||||||
return 'hour'
|
return 'hour'
|
||||||
|
|
||||||
if(intervalSec <= 32*24*60*60)
|
if(intervalSec <= 32 * intervals.day)
|
||||||
return 'day'
|
return 'day'
|
||||||
|
|
||||||
if(intervalSec <= 32*7*24*60*60)
|
if(intervalSec <= 32 * intervals.week)
|
||||||
return 'week'
|
return 'week'
|
||||||
|
|
||||||
if(intervalSec <= 32*30.4375*24*60*60)
|
if(intervalSec <= 32 * intervals.year / 12)
|
||||||
return 'month'
|
return 'month'
|
||||||
|
|
||||||
if(intervalSec <= 32*121.75*24*60*60)
|
if(intervalSec <= 32 * intervals.year / 3)
|
||||||
return 'quarter'
|
return 'quarter'
|
||||||
else
|
|
||||||
return 'year'
|
return 'year'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const timeParamsByInterval = (intervalSec: number): TimeParams => {
|
export const timeParamsByInterval = (intervalSec: number): TimeParams => {
|
||||||
let stepSize = intervalSec
|
const unit = timeUnitByInterval(intervalSec)
|
||||||
let unit = timeUnitByInterval(intervalSec)
|
const stepSize = Math.max(1, Math.round(intervalSec / intervals[unit] / linesPerInterval))
|
||||||
|
|
||||||
switch(unit){
|
|
||||||
case 'millisecond':
|
|
||||||
stepSize *= 1000
|
|
||||||
break;
|
|
||||||
case 'second':
|
|
||||||
//stepSize *= 1
|
|
||||||
break;
|
|
||||||
case 'minute':
|
|
||||||
stepSize /= 60
|
|
||||||
break;
|
|
||||||
case 'hour':
|
|
||||||
stepSize /= 60*60
|
|
||||||
break;
|
|
||||||
case 'day':
|
|
||||||
stepSize /= 24*60*60
|
|
||||||
break;
|
|
||||||
case 'week':
|
|
||||||
stepSize /= 7*24*60*60
|
|
||||||
break;
|
|
||||||
case 'month':
|
|
||||||
stepSize /= 30*24*60*60
|
|
||||||
break;
|
|
||||||
case 'quarter':
|
|
||||||
stepSize /= 91*24*60*60
|
|
||||||
break;
|
|
||||||
case 'year':
|
|
||||||
stepSize /= 365.25*24*60*60
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
stepSize = Math.round(stepSize/linesPerInterval)
|
|
||||||
stepSize = stepSize > 0 ? stepSize : 1
|
|
||||||
return { unit, stepSize }
|
return { unit, stepSize }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,21 +183,22 @@ export const ChartTimeBase: React.FC<ChartTimeBaseProps> = ({options, dataParams
|
|||||||
const [chart, setChart] = useState<any>()
|
const [chart, setChart] = useState<any>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let thisOptions = {}
|
const chartOptions: ChartOptions = {}
|
||||||
Object.assign(thisOptions, defaultOptions, options)
|
Object.assign(chartOptions, defaultOptions, options)
|
||||||
|
|
||||||
let newChart = new Chart(chartRef.current ?? "", {
|
const newChart = new Chart(chartRef.current ?? '', {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
plugins: [ChartDataLabels],
|
plugins: [ChartDataLabels],
|
||||||
options: thisOptions,
|
options: chartOptions,
|
||||||
data: { datasets: [] }
|
data: { datasets: [] }
|
||||||
})
|
})
|
||||||
|
|
||||||
setChart(newChart)
|
setChart(newChart)
|
||||||
return () => newChart?.destroy()
|
return () => newChart?.destroy()
|
||||||
}, [options])
|
}, [options])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!chart) return;
|
if (!chart) return
|
||||||
chart.data = dataParams.data
|
chart.data = dataParams.data
|
||||||
if(dataParams.yStart){
|
if(dataParams.yStart){
|
||||||
const interval = Number(dataParams.yInterval ?? 600_000)
|
const interval = Number(dataParams.yInterval ?? 600_000)
|
||||||
@ -227,7 +213,7 @@ export const ChartTimeBase: React.FC<ChartTimeBaseProps> = ({options, dataParams
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
chart.update()
|
chart.update(0)
|
||||||
}, [chart, dataParams])
|
}, [chart, dataParams])
|
||||||
|
|
||||||
return(<canvas ref={chartRef} />)
|
return(<canvas ref={chartRef} />)
|
||||||
|
@ -1,7 +1,44 @@
|
|||||||
|
import { ChartOptions, Scriptable, ScriptableContext } from 'chart.js'
|
||||||
import React, { useState, useEffect } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { ChartTimeBase } from './ChartTimeBase'
|
import { makeDateSorter } from '../Table'
|
||||||
|
import {
|
||||||
|
ChartTimeBase,
|
||||||
|
ChartTimeData,
|
||||||
|
ChartTimeDataset,
|
||||||
|
ChartTimeDataPoint,
|
||||||
|
ChartTimeDataParams
|
||||||
|
} from './ChartTimeBase'
|
||||||
|
|
||||||
const chartPluginsOptions = {
|
export type ColumnLineConfig = {
|
||||||
|
label?: string
|
||||||
|
units?: string
|
||||||
|
xAccessorName: string
|
||||||
|
yAccessorName: string
|
||||||
|
color?: string
|
||||||
|
showLine?: boolean
|
||||||
|
isShape?: boolean
|
||||||
|
xConstValue?: number | string
|
||||||
|
dash?: Array<number>
|
||||||
|
borderColor?: string
|
||||||
|
backgroundColor?: string
|
||||||
|
borderWidth?: Scriptable<number, ScriptableContext<'radar'>>
|
||||||
|
fill?: string
|
||||||
|
}
|
||||||
|
export type ColumnPostParsing = (data: ChartTimeDataParams) => void
|
||||||
|
export type ColumnData = { [accessors: string]: any }
|
||||||
|
export type ColumnAdditionalData = (point: ColumnData, cfg: ColumnLineConfig) => object
|
||||||
|
export type ColumnProps = {
|
||||||
|
postParsing?: ColumnPostParsing
|
||||||
|
additionalPointData?: ColumnAdditionalData
|
||||||
|
interval?: number
|
||||||
|
yDisplay?: boolean
|
||||||
|
yStart?: Date
|
||||||
|
lineGroup: ColumnLineConfig[]
|
||||||
|
data: ColumnData[]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const chartPluginsOptions: ChartOptions = {
|
||||||
plugins: {
|
plugins: {
|
||||||
datalabels: {
|
datalabels: {
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
@ -15,16 +52,16 @@ const chartPluginsOptions = {
|
|||||||
clip: true
|
clip: true
|
||||||
},
|
},
|
||||||
legend: { display: false },
|
legend: { display: false },
|
||||||
tooltip: { enable: true }
|
tooltip: { enabled: true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetRandomColor = () => '#' + Math.floor(Math.random() * (16**6 - 1)).toString(16)
|
const GetRandomColor = () => '#' + Math.floor(Math.random() * (16**6 - 1)).toString(16)
|
||||||
|
|
||||||
export const GetOrCreateDatasetByLineConfig = (data, lineConfig) => {
|
export const GetOrCreateDatasetByLineConfig = (data: ChartTimeData, lineConfig: ColumnLineConfig): ChartTimeDataset => {
|
||||||
let dataset = data?.datasets.find(d => d.label === lineConfig.label)
|
let dataset = data?.datasets.find(d => d.label === lineConfig.label)
|
||||||
if (!dataset) {
|
if (!dataset) {
|
||||||
let color = lineConfig.borderColor
|
const color = lineConfig.borderColor
|
||||||
?? lineConfig.backgroundColor
|
?? lineConfig.backgroundColor
|
||||||
?? lineConfig.color
|
?? lineConfig.color
|
||||||
?? GetRandomColor()
|
?? GetRandomColor()
|
||||||
@ -45,8 +82,8 @@ export const GetOrCreateDatasetByLineConfig = (data, lineConfig) => {
|
|||||||
return dataset
|
return dataset
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Column = React.memo(({ lineGroup, data, postParsing, additionalPointData, interval, yDisplay, yStart }) => {
|
export const Column: React.NamedExoticComponent<ColumnProps> = React.memo(({ lineGroup, data, postParsing, additionalPointData, interval, yDisplay, yStart }) => {
|
||||||
const [dataParams, setDataParams] = useState({data: {datasets:[]}, yStart, })
|
const [dataParams, setDataParams] = useState<ChartTimeDataParams>({data: {datasets:[]}, yStart, })
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
if((lineGroup.length === 0) || (data.length === 0)) return
|
if((lineGroup.length === 0) || (data.length === 0)) return
|
||||||
@ -54,16 +91,17 @@ export const Column = React.memo(({ lineGroup, data, postParsing, additionalPoin
|
|||||||
setDataParams((preDataParams) => {
|
setDataParams((preDataParams) => {
|
||||||
lineGroup.forEach(lineCfg => {
|
lineGroup.forEach(lineCfg => {
|
||||||
const dataset = GetOrCreateDatasetByLineConfig(preDataParams.data, lineCfg)
|
const dataset = GetOrCreateDatasetByLineConfig(preDataParams.data, lineCfg)
|
||||||
let points = data.map(dataItem => ({
|
let points: ChartTimeDataPoint[] = data.map(dataItem => ({
|
||||||
x: lineCfg.xConstValue ?? dataItem[lineCfg.xAccessorName],
|
x: lineCfg.xConstValue ?? dataItem[lineCfg.xAccessorName],
|
||||||
label: dataItem[lineCfg.xAccessorName],
|
label: dataItem[lineCfg.xAccessorName],
|
||||||
y: new Date(dataItem[lineCfg.yAccessorName]),
|
y: new Date(dataItem[lineCfg.yAccessorName]),
|
||||||
depth: dataItem.wellDepth,
|
|
||||||
...additionalPointData?.(dataItem, lineCfg)
|
...additionalPointData?.(dataItem, lineCfg)
|
||||||
})).filter(point => (point.x ?? null) !== null && (point.y ?? null) !== null)
|
}))
|
||||||
|
|
||||||
|
points = points.filter(point => (point.x ?? null) !== null && (point.y ?? null) !== null)
|
||||||
|
|
||||||
if(points?.length > 2)
|
if(points?.length > 2)
|
||||||
points.sort((a,b) => a.y > b.y ? 1 : -1)
|
points.sort(makeDateSorter('y'))
|
||||||
|
|
||||||
dataset.data = points
|
dataset.data = points
|
||||||
})
|
})
|
@ -4,8 +4,6 @@ import { ChartTimeOnlineFooter } from './ChartTimeOnlineFooter'
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { makeDateSorter } from '../../components/Table'
|
import { makeDateSorter } from '../../components/Table'
|
||||||
|
|
||||||
const stroke = (sz = '2px', c = 'white') => ({ textShadow: `-${sz} -${sz} 0 ${c}, ${sz} -${sz} 0 ${c}, -${sz} ${sz} 0 ${c}, ${sz} ${sz} 0 ${c}` })
|
|
||||||
|
|
||||||
const GetLimitShape = (flowChartData, points, accessor) => {
|
const GetLimitShape = (flowChartData, points, accessor) => {
|
||||||
const min = [], max = []
|
const min = [], max = []
|
||||||
|
|
||||||
@ -29,12 +27,12 @@ const RemoveSimilar = (input, accessor) => {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MonitoringColumn = ({ lineGroup, data, flowChartData, interval, showBorder, style, headerHeight, pointCount }) => {
|
export const MonitoringColumn = ({ lineGroup, data, flowChartData, interval, showBorder, style, headerHeight, pointCount = 2048, additionalLabels }) => {
|
||||||
const [dataStore, setDataStore] = useState([])
|
const [dataStore, setDataStore] = useState([])
|
||||||
const [lineGroupWithoutShapes, setLineGroupWithoutShapes] = useState([])
|
const [lineGroupWithoutShapes, setLineGroupWithoutShapes] = useState([])
|
||||||
const dataLast = data?.[data.length - 1]
|
const dataLast = data?.[data.length - 1]
|
||||||
const yStart = new Date(+(dataLast?.date ? new Date(dataLast.date) : new Date()) - interval * 0.97)
|
const yStart = new Date((dataLast?.date ? +new Date(dataLast.date) : Date.now()) - interval * 0.97)
|
||||||
const pv = lineGroup.filter(line => line.showLabels).map(line => ({
|
let pv = lineGroup.filter(line => line.showLabels).map(line => ({
|
||||||
color: line.color,
|
color: line.color,
|
||||||
label: line.label,
|
label: line.label,
|
||||||
unit: line.units,
|
unit: line.units,
|
||||||
@ -80,8 +78,30 @@ export const MonitoringColumn = ({ lineGroup, data, flowChartData, interval, sho
|
|||||||
</Grid>
|
</Grid>
|
||||||
<div style={{ position: 'relative' }}>
|
<div style={{ position: 'relative' }}>
|
||||||
<Grid className={'display_chart_values'}>
|
<Grid className={'display_chart_values'}>
|
||||||
{pv?.map((v, idx) => (
|
{pv?.map((v, idx) => {
|
||||||
<GridItem key={idx} col={1} row={idx} style={{ ...stroke(), color: v.color, padding: '0 4px' }}>{v.value?.toFixed(2) ?? '--'} {v.unit}</GridItem>
|
const text = `${v.value?.toFixed(2) ?? '--'} ${v.unit}`
|
||||||
|
return (
|
||||||
|
<GridItem
|
||||||
|
key={idx}
|
||||||
|
row={idx + 1}
|
||||||
|
col={1}
|
||||||
|
className={'monitoring_value'}
|
||||||
|
style={{ color: v.color, padding: '0 4px' }}
|
||||||
|
data-before={text}
|
||||||
|
children={text}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
{additionalLabels?.map((label, idx) => (
|
||||||
|
<GridItem
|
||||||
|
key={idx}
|
||||||
|
row={(pv?.length ?? 0) + idx + 1}
|
||||||
|
col={1}
|
||||||
|
className={'monitoring_value'}
|
||||||
|
style={{ color: 'black', padding: '0 4px' }}
|
||||||
|
data-before={label}
|
||||||
|
children={label}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</Grid>
|
</Grid>
|
||||||
<Column
|
<Column
|
||||||
@ -98,7 +118,3 @@ export const MonitoringColumn = ({ lineGroup, data, flowChartData, interval, sho
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
MonitoringColumn.defaultProps = {
|
|
||||||
pointCount: 2048
|
|
||||||
}
|
|
||||||
|
@ -13,6 +13,7 @@ import { Grid, GridItem, Flex } from '../../components/Grid'
|
|||||||
import { Subscribe } from '../../services/signalr'
|
import { Subscribe } from '../../services/signalr'
|
||||||
import {
|
import {
|
||||||
DrillFlowChartService,
|
DrillFlowChartService,
|
||||||
|
OperationStatService,
|
||||||
TelemetryDataSaubService,
|
TelemetryDataSaubService,
|
||||||
TelemetryDataSpinService,
|
TelemetryDataSpinService,
|
||||||
WellService
|
WellService
|
||||||
@ -306,6 +307,7 @@ export default function TelemetryView({ idWell }) {
|
|||||||
const [wellData, setWellData] = useState({ idState: 0 })
|
const [wellData, setWellData] = useState({ idState: 0 })
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
const [flowChartData, setFlowChartData] = useState([])
|
const [flowChartData, setFlowChartData] = useState([])
|
||||||
|
const [rop, setRop] = useState(null)
|
||||||
|
|
||||||
const handleDataSaub = (data) => {
|
const handleDataSaub = (data) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
@ -345,6 +347,8 @@ export default function TelemetryView({ idWell }) {
|
|||||||
useEffect(() => invokeWebApiWrapperAsync(
|
useEffect(() => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
const well = await WellService.get(idWell)
|
const well = await WellService.get(idWell)
|
||||||
|
const rop = await OperationStatService.getClusterRopStatByIdWell(idWell)
|
||||||
|
setRop(rop)
|
||||||
setWellData(well ?? {})
|
setWellData(well ?? {})
|
||||||
},
|
},
|
||||||
setShowLoader,
|
setShowLoader,
|
||||||
@ -403,6 +407,10 @@ export default function TelemetryView({ idWell }) {
|
|||||||
interval={chartInterval * 1000}
|
interval={chartInterval * 1000}
|
||||||
headerHeight={'50px'}
|
headerHeight={'50px'}
|
||||||
showBorder={getIndexOfDrillingBy(dataSaub) === index}
|
showBorder={getIndexOfDrillingBy(dataSaub) === index}
|
||||||
|
additionalLabels={rop && (index === 1) ? [
|
||||||
|
`ROP сред: ${rop.ropAverage.toFixed(2)} м/ч`,
|
||||||
|
`ROP макс: ${rop.ropMax.toFixed(2)} м/ч`
|
||||||
|
] : null}
|
||||||
/>
|
/>
|
||||||
</GridItem>
|
</GridItem>
|
||||||
)}
|
)}
|
||||||
|
@ -24,6 +24,21 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.monitoring_value {
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitoring_value:before {
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
left: 4px;
|
||||||
|
top: 0;
|
||||||
|
-webkit-text-stroke: 4px white;
|
||||||
|
content: attr(data-before);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.display_label{
|
.display_label{
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: rgb(70, 70, 70);
|
color: rgb(70, 70, 70);
|
||||||
|
Loading…
Reference in New Issue
Block a user