From 089dd3630332ef78b1c5c5bf8361613016b05d0f Mon Sep 17 00:00:00 2001 From: Vladimir Berdnikov Date: Fri, 17 Nov 2023 15:39:50 +0700 Subject: [PATCH] implemented DateService --- packages/services/index.js | 5 + packages/services/package-lock.json | 30 ++++++ packages/services/package.json | 30 ++++++ packages/services/rollup.config.js | 126 ++++++++++++++++++++++++ packages/services/src/date/constants.js | 5 + packages/services/src/date/dayjs.js | 60 +++++++++++ packages/services/src/date/index.js | 45 +++++++++ packages/services/src/date/utils.js | 94 ++++++++++++++++++ 8 files changed, 395 insertions(+) create mode 100644 packages/services/index.js create mode 100644 packages/services/package-lock.json create mode 100644 packages/services/package.json create mode 100644 packages/services/rollup.config.js create mode 100644 packages/services/src/date/constants.js create mode 100644 packages/services/src/date/dayjs.js create mode 100644 packages/services/src/date/index.js create mode 100644 packages/services/src/date/utils.js diff --git a/packages/services/index.js b/packages/services/index.js new file mode 100644 index 00000000..b23882db --- /dev/null +++ b/packages/services/index.js @@ -0,0 +1,5 @@ +export { DateService } from './src/date' + +export { HOURS_IN_DAY, MINUTES_IN_HOUR, SECONDS_IN_MINUTE } from './src/date/constants' + +export { getTimeBeforeDeadline, getDoubleXpDeadlineText } from './src/date/utils' diff --git a/packages/services/package-lock.json b/packages/services/package-lock.json new file mode 100644 index 00000000..425d811d --- /dev/null +++ b/packages/services/package-lock.json @@ -0,0 +1,30 @@ +{ + "name": "@ulms/services", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@ulms/services", + "version": "1.0.0", + "license": "MIT", + "peerDependencies": { + "dayjs": "1.10.7" + } + }, + "node_modules/dayjs": { + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz", + "integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==", + "peer": true + } + }, + "dependencies": { + "dayjs": { + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz", + "integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==", + "peer": true + } + } +} diff --git a/packages/services/package.json b/packages/services/package.json new file mode 100644 index 00000000..963576e4 --- /dev/null +++ b/packages/services/package.json @@ -0,0 +1,30 @@ +{ + "name": "@ulms/services", + "version": "1.0.0", + "description": "", + "keywords": [ + "lerna" + ], + "homepage": "https://github.com/netology-group/ulms-media-ui#readme", + "bugs": { + "url": "https://github.com/netology-group/ulms-media-ui/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/netology-group/ulms-media-ui.git" + }, + "license": "MIT", + "author": "", + "main": "index.js", + "module": "es/index.js", + "files": [ + "es", + "src" + ], + "scripts": { + "build": "rm -rf ./es/* && BABEL_ENV=es rollup --config ./rollup.config.js index.js" + }, + "peerDependencies": { + "dayjs": "1.10.7" + } +} diff --git a/packages/services/rollup.config.js b/packages/services/rollup.config.js new file mode 100644 index 00000000..194c8b04 --- /dev/null +++ b/packages/services/rollup.config.js @@ -0,0 +1,126 @@ +/* eslint-disable */ +const cssdupl = require('postcss-discard-duplicates') +const cssnext = require('postcss-cssnext') +const cssurl = require('postcss-url') +const Debug = require('debug') +const env = require('postcss-preset-env') +const svgr = require('@svgr/rollup') +import strip from '@rollup/plugin-strip'; +import terser from '@rollup/plugin-terser'; +import json from '@rollup/plugin-json'; +import { nodeResolve as npm } from '@rollup/plugin-node-resolve'; +import { babel } from '@rollup/plugin-babel'; +import commonjs from '@rollup/plugin-commonjs'; +import postcss from 'rollup-plugin-postcss'; +import cssnano from 'cssnano'; + +const { name, peerDependencies } = require('./package.json') +const { postcssLoader } = require('./rollup/loaders') +const babelrc = require('./.babelrc.json') + +const babel_rc = babelrc.env[process.env.BABEL_ENV || 'es'] + +// const warn = console.warn +console.warn = (...argv) => process.env.LOG_WARN && Debug(`${name}:console.warn`)(...argv) +// monkeypatch warn method to disable annoying postcss warning + +// const globalDebug = Debug(`${name}:rollup.config.js`) + +const shouldMinifyCss = options => process.env.NODE_ENV === 'production' ? cssnano(options) : [] + +const shouldUglify = options => process.env.NODE_ENV === 'production' ? [terser(options), strip({ + functions: ['console.log', 'assert.*'], // Убираем только console.log, warn и error оставляем! +})] : [] + +const processAsCssModule = function(){ + this.options = { + ...this.options, modules: true, namedExports: true, + } +} + +const rollupPlugins = [ // order matters + json(), + svgr(), + postcss({ + extract: true, + plugins: [ + cssurl({ url: 'inline' }), + env(), + cssnext(), + cssdupl() + ].concat(shouldMinifyCss()), + loaders: [ + { + name: 'postcss', + alwaysProcess: true, + test: /\.css$/, + process (_) { + if(/node_modules\/@foxford\/ui\/.*\.css$/.exec(this.id)){ + // do nothing as we going to import file as is + } else if(/node_modules\/.*\.css$/.exec(this.id)){ + // do nothing as we going to import file as is + } else { + processAsCssModule.call(this) + // process css as css-modules + } + // :up is crucial to allow transpile local and external .css separately + + return postcssLoader.process.call(this, _) + }, + }, + ], + }), + npm({ + browser: true, + extensions: ['.js', '.jsx'] + }), + commonjs({ + include: 'node_modules/**', + namedExports: { + 'react-sizeme': ['SizeMe'], + 'fabric': ['fabric'] + } + }), + babel({ + babelrc: false, + presets: babel_rc.presets, + plugins: [ + ...babelrc.plugins, + ...babel_rc.plugins + ] + }), +].concat(shouldUglify()) + +const dist = (entry = 'index.js', frm = './', out = './es') => { + const opts = ({ + input: `${frm}/${entry}`, + output: { + file: `${out}/${entry}`, + format: 'es', + sourcemap: true, + }, + file: `${out}/${entry}`, // that's important duplicate + external: Object.keys(peerDependencies), + plugins: rollupPlugins, + onwarn: function(warning){ + if(!warning.code) return //globalDebug(warning.message) + + //const debug = Debug(`${name}:${warning.code}`) + + // if(process.env.LOG_DEBUG) debug(warning) + + if(warning.code === 'UNKNOWN_OPTION'){ + //if(process.env.LOG_DEBUG) debug(warning.message) + // return + } else if(warning.code) { + //return debug(warning.message) + } + + //globalDebug(warning.message) + } + }) + + return opts +} + +module.exports = dist() diff --git a/packages/services/src/date/constants.js b/packages/services/src/date/constants.js new file mode 100644 index 00000000..ec913246 --- /dev/null +++ b/packages/services/src/date/constants.js @@ -0,0 +1,5 @@ +export const HOURS_IN_DAY = 24 + +export const MINUTES_IN_HOUR = 60 + +export const SECONDS_IN_MINUTE = 60 diff --git a/packages/services/src/date/dayjs.js b/packages/services/src/date/dayjs.js new file mode 100644 index 00000000..e97d1e01 --- /dev/null +++ b/packages/services/src/date/dayjs.js @@ -0,0 +1,60 @@ +import dj from 'dayjs' +import dayjs_en from 'dayjs/locale/en' +import dayjs_ru from 'dayjs/locale/ru' +import customParseFormat from 'dayjs/plugin/customParseFormat' +import isBetween from 'dayjs/plugin/isBetween' +import LocalizedFormat from 'dayjs/plugin/localizedFormat' +import relativeTime from 'dayjs/plugin/relativeTime' +import timezone from 'dayjs/plugin/timezone' +import utc from 'dayjs/plugin/utc' +import weekday from 'dayjs/plugin/weekday' + +const LOCALES = { + en: dayjs_en, + ru: dayjs_ru, +} + +// Create instance of dayjs with settings https://github.com/iamkun/dayjs/issues/1227 +export class DayjsAdapter { + static LOCALES = LOCALES + + constructor () { + this.dayjs = dj + this.extend() + } + + extend () { + this.dayjs.extend(utc) + this.dayjs.extend(timezone) + this.dayjs.extend(customParseFormat) + this.dayjs.extend(weekday) + this.dayjs.extend(LocalizedFormat) + this.dayjs.extend(isBetween) + this.dayjs.extend(relativeTime) + } + + initLocale (locale) { + const browserLocale = navigator.language.split('-')[0] + + if (locale === undefined || locale === null) { + this.dayjs.locale(browserLocale) + + return + } + if (!DayjsAdapter.LOCALES[locale]) { + this.dayjs.locale('ru', DayjsAdapter.LOCALES.ru) + + return + } + this.dayjs.locale(locale, DayjsAdapter.LOCALES[locale]) + } + + setDefaultLocale = () => {} + + setTimezone = (tz = 'Europe/Moscow') => { + this.timezone = tz + this.dayjs.tz.setDefault(tz) + } + + getInstance = () => this.dayjs +} diff --git a/packages/services/src/date/index.js b/packages/services/src/date/index.js new file mode 100644 index 00000000..33a47c22 --- /dev/null +++ b/packages/services/src/date/index.js @@ -0,0 +1,45 @@ +import { DayjsAdapter } from './dayjs' + +class DateService { + constructor (adapter) { + this.provider = adapter + } + + initLocale (locale = navigator.language.split('-')[0]) { + this.provider.initLocale(locale) + } + + setDateTimezone (tz) { + this.provider.setTimezone(tz) + } + + getProvider () { + return this.provider.getInstance() + } + + _ (...args) { + return this.getProvider()(...args) + } + + tz (date, timezone) { + // Порядок вызова нельзя трогать, если надо лучше создать отдельный метод + return this.getProvider()(date).tz(timezone) + } + + utc (...args) { + return this.getProvider().utc(...args) + } + + unix (arg) { + return this.getProvider().unix(arg) + } +} + +const adapter = new DayjsAdapter() + +adapter.setTimezone() +const service = new DateService(adapter) + +service.setDateTimezone(new Intl.DateTimeFormat()?.resolvedOptions?.()?.timeZone) + +export { service as DateService } diff --git a/packages/services/src/date/utils.js b/packages/services/src/date/utils.js new file mode 100644 index 00000000..b5bcf568 --- /dev/null +++ b/packages/services/src/date/utils.js @@ -0,0 +1,94 @@ +import { DateService } from './index' + +import { + HOURS_IN_DAY, + MINUTES_IN_HOUR, + SECONDS_IN_MINUTE, +} from './constants' + +/* eslint-disable */ +/** Examples: + * declensionOfNumber(1, ['минута', 'минуты', 'минут']) => 'минута' + * declensionOfNumber(2, ['минута', 'минуты', 'минут']) => 'минуты' + * declensionOfNumber(5, ['минута', 'минуты', 'минут']) => 'минут' + */ +export const declensionOfNumber = (number, words) => + words[ + number % 100 > 4 && number % 100 < 20 + ? 2 + : [2, 0, 1, 1, 1, 2][number % 10 < 5 ? number % 10 : 5] + ]; +/* eslint-enable */ + +export const getTimeBeforeDeadline = (deadline) => { + const deadlineDate = DateService.tz(deadline) + const readableDeadline = deadlineDate.format('DD MMMM HH:mm') + + // Дедлайн вышел + if (!deadline || deadlineDate.diff(Date.now()) < 0) { + return { + daysToDeadline: 0, + hoursToDeadline: 0, + minutesToDeadline: 0, + readableDeadline, + secondsToDeadline: 0, + } + } + + const secondsToDeadline = deadlineDate.diff(Date.now(), 'second') + const minutesToDeadline = Math.floor(secondsToDeadline / SECONDS_IN_MINUTE) + const hoursToDeadline = Math.floor(minutesToDeadline / MINUTES_IN_HOUR) + const daysToDeadline = Math.floor(hoursToDeadline / HOURS_IN_DAY) + + return { + daysToDeadline, + hoursToDeadline: hoursToDeadline - daysToDeadline * HOURS_IN_DAY, + minutesToDeadline: minutesToDeadline - hoursToDeadline * MINUTES_IN_HOUR, + readableDeadline, + secondsToDeadline: + secondsToDeadline - minutesToDeadline * SECONDS_IN_MINUTE, + } +} + +export const getDoubleXpDeadlineText = (deadline) => { + const { + daysToDeadline, + hoursToDeadline, + minutesToDeadline, + secondsToDeadline, + } = getTimeBeforeDeadline(deadline) + + if ( + daysToDeadline === 0 + && hoursToDeadline === 0 + && minutesToDeadline === 0 + && secondsToDeadline === 0 + ) return '' + + const days = + (daysToDeadline > 0 + && (hoursToDeadline > 0 + || minutesToDeadline > 0 + || secondsToDeadline > 0)) + || hoursToDeadline >= HOURS_IN_DAY - 1 + ? daysToDeadline + 1 + : daysToDeadline + const hours = + minutesToDeadline > 0 || secondsToDeadline > 0 + ? hoursToDeadline + 1 + : hoursToDeadline + + let doubleXpText + + if (days > 0) { + const deadlineInDays = `${days} ${declensionOfNumber(days, ['день', 'дня', 'дней'])}` + + doubleXpText = `${declensionOfNumber(days, ['Остался', 'Осталось', 'Осталось'])} ${deadlineInDays}` + } else { + const deadlineInHours = `${hours} ${declensionOfNumber(hours, ['час', 'часа', 'часов'])}` + + doubleXpText = `${declensionOfNumber(hours, ['Остался', 'Осталось', 'Осталось'])} ${deadlineInHours}` + } + + return doubleXpText +}