Files
oxygen-ui/src/util/common.tsx

540 lines
13 KiB
TypeScript

import { createRoot } from 'react-dom/client'
import { editor, languages, Position } from 'monaco-editor'
import { Monaco } from '@monaco-editor/react'
import { MessageInstance } from 'antd/es/message/interface'
import { HookAPI } from 'antd/es/modal/useModal'
import { NotificationInstance } from 'antd/es/notification/interface'
import { Theme, css } from 'antd-style'
import { floor } from 'lodash'
import {
STORAGE_COLLAPSE_SIDEBAR_KEY,
STORAGE_THEME_MODE_KEY,
STORAGE_TOOL_MENU_ITEM_KEY,
THEME_DARK,
THEME_FOLLOW_SYSTEM,
THEME_LIGHT
} from '@/constants/common.constants'
import { getLocalStorage, setLocalStorage } from '@/util/browser'
import FullscreenLoadingMask from '@/components/common/FullscreenLoadingMask'
export type ThemeMode = typeof THEME_FOLLOW_SYSTEM | typeof THEME_LIGHT | typeof THEME_DARK
let message: MessageInstance
let notification: NotificationInstance
let modal: HookAPI
export const init = (
messageInstance: MessageInstance,
notificationInstance: NotificationInstance,
modalInstance: HookAPI
) => {
message = messageInstance
notification = notificationInstance
modal = modalInstance
}
export { message, notification, modal }
export const randomInt = (start: number, end: number) => {
if (start > end) {
const t = start
start = end
end = t
}
start = Math.ceil(start)
end = Math.floor(end)
return start + Math.floor(Math.random() * (end - start))
}
export const randomFloat = (start: number, end: number) => {
return start + Math.random() * (end - start)
}
export const randomColor = (start: number, end: number) => {
return `rgb(${randomInt(start, end)},${randomInt(start, end)},${randomInt(start, end)})`
}
export const floorNumber = (num: number, digits: number) => {
if (digits > 0) {
return Math.floor(num / Math.pow(10, digits - 1)) * Math.pow(10, digits - 1)
} else {
const regExpMatchArray = num.toString().match(new RegExp('^\\d\\.\\d{' + -digits + '}'))
if (regExpMatchArray !== null) {
return parseFloat(regExpMatchArray[0]).toFixed(-digits)
} else {
return num
}
}
}
export const showLoadingMask = (id: string) => {
if (document.querySelector(`#${id}`)) {
return
}
const container = document.createElement('div')
document.body.appendChild(container)
container.id = id
container.setAttribute(
'style',
'position: fixed; width: 100vw; height: 100vh; z-index: 10000; left: 0; top: 0;'
)
return createRoot(container).render(<FullscreenLoadingMask />)
}
export const removeLoadingMask = (id: string) => {
document.querySelectorAll(`#${id}`).forEach((value) => {
value.parentNode?.removeChild(value)
})
}
export enum ByteUnit {
B = 'B',
KiB = 'KiB',
Mib = 'Mib',
GiB = 'GiB',
TiB = 'TiB',
PiB = 'PiB',
EiB = 'EiB',
ZiB = 'ZiB',
YiB = 'YiB'
}
export const formatByteSize = (byteSize: number): string => {
const BASE = 1024
if (byteSize <= -1) {
return byteSize.toString()
}
if (floor(byteSize / BASE) <= 0) {
return formatByte(byteSize, ByteUnit.B)
}
byteSize /= BASE
if (floor(byteSize / BASE) <= 0) {
return formatByte(byteSize, ByteUnit.KiB)
}
byteSize /= BASE
if (floor(byteSize / BASE) <= 0) {
return formatByte(byteSize, ByteUnit.Mib)
}
byteSize /= BASE
if (floor(byteSize / BASE) <= 0) {
return formatByte(byteSize, ByteUnit.GiB)
}
byteSize /= BASE
if (floor(byteSize / BASE) <= 0) {
return formatByte(byteSize, ByteUnit.TiB)
}
byteSize /= BASE
if (floor(byteSize / BASE) <= 0) {
return formatByte(byteSize, ByteUnit.PiB)
}
byteSize /= BASE
if (floor(byteSize / BASE) <= 0) {
return formatByte(byteSize, ByteUnit.EiB)
}
byteSize /= BASE
if (floor(byteSize / BASE) <= 0) {
return formatByte(byteSize, ByteUnit.ZiB)
}
byteSize /= BASE
return formatByte(byteSize, ByteUnit.YiB)
}
const formatByte = (size: number, unit: ByteUnit): string => {
let precision: number
if ((size * 1000) % 10 > 0) {
precision = 3
} else if ((size * 100) % 10 > 0) {
precision = 2
} else if ((size * 10) % 10 > 0) {
precision = 1
} else {
precision = 0
}
return `${size.toFixed(precision)}${unit}`
}
export const checkDesktop = () => import.meta.env.VITE_PLATFORM === 'DESKTOP'
export const saveToolMenuItem = (toolMenuItem: ToolMenuItem[]) => {
setLocalStorage(STORAGE_TOOL_MENU_ITEM_KEY, JSON.stringify(toolMenuItem))
}
export const getToolMenuItem = (): ToolMenuItem[] => {
const s = getLocalStorage(STORAGE_TOOL_MENU_ITEM_KEY)
if (!s) {
return []
}
return JSON.parse(s) as ToolMenuItem[]
}
export const omitText = (text: string, length: number) => {
if (text.length <= length) {
return text
}
return `${text.substring(0, length)}...`
}
const getByteLength = (str: string) => {
let length = 0
for (let i = 0; i < str.length; i++) {
if (str.charCodeAt(i) > 255) {
length += 2
} else {
length++
}
}
return length
}
const substringByByte = (str: string, start: number, length: number) => {
let byteLength = 0
let result = ''
for (let i = 0; i < str.length; i++) {
const charCode = str.charCodeAt(i)
byteLength += charCode > 255 ? 2 : 1
if (byteLength > start + length) {
break
} else if (byteLength > start) {
result += str[i]
}
}
return result
}
export const omitTextByByte = (text: string, length: number) => {
if (getByteLength(text) <= length) {
return text
}
return `${substringByByte(text, 0, length)}...`
}
export const getSidebarCollapse = () => getLocalStorage(STORAGE_COLLAPSE_SIDEBAR_KEY) === 'true'
export const setSidebarCollapse = (isCollapse: boolean) => {
setLocalStorage(STORAGE_COLLAPSE_SIDEBAR_KEY, isCollapse ? 'true' : 'false')
}
export const getThemeMode = (): ThemeMode => {
switch (getLocalStorage(STORAGE_THEME_MODE_KEY)) {
case THEME_FOLLOW_SYSTEM:
case THEME_LIGHT:
case THEME_DARK:
return getLocalStorage(STORAGE_THEME_MODE_KEY) as ThemeMode
default:
return THEME_FOLLOW_SYSTEM
}
}
export const setThemeMode = (themeMode: ThemeMode) => {
setLocalStorage(STORAGE_THEME_MODE_KEY, themeMode)
}
const cssColors = [
'blue',
'purple',
'cyan',
'green',
'magenta',
'pink',
'red',
'orange',
'yellow',
'volcano',
'geekblue',
'gold',
'lime'
].reduce((prev: string[], current) => {
let temp: string[] = []
for (let i = 1; i <= 10; i++) {
temp = [...temp, `${current}${i}`]
}
return [...prev, current, ...temp]
}, [])
const cssVariables: string[] = [
...cssColors,
'colorPrimary',
'colorSuccess',
'colorWarning',
'colorError',
'colorInfo',
'colorLink',
'colorTextBase',
'colorBgBase',
'fontFamily',
'fontFamilyCode',
'fontSize',
'lineWidth',
'lineType',
'motionUnit',
'motionBase',
'motionEaseOutCirc',
'motionEaseInOutCirc',
'motionEaseOut',
'motionEaseInOut',
'motionEaseOutBack',
'motionEaseInBack',
'motionEaseInQuint',
'motionEaseOutQuint',
'borderRadius',
'sizeUnit',
'sizeStep',
'sizePopupArrow',
'controlHeight',
'zIndexBase',
'zIndexPopupBase',
'opacityImage',
'wireframe',
'motion',
'colorLinkHover',
'colorText',
'colorTextSecondary',
'colorTextTertiary',
'colorTextQuaternary',
'colorFill',
'colorFillSecondary',
'colorFillTertiary',
'colorFillQuaternary',
'colorBgSolid',
'colorBgSolidHover',
'colorBgSolidActive',
'colorBgLayout',
'colorBgContainer',
'colorBgElevated',
'colorBgSpotlight',
'colorBgBlur',
'colorBorder',
'colorBorderSecondary',
'colorPrimaryBg',
'colorPrimaryBgHover',
'colorPrimaryBorder',
'colorPrimaryBorderHover',
'colorPrimaryHover',
'colorPrimaryActive',
'colorPrimaryTextHover',
'colorPrimaryText',
'colorPrimaryTextActive',
'colorSuccessBg',
'colorSuccessBgHover',
'colorSuccessBorder',
'colorSuccessBorderHover',
'colorSuccessHover',
'colorSuccessActive',
'colorSuccessTextHover',
'colorSuccessText',
'colorSuccessTextActive',
'colorErrorBg',
'colorErrorBgHover',
'colorErrorBgFilledHover',
'colorErrorBgActive',
'colorErrorBorder',
'colorErrorBorderHover',
'colorErrorHover',
'colorErrorActive',
'colorErrorTextHover',
'colorErrorText',
'colorErrorTextActive',
'colorWarningBg',
'colorWarningBgHover',
'colorWarningBorder',
'colorWarningBorderHover',
'colorWarningHover',
'colorWarningActive',
'colorWarningTextHover',
'colorWarningText',
'colorWarningTextActive',
'colorInfoBg',
'colorInfoBgHover',
'colorInfoBorder',
'colorInfoBorderHover',
'colorInfoHover',
'colorInfoActive',
'colorInfoTextHover',
'colorInfoText',
'colorInfoTextActive',
'colorLinkActive',
'colorBgMask',
'colorWhite',
'fontSizeSM',
'fontSizeLG',
'fontSizeXL',
'fontSizeHeading1',
'fontSizeHeading2',
'fontSizeHeading3',
'fontSizeHeading4',
'fontSizeHeading5',
'lineHeight',
'lineHeightLG',
'lineHeightSM',
'lineHeightHeading1',
'lineHeightHeading2',
'lineHeightHeading3',
'lineHeightHeading4',
'lineHeightHeading5',
'sizeXXL',
'sizeXL',
'sizeLG',
'sizeMD',
'sizeMS',
'size',
'sizeSM',
'sizeXS',
'sizeXXS',
'controlHeightSM',
'controlHeightXS',
'controlHeightLG',
'motionDurationFast',
'motionDurationMid',
'motionDurationSlow',
'lineWidthBold',
'borderRadiusXS',
'borderRadiusSM',
'borderRadiusLG',
'borderRadiusOuter',
'colorFillContent',
'colorFillContentHover',
'colorFillAlter',
'colorBgContainerDisabled',
'colorBorderBg',
'colorSplit',
'colorTextPlaceholder',
'colorTextDisabled',
'colorTextHeading',
'colorTextLabel',
'colorTextDescription',
'colorTextLightSolid',
'colorHighlight',
'colorBgTextHover',
'colorBgTextActive',
'colorIcon',
'colorIconHover',
'colorErrorOutline',
'colorWarningOutline',
'fontSizeIcon',
'lineWidthFocus',
'controlOutlineWidth',
'controlInteractiveSize',
'controlItemBgHover',
'controlItemBgActive',
'controlItemBgActiveHover',
'controlItemBgActiveDisabled',
'controlOutline',
'fontWeightStrong',
'opacityLoading',
'linkDecoration',
'linkHoverDecoration',
'linkFocusDecoration',
'controlPaddingHorizontal',
'controlPaddingHorizontalSM',
'paddingXXS',
'paddingXS',
'paddingSM',
'padding',
'paddingMD',
'paddingLG',
'paddingXL',
'paddingContentHorizontalLG',
'paddingContentVerticalLG',
'paddingContentHorizontal',
'paddingContentVertical',
'paddingContentHorizontalSM',
'paddingContentVerticalSM',
'marginXXS',
'marginXS',
'marginSM',
'margin',
'marginMD',
'marginLG',
'marginXL',
'marginXXL',
'boxShadow',
'boxShadowSecondary',
'boxShadowTertiary',
'screenXS',
'screenXSMin',
'screenXSMax',
'screenSM',
'screenSMMin',
'screenSMMax',
'screenMD',
'screenMDMin',
'screenMDMax',
'screenLG',
'screenLGMin',
'screenLGMax',
'screenXL',
'screenXLMin',
'screenXLMax',
'screenXXL',
'screenXXLMin'
]
export const generateThemeCssVariable = (theme: Omit<Theme, 'prefixCls'>) => {
const cssContent = cssVariables
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
.map((variable) => `--${variable}: ${theme[variable]};`)
.join('\n')
return css`
:root {
${cssContent}
}
`
}
export const addExtraCssVariable = (monaco: Monaco) => {
monaco.languages.registerCompletionItemProvider('css', {
provideCompletionItems: (
model: editor.ITextModel,
position: Position
): languages.ProviderResult<languages.CompletionList> => {
const textUntilPosition = model.getValueInRange({
startLineNumber: 1,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column
})
if (!textUntilPosition.match(/var\(([^)]*)$/)) {
return { suggestions: [] }
}
const word = model.getWordUntilPosition(position)
const range = new monaco.Range(
position.lineNumber,
word.startColumn,
position.lineNumber,
word.endColumn
)
return {
suggestions: cssVariables.map(
(variable): languages.CompletionItem => ({
label: `--${variable}`,
kind: monaco.languages.CompletionItemKind.Variable,
insertText: `--${variable}`,
range,
detail: 'Oxygen Theme Variable'
})
)
}
}
})
}